package core:encoding/asn1

⌘K
Ctrl+K
or
/

    Overview

    Strict DER (Distinguished Encoding Rules) reader and writer for the PKIX subset of ASN.1, the substrate for X.509 certificates and related structures.

    Reader: a Cursor over the input; read_* procs return VIEWS into it (only oid_components / oid_to_string take an allocator). The input must outlive the results.

    Writer: build a declarative tree of Value nodes with the constructors, then encoded_len + encode (no allocation, into a caller buffer) or marshal (one allocation). Constructors BORROW their inputs, so build and encode within one expression (or back children with a slice that outlives the call); the encoded output is self-contained and aliases nothing.

    Scope & limitations:

    DER only (no BER/CER); strict (minimal lengths, minimal integers, ...). PKIX subset: no typed readers for STRING/REAL/... (walk with read_any); the writer emits low-tag-number identifiers only (tag number <= 30).

    Times use core:time.Time per the RFC 5280 DER profile (Zulu, seconds present, no fractional seconds). time.Time is i64 nanoseconds (tops out near year 2262) while UTCTime/GeneralizedTime reach 9999; on read, dates beyond that saturate.

    See: https://www.itu.int/rec/T-REC-X.690 https://www.rfc-editor.org/rfc/rfc5280

    Types

    Class ¶

    Class :: enum u8 {
    	Universal        = 0, 
    	Application      = 1, 
    	Context_Specific = 2, 
    	Private          = 3, 
    }
     

    Tag class from the identifier octet (X.690 section 8.1.2.2).

    Error ¶

    Error :: enum int {
    	None, 
    	Truncated,                 // The element (or its header) extends past the end of the input.
    	Invalid_Tag,               // Malformed identifier octets (non-minimal high-tag-number form, or a tag number that overflows u32).
    	Invalid_Length,            // Indefinite length, non-minimal length encoding, or a length beyond what this implementation supports.
    	Unexpected_Tag,            // An expect_*/read_* procedure found a different tag than required.
    	Invalid_Boolean,           // BOOLEAN content was not exactly one octet of 0x00 or 0xFF.
    	Invalid_Integer,           // INTEGER content was empty or not minimally encoded.
    	Integer_Overflow,          // INTEGER does not fit the requested machine type.
    	Negative_Integer,          // INTEGER was negative where an unsigned value is required.
    	Invalid_Bit_String,        // BIT STRING content violated X.690 sections 8.6/11.2 (bad unused-bit count, non-zero padding bits, or unused bits where none are permitted).
    	Invalid_Null,              // NULL with non-empty content.
    	Invalid_Object_Identifier, // OBJECT IDENTIFIER content was empty or not minimally encoded.
    	Invalid_Time,              // UTCTime/GeneralizedTime outside the RFC 5280 DER profile (YYMMDDHHMMSSZ / YYYYMMDDHHMMSSZ, Zulu only, seconds present, no fractional seconds), or an impossible date.
    	Leftover_Bytes,            // done() was called with input remaining.
    	// An OBJECT IDENTIFIER arc exceeds u64. Arc magnitude is unbounded per X.660 (e.g. 2.25 UUID-derived OIDs carry a 128-bit arc), so
    	// this is a representation limit of oid_components/oid_to_string, not a malformed input; compare such OIDs by their raw bytes.
    	Arc_Overflow, 
    	Allocation_Failed,         // OOM
    	Buffer_Too_Small,          // encode: the destination slice is shorter than encoded_len.
    }
    Related Procedures With Returns

    Tag ¶

    Tag :: struct {
    	class:       Class,
    	constructed: bool,
    	number:      u32,
    }
     

    Tag is a decoded identifier octet (plus high-tag-number form).

    Related Procedures With Parameters
    Related Procedures With Returns

    Tag_Number ¶

    Tag_Number :: enum u32 {
    	Boolean           = 1, 
    	Integer           = 2, 
    	Bit_String        = 3, 
    	Octet_String      = 4, 
    	Null              = 5, 
    	Object_Identifier = 6, 
    	Enumerated        = 10, 
    	UTF8_String       = 12, 
    	Sequence          = 16, 
    	Set               = 17, 
    	Numeric_String    = 18, 
    	Printable_String  = 19, 
    	Teletex_String    = 20, 
    	IA5_String        = 22, 
    	UTC_Time          = 23, 
    	Generalized_Time  = 24, 
    	Visible_String    = 26, 
    	BMP_String        = 30, 
    }
     

    Tag_Number enumerates the UNIVERSAL tag numbers relevant to DER as used by PKIX. Context-specific tags carry their number directly in Tag.number.

    Related Procedures With Parameters

    Value ¶

    Value :: struct {
    	tag:      Tag,
    	form:     _Form,
    	content:  []u8,
    	children: []Value,
    	_when:    time.Time,
    }
     

    Value is a node in a to-be-encoded DER tree. Construct it with the helpers below rather than by hand; the fields are an implementation detail. The byte/child inputs are borrowed (see the package lifetime note).

    Related Procedures With Parameters
    Related Procedures With Returns

    Constants

    This section is empty.

    Variables

    This section is empty.

    Procedures

    bit_string_octets ¶

    bit_string_octets :: proc "contextless" (payload: []u8) -> Value {…}
     

    Builds a BIT STRING from a whole-octet payload (unused-bits count 0), the only form PKIX uses for SubjectPublicKeyInfo keys and signature values.

    bit_string_wrap ¶

    bit_string_wrap :: proc "contextless" (children: []Value) -> Value {…}
     

    Builds a BIT STRING (whole octets) whose payload is the DER encoding of children, the form SubjectPublicKeyInfo uses to carry an RSAPublicKey SEQUENCE inside the subjectPublicKey bit string.

    boolean ¶

    boolean :: proc "contextless" (v: bool) -> Value {…}
     

    Builds a BOOLEAN (DER: 0x00 / 0xFF).

    context_explicit ¶

    context_explicit :: proc "contextless" (number: u32, children: []Value) -> Value {…}
     

    Builds a constructed [number] EXPLICIT wrapper around the given sub-values, e.g. TBSCertificate's version [0] EXPLICIT INTEGER.

    context_primitive ¶

    context_primitive :: proc "contextless" (number: u32, content: []u8) -> Value {…}
     

    Builds a primitive [number] IMPLICIT value carrying raw content octets, e.g. AuthorityKeyIdentifier's keyIdentifier [0] IMPLICIT OCTET STRING.

    context_specific ¶

    context_specific :: proc "contextless" (number: u32, constructed: bool = true) -> Tag {…}
     

    context_specific builds a CONTEXT-SPECIFIC-class tag ("[n]").

    cursor_init ¶

    cursor_init :: proc "contextless" (r: ^Cursor, data: []u8) {…}
     

    Points a Cursor at data and rewinds it to the start.

    done ¶

    done :: proc "contextless" (r: ^Cursor) -> Error {…}
     

    Returns Leftover_Bytes if input remains. DER structures are exact: every SEQUENCE walk should end with done() on its sub-cursor.

    encode ¶

    encode :: proc(v: Value, dst: []u8) -> (n: int, err: Error) {…}
     

    Writes the DER encoding of v into dst and returns the number of bytes written. dst must be at least encoded_len(v) bytes; if it is shorter, nothing is written and Buffer_Too_Small is returned.

    encoded_len ¶

    encoded_len :: proc(v: Value) -> int {…}
     

    Returns the exact number of bytes encode/marshal will write for v, including its identifier and length octets.

    expect ¶

    expect :: proc "contextless" (r: ^Cursor, tag: Tag) -> (content: []u8, err: Error) {…}
     

    Reads one element and requires its tag to match exactly.

    generalized_time ¶

    generalized_time :: proc "contextless" (at: time.Time) -> Value {…}
     

    Builds a GeneralizedTime ("YYYYMMDDHHMMSSZ", RFC 5280 DER profile: Zulu, seconds present, no fractional part). The inverse of read_generalized_time.

    integer_raw ¶

    integer_raw :: proc "contextless" (content: []u8) -> Value {…}
     

    Builds an INTEGER from content octets that are ALREADY a minimal two's-complement encoding (e.g. a serial preserved verbatim from read_integer_bytes). No normalization is applied.

    integer_unsigned ¶

    integer_unsigned :: proc "contextless" (magnitude: []u8) -> Value {…}
     

    Builds an INTEGER from an unsigned big-endian magnitude: leading zero octets are dropped (minimal encoding) and a single 0x00 sign octet is inserted when the top bit would otherwise read as negative. An empty or all-zero magnitude encodes as 0. The inverse of read_unsigned_integer_bytes.

    is_empty ¶

    is_empty :: proc "contextless" (r: ^Cursor) -> bool {…}
     

    Reports whether the Cursor has been fully consumed.

    marshal ¶

    marshal :: proc(v: Value, allocator := context.allocator) -> (out: []u8, err: Error) {…}
     

    Encodes v into a freshly allocated slice the caller owns.

    null ¶

    null :: proc "contextless" () -> Value {…}
     

    Builds a NULL (empty content).

    object_identifier ¶

    object_identifier :: proc "contextless" (content: []u8) -> Value {…}
     

    Builds an OBJECT IDENTIFIER from already-encoded content octets (the form PKIX OIDs are held and compared in, e.g. the package's known-OID constants). The content is emitted verbatim; the caller owns its validity.

    octet_string ¶

    octet_string :: proc "contextless" (content: []u8) -> Value {…}
     

    Builds an OCTET STRING wrapping content.

    octet_string_wrap ¶

    octet_string_wrap :: proc "contextless" (children: []Value) -> Value {…}
     

    Builds an OCTET STRING whose content is the DER encoding of children, the form X.509 Extension.extnValue uses to carry an extension's value. The OCTET STRING stays primitive (0x04); its content is just the children's concatenated DER, so this reuses the constructed-content machinery without a wrapper octet.

    oid_components ¶

    oid_components :: proc(raw: []u8, allocator := context.allocator) -> (arcs: []u64, err: Error) {…}
     

    Decodes validated OID content octets (from read_oid) into their integer arcs, e.g. {1, 2, 840, 113549, 1, 1, 1}. Arcs beyond u64 (legal per X.660, see Arc_Overflow) are not representable; compare such OIDs by their raw bytes instead.

    oid_to_string ¶

    oid_to_string :: proc(raw: []u8, allocator := context.allocator) -> (str: string, err: Error) {…}
     

    Renders OID content octets in dotted-decimal form ("1.2.840.113549.1.1.1") for diagnostics. The arcs are streamed directly into the result; the only allocation is the returned string.

    peek_tag ¶

    peek_tag :: proc "contextless" (r: ^Cursor) -> (tag: Tag, err: Error) {…}
     

    Decodes the next element's tag without consuming anything.

    primitive ¶

    primitive :: proc "contextless" (tag: Tag, content: []u8) -> Value {…}
     

    Builds a primitive value with tag and the exact content octets (emitted verbatim). The caller owns canonical-form correctness.

    raw ¶

    raw :: proc "contextless" (encoded: []u8) -> Value {…}
     

    Builds a value whose encoding IS encoded verbatim, a complete already-DER-encoded element spliced in as-is (no tag/length added). The composition primitive for nesting an independently-marshalled structure (a signed CertificationRequestInfo, a pre-built TBSCertificate) inside a parent without re-encoding it.

    read_any ¶

    read_any :: proc "contextless" (r: ^Cursor) -> (tag: Tag, content: []u8, err: Error) {…}
     

    Reads one complete TLV element of any tag, returning the tag and a view of the content octets.

    read_bit_string ¶

    read_bit_string :: proc "contextless" (r: ^Cursor) -> (bits: []u8, unused: int, err: Error) {…}
     

    Reads a BIT STRING, returning the payload octets and the count of unused trailing bits in the final octet. DER: primitive form only, unused count 0..7 (0 if the payload is empty), and the unused bits themselves must be zero.

    read_bit_string_octets ¶

    read_bit_string_octets :: proc "contextless" (r: ^Cursor) -> (octets: []u8, err: Error) {…}
     

    Reads a BIT STRING that must be a whole number of octets (unused == 0).

    read_boolean ¶

    read_boolean :: proc "contextless" (r: ^Cursor) -> (value: bool, err: Error) {…}
     

    Reads a BOOLEAN. DER: exactly one octet, 0x00 or 0xFF.

    read_explicit ¶

    read_explicit :: proc "contextless" (r: ^Cursor, number: u32) -> (inner: Cursor, present: bool, err: Error) {…}
     

    Handles [number] EXPLICIT ... OPTIONAL: if the next element is the given constructed context-specific tag, it is consumed and a sub-cursor over its content returned with present=true. Otherwise nothing is consumed.

    read_generalized_time ¶

    read_generalized_time :: proc "contextless" (r: ^Cursor) -> (value: time.Time, err: Error) {…}
     

    Reads a GeneralizedTime in the RFC 5280 DER profile: "YYYYMMDDHHMMSSZ", Zulu only, no fractional seconds.

    read_i64 ¶

    read_i64 :: proc "contextless" (r: ^Cursor) -> (value: i64, err: Error) {…}
     

    Reads an INTEGER that must fit in an i64.

    read_integer_bytes ¶

    read_integer_bytes :: proc "contextless" (r: ^Cursor) -> (content: []u8, err: Error) {…}
     

    Reads an INTEGER and returns the validated, minimally-encoded two's-complement content octets.

    read_null ¶

    read_null :: proc "contextless" (r: ^Cursor) -> Error {…}
     

    Reads a NULL (content must be empty).

    read_octet_string ¶

    read_octet_string :: proc "contextless" (r: ^Cursor) -> (octets: []u8, err: Error) {…}
     

    Reads an OCTET STRING (primitive form only).

    read_oid ¶

    read_oid :: proc "contextless" (r: ^Cursor) -> (raw: []u8, err: Error) {…}
     

    Reads an OBJECT IDENTIFIER and returns a view of its content octets, validated for minimal base-128 encoding. The validation is structural only: arc magnitude is unbounded per X.660, so PKIX consumers should compare these bytes directly against known-OID constants. oid_components/oid_to_string decode arcs when needed, reporting Arc_Overflow for arcs beyond u64.

    read_sequence ¶

    read_sequence :: proc "contextless" (r: ^Cursor) -> (seq: Cursor, err: Error) {…}
     

    Enters a SEQUENCE, returning a sub-cursor over its content.

    read_set ¶

    read_set :: proc "contextless" (r: ^Cursor) -> (set: Cursor, err: Error) {…}
     

    Enters a SET, returning a sub-cursor over its content. NOTE: DER requires SET OF contents to be sorted; this cursor does not verify ordering, consumers that care (none in PKIX cert parsing) must check.

    read_time ¶

    read_time :: proc "contextless" (r: ^Cursor) -> (value: time.Time, err: Error) {…}
     

    Reads either time form, PKIX Validity uses UTCTime for dates through 2049 and GeneralizedTime from 2050 on.

    read_unsigned_integer_bytes ¶

    read_unsigned_integer_bytes :: proc "contextless" (r: ^Cursor) -> (magnitude: []u8, err: Error) {…}
     

    Reads a non-negative INTEGER and returns its magnitude octets with any leading 0x00 sign octet stripped.

    read_utc_time ¶

    read_utc_time :: proc "contextless" (r: ^Cursor) -> (value: time.Time, err: Error) {…}
     

    read_utc_time reads a UTCTime in the RFC 5280 DER profile: "YYMMDDHHMMSSZ", with the sliding century window (00-49 → 20xx, 50-99 → 19xx).

    remaining ¶

    remaining :: proc "contextless" (r: ^Cursor) -> int {…}
     

    Returns the number of unconsumed bytes.

    sequence ¶

    sequence :: proc "contextless" (children: []Value) -> Value {…}
     

    Builds a SEQUENCE from its sub-values (borrowed).

    set ¶

    set :: proc "contextless" (children: []Value) -> Value {…}
     

    Builds a SET from its sub-values, emitted in the given order. DER SET OF requires components sorted by their encoding (X.690 section 11.6); this constructor does NOT sort, so a SET OF with more than one element must be given pre-sorted (a single-element RDN, the common PKIX case, is trivially ordered).

    set_of ¶

    set_of :: proc(children: []Value, allocator := context.allocator) -> (value: Value, err: Error) {…}
     

    Builds a SET OF from its sub-values, sorted into the DER canonical order (X.690 section 11.6: component encodings ascending, shorter padded with trailing 0-octets). Unlike the other constructors this one ALLOCATES, it must encode each child to compare them, and it sorts children in place; the returned Value then borrows that reordered slice as usual. Scratch is taken from and released to allocator. 0/1-element sets need no work.

    skip ¶

    skip :: proc "contextless" (r: ^Cursor) -> Error {…}
     

    Consumes one complete element of any tag.

    time ¶

    time :: proc "contextless" (at: time.Time) -> Value {…}
     

    Builds a UTCTime or GeneralizedTime, auto-selecting the form per the RFC 5280 profile: UTCTime for instants in 1950..=2049, GeneralizedTime from 2050 on. (X.509 validity dates are written this way.)

    universal ¶

    universal :: proc "contextless" (number: Tag_Number, constructed: bool = false) -> Tag {…}
     

    universal builds a UNIVERSAL-class tag.

    utc_time ¶

    utc_time :: proc "contextless" (at: time.Time) -> Value {…}
     

    Builds a UTCTime ("YYMMDDHHMMSSZ", RFC 5280 DER profile). Appropriate for instants in 1950..=2049; the sliding-window century is what the reader (read_utc_time) decodes, so use generalized_time outside that range.

    Procedure Groups

    This section is empty.

    Source Files

    Generation Information

    Generated with odin version dev-2026-07 (vendor "odin") Windows_amd64 @ 2026-07-20 21:55:29.202779300 +0000 UTC