Skip to content

Commit

Permalink
adapt to Admissions extension added in cryptography 44
Browse files Browse the repository at this point in the history
  • Loading branch information
mathiasertl committed Nov 30, 2024
1 parent ac01d73 commit 355708c
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 6 deletions.
19 changes: 18 additions & 1 deletion ca/django_ca/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
from collections import defaultdict
from types import MappingProxyType

import packaging.version

import asn1crypto.core
import cryptography
from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import dsa, ec, ed448, ed25519, rsa
Expand All @@ -30,7 +33,7 @@
from cryptography.x509.oid import (
AuthorityInformationAccessOID,
ExtendedKeyUsageOID as _ExtendedKeyUsageOID,
ExtensionOID,
ExtensionOID as _ExtensionOID,
NameOID,
SubjectInformationAccessOID,
)
Expand All @@ -55,6 +58,16 @@
ParsableKeyType,
)

CRYPTOGRAPHY_VERSION = packaging.version.parse(cryptography.__version__).release


class ExtensionOID(_ExtensionOID):
"""Extend the ExtensionOID object with any OIDs not known to cryptography."""

if CRYPTOGRAPHY_VERSION < (44, 0): # pragma: only cryptography<4cccccbhlnnivu4.0
ADMISSIONS = x509.ObjectIdentifier("1.3.36.8.3.3")


ACCESS_METHOD_TYPES: MappingProxyType[AccessMethods, x509.ObjectIdentifier] = MappingProxyType(
{
"ocsp": AuthorityInformationAccessOID.OCSP,
Expand Down Expand Up @@ -176,6 +189,7 @@ class ExtendedKeyUsageOID(_ExtendedKeyUsageOID):
#: Map of ExtensionOIDs to a human-readable text describing if the extension should/must/... be critical.
EXTENSION_CRITICAL_HELP = MappingProxyType(
{
ExtensionOID.ADMISSIONS: _("may or may not be critical"),
ExtensionOID.AUTHORITY_INFORMATION_ACCESS: _("MUST be non-critical"),
ExtensionOID.AUTHORITY_KEY_IDENTIFIER: _("MUST be non-critical"),
ExtensionOID.BASIC_CONSTRAINTS: _("MUST usually be critical, but allows non-critical in some cases"),
Expand Down Expand Up @@ -212,6 +226,7 @@ class ExtendedKeyUsageOID(_ExtendedKeyUsageOID):
#: Map of ExtensionOIDs to the default critical values as defined in the RFC where they are defined.
EXTENSION_DEFAULT_CRITICAL = MappingProxyType(
{
ExtensionOID.ADMISSIONS: False, # Common PKI v2 doesn't really say
ExtensionOID.AUTHORITY_INFORMATION_ACCESS: False, # MUST mark this extension as non-critical.
ExtensionOID.AUTHORITY_KEY_IDENTIFIER: False, # MUST mark this extension as non-critical
ExtensionOID.BASIC_CONSTRAINTS: True, # RFC 5280 is more complex here, True is a good efault
Expand Down Expand Up @@ -243,6 +258,7 @@ class ExtendedKeyUsageOID(_ExtendedKeyUsageOID):
CONFIGURABLE_EXTENSION_KEYS: MappingProxyType[x509.ObjectIdentifier, ConfigurableExtensionKeys] = (
MappingProxyType(
{
ExtensionOID.ADMISSIONS: "admissions",
ExtensionOID.AUTHORITY_INFORMATION_ACCESS: "authority_information_access",
ExtensionOID.CERTIFICATE_POLICIES: "certificate_policies",
ExtensionOID.CRL_DISTRIBUTION_POINTS: "crl_distribution_points",
Expand Down Expand Up @@ -319,6 +335,7 @@ class ExtendedKeyUsageOID(_ExtendedKeyUsageOID):
#: Map of ExtensionOIDs to human-readable names as they appear in the RFC where they are defined.
EXTENSION_NAMES = MappingProxyType(
{
ExtensionOID.ADMISSIONS: "Admissions",
ExtensionOID.AUTHORITY_INFORMATION_ACCESS: "Authority Information Access",
ExtensionOID.AUTHORITY_KEY_IDENTIFIER: "Authority Key Identifier",
ExtensionOID.BASIC_CONSTRAINTS: "Basic Constraints",
Expand Down
2 changes: 1 addition & 1 deletion ca/django_ca/tests/test_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.x509.oid import ExtensionOID

from django_ca import constants, typehints
from django_ca.constants import ExtensionOID
from django_ca.typehints import GeneralNames, HashAlgorithms


Expand Down
19 changes: 15 additions & 4 deletions ca/django_ca/tests/test_typehints.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,36 @@

from typing import Any, get_args

from cryptography import x509

import pytest

from django_ca import constants, typehints
from django_ca.tests.test_constants import oid_sorter


def _oid_sorter(oid: x509.ObjectIdentifier) -> str:
return oid.dotted_string


def test_configurable_extension_keys() -> None:
"""Test that ConfigurableExtensionKeys matches ConfigurableExtensionType."""
keys = get_args(typehints.ConfigurableExtensionKeys)
expected = tuple(ext.oid for ext in get_args(typehints.ConfigurableExtensionType))
assert tuple(constants.CONFIGURABLE_EXTENSION_KEY_OIDS[v] for v in keys) == expected
expected = sorted((ext.oid for ext in get_args(typehints.ConfigurableExtensionType)), key=oid_sorter)
actual = sorted((constants.CONFIGURABLE_EXTENSION_KEY_OIDS[v] for v in keys), key=oid_sorter)
assert actual == expected


def test_end_entity_certificate_extension_keys() -> None:
"""Test EndEntityCertificateExtensionKeys matches EndEntityCertificateExtension."""
configurable_keys, added_keys = get_args(typehints.EndEntityCertificateExtensionKeys)
keys = get_args(configurable_keys) + get_args(added_keys)

expected = tuple(get_args(ext)[0].oid for ext in get_args(typehints.EndEntityCertificateExtension))
assert tuple(constants.END_ENTITY_CERTIFICATE_EXTENSION_KEY_OIDS[v] for v in keys) == expected
expected = sorted(
(get_args(ext)[0].oid for ext in get_args(typehints.EndEntityCertificateExtension)), key=oid_sorter
)
actual = sorted((constants.END_ENTITY_CERTIFICATE_EXTENSION_KEY_OIDS[v] for v in keys), key=oid_sorter)
assert actual == expected


@pytest.mark.parametrize(
Expand Down
12 changes: 12 additions & 0 deletions ca/django_ca/typehints.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
from collections.abc import Iterable, Mapping, Sequence
from typing import TYPE_CHECKING, Any, Literal, Optional, TypedDict, TypeVar, Union

import packaging.version

import cryptography
from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.serialization import Encoding
Expand All @@ -43,6 +46,8 @@
else: # pragma: only py>=3.11
from typing import Self as Self # noqa: PLC0414

CRYPTOGRAPHY_VERSION = packaging.version.parse(cryptography.__version__).release

# pylint: disable-next=invalid-name
JSON = Union[dict[str, "JSON"], list["JSON"], str, int, float, bool, None]

Expand Down Expand Up @@ -170,6 +175,7 @@ class ParsableAuthorityKeyIdentifierDict(TypedDict, total=False):

#: Extension keys for extensions that may be configured by the user when issuing certificates.
ConfigurableExtensionKeys = Literal[
"admissions",
"authority_information_access",
"certificate_policies",
"crl_distribution_points",
Expand Down Expand Up @@ -326,6 +332,10 @@ class ParsableAuthorityKeyIdentifierDict(TypedDict, total=False):
x509.SubjectAlternativeName,
x509.TLSFeature,
]

if CRYPTOGRAPHY_VERSION >= (44, 0): # pragma: only cryptography>=44.0
ConfigurableExtensionType = Union[ConfigurableExtensionType, x509.Admissions]

#: :py:class:`~cg:cryptography.x509.ExtensionType` classes that may appear in an end entity certificate.
#:
#: This union is based on :py:attr:`~django_ca.typehints.ConfigurableExtensionType` and adds extension types
Expand Down Expand Up @@ -367,6 +377,8 @@ class ParsableAuthorityKeyIdentifierDict(TypedDict, total=False):
x509.Extension[x509.SubjectAlternativeName],
x509.Extension[x509.TLSFeature],
]
if CRYPTOGRAPHY_VERSION >= (44, 0): # pragma: only cryptography>=44.0
ConfigurableExtension = Union[ConfigurableExtension, x509.Extension[x509.Admissions]]

EndEntityCertificateExtension = Union[
ConfigurableExtension,
Expand Down

0 comments on commit 355708c

Please sign in to comment.