//! Bindings to winapi's certificate-store related APIs. use std::cmp; use std::ffi::OsStr; use std::fmt; use std::io; use std::mem; use std::os::windows::prelude::*; use std::ptr; use windows_sys::Win32::Security::Cryptography; use crate::cert_context::CertContext; use crate::ctl_context::CtlContext; use crate::Inner; /// Representation of certificate store on Windows, wrapping a `HCERTSTORE`. pub struct CertStore(Cryptography::HCERTSTORE); unsafe impl Sync for CertStore {} unsafe impl Send for CertStore {} impl fmt::Debug for CertStore { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("CertStore").finish() } } impl Drop for CertStore { fn drop(&mut self) { unsafe { Cryptography::CertCloseStore(self.0, 0); } } } impl Clone for CertStore { fn clone(&self) -> CertStore { unsafe { CertStore(Cryptography::CertDuplicateStore(self.0)) } } } inner!(CertStore, Cryptography::HCERTSTORE); /// Argument to the `add_cert` function indicating how a certificate should be /// added to a `CertStore`. pub enum CertAdd { /// The function makes no check for an existing matching certificate or link /// to a matching certificate. A new certificate is always added to the /// store. This can lead to duplicates in a store. Always = Cryptography::CERT_STORE_ADD_ALWAYS as isize, /// If a matching certificate or a link to a matching certificate exists, /// the operation fails. New = Cryptography::CERT_STORE_ADD_NEW as isize, /// If a matching certificate or a link to a matching certificate exists and /// the NotBefore time of the existing context is equal to or greater than /// the NotBefore time of the new context being added, the operation fails. /// /// If the NotBefore time of the existing context is less than the NotBefore /// time of the new context being added, the existing certificate or link is /// deleted and a new certificate is created and added to the store. If a /// matching certificate or a link to a matching certificate does not exist, /// a new link is added. Newer = Cryptography::CERT_STORE_ADD_NEWER as isize, /// If a matching certificate or a link to a matching certificate exists and /// the NotBefore time of the existing context is equal to or greater than /// the NotBefore time of the new context being added, the operation fails. /// /// If the NotBefore time of the existing context is less than the NotBefore /// time of the new context being added, the existing context is deleted /// before creating and adding the new context. The new added context /// inherits properties from the existing certificate. NewerInheritProperties = Cryptography::CERT_STORE_ADD_NEWER_INHERIT_PROPERTIES as isize, /// If a link to a matching certificate exists, that existing certificate or /// link is deleted and a new certificate is created and added to the store. /// If a matching certificate or a link to a matching certificate does not /// exist, a new link is added. ReplaceExisting = Cryptography::CERT_STORE_ADD_REPLACE_EXISTING as isize, /// If a matching certificate exists in the store, the existing context is /// not replaced. The existing context inherits properties from the new /// certificate. ReplaceExistingInheritProperties = Cryptography::CERT_STORE_ADD_REPLACE_EXISTING_INHERIT_PROPERTIES as isize, /// If a matching certificate or a link to a matching certificate exists, /// that existing certificate or link is used and properties from the /// new certificate are added. The function does not fail, but it does /// not add a new context. The existing context is duplicated and returned. /// /// If a matching certificate or a link to a matching certificate does /// not exist, a new certificate is added. UseExisting = Cryptography::CERT_STORE_ADD_USE_EXISTING as isize, } impl CertStore { /// Opens up the specified key store within the context of the current user. /// /// Common valid values for `which` are "My", "Root", "Trust", "CA". /// Additonal MSDN docs https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certopenstore#remarks pub fn open_current_user(which: &str) -> io::Result { unsafe { let data = OsStr::new(which) .encode_wide() .chain(Some(0)) .collect::>(); let store = Cryptography::CertOpenStore( Cryptography::CERT_STORE_PROV_SYSTEM_W, Cryptography::CERT_QUERY_ENCODING_TYPE::default(), Cryptography::HCRYPTPROV_LEGACY::default(), Cryptography::CERT_SYSTEM_STORE_CURRENT_USER_ID << Cryptography::CERT_SYSTEM_STORE_LOCATION_SHIFT, data.as_ptr() as *mut _, ); if !store.is_null() { Ok(CertStore(store)) } else { Err(io::Error::last_os_error()) } } } /// Opens up the specified key store within the context of the local machine. /// /// Common valid values for `which` are "My", "Root", "Trust", "CA". /// Additonal MSDN docs https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certopenstore#remarks pub fn open_local_machine(which: &str) -> io::Result { unsafe { let data = OsStr::new(which) .encode_wide() .chain(Some(0)) .collect::>(); let store = Cryptography::CertOpenStore( Cryptography::CERT_STORE_PROV_SYSTEM_W, Cryptography::CERT_QUERY_ENCODING_TYPE::default(), Cryptography::HCRYPTPROV_LEGACY::default(), Cryptography::CERT_SYSTEM_STORE_LOCAL_MACHINE_ID << Cryptography::CERT_SYSTEM_STORE_LOCATION_SHIFT, data.as_ptr() as *mut _, ); if !store.is_null() { Ok(CertStore(store)) } else { Err(io::Error::last_os_error()) } } } /// Imports a PKCS#12-encoded key/certificate pair, returned as a /// `CertStore` instance. /// /// The password must also be provided to decrypt the encoded data. pub fn import_pkcs12(data: &[u8], password: Option<&str>) -> io::Result { unsafe { let blob = Cryptography::CRYPT_INTEGER_BLOB { cbData: data.len() as u32, pbData: data.as_ptr() as *mut u8, }; let password = password.map(|s| { OsStr::new(s) .encode_wide() .chain(Some(0)) .collect::>() }); let password = password.as_ref().map(|s| s.as_ptr()); let password = password.unwrap_or(ptr::null()); let res = Cryptography::PFXImportCertStore( &blob, password, Cryptography::CRYPT_KEY_FLAGS::default(), ); if !res.is_null() { Ok(CertStore(res)) } else { Err(io::Error::last_os_error()) } } } /// Returns an iterator over the certificates in this certificate store. pub fn certs(&self) -> Certs { Certs { store: self, cur: None, } } /// Adds a certificate context to this store. /// /// This function will add the certificate specified in `cx` to this store. /// A copy of the added certificate is returned. pub fn add_cert(&mut self, cx: &CertContext, how: CertAdd) -> io::Result { unsafe { let how = how as u32; let mut ret = ptr::null_mut(); let res = Cryptography::CertAddCertificateContextToStore( self.0, cx.as_inner(), how, &mut ret, ); if res == 0 { Err(io::Error::last_os_error()) } else { Ok(CertContext::from_inner(ret)) } } } /// Exports this certificate store as a PKCS#12-encoded blob. /// /// The password specified will be the password used to unlock the returned /// data. pub fn export_pkcs12(&self, password: &str) -> io::Result> { unsafe { let password = password.encode_utf16().chain(Some(0)).collect::>(); let mut blob = mem::zeroed(); let res = Cryptography::PFXExportCertStore( self.0, &mut blob, password.as_ptr(), Cryptography::EXPORT_PRIVATE_KEYS, ); if res == 0 { return Err(io::Error::last_os_error()); } let mut ret = Vec::with_capacity(blob.cbData as usize); blob.pbData = ret.as_mut_ptr(); let res = Cryptography::PFXExportCertStore( self.0, &mut blob, password.as_ptr(), Cryptography::EXPORT_PRIVATE_KEYS, ); if res == 0 { return Err(io::Error::last_os_error()); } ret.set_len(blob.cbData as usize); Ok(ret) } } } /// An iterator over the certificates contained in a `CertStore`, returned by /// `CertStore::iter` pub struct Certs<'a> { store: &'a CertStore, cur: Option, } impl<'a> Iterator for Certs<'a> { type Item = CertContext; fn next(&mut self) -> Option { unsafe { let cur = self.cur.take().map(|p| { let ptr = p.as_inner(); mem::forget(p); ptr }); let cur = cur.unwrap_or(ptr::null_mut()); let next = Cryptography::CertEnumCertificatesInStore(self.store.0, cur); if next.is_null() { self.cur = None; None } else { let next = CertContext::from_inner(next); self.cur = Some(next.clone()); Some(next) } } } } /// A builder type for imports of PKCS #12 archives. #[derive(Default)] pub struct PfxImportOptions { password: Option>, flags: u32, } impl PfxImportOptions { /// Returns a new `PfxImportOptions` with default settings. pub fn new() -> PfxImportOptions { PfxImportOptions::default() } /// Sets the password to be used to decrypt the archive. pub fn password(&mut self, password: &str) -> &mut PfxImportOptions { self.password = Some(password.encode_utf16().chain(Some(0)).collect()); self } /// If set, the private key in the archive will not be persisted. /// /// If not set, private keys are persisted on disk and must be manually deleted. pub fn no_persist_key(&mut self, no_persist_key: bool) -> &mut PfxImportOptions { self.flag(Cryptography::PKCS12_NO_PERSIST_KEY, no_persist_key) } /// If set, all extended properties of the certificate will be imported. pub fn include_extended_properties( &mut self, include_extended_properties: bool, ) -> &mut PfxImportOptions { self.flag( Cryptography::PKCS12_INCLUDE_EXTENDED_PROPERTIES, include_extended_properties, ) } /// If set, the private key in the archive will be exportable. pub fn exportable_private_key( &mut self, exportable_private_key: bool, ) -> &mut PfxImportOptions { self.flag(Cryptography::CRYPT_EXPORTABLE, exportable_private_key) } fn flag(&mut self, flag: u32, set: bool) -> &mut PfxImportOptions { if set { self.flags |= flag; } else { self.flags &= !flag; } self } /// If set, the private keys are stored under the local computer and not under the current user. pub fn machine_keyset(&mut self, machine_keyset: bool) -> &mut PfxImportOptions { self.flag(Cryptography::CRYPT_MACHINE_KEYSET, machine_keyset) } /// Imports certificates from a PKCS #12 archive, returning a `CertStore` containing them. pub fn import(&self, data: &[u8]) -> io::Result { unsafe { let blob = Cryptography::CRYPT_INTEGER_BLOB { cbData: cmp::min(data.len(), u32::max_value() as usize) as u32, pbData: data.as_ptr() as *mut _, }; let password = self.password.as_ref().map_or(ptr::null(), |p| p.as_ptr()); let store = Cryptography::PFXImportCertStore(&blob, password, self.flags); if !store.is_null() { Ok(CertStore(store)) } else { Err(io::Error::last_os_error()) } } } } /// Representation of an in-memory certificate store. /// /// Internally this contains a `CertStore` which this type can be converted to. pub struct Memory(CertStore); impl Memory { /// Creates a new in-memory certificate store which certificates and CTLs /// can be added to. /// /// Initially the returned certificate store contains no certificates. pub fn new() -> io::Result { unsafe { let store = Cryptography::CertOpenStore( Cryptography::CERT_STORE_PROV_MEMORY, Cryptography::CERT_QUERY_ENCODING_TYPE::default(), Cryptography::HCRYPTPROV_LEGACY::default(), Cryptography::CERT_OPEN_STORE_FLAGS::default(), ptr::null_mut(), ); if !store.is_null() { Ok(Memory(CertStore(store))) } else { Err(io::Error::last_os_error()) } } } /// Adds a new certificate to this memory store. /// /// For example the bytes could be a DER-encoded certificate. pub fn add_encoded_certificate(&mut self, cert: &[u8]) -> io::Result { unsafe { let mut cert_context = ptr::null_mut(); let res = Cryptography::CertAddEncodedCertificateToStore( (self.0).0, Cryptography::X509_ASN_ENCODING | Cryptography::PKCS_7_ASN_ENCODING, cert.as_ptr(), cert.len() as u32, Cryptography::CERT_STORE_ADD_ALWAYS, &mut cert_context, ); if res != 0 { Ok(CertContext::from_inner(cert_context)) } else { Err(io::Error::last_os_error()) } } } /// Adds a new CTL to this memory store, in its encoded form. /// /// This can be created through the `ctl_context::Builder` type. pub fn add_encoded_ctl(&mut self, ctl: &[u8]) -> io::Result { unsafe { let mut ctl_context = ptr::null_mut(); let res = Cryptography::CertAddEncodedCTLToStore( (self.0).0, Cryptography::X509_ASN_ENCODING | Cryptography::PKCS_7_ASN_ENCODING, ctl.as_ptr(), ctl.len() as u32, Cryptography::CERT_STORE_ADD_ALWAYS, &mut ctl_context, ); if res != 0 { Ok(CtlContext::from_inner(ctl_context)) } else { Err(io::Error::last_os_error()) } } } /// Consumes this memory store, returning the underlying `CertStore`. pub fn into_store(self) -> CertStore { self.0 } } #[cfg(test)] mod test { use crate::ctl_context::CtlContext; use super::*; #[test] fn load() { let cert = include_bytes!("../test/cert.der"); let mut store = Memory::new().unwrap(); store.add_encoded_certificate(cert).unwrap(); } #[test] fn create_ctl() { let cert = include_bytes!("../test/self-signed.badssl.com.cer"); let mut store = Memory::new().unwrap(); let cert = store.add_encoded_certificate(cert).unwrap(); CtlContext::builder() .certificate(cert) .usage("1.3.6.1.4.1.311.2.2.2") .encode_and_sign() .unwrap(); } #[test] fn pfx_import() { let pfx = include_bytes!("../test/identity.p12"); let store = PfxImportOptions::new() .include_extended_properties(true) .password("mypass") .import(pfx) .unwrap(); assert_eq!(store.certs().count(), 2); let pkeys = store .certs() .filter(|c| { c.private_key() .compare_key(true) .silent(true) .acquire() .is_ok() }) .count(); assert_eq!(pkeys, 1); } }