//! 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 winapi::shared::minwindef as winapi; use winapi::shared::ntdef; use winapi::um::wincrypt; 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(wincrypt::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 { wincrypt::CertCloseStore(self.0, 0); } } } impl Clone for CertStore { fn clone(&self) -> CertStore { unsafe { CertStore(wincrypt::CertDuplicateStore(self.0)) } } } inner!(CertStore, wincrypt::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 = wincrypt::CERT_STORE_ADD_ALWAYS as isize, /// If a matching certificate or a link to a matching certificate exists, /// the operation fails. New = wincrypt::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 = wincrypt::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 = wincrypt::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 = wincrypt::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 = wincrypt::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 = wincrypt::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 = wincrypt::CertOpenStore(wincrypt::CERT_STORE_PROV_SYSTEM_W as ntdef::LPCSTR, 0, 0, wincrypt::CERT_SYSTEM_STORE_CURRENT_USER, data.as_ptr() as *mut _); if store.is_null() { Err(io::Error::last_os_error()) } else { Ok(CertStore(store)) } } } /// 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 = wincrypt::CertOpenStore(wincrypt::CERT_STORE_PROV_SYSTEM_W as ntdef::LPCSTR, 0, 0, wincrypt::CERT_SYSTEM_STORE_LOCAL_MACHINE, data.as_ptr() as *mut _); if store.is_null() { Err(io::Error::last_os_error()) } else { Ok(CertStore(store)) } } } /// 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 mut blob = wincrypt::CRYPT_INTEGER_BLOB { cbData: data.len() as winapi::DWORD, 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 = wincrypt::PFXImportCertStore(&mut blob, password, 0); if res.is_null() { Err(io::Error::last_os_error()) } else { Ok(CertStore(res)) } } } /// 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 winapi::DWORD; let mut ret = ptr::null(); let res = wincrypt::CertAddCertificateContextToStore(self.0, cx.as_inner(), how, &mut ret); if res != winapi::TRUE { 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 = wincrypt::CRYPT_DATA_BLOB { cbData: 0, pbData: 0 as *mut _, }; let res = wincrypt::PFXExportCertStore(self.0, &mut blob, password.as_ptr(), wincrypt::EXPORT_PRIVATE_KEYS); if res != winapi::TRUE { 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 = wincrypt::PFXExportCertStore(self.0, &mut blob, password.as_ptr(), wincrypt::EXPORT_PRIVATE_KEYS); if res != winapi::TRUE { 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 = wincrypt::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: winapi::DWORD, } 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(wincrypt::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(wincrypt::PKCS12_INCLUDE_EXTENDED_PROPERTIES, include_extended_properties) } fn flag(&mut self, flag: winapi::DWORD, set: bool) -> &mut PfxImportOptions { if set { self.flags |= flag; } else { self.flags &= !flag; } self } /// Imports certificates from a PKCS #12 archive, returning a `CertStore` containing them. pub fn import(&self, data: &[u8]) -> io::Result { unsafe { let mut blob = wincrypt::CRYPT_DATA_BLOB { cbData: cmp::min(data.len(), winapi::DWORD::max_value() as usize) as winapi::DWORD, pbData: data.as_ptr() as *const _ as *mut _, }; let password = self.password.as_ref().map_or(ptr::null(), |p| p.as_ptr()); let store = wincrypt::PFXImportCertStore(&mut blob, password, self.flags); if store.is_null() { return Err(io::Error::last_os_error()); } Ok(CertStore(store)) } } } /// 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 = wincrypt::CertOpenStore(wincrypt::CERT_STORE_PROV_MEMORY as ntdef::LPCSTR, 0, 0, 0, ptr::null_mut()); if store.is_null() { Err(io::Error::last_os_error()) } else { Ok(Memory(CertStore(store))) } } } /// 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(); let res = wincrypt::CertAddEncodedCertificateToStore((self.0).0, wincrypt::X509_ASN_ENCODING | wincrypt::PKCS_7_ASN_ENCODING, cert.as_ptr() as *const _, cert.len() as winapi::DWORD, wincrypt::CERT_STORE_ADD_ALWAYS, &mut cert_context); if res == winapi::TRUE { 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(); let res = wincrypt::CertAddEncodedCTLToStore((self.0).0, wincrypt::X509_ASN_ENCODING | wincrypt::PKCS_7_ASN_ENCODING, ctl.as_ptr() as *const _, ctl.len() as winapi::DWORD, wincrypt::CERT_STORE_ADD_ALWAYS, &mut ctl_context); if res == winapi::TRUE { 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 super::*; use crate::ctl_context::CtlContext; #[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); } }