3.1 KiB
3.1 KiB
asn1/decoder — TLV decoder for the X.509 ASN.1 subset
Purpose
Implements decodeAsn1(bytes, mode) covering every ASN.1 production used by RFC 5280 Appendix A: primitive scalars (INTEGER, BIT STRING, OCTET STRING, OBJECT IDENTIFIER, NULL, BOOLEAN), string types, time types, and constructed types (SEQUENCE, SET, [n] EXPLICIT/IMPLICIT). The output is a discriminated-union Asn1Node tree the encoder can round-trip byte-identically. Strict DER mode (default) rejects indefinite-length encodings; permissive BER mode accepts them.
Features
- Long-form tag-number decoding (X.690 paragraph 8.1.2.4) with overflow guard and minimality check.
- Definite-length decoding (X.690 paragraph 8.1.3.3-5) with maximum 4-octet length and minimality check in DER mode.
- Indefinite-length decoding in BER mode (X.690 paragraph 8.1.3.6) terminated by
00 00end-of-contents marker. - Trailing-bytes rejection on the outermost item (both modes).
- Sub-array views — primitive
contentreferences a copiedUint8Arrayslice so the caller can store / hash it without re-entering the decoder.
Usage: Basic Example
import { decodeAsn1 } from "@suaveplan/x509/asn1";
const node = decodeAsn1(new Uint8Array([0x30, 0x03, 0x02, 0x01, 0x05]));
// node.tag.tagNumber === 16 (SEQUENCE)
// node.children[0].tag.tagNumber === 2 (INTEGER) carrying byte 0x05
Usage: Advanced Example
import { decodeAsn1, decodeAsn1Stream } from "@suaveplan/x509/asn1";
// Permissive BER mode accepts indefinite-length encoding (legacy systems).
const ber = decodeAsn1(berBytes, "ber");
// Decode N consecutive items extracted from an outer SEQUENCE body.
const seq = decodeAsn1(outerBytes);
if ("children" in seq) {
for (const child of seq.children) {
// ... structural pattern-match ...
}
}
// Or decode a raw body stream:
const items = decodeAsn1Stream(rawBody);
API Reference
decodeAsn1(bytes: Uint8Array, mode?: "der" | "ber"): Asn1Node— decode one TLV; throwsX509Error(ASN1_MALFORMED)on any structural failure or trailing bytes.decodeAsn1Stream(bytes: Uint8Array, mode?): readonly Asn1Node[]— decode N consecutive TLVs across an entire buffer; the buffer must be fully consumed.UNIVERSAL_TAGS— the universal-class tag numbers referenced by callers.
Implementation Notes
- The decoder makes no allocations beyond the
Asn1Nodetree and per-nodeUint8Arraycopies of primitive content. Constructed bodies are walked through without copying. tagNumberoverflow is bounded at 24-bit; long-form tag numbers larger than that throwASN1_MALFORMED. X.509 never uses tag numbers anywhere near that magnitude.- Length encoding accepts the 4-octet maximum (
>>>to ~4 GiB), which covers every realistic certificate input on the wire (largest known WebPKI certs are under 64 KiB). - BER mode is accepted but the
validateChainboundary throws on BER inputs by passing"der"explicitly; BER support exists so callers parsing legacy data feeds (e.g. older PKCS#7 / CMS) can opt in. decodeAsn1rejects an indefinite-length form on a primitive tag — primitive types in DER and BER both use definite-length encoding only.