Skip to content

Commit

Permalink
verification: WIP client verification skeleton
Browse files Browse the repository at this point in the history
Signed-off-by: William Woodruff <[email protected]>
  • Loading branch information
woodruffw committed Feb 4, 2024
1 parent 46b2921 commit 7c42c64
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 17 deletions.
66 changes: 65 additions & 1 deletion docs/x509/verification.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,55 @@ the root of trust:
:class:`cryptography.x509.general_name.DNSName`,
:class:`cryptography.x509.general_name.IPAddress`.

.. class:: ClientVerifier

.. versionadded:: 43.0.0

A ClientVerifier verifies client certificates.

It contains and describes various pieces of configurable path
validation logic, such as which subject to expect, how deep prospective
validation chains may go, which signature algorithms are allowed, and
so forth.

ClientVerifier instances cannot be constructed directly;
:class:`PolicyBuilder` must be used.

.. attribute:: validation_time

:type: :class:`datetime.datetime`

The verifier's validation time.

.. attribute:: max_chain_depth

:type: :class:`int`

The verifier's maximum intermediate CA chain depth.

.. attribute:: store

:type: :class:`Store`

The verifier's trust store.

.. method:: verify(leaf, intermediates)

Performs path validation on ``leaf``, returning a valid path
if one exists. The path is returned in leaf-first order:
the first member is ``leaf``, followed by the intermediates used
(if any), followed by a member of the ``store``.

:param leaf: The leaf :class:`~cryptography.x509.Certificate` to validate
:param intermediates: A :class:`list` of intermediate :class:`~cryptography.x509.Certificate` to attempt to use

:returns:
A three-tuple of the client certificate's subject,
the client certificate's SAN (or ``None``), and a ``list`` containing
the built chain.

:raises VerificationError: If a valid chain cannot be constructed

.. class:: ServerVerifier

.. versionadded:: 42.0.0
Expand Down Expand Up @@ -174,7 +223,8 @@ the root of trust:
Sets the verifier's verification time.

If not called explicitly, this is set to :meth:`datetime.datetime.now`
when :meth:`build_server_verifier` is called.
when :meth:`build_server_verifier` or :meth:`build_client_verifier`
is called.

:param new_time: The :class:`datetime.datetime` to use in the verifier

Expand Down Expand Up @@ -209,3 +259,17 @@ the root of trust:
:param subject: A :class:`Subject` to use in the verifier

:returns: An instance of :class:`ServerVerifier`

.. method:: build_client_verifier()

.. versionadded:: 43.0.0

Builds a verifier for verifying client certificates.

.. warning::

This API is not suitable for website (i.e. server) certificate
verification. You **must** use :meth:`build_server_verifier`
for server verification.

:returns: An instance of :class:`ClientVerifier`
16 changes: 16 additions & 0 deletions src/cryptography/hazmat/bindings/_rust/x509.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,26 @@ class PolicyBuilder:
def time(self, new_time: datetime.datetime) -> PolicyBuilder: ...
def store(self, new_store: Store) -> PolicyBuilder: ...
def max_chain_depth(self, new_max_chain_depth: int) -> PolicyBuilder: ...
def build_client_verifier(self) -> ClientVerifier: ...
def build_server_verifier(
self, subject: x509.verification.Subject
) -> ServerVerifier: ...

class ClientVerifier:
@property
def validation_time(self) -> datetime.datetime: ...
@property
def store(self) -> Store: ...
@property
def max_chain_depth(self) -> int: ...
def verify(
self,
leaf: x509.Certificate,
intermediates: list[x509.Certificate],
) -> tuple[
x509.Name, x509.SubjectAlternativeName | None, list[x509.Certificate]
]: ...

class ServerVerifier:
@property
def subject(self) -> x509.verification.Subject: ...
Expand Down
1 change: 1 addition & 0 deletions src/cryptography/x509/verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

Store = rust_x509.Store
Subject = typing.Union[DNSName, IPAddress]
ClientVerifier = rust_x509.ClientVerifier
ServerVerifier = rust_x509.ServerVerifier
PolicyBuilder = rust_x509.PolicyBuilder
VerificationError = rust_x509.VerificationError
10 changes: 5 additions & 5 deletions src/rust/cryptography-x509-verification/src/policy/extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,7 @@ mod tests {
let cert_pem = v1_cert_pem();
let cert = cert(&cert_pem);
let ops = PublicKeyErrorOps {};
let policy = Policy::new(
let policy = Policy::server(
ops,
Subject::DNS(DNSName::new("example.com").unwrap()),
epoch(),
Expand Down Expand Up @@ -639,7 +639,7 @@ mod tests {
let cert_pem = v1_cert_pem();
let cert = cert(&cert_pem);
let ops = PublicKeyErrorOps {};
let policy = Policy::new(
let policy = Policy::server(
ops,
Subject::DNS(DNSName::new("example.com").unwrap()),
epoch(),
Expand Down Expand Up @@ -673,7 +673,7 @@ mod tests {
let cert_pem = v1_cert_pem();
let cert = cert(&cert_pem);
let ops = PublicKeyErrorOps {};
let policy = Policy::new(
let policy = Policy::server(
ops,
Subject::DNS(DNSName::new("example.com").unwrap()),
epoch(),
Expand Down Expand Up @@ -704,7 +704,7 @@ mod tests {
let cert_pem = v1_cert_pem();
let cert = cert(&cert_pem);
let ops = PublicKeyErrorOps {};
let policy = Policy::new(
let policy = Policy::server(
ops,
Subject::DNS(DNSName::new("example.com").unwrap()),
epoch(),
Expand Down Expand Up @@ -733,7 +733,7 @@ mod tests {
let cert_pem = v1_cert_pem();
let cert = cert(&cert_pem);
let ops = PublicKeyErrorOps {};
let policy = Policy::new(
let policy = Policy::server(
ops,
Subject::DNS(DNSName::new("example.com").unwrap()),
epoch(),
Expand Down
46 changes: 36 additions & 10 deletions src/rust/cryptography-x509-verification/src/policy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,18 +234,17 @@ pub struct Policy<'a, B: CryptoOps> {
}

impl<'a, B: CryptoOps> Policy<'a, B> {
/// Create a new policy with defaults for the certificate profile defined in
/// the CA/B Forum's Basic Requirements.
pub fn new(
fn new(
ops: B,
subject: Subject<'a>,
subject: Option<Subject<'a>>,
time: asn1::DateTime,
max_chain_depth: Option<u8>,
) -> Self {
let has_subject = subject.is_some();
Self {
ops,
max_chain_depth: max_chain_depth.unwrap_or(DEFAULT_MAX_CHAIN_DEPTH),
subject: Some(subject),
subject,
validation_time: time,
extended_key_usage: EKU_SERVER_AUTH_OID.clone(),
minimum_rsa_modulus: WEBPKI_MINIMUM_RSA_MODULUS,
Expand Down Expand Up @@ -315,11 +314,17 @@ impl<'a, B: CryptoOps> Policy<'a, B> {
Criticality::Agnostic,
Some(ee::key_usage),
),
// CA/B 7.1.2.7.12 Subscriber Certificate Subject Alternative Name
subject_alternative_name: ExtensionValidator::present(
Criticality::Agnostic,
Some(ee::subject_alternative_name),
),
subject_alternative_name: match has_subject {
// CA/B 7.1.2.7.12 Subscriber Certificate Subject Alternative Name
true => ExtensionValidator::present(
Criticality::Agnostic,
Some(ee::subject_alternative_name),
),
false => ExtensionValidator::MaybePresent {
criticality: Criticality::Agnostic,
validator: None,
},
},
// 5280 4.2.1.9: Basic Constraints
basic_constraints: ExtensionValidator::maybe_present(
Criticality::Agnostic,
Expand All @@ -337,6 +342,27 @@ impl<'a, B: CryptoOps> Policy<'a, B> {
}
}

/// Create a new policy with suitable defaults for client certification
/// validation.
///
/// **IMPORTANT**: This is **not** the appropriate API for verifying
/// website (i.e. server) certificates. For that, you **must** use
/// [`Policy::server`].
pub fn client(ops: B, time: asn1::DateTime, max_chain_depth: Option<u8>) -> Self {
Self::new(ops, None, time, max_chain_depth)
}

/// Create a new policy with defaults for the server certificate profile
/// defined in the CA/B Forum's Basic Requirements.
pub fn server(
ops: B,
subject: Subject<'a>,
time: asn1::DateTime,
max_chain_depth: Option<u8>,
) -> Self {
Self::new(ops, Some(subject), time, max_chain_depth)
}

fn permits_basic(&self, cert: &Certificate<'_>) -> Result<(), ValidationError> {
// CA/B 7.1.1:
// Certificates MUST be of type X.509 v3.
Expand Down
32 changes: 31 additions & 1 deletion src/rust/src/x509/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,25 @@ impl PolicyBuilder {
})
}

fn build_client_verifier(&self, _py: pyo3::Python<'_>) -> CryptographyResult<PyClientVerifier> {
// let store = match self.store.as_ref() {
// Some(s) => s.clone_ref(py),
// None => {
// return Err(CryptographyError::from(
// pyo3::exceptions::PyValueError::new_err(
// "A client verifier must have a trust store.",
// ),
// ));
// }
// };

// let time = match self.time.as_ref() {
// Some(t) => t.clone(),
// None => datetime_now(py)?,
// };
todo!()
}

fn build_server_verifier(
&self,
py: pyo3::Python<'_>,
Expand All @@ -142,7 +161,7 @@ impl PolicyBuilder {

let policy = OwnedPolicy::try_new(subject_owner, |subject_owner| {
let subject = build_subject(py, subject_owner)?;
Ok::<PyCryptoPolicy<'_>, pyo3::PyErr>(PyCryptoPolicy(Policy::new(
Ok::<PyCryptoPolicy<'_>, pyo3::PyErr>(PyCryptoPolicy(Policy::server(
PyCryptoOps {},
subject,
time,
Expand Down Expand Up @@ -180,6 +199,17 @@ self_cell::self_cell!(
}
);

#[pyo3::pyclass(
frozen,
name = "ClientVerifier",
module = "cryptography.hazmat.bindings._rust.x509"
)]
struct PyClientVerifier {
_policy: OwnedPolicy,
#[pyo3(get)]
store: pyo3::Py<PyStore>,
}

#[pyo3::pyclass(
frozen,
name = "ServerVerifier",
Expand Down

0 comments on commit 7c42c64

Please sign in to comment.