Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

verification: client verification APIs #10345

Merged
merged 29 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7c42c64
verification: WIP client verification skeleton
woodruffw Feb 4, 2024
543f055
verify: fill in build_client_verifier
woodruffw Feb 4, 2024
8a540d3
implement ClientVerifier.verify
woodruffw Feb 8, 2024
b7761d6
verification: make Python 3.8 happy
woodruffw Feb 8, 2024
a9f24a8
switch to a full VerifiedClient type
woodruffw Feb 8, 2024
d179b7e
Merge branch 'main' into ww/client-verification
woodruffw Feb 11, 2024
6e09ac1
remove the SubjectOwner::None hack
woodruffw Feb 11, 2024
0b92cb5
docs: fix ClientVerifier
woodruffw Feb 11, 2024
00c0c6f
verification: replace match with if
woodruffw Feb 11, 2024
aeaceb2
return GNs directly, not whole extension
woodruffw Feb 11, 2024
dd89489
docs/verification: document UnsupportedGeneralNameType raise
woodruffw Feb 11, 2024
ee987d3
Merge remote-tracking branch 'upstream/main' into ww/client-verification
woodruffw Feb 26, 2024
afc1e14
Merge remote-tracking branch 'upstream/main' into ww/client-verification
woodruffw Feb 29, 2024
0a49f04
lib: RFC822 checks on NCs
woodruffw Feb 29, 2024
e9cb771
Merge branch 'main' into ww/client-verification
woodruffw Mar 5, 2024
83fedfd
test_limbo: enable client tests
woodruffw Mar 5, 2024
63a8b3f
tests: flake
woodruffw Mar 5, 2024
12a5452
test_verification: more Python API coverage
woodruffw Mar 5, 2024
0da5fd6
Merge remote-tracking branch 'upstream/main' into ww/client-verification
woodruffw Mar 7, 2024
2c32109
Merge remote-tracking branch 'upstream/main' into ww/client-verification
woodruffw Mar 10, 2024
eeda511
verification: filter GNs by NC support
woodruffw Mar 10, 2024
b3fa9cf
verification: forbid unsupported NC GNs
woodruffw Mar 10, 2024
93a502e
Merge branch 'main' into ww/client-verification
woodruffw Mar 13, 2024
df2d3ef
docs/verification: remove old sentence
woodruffw Mar 13, 2024
5e2cfe9
verification: ensure the right EKU for client/server paths
woodruffw Mar 15, 2024
986ae7f
Merge remote-tracking branch 'upstream/main' into ww/client-verification
woodruffw Mar 20, 2024
4c9ac45
test_limbo: fixup EKU assertion
woodruffw Mar 20, 2024
7204e3f
Merge remote-tracking branch 'upstream/main' into ww/client-verification
woodruffw Mar 21, 2024
26f800a
verification: feedback
woodruffw Mar 21, 2024
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
83 changes: 82 additions & 1 deletion docs/x509/verification.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,72 @@ the root of trust:
:class:`cryptography.x509.general_name.DNSName`,
:class:`cryptography.x509.general_name.IPAddress`.

.. class:: VerifiedClient

.. versionadded:: 43.0.0

.. attribute:: subject

:type: :class:`~cryptography.x509.Extension`
woodruffw marked this conversation as resolved.
Show resolved Hide resolved

The subject of the verified client certificate, as presented in the
:class:`~cryptography.x509.SubjectAlternativeName` extension.

.. attribute:: chain

:type: A list of :class:`~cryptography.x509.Certificate`, in leaf-first order

The chain of certificates that forms the valid chain to the client
certificate.


.. 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
woodruffw marked this conversation as resolved.
Show resolved Hide resolved
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 new instance of :class:`VerifiedClient`

:raises VerificationError: If a valid chain cannot be constructed

.. class:: ServerVerifier

.. versionadded:: 42.0.0
Expand Down Expand Up @@ -174,7 +240,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 +276,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`
20 changes: 20 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,30 @@ 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 VerifiedClient:
@property
def subject(self) -> x509.Extension[x509.SubjectAlternativeName]: ...
@property
def chain(self) -> list[x509.Certificate]: ...

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],
) -> VerifiedClient: ...

class ServerVerifier:
@property
def subject(self) -> x509.verification.Subject: ...
Expand Down
4 changes: 4 additions & 0 deletions src/cryptography/x509/verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@
__all__ = [
"Store",
"Subject",
"VerifiedClient",
"ClientVerifier",
"ServerVerifier",
"PolicyBuilder",
"VerificationError",
]

Store = rust_x509.Store
Subject = typing.Union[DNSName, IPAddress]
VerifiedClient = rust_x509.VerifiedClient
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(
woodruffw marked this conversation as resolved.
Show resolved Hide resolved
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();
woodruffw marked this conversation as resolved.
Show resolved Hide resolved
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 {
woodruffw marked this conversation as resolved.
Show resolved Hide resolved
// CA/B 7.1.2.7.12 Subscriber Certificate Subject Alternative Name
true => ExtensionValidator::present(
Criticality::Agnostic,
Some(ee::subject_alternative_name),
woodruffw marked this conversation as resolved.
Show resolved Hide resolved
),
// Under client validation, we return the SAN rather than verifying
// it directly against the profile. As such, we require it here
// but don't supply any custom validator logic.
false => ExtensionValidator::present(Criticality::Agnostic, 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
Loading
Loading