Skip to content

[META] Declarative API for ASN.1 in PythonΒ #12283

@woodruffw

Description

@woodruffw

This is a meta-issue/design issue for tracking a declarative ASN.1 API for Cryptography!

The goal: an importable Python API that users of Cryptography can define ASN.1 structures with, which can then be ser/de'd to and from DER (and only DER).

A rough sketch, demonstrating the basic idioms of the API:

from cryptography.hazmat import asn1

# Corresponding to:
#
# Signature ::= Sequence {
#   r INTEGER,
#   s INTEGER
# }
@asn1.sequence
class Signature:
    r: int
    s: int

sig = Signature.from_der(b"...")
raw: bytes = sig.to_der()

This declarative API should have fully generality/expressivity with respect to ASN.1's own feature set, including qualifiers like EXPLICIT and IMPLICIT:

@asn1.sequence
class Signature:
    r: Annotated[int, asn1.explicit(0)]
    s: Annotated[int, asn1.implicit(1)]

This would also (naturally) include generality over user-defined types:

@asn1.sequence
class Frob:
    ...

@asn1.sequence
class FrobHolder:
    frob: Frob

Key design constraints:

  • This API should be 100% declarative: there should be no imperative effects on ASN.1 ser/de

Open design questions:

  • What's the best way to handle ANY? asn1.Any as a generic TLV type, similar to rust-asn1?

  • What's the best way to handle ANY DEFINED BY?

    Maybe something like this:

     @asn1.sequence
     class VaryMe:
         content_type: asn1.ObjectIdentifier
         content: Annotated[Varied, asn1.defined_by("content_type")]
    
     # or maybe we can do this with the standard enum.Enum?
     # or maybe @asn1.varied?
     class Varied(asn1.Enum):
         foo: Annotated[VariantA, asn1.defined_by(SOME_OID)]
         bar: Annotated[VariantB, asn1.defined_by(ANOTHER_OID)]
  • To what extent/how can we best support trivial "native" Python types (int, str, etc.) versus "synthetic" types?

    • Moreover, what's the appropriate isomorph for str? Probably UTF8String, with all other string-ish types being bytes?
  • What about non-trivial native types like list[T], set[T], etc? Should we support these with fixed mappings (e.g. list[T] -> SEQUENCE OF), or should we have our own types that don't require as much object conversion (e.g. asn.List[T])?

  • To what extent should we support datetime as a time type/map between datetime and UTCTime/GeneralizedTime?

    • One pitfall that we want to avoid is surprising serializations, e.g. a user really wants UTCTime OR GeneralizedTime but instead gets only GeneralizedTime
  • What's the best way to handle ASN.1 type constraints, e.g. ranged integers and min/max sequence/set lengths?

    • Probably additional fields on Annotated, e.g. Annotated[list[T], asn1.size(1...10)]
    • Not all of these make sense for an MVP, since plenty are obscure/not widely used (e.g. contained subtypes)

Open integration questions:

  • Where should this live within cryptography? Does cryptography.hazmat.asn1 make sense, or should it be cryptography.asn1, or something else?

There are probably many other questions too, and I'm sure I've missed some in my notes πŸ™‚

CC @facutuesca

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions