Files
2026-05-27 23:47:53 +01:00

2.8 KiB

asn1/strings — typed-string decoders

Purpose

Decodes and encodes the typed ASN.1 string types used inside X.509 Names and SubjectAltName entries: UTF8String, PrintableString, IA5String, TeletexString, BMPString, UniversalString. The encoder picks the most-restrictive form that holds the input value per RFC 5280 paragraph 7. Decoder is strict about character-set membership — invalid PrintableString characters are rejected as ASN1_MALFORMED.

Features

  • UTF-8 decode via TextDecoder with fatal: true, so invalid UTF-8 is rejected (not replaced).
  • PrintableString alphabet check per ITU-T X.520 — letters, digits, space, and the eleven punctuation characters allowed.
  • IA5String 0x00-0x7F check.
  • BMPString (UCS-2 big-endian) and UniversalString (UCS-4 big-endian) decode.
  • TeletexString pragmatically decoded as Latin-1; sufficient for the rare legacy CAs that still emit it.
  • Encoder picks PrintableString when valid, falling back to UTF8String — matches RFC 5280 paragraph 7.1 "most-restrictive" rule.

Usage: Basic Example

import { decodeAsn1String, encodeAsn1String, UNIVERSAL_TAGS } from "@suaveplan/x509/asn1";

const s = decodeAsn1String(UNIVERSAL_TAGS.UTF8_STRING, new Uint8Array([0x68, 0x69])); // "hi"

const { tagNumber, body } = encodeAsn1String("Example CA");
// tagNumber === UNIVERSAL_TAGS.PRINTABLE_STRING

Usage: Advanced Example

import { decodeAsn1, decodeAsn1String, UNIVERSAL_TAGS } from "@suaveplan/x509/asn1";

// Decode a Name attribute value where the tag depends on the CA's choice:
const attrNode = decodeAsn1(attrBytes);
if (!("content" in attrNode)) throw new Error("expected primitive");
const cn = decodeAsn1String(attrNode.tag.tagNumber, attrNode.content);

API Reference

  • decodeAsn1String(tagNumber: number, body: Uint8Array): string — dispatches on the universal tag; throws X509Error(ASN1_MALFORMED) on unsupported tag or character-set violation.
  • encodeAsn1String(value: string): { tagNumber, body } — returns the most-restrictive choice that holds the value.

Implementation Notes

  • PrintableString regex covers ITU-T X.520 paragraph 6.1 — letters, digits, space, and the eleven allowed punctuation marks.
  • TeletexString (T.61) is technically a 7/8-bit ISO 2022 escape system with national variants; decoding as Latin-1 is the de-facto industry practice (BoringSSL, OpenSSL).
  • BMPString and UniversalString are byte-order strict — the BMP form is UCS-2 big-endian (2 bytes per char), Universal is UCS-4 big-endian (4 bytes per char).
  • The encoder does not pick IA5String, TeletexString, BMPString, or UniversalString — these are accepted when reading legacy certs but every newly-encoded value defaults to UTF8String (the modern X.509 recommendation per RFC 5280 paragraph 4.1.2.4).