Skip to content

Commit c197ce6

Browse files
committed
verification: implement CRL revocation checking
1 parent 88c32a3 commit c197ce6

15 files changed

Lines changed: 696 additions & 49 deletions

File tree

src/cryptography/hazmat/bindings/_rust/x509.pyi

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,10 @@ class PolicyBuilder:
218218
def extension_policies(
219219
self, *, ca_policy: ExtensionPolicy, ee_policy: ExtensionPolicy
220220
) -> PolicyBuilder: ...
221+
def revocation_checker(
222+
self,
223+
revocation_checker: x509.verification.CRLRevocationChecker,
224+
) -> PolicyBuilder: ...
221225
def build_client_verifier(self) -> ClientVerifier: ...
222226
def build_server_verifier(
223227
self, subject: x509.verification.Subject
@@ -279,6 +283,12 @@ class ExtensionPolicy:
279283
validator: PresentExtensionValidatorCallback[T] | None,
280284
) -> ExtensionPolicy: ...
281285

286+
class CRLRevocationChecker:
287+
def __init__(
288+
self,
289+
crls: list[tuple[x509.Certificate, x509.CertificateRevocationList]],
290+
) -> None: ...
291+
282292
class VerifiedClient:
283293
@property
284294
def subjects(self) -> list[x509.GeneralName] | None: ...

src/cryptography/x509/verification.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from cryptography.x509.general_name import DNSName, IPAddress
1111

1212
__all__ = [
13+
"CRLRevocationChecker",
1314
"ClientVerifier",
1415
"Criticality",
1516
"ExtensionPolicy",
@@ -32,3 +33,4 @@
3233
ExtensionPolicy = rust_x509.ExtensionPolicy
3334
Criticality = rust_x509.Criticality
3435
VerificationError = rust_x509.VerificationError
36+
CRLRevocationChecker = rust_x509.CRLRevocationChecker

src/rust/cryptography-x509-verification/src/certificate.rs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ pub(crate) fn cert_is_self_issued(cert: &Certificate<'_>) -> bool {
1414
pub(crate) mod tests {
1515
use super::cert_is_self_issued;
1616
use crate::certificate::Certificate;
17-
use crate::ops::tests::{cert, v1_cert_pem};
17+
use crate::ops::tests::{cert, crl, v1_cert_pem};
1818
use crate::ops::CryptoOps;
19+
use cryptography_x509::crl::CertificateRevocationList;
1920

2021
#[test]
2122
fn test_certificate_v1() {
@@ -25,7 +26,7 @@ pub(crate) mod tests {
2526
assert!(!cert_is_self_issued(&cert));
2627
}
2728

28-
fn ca_pem() -> pem::Pem {
29+
pub(crate) fn ca_pem() -> pem::Pem {
2930
// From vectors/cryptography_vectors/x509/custom/ca/ca.pem
3031
pem::parse(
3132
"-----BEGIN CERTIFICATE-----
@@ -42,6 +43,25 @@ Xw4nMqk=
4243
.unwrap()
4344
}
4445

46+
pub(crate) fn crl_pem() -> pem::Pem {
47+
// From vectors/cryptography_vectors/x509/custom/crl_empty.pem
48+
pem::parse(
49+
"-----BEGIN X509 CRL-----
50+
MIIBxTCBrgIBATANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQGEwJVUzERMA8GA1UE
51+
CAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xETAPBgNVBAoMCHI1MDkgTExD
52+
MRowGAYDVQQDDBFyNTA5IENSTCBEZWxlZ2F0ZRcNMTUxMjIwMjM0NDQ3WhcNMTUx
53+
MjI4MDA0NDQ3WqAZMBcwCgYDVR0UBAMCAQEwCQYDVR0jBAIwADANBgkqhkiG9w0B
54+
AQUFAAOCAQEAXebqoZfEVAC4NcSEB5oGqUviUn/AnY6TzB6hUe8XC7yqEkBcyTgk
55+
G1Zq+b+T/5X1ewTldvuUqv19WAU/Epbbu4488PoH5qMV8Aii2XcotLJOR9OBANp0
56+
Yy4ir/n6qyw8kM3hXJloE+xgkELhd5JmKCnlXihM1BTl7Xp7jyKeQ86omR+DhItb
57+
CU+9RoqOK9Hm087Z7RurXVrz5RKltQo7VLCp8VmrxFwfALCZENXGEQ+g5VkvoCjc
58+
ph5jqOSyzp7aZy1pnLE/6U6V32ItskrwqA+x4oj2Wvzir/Q23y2zYfqOkuq4fTd2
59+
lWW+w5mB167fIWmd6efecDn1ZqbdECDPUg==
60+
-----END X509 CRL-----",
61+
)
62+
.unwrap()
63+
}
64+
4565
#[test]
4666
fn test_certificate_ca() {
4767
let cert_pem = ca_pem();
@@ -62,6 +82,14 @@ Xw4nMqk=
6282
Err(())
6383
}
6484

85+
fn verify_crl_signed_by(
86+
&self,
87+
_crl: &CertificateRevocationList<'_>,
88+
_key: &Self::Key,
89+
) -> Result<(), Self::Err> {
90+
Ok(())
91+
}
92+
6593
fn verify_signed_by(
6694
&self,
6795
_cert: &Certificate<'_>,
@@ -98,9 +126,12 @@ Xw4nMqk=
98126
// Just to get coverage on the `PublicKeyErrorOps` helper.
99127
let cert_pem = ca_pem();
100128
let cert = cert(&cert_pem);
129+
let crl_pem = crl_pem();
130+
let crl = crl(&crl_pem);
101131
let ops = PublicKeyErrorOps {};
102132

103133
assert!(ops.public_key(&cert).is_err());
104134
assert!(ops.verify_signed_by(&cert, &()).is_ok());
135+
assert!(ops.verify_crl_signed_by(&crl, &()).is_ok());
105136
}
106137
}

src/rust/cryptography-x509-verification/src/lib.rs

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
pub mod certificate;
1010
pub mod ops;
1111
pub mod policy;
12+
pub mod revocation;
1213
pub mod trust_store;
1314
pub mod types;
1415

@@ -30,6 +31,7 @@ use cryptography_x509::oid::{
3031
use crate::certificate::cert_is_self_issued;
3132
use crate::ops::{CryptoOps, VerificationCertificate};
3233
use crate::policy::Policy;
34+
use crate::revocation::CrlRevocationChecker;
3335
use crate::trust_store::Store;
3436
use crate::types::{
3537
DNSConstraint, DNSPattern, IPAddress, IPConstraint, RFC822Constraint, RFC822Name,
@@ -44,6 +46,7 @@ pub enum ValidationErrorKind<'chain, B: CryptoOps> {
4446
reason: &'static str,
4547
},
4648
FatalError(&'static str),
49+
RevocationNotDetermined(String),
4750
Other(String),
4851
}
4952

@@ -95,6 +98,9 @@ impl<B: CryptoOps> Display for ValidationError<'_, B> {
9598
write!(f, "invalid extension: {oid}: {reason}")
9699
}
97100
ValidationErrorKind::FatalError(err) => write!(f, "fatal error: {err}"),
101+
ValidationErrorKind::RevocationNotDetermined(reason) => {
102+
write!(f, "unable to determine revocation status: {reason}")
103+
}
98104
ValidationErrorKind::Other(err) => write!(f, "{err}"),
99105
}
100106
}
@@ -306,9 +312,10 @@ pub fn verify<'chain, B: CryptoOps>(
306312
leaf: &VerificationCertificate<'chain, B>,
307313
intermediates: &[VerificationCertificate<'chain, B>],
308314
policy: &Policy<'_, B>,
315+
revocation_checker: Option<&'_ CrlRevocationChecker<'_>>,
309316
store: &Store<'chain, B>,
310317
) -> ValidationResult<'chain, Chain<'chain, B>, B> {
311-
let builder = ChainBuilder::new(intermediates, policy, store);
318+
let builder = ChainBuilder::new(intermediates, policy, revocation_checker, store);
312319

313320
let mut budget = Budget::new();
314321
builder.build_chain(leaf, &mut budget)
@@ -317,6 +324,7 @@ pub fn verify<'chain, B: CryptoOps>(
317324
struct ChainBuilder<'a, 'chain, B: CryptoOps> {
318325
intermediates: &'a [VerificationCertificate<'chain, B>],
319326
policy: &'a Policy<'a, B>,
327+
revocation_checker: Option<&'a CrlRevocationChecker<'a>>,
320328
store: &'a Store<'chain, B>,
321329
}
322330

@@ -353,11 +361,13 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> {
353361
fn new(
354362
intermediates: &'a [VerificationCertificate<'chain, B>],
355363
policy: &'a Policy<'a, B>,
364+
revocation_checker: Option<&'a CrlRevocationChecker<'a>>,
356365
store: &'a Store<'chain, B>,
357366
) -> Self {
358367
Self {
359368
intermediates,
360369
policy,
370+
revocation_checker,
361371
store,
362372
}
363373
}
@@ -464,6 +474,14 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> {
464474
return Ok(vec![working_cert.clone()]);
465475
}
466476

477+
if let Some(revocation_checker) = self.revocation_checker {
478+
if revocation_checker.is_revoked(working_cert, self.policy)? {
479+
return Err(ValidationError::new(ValidationErrorKind::Other(
480+
"certificate revoked".to_string(),
481+
)));
482+
}
483+
}
484+
467485
// Check that our current depth does not exceed our policy-configured
468486
// max depth. We do this after the root set check, since the depth
469487
// only measures the intermediate chain's length, not the root or leaf.
@@ -599,7 +617,8 @@ mod tests {
599617
use cryptography_x509::certificate::Certificate;
600618
use cryptography_x509::oid::SUBJECT_ALTERNATIVE_NAME_OID;
601619

602-
use crate::certificate::tests::PublicKeyErrorOps;
620+
use crate::certificate::tests::{crl_pem, PublicKeyErrorOps};
621+
use crate::ops::tests::{crl, NullOps};
603622
use crate::ops::{CryptoOps, VerificationCertificate};
604623
use crate::policy::{Policy, PolicyDefinition, Subject};
605624
use crate::trust_store::Store;
@@ -622,43 +641,27 @@ mod tests {
622641
"invalid extension: 2.5.29.17: duplicate extension"
623642
);
624643

644+
let err = ValidationError::<PublicKeyErrorOps>::new(
645+
ValidationErrorKind::RevocationNotDetermined("oops".to_owned()),
646+
);
647+
assert_eq!(
648+
err.to_string(),
649+
"unable to determine revocation status: oops"
650+
);
651+
625652
let err =
626653
ValidationError::<PublicKeyErrorOps>::new(ValidationErrorKind::FatalError("oops"));
627654
assert_eq!(err.to_string(), "fatal error: oops");
628655
}
629656

630-
/// A `CryptoOps` whose public key extraction and signature verification
631-
/// always succeed, so that `valid_issuer` can be driven to completion
632-
/// without real cryptographic material.
633-
struct NullOps;
634-
635-
impl CryptoOps for NullOps {
636-
type Key = ();
637-
type Err = ();
638-
type CertificateExtra = ();
639-
type PolicyExtra = ();
640-
641-
fn public_key(&self, _cert: &Certificate<'_>) -> Result<Self::Key, Self::Err> {
642-
Ok(())
643-
}
644-
645-
fn verify_signed_by(
646-
&self,
647-
_cert: &Certificate<'_>,
648-
_key: &Self::Key,
649-
) -> Result<(), Self::Err> {
650-
Ok(())
651-
}
652-
653-
fn clone_public_key(_key: &Self::Key) -> Self::Key {}
654-
655-
fn clone_extra(_extra: &Self::CertificateExtra) -> Self::CertificateExtra {}
656-
}
657-
658657
#[test]
659658
fn test_clone() {
660659
assert_eq!(NullOps::clone_public_key(&()), ());
661660
assert_eq!(NullOps::clone_extra(&()), ());
661+
662+
let crl_pem = crl_pem();
663+
let crl = crl(&crl_pem);
664+
assert!(NullOps.verify_crl_signed_by(&crl, &()).is_ok());
662665
}
663666

664667
// A self-issued ("looping") CA certificate that is its own issuer.
@@ -706,7 +709,7 @@ qolIOwIgCaIgj9ipK0Q0p+45UJiq+L/ncrxsweJkFq/UYubzhX0=
706709
PolicyDefinition::server(NullOps, subject, time, Some(u8::MAX), None, None).unwrap();
707710
let policy = Policy::new(&policy_def, ());
708711

709-
let builder = ChainBuilder::new(&intermediates, &policy, &store);
712+
let builder = ChainBuilder::new(&intermediates, &policy, None, &store);
710713
let mut budget = Budget {
711714
name_constraint_checks: usize::MAX,
712715
signature_checks: usize::MAX,

src/rust/cryptography-x509-verification/src/ops.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use std::sync::OnceLock;
66

77
use cryptography_x509::certificate::Certificate;
8+
use cryptography_x509::crl::CertificateRevocationList;
89

910
pub struct VerificationCertificate<'a, B: CryptoOps> {
1011
cert: &'a Certificate<'a>,
@@ -86,6 +87,14 @@ pub trait CryptoOps {
8687
/// if the key is malformed.
8788
fn public_key(&self, cert: &Certificate<'_>) -> Result<Self::Key, Self::Err>;
8889

90+
/// Verifies the signature on `CertificateRevocationList` using
91+
/// the given `Key`.
92+
fn verify_crl_signed_by(
93+
&self,
94+
crl: &CertificateRevocationList<'_>,
95+
key: &Self::Key,
96+
) -> Result<(), Self::Err>;
97+
8998
/// Verifies the signature on `Certificate` using the given
9099
/// `Key`.
91100
fn verify_signed_by(&self, cert: &Certificate<'_>, key: &Self::Key) -> Result<(), Self::Err>;
@@ -100,10 +109,47 @@ pub trait CryptoOps {
100109
#[cfg(test)]
101110
pub(crate) mod tests {
102111
use cryptography_x509::certificate::Certificate;
112+
use cryptography_x509::crl::CertificateRevocationList;
103113

104-
use super::VerificationCertificate;
114+
use super::{CryptoOps, VerificationCertificate};
105115
use crate::certificate::tests::PublicKeyErrorOps;
106116

117+
/// A `CryptoOps` whose public key extraction and signature verification
118+
/// always succeed, so that `valid_issuer` can be driven to completion
119+
/// without real cryptographic material.
120+
pub(crate) struct NullOps;
121+
122+
impl CryptoOps for NullOps {
123+
type Key = ();
124+
type Err = ();
125+
type CertificateExtra = ();
126+
type PolicyExtra = ();
127+
128+
fn public_key(&self, _cert: &Certificate<'_>) -> Result<Self::Key, Self::Err> {
129+
Ok(())
130+
}
131+
132+
fn verify_crl_signed_by(
133+
&self,
134+
_crl: &CertificateRevocationList<'_>,
135+
_key: &Self::Key,
136+
) -> Result<(), Self::Err> {
137+
Ok(())
138+
}
139+
140+
fn verify_signed_by(
141+
&self,
142+
_cert: &Certificate<'_>,
143+
_key: &Self::Key,
144+
) -> Result<(), Self::Err> {
145+
Ok(())
146+
}
147+
148+
fn clone_public_key(_key: &Self::Key) -> Self::Key {}
149+
150+
fn clone_extra(_extra: &Self::CertificateExtra) -> Self::CertificateExtra {}
151+
}
152+
107153
pub(crate) fn v1_cert_pem() -> pem::Pem {
108154
pem::parse(
109155
"
@@ -129,6 +175,10 @@ zl9HYIMxATFyqSiD9jsx
129175
asn1::parse_single(cert_pem.contents()).unwrap()
130176
}
131177

178+
pub(crate) fn crl(crl_pem: &pem::Pem) -> CertificateRevocationList<'_> {
179+
asn1::parse_single(crl_pem.contents()).unwrap()
180+
}
181+
132182
#[test]
133183
fn test_verification_certificate_debug() {
134184
let p = v1_cert_pem();

src/rust/cryptography-x509-verification/src/policy/mod.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use cryptography_x509::common::{
1616
PSS_SHA256_HASH_ALG, PSS_SHA256_MASK_GEN_ALG, PSS_SHA384_HASH_ALG, PSS_SHA384_MASK_GEN_ALG,
1717
PSS_SHA512_HASH_ALG, PSS_SHA512_MASK_GEN_ALG,
1818
};
19+
use cryptography_x509::crl::CertificateRevocationList;
1920
use cryptography_x509::extensions::{BasicConstraints, Extensions, SubjectAlternativeName};
2021
use cryptography_x509::name::GeneralName;
2122
use cryptography_x509::oid::{
@@ -580,9 +581,35 @@ impl<'a, B: CryptoOps> Policy<'a, B> {
580581

581582
Ok(())
582583
}
584+
585+
pub(crate) fn permits_crl<'chain>(
586+
&self,
587+
crl: &CertificateRevocationList<'_>,
588+
) -> ValidationResult<'chain, (), B> {
589+
let this_update = crl.tbs_cert_list.this_update.as_datetime();
590+
permits_validity_date(&crl.tbs_cert_list.this_update)?;
591+
592+
// 5280 5: CRLs MUST include the nextUpdate field
593+
let next_update = if let Some(next_update) = crl.tbs_cert_list.next_update.as_ref() {
594+
permits_validity_date(next_update)?;
595+
next_update.as_datetime()
596+
} else {
597+
return Err(ValidationError::new(ValidationErrorKind::Other(
598+
"CRL missing required nextUpdate field".to_string(),
599+
)));
600+
};
601+
602+
if &self.validation_time < this_update || &self.validation_time > next_update {
603+
return Err(ValidationError::new(ValidationErrorKind::Other(
604+
"CRL is not in effect at validation time".to_string(),
605+
)));
606+
}
607+
608+
Ok(())
609+
}
583610
}
584611

585-
fn permits_validity_date<'chain, B: CryptoOps>(
612+
pub(crate) fn permits_validity_date<'chain, B: CryptoOps>(
586613
validity_date: &Time,
587614
) -> ValidationResult<'chain, (), B> {
588615
const GENERALIZED_DATE_INVALIDITY_RANGE: Range<u16> = 1950..2050;

0 commit comments

Comments
 (0)