//! Security Framework type import/export support.

use core_foundation::array::CFArray;
use core_foundation::base::{CFType, TCFType};
use core_foundation::data::CFData;
use core_foundation::dictionary::CFDictionary;
use core_foundation::string::CFString;
use security_framework_sys::import_export::*;
use std::ptr;

use crate::base::Result;
use crate::certificate::SecCertificate;
use crate::cvt;
use crate::identity::SecIdentity;
#[cfg(target_os = "macos")]
use crate::os::macos::access::SecAccess;
#[cfg(target_os = "macos")]
use crate::os::macos::keychain::SecKeychain;
use crate::trust::SecTrust;

/// Information about an imported identity.
pub struct ImportedIdentity {
    /// The label of the identity.
    pub label: Option<String>,
    /// The ID of the identity. Typically the SHA-1 hash of the public key.
    pub key_id: Option<Vec<u8>>,
    /// A `SecTrust` object set up to validate this identity.
    pub trust: Option<SecTrust>,
    /// A certificate chain validating this identity.
    pub cert_chain: Option<Vec<SecCertificate>>,
    /// The identity itself.
    pub identity: Option<SecIdentity>,
    _p: (),
}

/// A builder type to import an identity from PKCS#12 formatted data.
#[derive(Default)]
pub struct Pkcs12ImportOptions {
    passphrase: Option<CFString>,
    #[cfg(target_os = "macos")]
    keychain: Option<SecKeychain>,
    #[cfg(target_os = "macos")]
    access: Option<SecAccess>,
}

#[cfg(target_os = "macos")]
impl crate::Pkcs12ImportOptionsInternals for Pkcs12ImportOptions {
    #[inline(always)]
    fn keychain(&mut self, keychain: SecKeychain) -> &mut Self {
        self.keychain = Some(keychain);
        self
    }

    #[inline(always)]
    fn access(&mut self, access: SecAccess) -> &mut Self {
        self.access = Some(access);
        self
    }
}

impl Pkcs12ImportOptions {
    /// Creates a new builder with default options.
    #[inline(always)]
    pub fn new() -> Self {
        Self::default()
    }

    /// Specifies the passphrase to be used to decrypt the data.
    ///
    /// This must be specified, as unencrypted PKCS#12 data is not supported.
    #[inline]
    pub fn passphrase(&mut self, passphrase: &str) -> &mut Self {
        self.passphrase = Some(CFString::new(passphrase));
        self
    }

    /// Imports identities from PKCS#12 encoded data.
    pub fn import(&self, pkcs12_data: &[u8]) -> Result<Vec<ImportedIdentity>> {
        unsafe {
            let pkcs12_data = CFData::from_buffer(pkcs12_data);

            let mut options = vec![];

            if let Some(ref passphrase) = self.passphrase {
                options.push((
                    CFString::wrap_under_get_rule(kSecImportExportPassphrase),
                    passphrase.as_CFType(),
                ));
            }

            self.import_setup(&mut options);

            let options = CFDictionary::from_CFType_pairs(&options);

            let mut raw_items = ptr::null();
            cvt(SecPKCS12Import(
                pkcs12_data.as_concrete_TypeRef(),
                options.as_concrete_TypeRef(),
                &mut raw_items,
            ))?;
            let raw_items = CFArray::<CFDictionary<CFString, *const _>>::wrap_under_create_rule(raw_items);

            let mut items = vec![];

            for raw_item in &raw_items {
                let label = raw_item
                    .find(kSecImportItemLabel)
                    .map(|label| CFString::wrap_under_get_rule(*label as *const _).to_string());
                let key_id = raw_item
                    .find(kSecImportItemKeyID)
                    .map(|key_id| CFData::wrap_under_get_rule(*key_id as *const _).to_vec());
                let trust = raw_item
                    .find(kSecImportItemTrust)
                    .map(|trust| SecTrust::wrap_under_get_rule(*trust as *mut _));
                let cert_chain = raw_item.find(kSecImportItemCertChain as *const _).map(
                    |cert_chain| {
                        CFArray::<SecCertificate>::wrap_under_get_rule(*cert_chain as *const _)
                            .iter()
                            .map(|c| c.clone())
                            .collect()
                    },
                );
                let identity = raw_item
                    .find(kSecImportItemIdentity)
                    .map(|identity| SecIdentity::wrap_under_get_rule(*identity as *mut _));

                items.push(ImportedIdentity {
                    label,
                    key_id,
                    trust,
                    cert_chain,
                    identity,
                    _p: (),
                });
            }

            Ok(items)
        }
    }

    #[cfg(target_os = "macos")]
    fn import_setup(&self, options: &mut Vec<(CFString, CFType)>) {
        unsafe {
            if let Some(ref keychain) = self.keychain {
                options.push((
                    CFString::wrap_under_get_rule(kSecImportExportKeychain),
                    keychain.as_CFType(),
                ));
            }

            if let Some(ref access) = self.access {
                options.push((
                    CFString::wrap_under_get_rule(kSecImportExportAccess),
                    access.as_CFType(),
                ));
            }
        }
    }

    #[cfg(not(target_os = "macos"))]
    fn import_setup(&self, _: &mut Vec<(CFString, CFType)>) {}
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn missing_passphrase() {
        let data = include_bytes!("../test/server.p12");
        assert!(Pkcs12ImportOptions::new().import(data).is_err());
    }
}
