Skip to content

x509 Verification: Extension policies documentation. #11800

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

Merged
merged 12 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions docs/spelling_wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ committers
conda
CPython
Cryptanalysis
criticalities
crypto
cryptographic
cryptographically
Expand Down
239 changes: 214 additions & 25 deletions docs/x509/verification.rst
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ the root of trust:

.. versionadded:: 43.0.0

.. versionchanged:: 44.0.0
.. versionchanged:: 45.0.0
Made ``subjects`` optional with the addition of custom extension policies.

.. attribute:: subjects
Expand All @@ -133,6 +133,11 @@ the root of trust:

.. versionadded:: 43.0.0

.. versionchanged:: 45.0.0
``verification_time`` and ``max_chain_depth`` were deprecated and will be
removed in version 46.0.0.
The new ``policy`` property should be used to access these values instead.

A ClientVerifier verifies client certificates.

It contains and describes various pieces of configurable path
Expand All @@ -142,17 +147,11 @@ the root of trust:
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
.. attribute:: policy

:type: :class:`int`
:type: :class:`Policy`

The verifier's maximum intermediate CA chain depth.
The policy used by the verifier. Can be used to access verification time, maximum chain depth, etc.

.. attribute:: store

Expand Down Expand Up @@ -181,6 +180,12 @@ the root of trust:

.. versionadded:: 42.0.0

.. versionchanged:: 45.0.0
``subject``, ``verification_time`` and ``max_chain_depth`` were deprecated and will be
removed in version 46.0.0.
The new ``policy`` property should be used to access these values instead.


A ServerVerifier verifies server certificates.

It contains and describes various pieces of configurable path
Expand All @@ -191,23 +196,11 @@ the root of trust:
ServerVerifier instances cannot be constructed directly;
:class:`PolicyBuilder` must be used.

.. attribute:: subject

:type: :class:`Subject`

The verifier's subject.

.. attribute:: validation_time

:type: :class:`datetime.datetime`

The verifier's validation time.
.. attribute:: policy

.. attribute:: max_chain_depth

:type: :class:`int`
:type: :class:`Policy`

The verifier's maximum intermediate CA chain depth.
The policy used by the verifier. Can be used to access verification time, maximum chain depth, etc.

.. attribute:: store

Expand Down Expand Up @@ -276,6 +269,20 @@ the root of trust:

:returns: A new instance of :class:`PolicyBuilder`

.. method:: extension_policies(new_ee_policy, new_ca_policy)

.. versionadded:: 45.0.0

Sets the EE and CA extension policies for the verifier.
The default policies used are those returned by :meth:`ExtensionPolicy.webpki_defaults_ee`
and :meth:`ExtensionPolicy.webpki_defaults_ca`.

:param ExtensionPolicy new_ca_policy: The CA extension policy to use.
:param ExtensionPolicy new_ee_policy: The EE extension policy to use.

:returns: A new instance of :class:`PolicyBuilder`


.. method:: build_server_verifier(subject)

Builds a verifier for verifying server certificates.
Expand All @@ -297,3 +304,185 @@ the root of trust:
for server verification.

:returns: An instance of :class:`ClientVerifier`

.. class:: ExtensionPolicy

.. versionadded:: 45.0.0

ExtensionPolicy provides a set of static methods to construct predefined
extension policies, and a builder-style interface for modifying them.

.. note:: Calling any of the builder methods (:meth:`require_not_present`, :meth:`may_be_present`, or :meth:`require_present`)
multiple times with the same extension type will raise an exception.

.. note:: Currently only the following extension types are supported in the ExtensionPolicy API:
:class:`~cryptography.x509.AuthorityInformationAccess`,
:class:`~cryptography.x509.AuthorityKeyIdentifier`,
:class:`~cryptography.x509.SubjectKeyIdentifier`,
:class:`~cryptography.x509.KeyUsage`,
:class:`~cryptography.x509.SubjectAlternativeName`,
:class:`~cryptography.x509.BasicConstraints`,
:class:`~cryptography.x509.NameConstraints`,
:class:`~cryptography.x509.ExtendedKeyUsage`.

.. staticmethod:: permit_all()

Creates an ExtensionPolicy initialized with a policy that does
not put any constraints on a certificate's extensions.
This can serve as a base for a fully custom extension policy.

:returns: An instance of :class:`ExtensionPolicy`

.. staticmethod:: webpki_defaults_ca()

Creates an ExtensionPolicy initialized with a
CA extension policy based on CA/B Forum guidelines.

This is the CA extension policy used by :class:`PolicyBuilder`.

:returns: An instance of :class:`ExtensionPolicy`

.. staticmethod:: webpki_defaults_ee()

Creates an ExtensionPolicy initialized with an
EE extension policy based on CA/B Forum guidelines.

This is the EE extension policy used by :class:`PolicyBuilder`.

:returns: An instance of :class:`ExtensionPolicy`

.. method:: require_not_present(extension_type)

Specifies that the extension identified by `extension_type` must not be present (must be absent).

:param type[ExtensionType] extension_type: The extension_type of the extension that must not be present.

:returns: An instance of :class:`ExtensionPolicy`

.. method:: may_be_present(extension_type, criticality, validator_cb)

Specifies that the extension identified by `extension_type` is optional.
If it is present, it must conform to the given criticality constraint.
An optional validator callback may be provided.

If a validator callback is provided, the callback will be invoked
when :meth:`ClientVerifier.verify` or :meth:`ServerVerifier.verify` is called on a verifier
that uses the extension policy. For details on the callback signature, see :type:`MaybeExtensionValidatorCallback`.

:param type[ExtensionType] extension_type: A concrete class derived from :type:`~cryptography.x509.ExtensionType`
indicating which extension may be present.
:param Criticality criticality: The criticality of the extension
:param validator_cb: An optional Python callback to validate the extension value.
Must accept extensions of type `extension_type`.
:type validator_cb: :type:`MaybeExtensionValidatorCallback` or None

:returns: An instance of :class:`ExtensionPolicy`

.. method:: require_present(extension_type, criticality, validator_cb)

Specifies that the extension identified by `extension_type`` must be present
and conform to the given criticality constraint. An optional validator callback may be provided.

If a validator callback is provided, the callback will be invoked
when :meth:`ClientVerifier.verify` or :meth:`ServerVerifier.verify` is called on a verifier
that uses the extension policy. For details on the callback signature, see :type:`PresentExtensionValidatorCallback`.

:param type[ExtensionType] extension_type: A concrete class derived from :type:`~cryptography.x509.ExtensionType`
indicating which extension is required to be present.
:param Criticality criticality: The criticality of the extension
:param validator_cb: An optional Python callback to validate the extension value.
Must accept extensions of type `extension_type`.
:type validator_cb: :type:`PresentExtensionValidatorCallback` or None

:returns: An instance of :class:`ExtensionPolicy`

.. class:: Criticality

.. versionadded:: 45.0.0

An enumeration of criticality constraints for certificate extensions.

.. attribute:: CRITICAL

The extension must be marked as critical.

.. attribute:: AGNOSTIC

The extension may be marked either as critical or non-critical.

.. attribute:: NON_CRITICAL

The extension must not be marked as critical.

.. class:: Policy

.. versionadded:: 45.0.0

Represents a policy for certificate verification. Passed to extension validator callbacks and
accessible via :class:`ClientVerifier` and :class:`ServerVerifier`.

.. attribute:: max_chain_depth

The maximum chain depth (as described in :meth:`PolicyBuilder.max_chain_depth`).

:type: int

.. attribute:: subject

The subject used during verification.
Will be None if the verifier is a :class:`ClientVerifier`.

:type: x509.verification.Subject or None

.. attribute:: validation_time

The validation time.

:type: datetime.datetime

.. attribute:: extended_key_usage

The Extended Key Usage required by the policy.

:type: x509.ObjectIdentifier

.. attribute:: minimum_rsa_modulus

The minimum RSA modulus size required by the policy.

:type: int

.. type:: MaybeExtensionValidatorCallback
:canonical: Callable[[Policy, Certificate, Optional[ExtensionType]], None]
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, is this the right way to document the generic parameter?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would also like to know 😄 I was trying to find documentation on how this is supposed to be done, but I didn't find anything specific to type aliases. I did try a couple things just now:

  1. Version A

    .. type:: MaybeExtensionValidatorCallback[T: ExtensionType]
        :canonical: Callable[[Policy, Certificate, Optional[T]], None]

    results in
    Screenshot 2025-02-14 at 8 44 20
    which is a bit too cluttered imo, and also triggers a warning verification.rst:445: WARNING: py:class reference target not found: T [ref.class]

  2. Version B

    .. type:: MaybeExtensionValidatorCallback
        :canonical: Callable[[Policy, Certificate, Optional[T:ExtensionType]], None]

    This is less cluttered, but doesn't show that this is a generic type as well, and still triggers a warning.
    Screenshot 2025-02-14 at 8 47 06

I don't really know if there is a proper way to do this without triggering warnings. I couldn't find anything in the docs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are also methods on ExtensionPolicy which are generic, but I didn't document as such in this PR. After doing some quick research, I think the way this is intended to be used might be incompatible with how the cryptography docs are structured. The sphinx docs suggest here that generic functions/methods should be documented as such:

.. py:function:: add[T](a: T, b: T) -> T

but cryptography docs don't put types in the declaration there, just the names (from what I saw at least). And using a generic "defined" in the function declaration part of the doc in the argument documentation once again results in warnings and looks too cluttered imo anyway:

    .. method:: may_be_present[T: ExtensionType](extension_type, criticality, validator_cb)

        Specifies that the extension...

        :param type[T] extension_type: A concrete class derived from :type:`~cryptography.x509.ExtensionType`
            indicating which extension may be present.
        :param Criticality criticality: The criticality of the extension
        :param validator_cb: An optional Python callback to validate the extension value. 
            Must accept extensions of type `extension_type`.
        :type validator_cb: :type:`MaybeExtensionValidatorCallback[T]` or None

Warnings:

verification.rst:2: WARNING: py:class reference target not found: T [ref.class]
verification.rst:367: WARNING: py:type reference target not found: MaybeExtensionValidatorCallback[T] [ref.type]

Result:
Screenshot 2025-02-14 at 8 58 41

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In general I think I might prefer to leave the docs as is even if there is a proper way to document this without the warnings - I think the API is understandable without this in the docs, and it should be freely and conveniently available for anyone actually using the library with a type checker, while adding this might actually make the docs somewhat harder to parse visually and make things seem more complex than they are from a user perspective.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

PS: Even if this is not the right way to document this, writing ExtensionType does communicate that any T: ExtensionType will be accepted. The only thing we lose is the information that the type alias is generic, which I'm not sure is that important to the user.


.. versionadded:: 45.0.0


A Python callback that validates an extension that may or may not be present.
If the extension is not present, the callback will be invoked with `ext` set to `None`.

To fail the validation, the callback must raise an exception.

:param Policy policy: The verification policy.
:param Certificate certificate: The certificate being verified.
:param ExtensionType or None extension: The extension value or `None` if the extension is not present.

:returns: An extension validator callback must return `None`.
If the validation fails, the validator must raise an exception.

.. type:: PresentExtensionValidatorCallback
:canonical: Callable[[Policy, Certificate, ExtensionType], None]

.. versionadded:: 45.0.0


A Python callback that validates an extension that must be present.

To fail the validation, the callback must raise an exception.

:param Policy policy: The verification policy.
:param Certificate certificate: The certificate being verified.
:param ExtensionType extension: The extension value.

:returns: An extension validator callback must return `None`.
If the validation fails, the validator must raise an exception.