Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/cryptography/hazmat/bindings/_rust/x509.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,9 @@ class PolicyBuilder:
def extension_policies(
self, *, ca_policy: ExtensionPolicy, ee_policy: ExtensionPolicy
) -> PolicyBuilder: ...
def revocation_checker(
self, revocation_checker: x509.verification.RevocationChecker
) -> PolicyBuilder: ...
def build_client_verifier(self) -> ClientVerifier: ...
def build_server_verifier(
self, subject: x509.verification.Subject
Expand Down Expand Up @@ -278,6 +281,9 @@ class ExtensionPolicy:
validator: PresentExtensionValidatorCallback[T] | None,
) -> ExtensionPolicy: ...

class CRLRevocationChecker:
def __init__(self, crls: list[x509.CertificateRevocationList]) -> None: ...

class VerifiedClient:
@property
def subjects(self) -> list[x509.GeneralName] | None: ...
Expand Down
13 changes: 13 additions & 0 deletions src/cryptography/x509/verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@

from __future__ import annotations

import abc
import typing

from cryptography.hazmat.bindings._rust import x509 as rust_x509
from cryptography.x509.general_name import DNSName, IPAddress

__all__ = [
"CRLRevocationChecker",
"ClientVerifier",
"Criticality",
"ExtensionPolicy",
"Policy",
"PolicyBuilder",
"RevocationChecker",
"ServerVerifier",
"Store",
"Subject",
Expand All @@ -32,3 +35,13 @@
ExtensionPolicy = rust_x509.ExtensionPolicy
Criticality = rust_x509.Criticality
VerificationError = rust_x509.VerificationError
CRLRevocationChecker = rust_x509.CRLRevocationChecker


class RevocationChecker(metaclass=abc.ABCMeta):
"""
An interface for revocation checkers.
"""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So right now the idea is that we won't actually have a Python API you can implement for revocation checking, it'll be handled internally and these are more markers, is that the idea?

Copy link
Copy Markdown
Contributor Author

@tnytown tnytown Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, sorry that this isn't more clear—I experimented with having an extensible Python API but it was a bit complicated to shim over considering that the implementation for CRL is going to be in Rust. I can sketch out a shim if you'd like? see latest diff which supports Python-extensible revocation checkers



RevocationChecker.register(CRLRevocationChecker)
33 changes: 32 additions & 1 deletion src/rust/cryptography-x509-verification/src/certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ pub(crate) fn cert_is_self_issued(cert: &Certificate<'_>) -> bool {
pub(crate) mod tests {
use super::cert_is_self_issued;
use crate::certificate::Certificate;
use crate::ops::tests::{cert, v1_cert_pem};
use crate::ops::tests::{cert, crl, v1_cert_pem};
use crate::ops::CryptoOps;
use cryptography_x509::crl::CertificateRevocationList;

#[test]
fn test_certificate_v1() {
Expand All @@ -42,6 +43,25 @@ Xw4nMqk=
.unwrap()
}

fn crl_pem() -> pem::Pem {
// From vectors/cryptography_vectors/x509/custom/crl_empty.pem
pem::parse(
"-----BEGIN X509 CRL-----
MIIBxTCBrgIBATANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQGEwJVUzERMA8GA1UE
CAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xETAPBgNVBAoMCHI1MDkgTExD
MRowGAYDVQQDDBFyNTA5IENSTCBEZWxlZ2F0ZRcNMTUxMjIwMjM0NDQ3WhcNMTUx
MjI4MDA0NDQ3WqAZMBcwCgYDVR0UBAMCAQEwCQYDVR0jBAIwADANBgkqhkiG9w0B
AQUFAAOCAQEAXebqoZfEVAC4NcSEB5oGqUviUn/AnY6TzB6hUe8XC7yqEkBcyTgk
G1Zq+b+T/5X1ewTldvuUqv19WAU/Epbbu4488PoH5qMV8Aii2XcotLJOR9OBANp0
Yy4ir/n6qyw8kM3hXJloE+xgkELhd5JmKCnlXihM1BTl7Xp7jyKeQ86omR+DhItb
CU+9RoqOK9Hm087Z7RurXVrz5RKltQo7VLCp8VmrxFwfALCZENXGEQ+g5VkvoCjc
ph5jqOSyzp7aZy1pnLE/6U6V32ItskrwqA+x4oj2Wvzir/Q23y2zYfqOkuq4fTd2
lWW+w5mB167fIWmd6efecDn1ZqbdECDPUg==
-----END X509 CRL-----",
)
.unwrap()
}

#[test]
fn test_certificate_ca() {
let cert_pem = ca_pem();
Expand All @@ -62,6 +82,14 @@ Xw4nMqk=
Err(())
}

fn verify_crl_signed_by(
&self,
_crl: &CertificateRevocationList<'_>,
_key: &Self::Key,
) -> Result<(), Self::Err> {
Ok(())
}

fn verify_signed_by(
&self,
_cert: &Certificate<'_>,
Expand Down Expand Up @@ -98,9 +126,12 @@ Xw4nMqk=
// Just to get coverage on the `PublicKeyErrorOps` helper.
let cert_pem = ca_pem();
let cert = cert(&cert_pem);
let crl_pem = crl_pem();
let crl = crl(&crl_pem);
let ops = PublicKeyErrorOps {};

assert!(ops.public_key(&cert).is_err());
assert!(ops.verify_signed_by(&cert, &()).is_ok());
assert!(ops.verify_crl_signed_by(&crl, &()).is_ok());
}
}
14 changes: 13 additions & 1 deletion src/rust/cryptography-x509-verification/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
pub mod certificate;
pub mod ops;
pub mod policy;
pub mod revocation;
pub mod trust_store;
pub mod types;

Expand All @@ -26,6 +27,7 @@ use cryptography_x509::oid::{NAME_CONSTRAINTS_OID, SUBJECT_ALTERNATIVE_NAME_OID}
use crate::certificate::cert_is_self_issued;
use crate::ops::{CryptoOps, VerificationCertificate};
use crate::policy::Policy;
use crate::revocation::RevocationChecker;
use crate::trust_store::Store;
use crate::types::{
DNSConstraint, DNSPattern, IPAddress, IPConstraint, RFC822Constraint, RFC822Name,
Expand All @@ -40,6 +42,7 @@ pub enum ValidationErrorKind<'chain, B: CryptoOps> {
reason: &'static str,
},
FatalError(&'static str),
RevocationNotDetermined(String),
Other(String),
}

Expand Down Expand Up @@ -91,6 +94,9 @@ impl<B: CryptoOps> Display for ValidationError<'_, B> {
write!(f, "invalid extension: {oid}: {reason}")
}
ValidationErrorKind::FatalError(err) => write!(f, "fatal error: {err}"),
ValidationErrorKind::RevocationNotDetermined(err) => {
write!(f, "unable to determine revocation status: {err}")
}
ValidationErrorKind::Other(err) => write!(f, "{err}"),
}
}
Expand Down Expand Up @@ -272,9 +278,10 @@ pub fn verify<'chain, B: CryptoOps>(
leaf: &VerificationCertificate<'chain, B>,
intermediates: &[VerificationCertificate<'chain, B>],
policy: &Policy<'_, B>,
revocation_checker: Option<RevocationChecker<'_>>,
store: &Store<'chain, B>,
) -> ValidationResult<'chain, Chain<'chain, B>, B> {
let builder = ChainBuilder::new(intermediates, policy, store);
let builder = ChainBuilder::new(intermediates, policy, revocation_checker, store);

let mut budget = Budget::new();
builder.build_chain(leaf, &mut budget)
Expand All @@ -283,6 +290,7 @@ pub fn verify<'chain, B: CryptoOps>(
struct ChainBuilder<'a, 'chain, B: CryptoOps> {
intermediates: &'a [VerificationCertificate<'chain, B>],
policy: &'a Policy<'a, B>,
revocation_checker: Option<RevocationChecker<'a>>,
store: &'a Store<'chain, B>,
}

Expand All @@ -309,11 +317,13 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> {
fn new(
intermediates: &'a [VerificationCertificate<'chain, B>],
policy: &'a Policy<'a, B>,
revocation_checker: Option<RevocationChecker<'a>>,
store: &'a Store<'chain, B>,
) -> Self {
Self {
intermediates,
policy,
revocation_checker,
store,
}
}
Expand All @@ -340,6 +350,8 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> {
name_chain: NameChain<'_, 'chain>,
budget: &mut Budget,
) -> ValidationResult<'chain, Chain<'chain, B>, B> {
let _revocation_checker = &self.revocation_checker;

if let Some(nc) = working_cert_extensions.get_extension(&NAME_CONSTRAINTS_OID) {
name_chain.evaluate_constraints(&nc.value()?, budget)?;
}
Expand Down
14 changes: 14 additions & 0 deletions src/rust/cryptography-x509-verification/src/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use std::sync::OnceLock;

use cryptography_x509::certificate::Certificate;
use cryptography_x509::crl::CertificateRevocationList;

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

/// Verifies the signature on `CertificateRevocationList` using
/// the given `Key`.
fn verify_crl_signed_by(
&self,
crl: &CertificateRevocationList<'_>,
key: &Self::Key,
) -> Result<(), Self::Err>;

/// Verifies the signature on `Certificate` using the given
/// `Key`.
fn verify_signed_by(&self, cert: &Certificate<'_>, key: &Self::Key) -> Result<(), Self::Err>;
Expand All @@ -100,6 +109,7 @@ pub trait CryptoOps {
#[cfg(test)]
pub(crate) mod tests {
use cryptography_x509::certificate::Certificate;
use cryptography_x509::crl::CertificateRevocationList;

use super::VerificationCertificate;
use crate::certificate::tests::PublicKeyErrorOps;
Expand Down Expand Up @@ -129,6 +139,10 @@ zl9HYIMxATFyqSiD9jsx
asn1::parse_single(cert_pem.contents()).unwrap()
}

pub(crate) fn crl(crl_pem: &pem::Pem) -> CertificateRevocationList<'_> {
asn1::parse_single(crl_pem.contents()).unwrap()
}

#[test]
fn test_verification_certificate_debug() {
let p = v1_cert_pem();
Expand Down
68 changes: 68 additions & 0 deletions src/rust/cryptography-x509-verification/src/revocation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// This file is dual licensed under the terms of the Apache License, Version
// 2.0, and the BSD License. See the LICENSE file in the root of this repository
// for complete details.

use cryptography_x509::crl::CertificateRevocationList;

use crate::{
ops::{CryptoOps, VerificationCertificate},
policy::Policy,
ValidationResult,
};

trait CheckRevocation<B: CryptoOps> {
fn is_revoked(
&self,
cert: &VerificationCertificate<'_, B>,
issuer: &VerificationCertificate<'_, B>,
policy: &Policy<'_, B>,
) -> ValidationResult<'_, bool, B>;
}

pub struct CrlRevocationChecker<'a> {
crls: Vec<&'a CertificateRevocationList<'a>>,
}

impl<'a, B: CryptoOps> CheckRevocation<B> for CrlRevocationChecker<'a> {
fn is_revoked(
&self,
cert: &VerificationCertificate<'_, B>,
issuer: &VerificationCertificate<'_, B>,
policy: &Policy<'_, B>,
) -> ValidationResult<'_, bool, B> {
let _crls = &self.crls;
let _cert = cert;
let _issuer = issuer;
let _policy = policy;

Ok(false)
}
}

impl<'a> CrlRevocationChecker<'a> {
pub fn new(crls: impl IntoIterator<Item = &'a CertificateRevocationList<'a>>) -> Self {
Self {
crls: crls.into_iter().collect(),
}
}
}

/// Wrapper for revocation checkers that dispatches `is_revoked` calls to the inner implementation.
/// This allows us to avoid trait object-based polymorphism in the verifier.
pub enum RevocationChecker<'a> {
Crl(&'a CrlRevocationChecker<'a>),
}

impl RevocationChecker<'_> {
/// Checks the revocation status of the leaf of the provided chain.
pub fn is_revoked<B: CryptoOps>(
&self,
cert: &VerificationCertificate<'_, B>,
issuer: &VerificationCertificate<'_, B>,
policy: &Policy<'_, B>,
) -> ValidationResult<'_, bool, B> {
match self {
RevocationChecker::Crl(c) => c.is_revoked(cert, issuer, policy),
}
}
}
5 changes: 3 additions & 2 deletions src/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,9 @@ mod _rust {
use crate::x509::sct::Sct;
#[pymodule_export]
use crate::x509::verify::{
PolicyBuilder, PyClientVerifier, PyCriticality, PyExtensionPolicy, PyPolicy,
PyServerVerifier, PyStore, PyVerifiedClient, VerificationError,
PolicyBuilder, PyClientVerifier, PyCriticality, PyCrlRevocationChecker,
PyExtensionPolicy, PyPolicy, PyServerVerifier, PyStore, PyVerifiedClient,
VerificationError,
};
}

Expand Down
3 changes: 3 additions & 0 deletions src/rust/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,9 @@ pub static ASN1_TYPE_BMP_STRING: LazyPyImport =
pub static ASN1_TYPE_UNIVERSAL_STRING: LazyPyImport =
LazyPyImport::new("cryptography.x509.name", &["_ASN1Type", "UniversalString"]);

pub static REVOCATION_CHECKER: LazyPyImport =
LazyPyImport::new("cryptography.x509.verification", &["RevocationChecker"]);

pub static PKCS7_OPTIONS: LazyPyImport = LazyPyImport::new(
"cryptography.hazmat.primitives.serialization.pkcs7",
&["PKCS7Options"],
Expand Down
4 changes: 2 additions & 2 deletions src/rust/src/x509/crl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ pub(crate) fn load_pem_x509_crl(
}

self_cell::self_cell!(
struct OwnedCertificateRevocationList {
pub(crate) struct OwnedCertificateRevocationList {
owner: pyo3::Py<pyo3::types::PyBytes>,
#[covariant]
dependent: RawCertificateRevocationList,
Expand All @@ -81,7 +81,7 @@ self_cell::self_cell!(

#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.x509")]
pub(crate) struct CertificateRevocationList {
owned: OwnedCertificateRevocationList,
pub(crate) owned: OwnedCertificateRevocationList,

revoked_certs: pyo3::sync::PyOnceLock<Vec<OwnedRevokedCertificate>>,
cached_extensions: pyo3::sync::PyOnceLock<pyo3::Py<pyo3::PyAny>>,
Expand Down
Loading
Loading