@@ -73,9 +73,28 @@ pub fn build_server_config(cert_source: CertificateSource) -> anyhow::Result<rus
73
73
CertificateSource :: External {
74
74
certificates,
75
75
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
+ }
79
98
80
99
#[ cfg( windows) ]
81
100
CertificateSource :: SystemStore {
@@ -116,6 +135,7 @@ pub mod windows {
116
135
use tokio_rustls:: rustls:: sign:: CertifiedKey ;
117
136
118
137
use crate :: config:: dto;
138
+ use crate :: tls:: { check_certificate, CertIssues } ;
119
139
120
140
const CACHE_DURATION : time:: Duration = time:: Duration :: seconds ( 45 ) ;
121
141
@@ -157,8 +177,6 @@ pub mod windows {
157
177
}
158
178
159
179
fn resolve ( & self , client_hello : ClientHello < ' _ > ) -> anyhow:: Result < Arc < CertifiedKey > > {
160
- use core:: fmt:: Write as _;
161
-
162
180
trace ! ( server_name = ?client_hello. server_name( ) , "Received ClientHello" ) ;
163
181
164
182
let request_server_name = client_hello
@@ -212,52 +230,54 @@ pub mod windows {
212
230
213
231
trace ! ( subject_name = %self . subject_name, count = contexts. len( ) , "Found certificate contexts" ) ;
214
232
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 ( ) ;
216
235
217
236
// Initial processing and filtering of the available candidates.
218
237
let mut contexts: Vec < CertHandleCtx > = contexts
219
238
. into_iter ( )
220
239
. enumerate ( )
221
240
. 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"
230
252
) ;
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" ) ;
237
253
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
+ ) ;
249
266
250
- if !has_server_auth_key_purpose {
267
+ if skip {
251
268
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"
253
273
) ;
254
274
return None ;
255
275
}
256
276
257
- not_after
277
+ report . not_after
258
278
}
259
279
Err ( error) => {
260
- debug ! ( %error, "Failed to parse store certificate number {idx} " ) ;
280
+ debug ! ( %idx , % error, "Failed to check store certificate" ) ;
261
281
picky:: x509:: date:: UtcDate :: ymd ( 1900 , 1 , 1 ) . expect ( "hardcoded" )
262
282
}
263
283
} ;
@@ -296,7 +316,9 @@ pub mod windows {
296
316
. ok ( )
297
317
. map ( |key| ( ctx, key) )
298
318
} )
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
+ } ) ?;
300
322
301
323
trace ! ( idx = context. idx, not_after = %context. not_after, key_algorithm_group = ?key. key( ) . algorithm_group( ) , key_algorithm = ?key. key( ) . algorithm( ) , "Selected certificate" ) ;
302
324
@@ -343,6 +365,92 @@ pub mod windows {
343
365
}
344
366
}
345
367
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
+
346
454
pub mod sanity {
347
455
use tokio_rustls:: rustls;
348
456
0 commit comments