@@ -40,17 +40,18 @@ use windows_sys::Win32::{
40
40
CERT_E_WRONG_USAGE , CRYPT_E_REVOKED , FILETIME , TRUE ,
41
41
} ,
42
42
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 ,
47
48
CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS , CERT_CHAIN_POLICY_PARA ,
48
49
CERT_CHAIN_POLICY_SSL , CERT_CHAIN_POLICY_STATUS ,
49
50
CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT , CERT_CHAIN_REVOCATION_CHECK_END_CERT ,
50
51
CERT_CONTEXT , CERT_OCSP_RESPONSE_PROP_ID , CERT_SET_PROPERTY_IGNORE_PERSIST_ERROR_FLAG ,
51
52
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 ,
54
55
} ,
55
56
} ;
56
57
@@ -76,8 +77,6 @@ struct CERT_CHAIN_PARA {
76
77
}
77
78
78
79
use crate :: verification:: invalid_certificate;
79
- #[ cfg( any( test, feature = "ffi-testing" , feature = "dbg" ) ) ]
80
- use windows_sys:: Win32 :: Security :: Cryptography :: CERT_CHAIN_ENGINE_CONFIG ;
81
80
82
81
// SAFETY: see method implementation
83
82
unsafe impl ZeroedWithSize for CERT_CHAIN_PARA {
@@ -110,7 +109,6 @@ unsafe impl ZeroedWithSize for CERT_CHAIN_POLICY_PARA {
110
109
}
111
110
}
112
111
113
- #[ cfg( any( test, feature = "ffi-testing" , feature = "dbg" ) ) ]
114
112
// SAFETY: see method implementation
115
113
unsafe impl ZeroedWithSize for CERT_CHAIN_ENGINE_CONFIG {
116
114
fn zeroed_with_size ( ) -> Self {
@@ -273,11 +271,36 @@ impl CertificateStore {
273
271
self . engine . map ( |e| e. as_ptr ( ) as isize ) . unwrap_or ( 0 )
274
272
}
275
273
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
+
276
300
#[ cfg( any( test, feature = "ffi-testing" , feature = "dbg" ) ) ]
277
301
fn new_with_fake_root ( root : & [ u8 ] ) -> Result < Self , TlsError > {
278
302
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 ,
281
304
} ;
282
305
283
306
let mut inner = Self :: new ( ) ?;
@@ -442,6 +465,9 @@ pub struct Verifier {
442
465
#[ cfg( any( test, feature = "ffi-testing" , feature = "dbg" ) ) ]
443
466
test_only_root_ca_override : Option < Vec < u8 > > ,
444
467
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 > > ,
445
471
}
446
472
447
473
impl Verifier {
@@ -456,6 +482,22 @@ impl Verifier {
456
482
#[ cfg( any( test, feature = "ffi-testing" , feature = "dbg" ) ) ]
457
483
test_only_root_ca_override : None ,
458
484
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 < _ > > ( ) ,
459
501
}
460
502
}
461
503
@@ -465,6 +507,7 @@ impl Verifier {
465
507
Self {
466
508
test_only_root_ca_override : Some ( root. into ( ) ) ,
467
509
crypto_provider : OnceCell :: new ( ) ,
510
+ extra_roots : Vec :: new ( ) ,
468
511
}
469
512
}
470
513
@@ -521,7 +564,27 @@ impl Verifier {
521
564
. chain ( Some ( 0 ) )
522
565
. collect ( ) ;
523
566
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
+ }
525
588
526
589
let status = cert_chain. verify_chain_policy ( server) ?;
527
590
0 commit comments