4
4
5
5
use std:: sync:: Arc ;
6
6
7
- use cryptography_x509:: extensions:: { Extension , Extensions } ;
7
+ use cryptography_x509:: extensions:: { Extension , Extensions , SubjectAlternativeName } ;
8
8
use cryptography_x509:: oid:: {
9
9
AUTHORITY_INFORMATION_ACCESS_OID , AUTHORITY_KEY_IDENTIFIER_OID , BASIC_CONSTRAINTS_OID ,
10
10
EXTENDED_KEY_USAGE_OID , KEY_USAGE_OID , NAME_CONSTRAINTS_OID , SUBJECT_ALTERNATIVE_NAME_OID ,
@@ -16,6 +16,13 @@ use crate::{
16
16
ops:: CryptoOps , policy:: Policy , ValidationError , ValidationErrorKind , ValidationResult ,
17
17
} ;
18
18
19
+ use super :: Subject ;
20
+
21
+ pub ( crate ) enum CertificateType {
22
+ EE ,
23
+ CA ,
24
+ }
25
+
19
26
#[ derive( Clone ) ]
20
27
pub struct ExtensionPolicy < ' cb , B : CryptoOps > {
21
28
pub authority_information_access : ExtensionValidator < ' cb , B > ,
@@ -116,9 +123,10 @@ impl<'cb, B: CryptoOps + 'cb> ExtensionPolicy<'cb, B> {
116
123
Some ( Arc :: new ( ee:: key_usage) ) ,
117
124
) ,
118
125
// CA/B 7.1.2.7.12 Subscriber Certificate Subject Alternative Name
119
- // This validator handles both client and server cases by only matching against
120
- // the SAN if the profile contains a subject, which it won't in the client
121
- // validation case.
126
+ // This validator only handles the criticality checks. Matching
127
+ // SANs against the subject in the profile is handled by
128
+ // `validate_subject_alternative_name_match` which is
129
+ // invoked for all EE certificates, irrespective of this field's contents.
122
130
subject_alternative_name : ExtensionValidator :: present (
123
131
Criticality :: Agnostic ,
124
132
Some ( Arc :: new ( ee:: subject_alternative_name) ) ,
@@ -142,6 +150,7 @@ impl<'cb, B: CryptoOps + 'cb> ExtensionPolicy<'cb, B> {
142
150
pub ( crate ) fn permits < ' chain > (
143
151
& self ,
144
152
policy : & Policy < ' _ , B > ,
153
+ certificate_type : CertificateType ,
145
154
cert : & VerificationCertificate < ' chain , B > ,
146
155
extensions : & Extensions < ' _ > ,
147
156
) -> ValidationResult < ' chain , ( ) , B > {
@@ -180,6 +189,12 @@ impl<'cb, B: CryptoOps + 'cb> ExtensionPolicy<'cb, B> {
180
189
subject_alternative_name_seen = true ;
181
190
self . subject_alternative_name
182
191
. permits ( policy, cert, Some ( & ext) ) ?;
192
+
193
+ if let CertificateType :: EE = certificate_type {
194
+ // This ensures that even custom ExtensionPolicies will always
195
+ // check the SAN against the policy's subject
196
+ validate_subject_alternative_name_match ( & policy. subject , & ext) ?;
197
+ }
183
198
}
184
199
BASIC_CONSTRAINTS_OID => {
185
200
basic_constraints_seen = true ;
@@ -231,10 +246,38 @@ impl<'cb, B: CryptoOps + 'cb> ExtensionPolicy<'cb, B> {
231
246
self . extended_key_usage . permits ( policy, cert, None ) ?;
232
247
}
233
248
249
+ if let CertificateType :: EE = certificate_type {
250
+ // SAN is always required if the policy contains a specific subject (server profile).
251
+ if policy. subject . is_some ( ) && !subject_alternative_name_seen {
252
+ return Err ( ValidationError :: new ( ValidationErrorKind :: Other (
253
+ "leaf server certificate has no subjectAltName" . into ( ) ,
254
+ ) ) ) ;
255
+ }
256
+ }
257
+
234
258
Ok ( ( ) )
235
259
}
236
260
}
237
261
262
+ /// This only verifies the SAN against `subject` if `subject` is not None.
263
+ /// This allows us to handle both client and server profiles,
264
+ /// **with the expectation** that `subject` is always set for server profiles.
265
+ pub ( crate ) fn validate_subject_alternative_name_match < ' chain , B : CryptoOps > (
266
+ subject : & Option < Subject < ' _ > > ,
267
+ extn : & Extension < ' _ > ,
268
+ ) -> ValidationResult < ' chain , ( ) , B > {
269
+ if let Some ( sub) = subject {
270
+ let san: SubjectAlternativeName < ' _ > = extn. value ( ) ?;
271
+ if !sub. matches ( & san) {
272
+ return Err ( ValidationError :: new ( ValidationErrorKind :: Other (
273
+ "leaf certificate has no matching subjectAltName" . into ( ) ,
274
+ ) ) ) ;
275
+ }
276
+ }
277
+
278
+ Ok ( ( ) )
279
+ }
280
+
238
281
/// Represents different criticality states for an extension.
239
282
#[ derive( Clone ) ]
240
283
pub enum Criticality {
@@ -389,9 +432,7 @@ impl<'cb, B: CryptoOps> ExtensionValidator<'cb, B> {
389
432
}
390
433
391
434
mod ee {
392
- use cryptography_x509:: extensions:: {
393
- BasicConstraints , ExtendedKeyUsage , Extension , KeyUsage , SubjectAlternativeName ,
394
- } ;
435
+ use cryptography_x509:: extensions:: { BasicConstraints , ExtendedKeyUsage , Extension , KeyUsage } ;
395
436
396
437
use crate :: ops:: { CryptoOps , VerificationCertificate } ;
397
438
use crate :: policy:: { Policy , ValidationError , ValidationErrorKind , ValidationResult } ;
@@ -415,7 +456,7 @@ mod ee {
415
456
}
416
457
417
458
pub ( crate ) fn subject_alternative_name < ' chain , B : CryptoOps > (
418
- policy : & Policy < ' _ , B > ,
459
+ _ : & Policy < ' _ , B > ,
419
460
cert : & VerificationCertificate < ' chain , B > ,
420
461
extn : & Extension < ' _ > ,
421
462
) -> ValidationResult < ' chain , ( ) , B > {
@@ -435,18 +476,8 @@ mod ee {
435
476
_ => ( ) ,
436
477
} ;
437
478
438
- // NOTE: We only verify the SAN against the policy's subject if the
439
- // policy actually contains one. This enables both client and server
440
- // profiles to use this validator, **with the expectation** that
441
- // server profile construction requires a subject to be present.
442
- if let Some ( sub) = policy. subject . as_ref ( ) {
443
- let san: SubjectAlternativeName < ' _ > = extn. value ( ) ?;
444
- if !sub. matches ( & san) {
445
- return Err ( ValidationError :: new ( ValidationErrorKind :: Other (
446
- "leaf certificate has no matching subjectAltName" . into ( ) ,
447
- ) ) ) ;
448
- }
449
- }
479
+ // NOTE: policy.subject is checked against SAN elsewhere (see `ExtensionPolicy::permits`)
480
+ // since we always want to check that, even if a custom ExtensionPolicy with a lax validator is used.
450
481
451
482
Ok ( ( ) )
452
483
}
0 commit comments