blob: 3f416d4a5dabfd6baee02a0fd37fb3e5ecb800e3 [file] [log] [blame]
//! 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<CertStore> {
unsafe {
let data = OsStr::new(which)
.encode_wide()
.chain(Some(0))
.collect::<Vec<_>>();
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<CertStore> {
unsafe {
let data = OsStr::new(which)
.encode_wide()
.chain(Some(0))
.collect::<Vec<_>>();
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<CertStore> {
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::<Vec<_>>()
});
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<CertContext> {
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<Vec<u8>> {
unsafe {
let password = password.encode_utf16().chain(Some(0)).collect::<Vec<_>>();
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<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 = 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<Vec<u16>>,
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<CertStore> {
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<Memory> {
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<CertContext> {
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<CtlContext> {
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);
}
}