first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
# `asn1/reader`
## Purpose
Strict-DER reader scoped to the RSA key shapes defined in RFC 8017 Appendix A
(PKCS#1), RFC 5280 §4.1 + Appendix A (`SubjectPublicKeyInfo`), and RFC 5208 / RFC 5958
(PKCS#8 `PrivateKeyInfo` / `OneAsymmetricKey`). The reader rejects every BER
construction that DER forbids — indefinite length, non-minimal length, non-minimal
integer encoding, non-zero unused-bits in `BIT STRING`, trailing data — by raising
`CryptoRsaError(CRYPTO_RSA.INVALID_KEY)`.
The implementation is intentionally minimal. It implements exactly the subset needed
to decode RSA keys; it is not a general-purpose ASN.1 library.
## Features
- Stateful cursor with `position`, `remaining`, `hasMore`, `peekTag`, `assertEnd`.
- `readInteger`, `readSequence`, `readObjectIdentifier`, `readBitString`,
`readOctetString`, `readNull`, `readContextSpecific(tagNumber)`.
- Strict DER length parsing — non-minimal long-form lengths are rejected.
- Strict DER integer parsing — leading-`0x00` only when MSB requires it; negative
encodings rejected (RSA INTEGERs are non-negative).
- `BIT STRING` reader copies the body so callers cannot mutate the input buffer.
- Sub-readers returned by `readSequence` / `readContextSpecific` are scoped to the
child body for ergonomic recursive descent.
## Usage: Basic Example
```ts
import { DerReader } from "@suaveplan/crypto-rsa/asn1";
const reader = new DerReader(pkcs1PublicKeyDer);
const seq = reader.readSequence();
const modulus = seq.readInteger();
const exponent = seq.readInteger();
seq.assertEnd();
reader.assertEnd();
```
## Usage: Advanced Example
```ts
import { DerReader, TAG_INTEGER } from "@suaveplan/crypto-rsa/asn1";
const reader = new DerReader(pkcs8Bytes);
const outer = reader.readSequence();
// version is OPTIONAL DEFAULT 0 in some PKCS#8 dialects
const version =
outer.peekTag() === TAG_INTEGER ? outer.readInteger() : 0n;
const algSeq = outer.readSequence();
const oid = algSeq.readObjectIdentifier();
algSeq.readNull();
const privateKeyOctet = outer.readOctetString();
const inner = new DerReader(privateKeyOctet).readSequence();
// … recurse into RSAPrivateKey body.
```
## API Reference
- `class DerReader { constructor(bytes: Uint8Array, offset?: number) }`
- `position: number` — current cursor offset (read-only).
- `remaining: number` — bytes remaining in the buffer.
- `hasMore(): boolean` — true if the cursor has not reached the end.
- `peekTag(): number | undefined` — tag byte at the cursor, or `undefined` at EOF.
- `readInteger(): bigint` — consumes a DER `INTEGER`; throws `INVALID_KEY` on bad encoding.
- `readSequence(): DerReader` — body sub-reader for the next `SEQUENCE`.
- `readObjectIdentifier(): string` — dotted-decimal OID (e.g. `"1.2.840.113549.1.1.1"`).
- `readBitString(): Uint8Array` — payload bytes; throws when unused-bits ≠ 0.
- `readOctetString(): Uint8Array` — payload bytes (defensive copy).
- `readNull(): void` — consumes a DER `NULL`; throws on non-empty body.
- `readContextSpecific(tagNumber: 0..30): DerReader` — body sub-reader for `[n]`.
- `assertEnd(): void` — throws `INVALID_KEY` if any unread bytes remain.
All failure modes throw `CryptoRsaError` with `code === CRYPTO_RSA.INVALID_KEY` and
a structured `context` payload (e.g. `{ expected, actual }`, `{ unusedBits }`).
## Implementation Notes
The reader rejects BER-only constructs even when they are syntactically unambiguous
because RSA wire formats are defined in DER and any tooling that emits BER is
non-conformant. This matters for security: silently accepting non-canonical encodings
is a known fingerprinting and signature-malleability vector.
Sub-readers returned by `readSequence` are constructed over `Uint8Array.subarray`
(a view), not a copy, so the parser remains zero-allocation on the hot path. The
single defensive copy is in `readBitString` because callers receive the payload
directly and the SPKI bit-string carries the inner PKCS#1 body which is then
re-parsed.