package core:encoding/asn1
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
Index
Constants (0)
This section is empty.
Variables (0)
This section is empty.
Procedures (49)
- bit_string_octets
- bit_string_wrap
- boolean
- context_explicit
- context_primitive
- context_specific
- cursor_init
- done
- encode
- encoded_len
- expect
- generalized_time
- integer_raw
- integer_unsigned
- is_empty
- marshal
- null
- object_identifier
- octet_string
- octet_string_wrap
- oid_components
- oid_to_string
- peek_tag
- primitive
- raw
- read_any
- read_bit_string
- read_bit_string_octets
- read_boolean
- read_explicit
- read_generalized_time
- read_i64
- read_integer_bytes
- read_null
- read_octet_string
- read_oid
- read_sequence
- read_set
- read_time
- read_unsigned_integer_bytes
- read_utc_time
- remaining
- sequence
- set
- set_of
- skip
- time
- universal
- utc_time
Procedure Groups (0)
This section is empty.
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).
Cursor ¶
Cursor is a position into DER input, advanced by the read_* procs.
Related Procedures With Parameters
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
- done
- encode
- expect
- marshal
- oid_components
- oid_to_string
- peek_tag
- read_any
- read_bit_string
- read_bit_string_octets
- read_boolean
- read_explicit
- read_generalized_time
- read_i64
- read_integer_bytes
- read_null
- read_octet_string
- read_oid
- read_sequence
- read_set
- read_time
- read_unsigned_integer_bytes
- read_utc_time
- set_of
- skip
Tag ¶
Tag is a decoded identifier octet (plus high-tag-number form).
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 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
Constants
This section is empty.
Variables
This section is empty.
Procedures
bit_string_octets ¶
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 ¶
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 ¶
Builds a BOOLEAN (DER: 0x00 / 0xFF).
context_explicit ¶
Builds a constructed [number] EXPLICIT wrapper around the given sub-values, e.g. TBSCertificate's version [0] EXPLICIT INTEGER.
context_primitive ¶
Builds a primitive [number] IMPLICIT value carrying raw content octets, e.g. AuthorityKeyIdentifier's keyIdentifier [0] IMPLICIT OCTET STRING.
context_specific ¶
context_specific builds a CONTEXT-SPECIFIC-class tag ("[n]").
cursor_init ¶
Points a Cursor at data and rewinds it to the start.
done ¶
Returns Leftover_Bytes if input remains. DER structures are exact: every SEQUENCE walk should end with done() on its sub-cursor.
encode ¶
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 ¶
Returns the exact number of bytes encode/marshal will write
for v, including its identifier and length octets.
expect ¶
Reads one element and requires its tag to match exactly.
generalized_time ¶
Builds a GeneralizedTime ("YYYYMMDDHHMMSSZ", RFC 5280 DER profile: Zulu, seconds present, no fractional part). The inverse of read_generalized_time.
integer_raw ¶
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 ¶
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 ¶
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.
object_identifier ¶
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 ¶
Builds an OCTET STRING wrapping content.
octet_string_wrap ¶
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 ¶
Decodes the next element's tag without consuming anything.
primitive ¶
Builds a primitive value with tag and the exact content
octets (emitted verbatim). The caller owns canonical-form correctness.
raw ¶
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 ¶
Reads one complete TLV element of any tag, returning the tag and a view of the content octets.
read_bit_string ¶
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 ¶
Reads a BIT STRING that must be a whole number of octets (unused == 0).
read_boolean ¶
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 ¶
Reads a GeneralizedTime in the RFC 5280 DER profile: "YYYYMMDDHHMMSSZ", Zulu only, no fractional seconds.
read_i64 ¶
Reads an INTEGER that must fit in an i64.
read_integer_bytes ¶
Reads an INTEGER and returns the validated, minimally-encoded two's-complement content octets.
read_null ¶
Reads a NULL (content must be empty).
read_octet_string ¶
Reads an OCTET STRING (primitive form only).
read_oid ¶
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 ¶
Enters a SEQUENCE, returning a sub-cursor over its content.
read_set ¶
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 ¶
Reads either time form, PKIX Validity uses UTCTime for dates through 2049 and GeneralizedTime from 2050 on.
read_unsigned_integer_bytes ¶
Reads a non-negative INTEGER and returns its magnitude octets with any leading 0x00 sign octet stripped.
read_utc_time ¶
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 ¶
Returns the number of unconsumed bytes.
sequence ¶
Builds a SEQUENCE from its sub-values (borrowed).
set ¶
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 ¶
Consumes one complete element of any tag.
time ¶
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 ¶
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