@@ -40,17 +40,18 @@ use windows_sys::Win32::{
4040 CERT_E_WRONG_USAGE , CRYPT_E_REVOKED , FILETIME , TRUE ,
4141 } ,
4242 Security :: Cryptography :: {
43- CertAddEncodedCertificateToStore , CertCloseStore , CertFreeCertificateChain ,
44- CertFreeCertificateChainEngine , CertFreeCertificateContext , CertGetCertificateChain ,
45- CertOpenStore , CertSetCertificateContextProperty , CertVerifyCertificateChainPolicy ,
46- HTTPSPolicyCallbackData , AUTHTYPE_SERVER , CERT_CHAIN_CACHE_END_CERT , CERT_CHAIN_CONTEXT ,
43+ CertAddEncodedCertificateToStore , CertCloseStore , CertCreateCertificateChainEngine ,
44+ CertFreeCertificateChain , CertFreeCertificateChainEngine , CertFreeCertificateContext ,
45+ CertGetCertificateChain , CertOpenStore , CertSetCertificateContextProperty ,
46+ CertVerifyCertificateChainPolicy , HTTPSPolicyCallbackData , AUTHTYPE_SERVER ,
47+ CERT_CHAIN_CACHE_END_CERT , CERT_CHAIN_CONTEXT , CERT_CHAIN_ENGINE_CONFIG ,
4748 CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS , CERT_CHAIN_POLICY_PARA ,
4849 CERT_CHAIN_POLICY_SSL , CERT_CHAIN_POLICY_STATUS ,
4950 CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT , CERT_CHAIN_REVOCATION_CHECK_END_CERT ,
5051 CERT_CONTEXT , CERT_OCSP_RESPONSE_PROP_ID , CERT_SET_PROPERTY_IGNORE_PERSIST_ERROR_FLAG ,
5152 CERT_STORE_ADD_ALWAYS , CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG , CERT_STORE_PROV_MEMORY ,
52- CERT_STRONG_SIGN_PARA , CERT_USAGE_MATCH , CRYPT_INTEGER_BLOB , CTL_USAGE ,
53- USAGE_MATCH_TYPE_AND , X509_ASN_ENCODING ,
53+ CERT_STRONG_SIGN_PARA , CERT_TRUST_IS_PARTIAL_CHAIN , CERT_USAGE_MATCH , CRYPT_INTEGER_BLOB ,
54+ CTL_USAGE , USAGE_MATCH_TYPE_AND , X509_ASN_ENCODING ,
5455 } ,
5556} ;
5657
@@ -76,8 +77,6 @@ struct CERT_CHAIN_PARA {
7677}
7778
7879use crate :: verification:: invalid_certificate;
79- #[ cfg( any( test, feature = "ffi-testing" , feature = "dbg" ) ) ]
80- use windows_sys:: Win32 :: Security :: Cryptography :: CERT_CHAIN_ENGINE_CONFIG ;
8180
8281// SAFETY: see method implementation
8382unsafe impl ZeroedWithSize for CERT_CHAIN_PARA {
@@ -110,7 +109,6 @@ unsafe impl ZeroedWithSize for CERT_CHAIN_POLICY_PARA {
110109 }
111110}
112111
113- #[ cfg( any( test, feature = "ffi-testing" , feature = "dbg" ) ) ]
114112// SAFETY: see method implementation
115113unsafe impl ZeroedWithSize for CERT_CHAIN_ENGINE_CONFIG {
116114 fn zeroed_with_size ( ) -> Self {
@@ -273,11 +271,36 @@ impl CertificateStore {
273271 self . engine . map ( |e| e. as_ptr ( ) as isize ) . unwrap_or ( 0 )
274272 }
275273
274+ fn new_with_extra_roots (
275+ roots : & [ pki_types:: CertificateDer < ' static > ] ,
276+ ) -> Result < Self , TlsError > {
277+ let mut inner = Self :: new ( ) ?;
278+ let mut exclusive_store = CertificateStore :: new ( ) ?;
279+ for root in roots {
280+ exclusive_store. add_cert ( root) ?;
281+ }
282+
283+ let mut config = CERT_CHAIN_ENGINE_CONFIG :: zeroed_with_size ( ) ;
284+ config. hExclusiveRoot = exclusive_store. inner . as_ptr ( ) ;
285+
286+ let mut engine = 0 ;
287+ // SAFETY: `engine` is valid to be written to and the config is valid to be read.
288+ let res = unsafe { CertCreateCertificateChainEngine ( & config, & mut engine) } ;
289+
290+ #[ allow( clippy:: as_conversions) ]
291+ let engine = call_with_last_error ( || match NonNull :: new ( engine as * mut c_void ) {
292+ Some ( c) if res == TRUE => Some ( c) ,
293+ _ => None ,
294+ } ) ?;
295+ inner. engine = Some ( engine) ;
296+
297+ Ok ( inner)
298+ }
299+
276300 #[ cfg( any( test, feature = "ffi-testing" , feature = "dbg" ) ) ]
277301 fn new_with_fake_root ( root : & [ u8 ] ) -> Result < Self , TlsError > {
278302 use windows_sys:: Win32 :: Security :: Cryptography :: {
279- CertCreateCertificateChainEngine , CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL ,
280- CERT_CHAIN_ENABLE_CACHE_AUTO_UPDATE ,
303+ CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL , CERT_CHAIN_ENABLE_CACHE_AUTO_UPDATE ,
281304 } ;
282305
283306 let mut inner = Self :: new ( ) ?;
@@ -442,6 +465,9 @@ pub struct Verifier {
442465 #[ cfg( any( test, feature = "ffi-testing" , feature = "dbg" ) ) ]
443466 test_only_root_ca_override : Option < Vec < u8 > > ,
444467 pub ( super ) crypto_provider : OnceCell < Arc < CryptoProvider > > ,
468+ /// Extra trust anchors to add to the verifier above and beyond those provided by
469+ /// the system-provided trust stores.
470+ extra_roots : Vec < pki_types:: CertificateDer < ' static > > ,
445471}
446472
447473impl Verifier {
@@ -456,6 +482,22 @@ impl Verifier {
456482 #[ cfg( any( test, feature = "ffi-testing" , feature = "dbg" ) ) ]
457483 test_only_root_ca_override : None ,
458484 crypto_provider : OnceCell :: new ( ) ,
485+ extra_roots : Vec :: new ( ) ,
486+ }
487+ }
488+
489+ /// Creates a new instance of a TLS certificate verifier that utilizes the
490+ /// Windows certificate facilities and augmented by the provided extra root certificates.
491+ ///
492+ /// A [`CryptoProvider`] must be set with
493+ /// [`set_provider`][Verifier::set_provider]/[`with_provider`][Verifier::with_provider] or
494+ /// [`CryptoProvider::install_default`] before the verifier can be used.
495+ pub fn new_with_extra_roots ( roots : Vec < pki_types:: CertificateDer < ' static > > ) -> Self {
496+ Self {
497+ #[ cfg( any( test, feature = "ffi-testing" , feature = "dbg" ) ) ]
498+ test_only_root_ca_override : None ,
499+ crypto_provider : OnceCell :: new ( ) ,
500+ extra_roots : roots. into_iter ( ) . map ( Into :: into) . collect :: < Vec < _ > > ( ) ,
459501 }
460502 }
461503
@@ -465,6 +507,7 @@ impl Verifier {
465507 Self {
466508 test_only_root_ca_override : Some ( root. into ( ) ) ,
467509 crypto_provider : OnceCell :: new ( ) ,
510+ extra_roots : Vec :: new ( ) ,
468511 }
469512 }
470513
@@ -521,7 +564,27 @@ impl Verifier {
521564 . chain ( Some ( 0 ) )
522565 . collect ( ) ;
523566
524- let cert_chain = store. new_chain_in ( & primary_cert, now) ?;
567+ let mut cert_chain = store. new_chain_in ( & primary_cert, now) ?;
568+
569+ // We only use `TrustStatus` here because it hasn't had verification performed on it.
570+ // SAFETY: The pointer is guaranteed to be non-null.
571+ let is_partial_chain = unsafe { * cert_chain. inner . as_ptr ( ) }
572+ . TrustStatus
573+ . dwErrorStatus
574+ & CERT_TRUST_IS_PARTIAL_CHAIN
575+ != 0 ;
576+
577+ // If we have extra roots and building the chain gave us an error, we try to build a
578+ // new one with the extra roots.
579+ if !self . extra_roots . is_empty ( ) && is_partial_chain {
580+ let mut store = CertificateStore :: new_with_extra_roots ( & self . extra_roots ) ?;
581+
582+ for cert in intermediate_certs. iter ( ) . copied ( ) {
583+ store. add_cert ( cert) ?;
584+ }
585+
586+ cert_chain = store. new_chain_in ( & primary_cert, now) ?;
587+ }
525588
526589 let status = cert_chain. verify_chain_policy ( server) ?;
527590
0 commit comments