diff options
Diffstat (limited to 'vendor/schannel/src/cert_store.rs')
-rw-r--r-- | vendor/schannel/src/cert_store.rs | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/vendor/schannel/src/cert_store.rs b/vendor/schannel/src/cert_store.rs new file mode 100644 index 0000000..484eaab --- /dev/null +++ b/vendor/schannel/src/cert_store.rs @@ -0,0 +1,471 @@ +//! 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<CertStore> { + unsafe { + let data = OsStr::new(which) + .encode_wide() + .chain(Some(0)) + .collect::<Vec<_>>(); + 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<CertStore> { + unsafe { + let data = OsStr::new(which) + .encode_wide() + .chain(Some(0)) + .collect::<Vec<_>>(); + 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<CertStore> { + 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::<Vec<_>>() + }); + 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<CertContext> { + 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<Vec<u8>> { + unsafe { + let password = password.encode_utf16().chain(Some(0)).collect::<Vec<_>>(); + 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<CertContext>, +} + +impl<'a> Iterator for Certs<'a> { + type Item = CertContext; + + fn next(&mut self) -> Option<CertContext> { + 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<Vec<u16>>, + 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, + ) + } + + fn flag(&mut self, flag: u32, 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<CertStore> { + 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<Memory> { + 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<CertContext> { + 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<CtlContext> { + 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); + } +} |