Skip to content

Add x509.CustomExtensionType for user-defined extension types - #15599

Open
alex wants to merge 5 commits into
mainfrom
claude/cryptography-api-feasibility-t9hglr
Open

Add x509.CustomExtensionType for user-defined extension types#15599
alex wants to merge 5 commits into
mainfrom
claude/cryptography-api-feasibility-t9hglr

Conversation

@alex

@alex alex commented Sep 6, 2026

Copy link
Copy Markdown
Member

Refs #15500.

Adds x509.CustomExtensionType, a base class for defining X.509 extension types that cryptography does not natively support, using cryptography.hazmat.asn1 types for their values:

@asn1.sequence
class Point:
    x: int
    y: int

class PointExtension(x509.CustomExtensionType[Point]):
    oid = x509.ObjectIdentifier("1.2.3.4")

ext = cert.extensions.get_extension_for_class(PointExtension)
ext.value.value.x

builder = builder.add_extension(PointExtension(Point(x=1, y=2)), critical=False)
  • Extensions.get_extension_for_class accepts CustomExtensionType subclasses. When the matching extension is present as an UnrecognizedExtension, its DER value is decoded into the custom class and returned as a new Extension. The value is typed end to end: mypy sees ext.value.value as Point.
  • Subclasses must be parameterized with a concrete ASN.1 type and define oid; both are validated at class-definition time.
  • Instances can be passed to the certificate, CRL, CSR, and OCSP builders. The shared Rust encode_extensions path calls public_bytes() for them, which encodes with asn1.encode_der.

Of the two shapes discussed on the issue, the alias form CustomExtensionType[T, ExtensionOID.X] is not feasible: type checkers reject a value as a type argument, and the runtime object is a typing._GenericAlias rather than a class. The subclass form is implemented here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Xp766iEvYJexKJZRx3Rm4v


Generated by Claude Code

alex and others added 5 commits September 6, 2026 12:50
Custom extensions previously had to be handled by looking up the
UnrecognizedExtension by OID and manually running its value through
asn1.decode_der. CustomExtensionType lets users define an extension
class parameterized with the ASN.1 type of its value:

    class PolicyMappings(x509.CustomExtensionType[list[PolicyMapping]]):
        oid = ExtensionOID.POLICY_MAPPINGS

    ext = cert.extensions.get_extension_for_class(PolicyMappings)
    ext.value.value  # list[PolicyMapping]

Extensions.get_extension_for_class parses the matching extension's DER
into the custom class, and instances can be passed to the certificate,
CRL, CSR, and OCSP builders' add_extension, which serialize them via the
declarative ASN.1 encoder.

To support values whose type can't be inferred from the value alone
(e.g. a top-level list[T]), the Rust declarative_asn1 module gains an
encode_der_with_type function that takes an explicit annotated type.

Refs #15500

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xp766iEvYJexKJZRx3Rm4v
The explicit-type encoder was orthogonal to custom extension support.
CustomExtensionType.public_bytes() now uses asn1.encode_der directly,
so values are encoded the same way as any other declarative ASN.1
value.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xp766iEvYJexKJZRx3Rm4v
- Simplify the changelog entry and drop the versionchanged note and
  documented TypeError from the docs.
- Only parse UnrecognizedExtension values in get_extension_for_class,
  directly from their raw DER bytes, in a single pass over the
  extensions.
- Use a simple Point sequence in the docs and tests instead of policy
  mappings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xp766iEvYJexKJZRx3Rm4v
This replaces the loop over __orig_bases__ with a direct read of the
single base, which is simpler and removes a branch that was never
taken in tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xp766iEvYJexKJZRx3Rm4v
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant