@@ -73,9 +73,28 @@ pub fn build_server_config(cert_source: CertificateSource) -> anyhow::Result<rus
7373 CertificateSource :: External {
7474 certificates,
7575 private_key,
76- } => builder
77- . with_single_cert ( certificates, private_key)
78- . context ( "failed to set server config cert" ) ,
76+ } => {
77+ let first_certificate = certificates. first ( ) . context ( "empty certificate list" ) ?;
78+
79+ if let Ok ( report) = check_certificate_now ( & first_certificate) {
80+ if report. issues . intersects (
81+ CertIssues :: MISSING_SERVER_AUTH_EXTENDED_KEY_USAGE | CertIssues :: MISSING_SUBJECT_ALT_NAME ,
82+ ) {
83+ let serial_number = report. serial_number ;
84+ let subject = report. subject ;
85+ let issuer = report. issuer ;
86+ let not_before = report. not_before ;
87+ let not_after = report. not_after ;
88+ let issues = report. issues ;
89+
90+ anyhow:: bail!( "found significant issues with the certificate: serial_number = {serial_number}, subject = {subject}, issuer = {issuer}, not_before = {not_before}, not_after = {not_after}, issues = {issues}" ) ;
91+ }
92+ }
93+
94+ builder
95+ . with_single_cert ( certificates, private_key)
96+ . context ( "failed to set server config cert" )
97+ }
7998
8099 #[ cfg( windows) ]
81100 CertificateSource :: SystemStore {
@@ -116,6 +135,7 @@ pub mod windows {
116135 use tokio_rustls:: rustls:: sign:: CertifiedKey ;
117136
118137 use crate :: config:: dto;
138+ use crate :: tls:: { check_certificate, CertIssues } ;
119139
120140 const CACHE_DURATION : time:: Duration = time:: Duration :: seconds ( 45 ) ;
121141
@@ -157,8 +177,6 @@ pub mod windows {
157177 }
158178
159179 fn resolve ( & self , client_hello : ClientHello < ' _ > ) -> anyhow:: Result < Arc < CertifiedKey > > {
160- use core:: fmt:: Write as _;
161-
162180 trace ! ( server_name = ?client_hello. server_name( ) , "Received ClientHello" ) ;
163181
164182 let request_server_name = client_hello
@@ -212,52 +230,54 @@ pub mod windows {
212230
213231 trace ! ( subject_name = %self . subject_name, count = contexts. len( ) , "Found certificate contexts" ) ;
214232
215- let x509_date_now = picky:: x509:: date:: UtcDate :: from ( now) ;
233+ // We will accumulate all the certificate issues we observe next.
234+ let mut cert_issues = CertIssues :: empty ( ) ;
216235
217236 // Initial processing and filtering of the available candidates.
218237 let mut contexts: Vec < CertHandleCtx > = contexts
219238 . into_iter ( )
220239 . enumerate ( )
221240 . filter_map ( |( idx, ctx) | {
222- let not_after = match picky:: x509:: Cert :: from_der ( ctx. as_der ( ) ) {
223- Ok ( cert) => {
224- let serial_number = cert. serial_number ( ) . 0 . iter ( ) . fold (
225- String :: new ( ) ,
226- |mut acc, byte| {
227- let _ = write ! ( acc, "{byte:X?}" ) ;
228- acc
229- } ,
241+ let not_after = match check_certificate ( ctx. as_der ( ) , now) {
242+ Ok ( report) => {
243+ trace ! (
244+ %idx,
245+ serial_number = %report. serial_number,
246+ subject = %report. subject,
247+ issuer = %report. issuer,
248+ not_before = %report. not_before,
249+ not_after = %report. not_after,
250+ issues = %report. issues,
251+ "Parsed store certificate"
230252 ) ;
231- let subject = cert. subject_name ( ) ;
232- let issuer = cert. issuer_name ( ) ;
233- let not_before = cert. valid_not_before ( ) ;
234- let not_after = cert. valid_not_after ( ) ;
235-
236- trace ! ( %idx, %serial_number, %subject, %issuer, %not_before, %not_after, "Parsed store certificate" ) ;
237253
238- if x509_date_now < not_before {
239- debug ! (
240- %idx, %serial_number, %not_before, "Filtered out certificate based on not before validity date"
241- ) ;
242- return None ;
243- }
244-
245- let has_server_auth_key_purpose = cert. extensions ( ) . iter ( ) . any ( |ext| match ext. extn_value ( ) {
246- picky:: x509:: extension:: ExtensionView :: ExtendedKeyUsage ( eku) => eku. contains ( picky:: oids:: kp_server_auth ( ) ) ,
247- _ => false ,
248- } ) ;
254+ // Accumulate the issues found.
255+ cert_issues |= report. issues ;
256+
257+ // Skip the certificate if any of the following is true:
258+ // - The certificate is not yet valid.
259+ // - The certificate is missing the server auth extended key usage.
260+ // - The certificate is missing a subject alternative name (SAN) extension.
261+ let skip = report. issues . intersects (
262+ CertIssues :: NOT_YET_VALID
263+ | CertIssues :: MISSING_SERVER_AUTH_EXTENDED_KEY_USAGE
264+ | CertIssues :: MISSING_SUBJECT_ALT_NAME ,
265+ ) ;
249266
250- if !has_server_auth_key_purpose {
267+ if skip {
251268 debug ! (
252- %idx, %serial_number, "Filtered out certificate because it does not have the server auth extended usage"
269+ %idx,
270+ serial_number = %report. serial_number,
271+ issues = %report. issues,
272+ "Filtered out certificate because it has significant issues"
253273 ) ;
254274 return None ;
255275 }
256276
257- not_after
277+ report . not_after
258278 }
259279 Err ( error) => {
260- debug ! ( %error, "Failed to parse store certificate number {idx} " ) ;
280+ debug ! ( %idx , % error, "Failed to check store certificate" ) ;
261281 picky:: x509:: date:: UtcDate :: ymd ( 1900 , 1 , 1 ) . expect ( "hardcoded" )
262282 }
263283 } ;
@@ -296,7 +316,9 @@ pub mod windows {
296316 . ok ( )
297317 . map ( |key| ( ctx, key) )
298318 } )
299- . context ( "no usable certificate found in the system store" ) ?;
319+ . with_context ( || {
320+ format ! ( "no usable certificate found in the system store; observed issues: {cert_issues}" )
321+ } ) ?;
300322
301323 trace ! ( idx = context. idx, not_after = %context. not_after, key_algorithm_group = ?key. key( ) . algorithm_group( ) , key_algorithm = ?key. key( ) . algorithm( ) , "Selected certificate" ) ;
302324
@@ -343,6 +365,92 @@ pub mod windows {
343365 }
344366}
345367
368+ pub struct CertReport {
369+ pub serial_number : String ,
370+ pub subject : picky:: x509:: name:: DirectoryName ,
371+ pub issuer : picky:: x509:: name:: DirectoryName ,
372+ pub not_before : picky:: x509:: date:: UtcDate ,
373+ pub not_after : picky:: x509:: date:: UtcDate ,
374+ pub issues : CertIssues ,
375+ }
376+
377+ bitflags:: bitflags! {
378+ #[ derive( Debug , Clone , Copy , PartialEq , Eq , Hash ) ]
379+ pub struct CertIssues : u8 {
380+ const NOT_YET_VALID = 0b00000001 ;
381+ const EXPIRED = 0b00000010 ;
382+ const MISSING_SERVER_AUTH_EXTENDED_KEY_USAGE = 0b00000100 ;
383+ const MISSING_SUBJECT_ALT_NAME = 0b00001000 ;
384+ }
385+ }
386+
387+ impl core:: fmt:: Display for CertIssues {
388+ fn fmt ( & self , f : & mut core:: fmt:: Formatter < ' _ > ) -> core:: fmt:: Result {
389+ bitflags:: parser:: to_writer ( self , f)
390+ }
391+ }
392+
393+ pub fn check_certificate_now ( cert : & [ u8 ] ) -> anyhow:: Result < CertReport > {
394+ check_certificate ( cert, time:: OffsetDateTime :: now_utc ( ) )
395+ }
396+
397+ pub fn check_certificate ( cert : & [ u8 ] , at : time:: OffsetDateTime ) -> anyhow:: Result < CertReport > {
398+ use anyhow:: Context as _;
399+ use core:: fmt:: Write as _;
400+
401+ let cert = picky:: x509:: Cert :: from_der ( cert) . context ( "failed to parse certificate" ) ?;
402+ let at = picky:: x509:: date:: UtcDate :: from ( at) ;
403+
404+ let mut issues = CertIssues :: empty ( ) ;
405+
406+ let serial_number = cert. serial_number ( ) . 0 . iter ( ) . fold ( String :: new ( ) , |mut acc, byte| {
407+ let _ = write ! ( acc, "{byte:X?}" ) ;
408+ acc
409+ } ) ;
410+ let subject = cert. subject_name ( ) ;
411+ let issuer = cert. issuer_name ( ) ;
412+ let not_before = cert. valid_not_before ( ) ;
413+ let not_after = cert. valid_not_after ( ) ;
414+
415+ if at < not_before {
416+ issues. insert ( CertIssues :: NOT_YET_VALID ) ;
417+ } else if not_after < at {
418+ issues. insert ( CertIssues :: EXPIRED ) ;
419+ }
420+
421+ let mut has_server_auth_key_purpose = false ;
422+ let mut has_san = false ;
423+
424+ for ext in cert. extensions ( ) {
425+ match ext. extn_value ( ) {
426+ picky:: x509:: extension:: ExtensionView :: ExtendedKeyUsage ( eku)
427+ if eku. contains ( picky:: oids:: kp_server_auth ( ) ) =>
428+ {
429+ has_server_auth_key_purpose = true ;
430+ }
431+ picky:: x509:: extension:: ExtensionView :: SubjectAltName ( _) => has_san = true ,
432+ _ => { }
433+ }
434+ }
435+
436+ if !has_server_auth_key_purpose {
437+ issues. insert ( CertIssues :: MISSING_SERVER_AUTH_EXTENDED_KEY_USAGE ) ;
438+ }
439+
440+ if !has_san {
441+ issues. insert ( CertIssues :: MISSING_SUBJECT_ALT_NAME ) ;
442+ }
443+
444+ Ok ( CertReport {
445+ serial_number,
446+ subject,
447+ issuer,
448+ not_before,
449+ not_after,
450+ issues,
451+ } )
452+ }
453+
346454pub mod sanity {
347455 use tokio_rustls:: rustls;
348456
0 commit comments