Initial Commit
This commit is contained in:
48
packages/security-stack/security/x509/src/asn1/strings.md
Normal file
48
packages/security-stack/security/x509/src/asn1/strings.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# `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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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).
|
||||
Reference in New Issue
Block a user