| //! Bindings to winapi's `PCCERT_CONTEXT` APIs. |
| |
| use std::ffi::{CStr, OsString}; |
| use std::io; |
| use std::mem; |
| use std::os::windows::prelude::*; |
| use std::ptr; |
| use std::slice; |
| use winapi::shared::minwindef as winapi; |
| use winapi::shared::ntdef; |
| use winapi::shared::winerror; |
| use winapi::um::wincrypt; |
| |
| use crate::Inner; |
| use crate::ncrypt_key::NcryptKey; |
| use crate::crypt_prov::{CryptProv, ProviderType}; |
| use crate::cert_store::CertStore; |
| |
| /// A supported hashing algorithm |
| pub struct HashAlgorithm(winapi::DWORD, usize); |
| |
| #[allow(missing_docs)] |
| impl HashAlgorithm { |
| pub fn md5() -> HashAlgorithm { |
| HashAlgorithm(wincrypt::CALG_MD5, 16) |
| } |
| |
| pub fn sha1() -> HashAlgorithm{ |
| HashAlgorithm(wincrypt::CALG_SHA1, 20) |
| } |
| |
| pub fn sha256() -> HashAlgorithm { |
| HashAlgorithm(wincrypt::CALG_SHA_256, 32) |
| } |
| |
| pub fn sha384() -> HashAlgorithm { |
| HashAlgorithm(wincrypt::CALG_SHA_384, 48) |
| } |
| |
| pub fn sha512() -> HashAlgorithm { |
| HashAlgorithm(wincrypt::CALG_SHA_512, 64) |
| } |
| } |
| |
| /// Wrapper of a winapi certificate, or a `PCCERT_CONTEXT`. |
| #[derive(Debug)] |
| pub struct CertContext(wincrypt::PCCERT_CONTEXT); |
| |
| unsafe impl Sync for CertContext {} |
| unsafe impl Send for CertContext {} |
| |
| impl Drop for CertContext { |
| fn drop(&mut self) { |
| unsafe { |
| wincrypt::CertFreeCertificateContext(self.0); |
| } |
| } |
| } |
| |
| impl Clone for CertContext { |
| fn clone(&self) -> CertContext { |
| unsafe { CertContext(wincrypt::CertDuplicateCertificateContext(self.0)) } |
| } |
| } |
| |
| inner!(CertContext, wincrypt::PCCERT_CONTEXT); |
| |
| impl CertContext { |
| /// Decodes a DER-formatted X509 certificate. |
| pub fn new(data: &[u8]) -> io::Result<CertContext> { |
| let ret = unsafe { |
| wincrypt::CertCreateCertificateContext(wincrypt::X509_ASN_ENCODING | |
| wincrypt::PKCS_7_ASN_ENCODING, |
| data.as_ptr(), |
| data.len() as winapi::DWORD) |
| }; |
| if ret.is_null() { |
| Err(io::Error::last_os_error()) |
| } else { |
| Ok(CertContext(ret)) |
| } |
| } |
| |
| /// Get certificate in binary DER form |
| pub fn to_der<'a>(&'a self) -> &'a [u8] { |
| self.get_encoded_bytes() |
| } |
| |
| /// Certificate subject public key info |
| pub fn subject_public_key_info_der(&self) -> io::Result<Vec<u8>> { |
| unsafe { |
| let mut len:u32 = 0; |
| let ok = wincrypt::CryptEncodeObjectEx(wincrypt::X509_ASN_ENCODING, |
| wincrypt::CERT_INFO_SUBJECT_PUBLIC_KEY_INFO_FLAG as *const u32 as *const _, |
| &(*(*self.0).pCertInfo).SubjectPublicKeyInfo as *const wincrypt::CERT_PUBLIC_KEY_INFO as _, |
| 0, |
| ptr::null_mut(), |
| ptr::null_mut(), |
| &mut len as *mut _); |
| if ok != winapi::TRUE { |
| return Err(io::Error::last_os_error()); |
| } |
| if len > 0 { |
| let mut buf = vec![0; len as usize]; |
| let ok = wincrypt::CryptEncodeObjectEx(wincrypt::X509_ASN_ENCODING, |
| wincrypt::CERT_INFO_SUBJECT_PUBLIC_KEY_INFO_FLAG as *const u32 as *const _, |
| &(*(*self.0).pCertInfo).SubjectPublicKeyInfo as *const wincrypt::CERT_PUBLIC_KEY_INFO as _, |
| 0, |
| ptr::null_mut(), |
| buf.as_mut_ptr() as _, |
| &mut len as *mut _); |
| if ok != winapi::TRUE { |
| return Err(io::Error::last_os_error()); |
| } |
| return Ok(buf); |
| } |
| } |
| Err(io::Error::last_os_error()) |
| } |
| |
| /// Decodes a PEM-formatted X509 certificate. |
| pub fn from_pem(pem: &str) -> io::Result<CertContext> { |
| unsafe { |
| assert!(pem.len() <= winapi::DWORD::max_value() as usize); |
| |
| let mut len = 0; |
| let ok = wincrypt::CryptStringToBinaryA(pem.as_ptr() as ntdef::LPCSTR, |
| pem.len() as winapi::DWORD, |
| wincrypt::CRYPT_STRING_BASE64HEADER, |
| ptr::null_mut(), |
| &mut len, |
| ptr::null_mut(), |
| ptr::null_mut()); |
| if ok != winapi::TRUE { |
| return Err(io::Error::last_os_error()); |
| } |
| |
| let mut buf = vec![0; len as usize]; |
| let ok = wincrypt::CryptStringToBinaryA(pem.as_ptr() as ntdef::LPCSTR, |
| pem.len() as winapi::DWORD, |
| wincrypt::CRYPT_STRING_BASE64HEADER, |
| buf.as_mut_ptr(), |
| &mut len, |
| ptr::null_mut(), |
| ptr::null_mut()); |
| if ok != winapi::TRUE { |
| return Err(io::Error::last_os_error()); |
| } |
| |
| CertContext::new(&buf) |
| } |
| } |
| |
| /// Get certificate as PEM-formatted X509 certificate. |
| pub fn to_pem(&self) -> io::Result<String> { |
| unsafe { |
| let mut len = 0; |
| let ok = wincrypt::CryptBinaryToStringA( |
| (*self.0).pbCertEncoded, |
| (*self.0).cbCertEncoded, |
| wincrypt::CRYPT_STRING_BASE64HEADER, |
| ptr::null_mut(), |
| &mut len, |
| ); |
| if ok != winapi::TRUE { |
| return Err(io::Error::last_os_error()); |
| } |
| |
| let mut buf = vec![0; len as usize]; |
| let ok = wincrypt::CryptBinaryToStringA( |
| (*self.0).pbCertEncoded, |
| (*self.0).cbCertEncoded, |
| wincrypt::CRYPT_STRING_BASE64HEADER, |
| buf.as_mut_ptr(), |
| &mut len, |
| ); |
| if ok != winapi::TRUE { |
| return Err(io::Error::last_os_error()); |
| } |
| |
| Ok(CStr::from_ptr(buf.as_ptr()).to_string_lossy().into_owned()) |
| } |
| } |
| |
| /// Returns a hash of this certificate |
| pub fn fingerprint(&self, alg: HashAlgorithm) -> io::Result<Vec<u8>> { |
| unsafe { |
| let mut buf = vec![0u8; alg.1]; |
| let mut len = buf.len() as winapi::DWORD; |
| |
| let ret = wincrypt::CryptHashCertificate(0, |
| alg.0, |
| 0, |
| (*self.0).pbCertEncoded, |
| (*self.0).cbCertEncoded, |
| buf.as_mut_ptr(), |
| &mut len); |
| |
| if ret != winapi::TRUE { |
| return Err(io::Error::last_os_error()); |
| } |
| Ok(buf) |
| } |
| } |
| |
| /// Returns the sha1 hash of this certificate |
| /// |
| /// The sha1 is returned as a 20-byte array representing the bits of the |
| /// sha1 hash. |
| #[deprecated(note = "please use fingerprint instead")] |
| pub fn sha1(&self) -> io::Result<[u8; 20]> { |
| let mut out = [0u8; 20]; |
| out.copy_from_slice(&self.fingerprint(HashAlgorithm::sha1())?); |
| Ok(out) |
| } |
| |
| /// Returns the `<SIGNATURE>/<HASH>` string representing the certificate |
| /// signature. |
| /// |
| /// The `<SIGNATURE>` value identifies the CNG public key |
| /// algorithm. The `<HASH>` value identifies the CNG hash algorithm. |
| /// |
| /// Common examples are: |
| /// |
| /// * `RSA/SHA1` |
| /// * `RSA/SHA256` |
| /// * `ECDSA/SHA256` |
| pub fn sign_hash_algorithms(&self) -> io::Result<String> { |
| self.get_string(wincrypt::CERT_SIGN_HASH_CNG_ALG_PROP_ID) |
| } |
| |
| /// Returns the signature hash. |
| pub fn signature_hash(&self) -> io::Result<Vec<u8>> { |
| self.get_bytes(wincrypt::CERT_SIGNATURE_HASH_PROP_ID) |
| } |
| |
| /// Returns the property displayed by the certificate UI. This property |
| /// allows the user to describe the certificate's use. |
| pub fn description(&self) -> io::Result<Vec<u8>> { |
| self.get_bytes(wincrypt::CERT_DESCRIPTION_PROP_ID) |
| } |
| |
| /// Returns a string that contains the display name for the certificate. |
| pub fn friendly_name(&self) -> io::Result<String> { |
| self.get_string(wincrypt::CERT_FRIENDLY_NAME_PROP_ID) |
| } |
| |
| /// Configures the string that contains the display name for this |
| /// certificate. |
| pub fn set_friendly_name(&self, name: &str) -> io::Result<()> { |
| self.set_string(wincrypt::CERT_FRIENDLY_NAME_PROP_ID, name) |
| } |
| |
| /// Verifies the time validity of this certificate relative to the system's |
| /// current time. |
| pub fn is_time_valid(&self) -> io::Result<bool> { |
| let ret = unsafe { wincrypt::CertVerifyTimeValidity(ptr::null_mut(), (*self.0).pCertInfo) }; |
| Ok(ret == 0) |
| } |
| |
| /// Returns a builder used to acquire the private key corresponding to this certificate. |
| pub fn private_key<'a>(&'a self) -> AcquirePrivateKeyOptions<'a> { |
| AcquirePrivateKeyOptions { |
| cert: self, |
| flags: 0, |
| } |
| } |
| |
| /// Deletes this certificate from its certificate store. |
| pub fn delete(self) -> io::Result<()> { |
| unsafe { |
| let ret = wincrypt::CertDeleteCertificateFromStore(self.0); |
| mem::forget(self); |
| if ret == winapi::TRUE { |
| Ok(()) |
| } else { |
| Err(io::Error::last_os_error()) |
| } |
| } |
| } |
| |
| /// Returns a builder used to set the private key associated with this certificate. |
| pub fn set_key_prov_info<'a>(&'a self) -> SetKeyProvInfo<'a> { |
| SetKeyProvInfo { |
| cert: self, |
| container: None, |
| provider: None, |
| type_: 0, |
| flags: 0, |
| key_spec: 0, |
| } |
| } |
| |
| /// Returns the valid uses for this certificate |
| pub fn valid_uses(&self) -> io::Result<ValidUses> { |
| unsafe { |
| let mut buf_len = 0; |
| let ok = wincrypt::CertGetEnhancedKeyUsage(self.0, 0, ptr::null_mut(), &mut buf_len); |
| |
| if ok != winapi::TRUE { |
| return Err(io::Error::last_os_error()); |
| } |
| |
| let mut buf = vec![0u8; buf_len as usize]; |
| let cert_enhkey_usage = buf.as_mut_ptr() as *mut wincrypt::CERT_ENHKEY_USAGE; |
| |
| let ok = wincrypt::CertGetEnhancedKeyUsage(self.0, 0, cert_enhkey_usage, &mut buf_len); |
| if ok != winapi::TRUE { |
| return Err(io::Error::last_os_error()); |
| } |
| |
| let use_cnt = (*cert_enhkey_usage).cUsageIdentifier; |
| if use_cnt == 0 { |
| let last_error = io::Error::last_os_error(); |
| match last_error.raw_os_error() { |
| Some(winerror::CRYPT_E_NOT_FOUND) => return Ok(ValidUses::All), |
| Some(0) => (), |
| _ => return Err(last_error), |
| }; |
| } |
| |
| let mut oids: Vec<String> = Vec::with_capacity(use_cnt as usize); |
| for i in 0..use_cnt { |
| let oid_ptr = (*cert_enhkey_usage).rgpszUsageIdentifier; |
| oids.push( |
| CStr::from_ptr(*(oid_ptr.offset(i as isize))) |
| .to_string_lossy() |
| .into_owned(), |
| ); |
| } |
| Ok(ValidUses::Oids(oids)) |
| } |
| } |
| |
| /// For a remote certificate, returns a certificate store containing any intermediate |
| /// certificates provided by the remote sender. |
| pub fn cert_store(&self) -> Option<CertStore> { |
| unsafe { |
| let chain = (*self.0).hCertStore; |
| if chain.is_null() { |
| None |
| } else { |
| Some(CertStore::from_inner(wincrypt::CertDuplicateStore(chain))) |
| } |
| } |
| } |
| |
| fn get_encoded_bytes<'a>(&'a self) -> &'a [u8] { |
| unsafe { |
| let cert_ctx = *self.0; |
| slice::from_raw_parts(cert_ctx.pbCertEncoded, cert_ctx.cbCertEncoded as usize) |
| } |
| } |
| |
| fn get_bytes(&self, prop: winapi::DWORD) -> io::Result<Vec<u8>> { |
| unsafe { |
| let mut len = 0; |
| let ret = |
| wincrypt::CertGetCertificateContextProperty(self.0, prop, ptr::null_mut(), &mut len); |
| if ret != winapi::TRUE { |
| return Err(io::Error::last_os_error()); |
| } |
| |
| let mut buf = vec![0u8; len as usize]; |
| let ret = wincrypt::CertGetCertificateContextProperty(self.0, |
| prop, |
| buf.as_mut_ptr() as winapi::LPVOID, |
| &mut len); |
| if ret != winapi::TRUE { |
| return Err(io::Error::last_os_error()); |
| } |
| Ok(buf) |
| } |
| } |
| |
| fn get_string(&self, prop: winapi::DWORD) -> io::Result<String> { |
| unsafe { |
| let mut len = 0; |
| let ret = |
| wincrypt::CertGetCertificateContextProperty(self.0, prop, ptr::null_mut(), &mut len); |
| if ret != winapi::TRUE { |
| return Err(io::Error::last_os_error()); |
| } |
| |
| // Divide by 2 b/c `len` is the byte length, but we're allocating |
| // u16 pairs which are 2 bytes each. |
| let amt = (len / 2) as usize; |
| let mut buf = vec![0u16; amt]; |
| let ret = wincrypt::CertGetCertificateContextProperty(self.0, |
| prop, |
| buf.as_mut_ptr() as winapi::LPVOID, |
| &mut len); |
| if ret != winapi::TRUE { |
| return Err(io::Error::last_os_error()); |
| } |
| |
| // Chop off the trailing nul byte |
| Ok(OsString::from_wide(&buf[..amt - 1]).into_string().unwrap()) |
| } |
| } |
| |
| fn set_string(&self, prop: winapi::DWORD, s: &str) -> io::Result<()> { |
| unsafe { |
| let data = s.encode_utf16().chain(Some(0)).collect::<Vec<_>>(); |
| let data = wincrypt::CRYPT_DATA_BLOB { |
| cbData: (data.len() * 2) as winapi::DWORD, |
| pbData: data.as_ptr() as *mut _, |
| }; |
| let ret = wincrypt::CertSetCertificateContextProperty(self.0, |
| prop, |
| 0, |
| &data as *const _ as *const _); |
| if ret != winapi::TRUE { |
| Err(io::Error::last_os_error()) |
| } else { |
| Ok(()) |
| } |
| } |
| } |
| } |
| |
| impl PartialEq for CertContext { |
| fn eq(&self, other: &CertContext) -> bool { |
| self.get_encoded_bytes() == other.get_encoded_bytes() |
| } |
| } |
| |
| /// A builder type for certificate private key lookup. |
| pub struct AcquirePrivateKeyOptions<'a> { |
| cert: &'a CertContext, |
| flags: winapi::DWORD, |
| } |
| |
| impl<'a> AcquirePrivateKeyOptions<'a> { |
| /// If set, the certificate's public key will be compared with the private key to ensure a |
| /// match. |
| pub fn compare_key(&mut self, compare_key: bool) -> &mut AcquirePrivateKeyOptions<'a> { |
| self.flag(wincrypt::CRYPT_ACQUIRE_COMPARE_KEY_FLAG, compare_key) |
| } |
| |
| /// If set, the lookup will not display any user interface, even if that causes the lookup to |
| /// fail. |
| pub fn silent(&mut self, silent: bool) -> &mut AcquirePrivateKeyOptions<'a> { |
| self.flag(wincrypt::CRYPT_ACQUIRE_SILENT_FLAG, silent) |
| } |
| |
| fn flag(&mut self, flag: winapi::DWORD, set: bool) -> &mut AcquirePrivateKeyOptions<'a> { |
| if set { |
| self.flags |= flag; |
| } else { |
| self.flags &= !flag; |
| } |
| self |
| } |
| |
| /// Acquires the private key handle. |
| pub fn acquire(&self) -> io::Result<PrivateKey> { |
| unsafe { |
| let flags = self.flags | wincrypt::CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG; |
| let mut handle = 0; |
| let mut spec = 0; |
| let mut free = winapi::FALSE; |
| let res = wincrypt::CryptAcquireCertificatePrivateKey(self.cert.0, |
| flags, |
| ptr::null_mut(), |
| &mut handle, |
| &mut spec, |
| &mut free); |
| if res != winapi::TRUE { |
| return Err(io::Error::last_os_error()); |
| } |
| assert!(free == winapi::TRUE); |
| if spec & wincrypt::CERT_NCRYPT_KEY_SPEC != 0 { |
| Ok(PrivateKey::NcryptKey(NcryptKey::from_inner(handle))) |
| } else { |
| Ok(PrivateKey::CryptProv(CryptProv::from_inner(handle))) |
| } |
| } |
| } |
| } |
| |
| /// The private key associated with a certificate context. |
| pub enum PrivateKey { |
| /// A CryptoAPI provider. |
| CryptProv(CryptProv), |
| /// A CNG provider. |
| NcryptKey(NcryptKey), |
| } |
| |
| /// A builder used to set the private key associated with a certificate. |
| pub struct SetKeyProvInfo<'a> { |
| cert: &'a CertContext, |
| container: Option<Vec<u16>>, |
| provider: Option<Vec<u16>>, |
| type_: winapi::DWORD, |
| flags: winapi::DWORD, |
| key_spec: winapi::DWORD, |
| } |
| |
| impl<'a> SetKeyProvInfo<'a> { |
| /// The name of the key container. |
| /// |
| /// If `type_` is not provided, this specifies the name of the key withing |
| /// the CNG key storage provider. |
| pub fn container(&mut self, container: &str) -> &mut SetKeyProvInfo<'a> { |
| self.container = Some(container.encode_utf16().chain(Some(0)).collect()); |
| self |
| } |
| |
| /// The name of the CSP. |
| /// |
| /// If `type_` is not provided, this contains the name of the CNG key |
| /// storage provider. |
| pub fn provider(&mut self, provider: &str) -> &mut SetKeyProvInfo<'a> { |
| self.provider = Some(provider.encode_utf16().chain(Some(0)).collect()); |
| self |
| } |
| |
| /// Sets the CSP type. |
| /// |
| /// If not provided, the key container is one of the CNG key storage |
| /// providers. |
| pub fn type_(&mut self, type_: ProviderType) -> &mut SetKeyProvInfo<'a> { |
| self.type_ = type_.as_raw(); |
| self |
| } |
| |
| /// If set, the handle to the key provider can be kept open for subsequent |
| /// calls to cryptographic functions. |
| pub fn keep_open(&mut self, keep_open: bool) -> &mut SetKeyProvInfo<'a> { |
| self.flag(wincrypt::CERT_SET_KEY_PROV_HANDLE_PROP_ID, keep_open) |
| } |
| |
| /// If set, the key container contains machine keys. |
| pub fn machine_keyset(&mut self, machine_keyset: bool) -> &mut SetKeyProvInfo<'a> { |
| self.flag(wincrypt::CRYPT_MACHINE_KEYSET, machine_keyset) |
| } |
| |
| /// If set, the key container will attempt to open keys without any user |
| /// interface prompts. |
| pub fn silent(&mut self, silent: bool) -> &mut SetKeyProvInfo<'a> { |
| self.flag(wincrypt::CRYPT_SILENT, silent) |
| } |
| |
| fn flag(&mut self, flag: winapi::DWORD, on: bool) -> &mut SetKeyProvInfo<'a> { |
| if on { |
| self.flags |= flag; |
| } else { |
| self.flags &= !flag; |
| } |
| self |
| } |
| |
| /// The specification of the private key to retrieve. |
| pub fn key_spec(&mut self, key_spec: KeySpec) -> &mut SetKeyProvInfo<'a> { |
| self.key_spec = key_spec.0; |
| self |
| } |
| |
| /// Sets the private key for this certificate. |
| pub fn set(&mut self) -> io::Result<()> { |
| unsafe { |
| let container = self.container.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null()); |
| let provider = self.provider.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null()); |
| |
| let info = wincrypt::CRYPT_KEY_PROV_INFO { |
| pwszContainerName: container as *mut _, |
| pwszProvName: provider as *mut _, |
| dwProvType: self.type_, |
| dwFlags: self.flags, |
| cProvParam: 0, |
| rgProvParam: ptr::null_mut(), |
| dwKeySpec: self.key_spec, |
| }; |
| |
| let res = |
| wincrypt::CertSetCertificateContextProperty(self.cert.0, |
| wincrypt::CERT_KEY_PROV_INFO_PROP_ID, |
| 0, |
| &info as *const _ as *const _); |
| if res == winapi::TRUE { |
| Ok(()) |
| } else { |
| Err(io::Error::last_os_error()) |
| } |
| } |
| } |
| } |
| |
| /// The specification of a private key. |
| #[derive(Copy, Clone)] |
| pub struct KeySpec(winapi::DWORD); |
| |
| impl KeySpec { |
| /// A key used to encrypt/decrypt session keys. |
| pub fn key_exchange() -> KeySpec { |
| KeySpec(wincrypt::AT_KEYEXCHANGE) |
| } |
| |
| /// A key used to create and verify digital signatures. |
| pub fn signature() -> KeySpec { |
| KeySpec(wincrypt::AT_SIGNATURE) |
| } |
| } |
| |
| /// Valid uses of a Certificate - All, or specific OIDs |
| pub enum ValidUses { |
| /// Certificate is valid for all uses |
| All, |
| |
| /// Certificate is valid for uses specified. No entries means that the certificate |
| /// has no valid uses. |
| Oids(Vec<String>), |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| |
| #[test] |
| fn decode() { |
| let der = include_bytes!("../test/cert.der"); |
| let pem = include_str!("../test/cert.pem"); |
| |
| let der = CertContext::new(der).unwrap(); |
| let pem = CertContext::from_pem(pem).unwrap(); |
| assert_eq!(der, pem); |
| } |
| |
| #[test] |
| fn certcontext_to_der() { |
| let der = include_bytes!("../test/cert.der"); |
| let cert = CertContext::new(der).unwrap(); |
| let der2 = CertContext::to_der(&cert); |
| assert_eq!(der as &[u8], der2); |
| } |
| |
| #[test] |
| fn certcontext_to_pem() { |
| let der = include_bytes!("../test/cert.der"); |
| let pem1 = include_str!("../test/cert.pem").replace("\r", ""); |
| |
| let der = CertContext::new(der).unwrap(); |
| let pem2 = CertContext::to_pem(&der).unwrap().replace("\r", ""); |
| assert_eq!(pem1, pem2); |
| } |
| |
| #[test] |
| fn fingerprint() { |
| let der = include_bytes!("../test/cert.der"); |
| let pem = include_str!("../test/cert.pem"); |
| |
| let der = CertContext::new(der).unwrap(); |
| let pem = CertContext::from_pem(pem).unwrap(); |
| |
| let hash = der.fingerprint(HashAlgorithm::sha1()).unwrap(); |
| assert_eq!(hash, vec![ |
| 0x59, 0x17, 0x2D, 0x93, 0x13, 0xE8, 0x44, 0x59, 0xBC, 0xFF, |
| 0x27, 0xF9, 0x67, 0xE7, 0x9E, 0x6E, 0x92, 0x17, 0xE5, 0x84 |
| ]); |
| assert_eq!(hash, pem.fingerprint(HashAlgorithm::sha1()).unwrap()); |
| |
| let hash = der.fingerprint(HashAlgorithm::sha256()).unwrap(); |
| assert_eq!(hash, vec![ |
| 0x47, 0x12, 0xB9, 0x39, 0xFB, 0xCB, 0x42, 0xA6, 0xB5, 0x10, |
| 0x1B, 0x42, 0x13, 0x9A, 0x25, 0xB1, 0x4F, 0x81, 0xB4, 0x18, |
| 0xFA, 0xCA, 0xBD, 0x37, 0x87, 0x46, 0xF1, 0x2F, 0x85, 0xCC, |
| 0x65, 0x44 |
| ]); |
| assert_eq!(hash, pem.fingerprint(HashAlgorithm::sha256()).unwrap()); |
| } |
| } |