micro-key-producer
Version:
Produces secure passwords & keys for WebCrypto, SSH, PGP, SLIP10, OTP and many others
1,208 lines (1,207 loc) • 102 kB
JavaScript
/*! micro-key-producer - MIT License (c) 2024 Paul Miller (paulmillr.com) */
/**
* x509 certificates. Conforms to parts of RFC 3820, RFC 5280, RFC 5652, RFC 5754, RFC 5912, RFC 7633.
* @module
*/
import { ed25519 } from '@noble/curves/ed25519.js';
import { ed448 } from '@noble/curves/ed448.js';
import { brainpoolP256r1, brainpoolP384r1, brainpoolP512r1 } from '@noble/curves/misc.js';
import { p256, p384, p521 } from '@noble/curves/nist.js';
import { asciiToBytes, equalBytes } from '@noble/curves/utils.js';
import { sha224, sha256, sha384, sha512 } from '@noble/hashes/sha2.js';
import { bytesToHex, concatBytes, hexToBytes } from '@noble/hashes/utils.js';
import { base64, hex } from '@scure/base';
import * as P from 'micro-packed';
import { CurveOID, DERUtils, curveOID } from "./convert.js";
const pemRE = /-----BEGIN ([^-]+)-----([\s\S]*?)-----END \1-----/g;
const hashOid = (h) => {
if (!h.oid)
throw new Error('hash.oid is missing');
return DERUtils.ASN1.OID.decode(h.oid);
};
const OID_NAME_RE = /^[0-9]+(?:\.[0-9]+)+$/;
const oidName = (m, oid) => m[oid] || `OID:${oid}`;
const oidValue = (m, v, what) => {
if (m[v])
return m[v];
if (v.startsWith('OID:'))
return v.slice(4);
if (OID_NAME_RE.test(v))
return v;
throw new Error(`unknown ${what} ${v}`);
};
/**
* Extracts all PEM blocks from a text blob.
* @param text - Text containing one or more PEM blocks.
* @returns Parsed PEM blocks with decoded DER bytes.
* @example
* Extract all PEM blocks from a text blob.
* ```ts
* import { pemBlocks } from 'micro-key-producer/x509.js';
* pemBlocks(`-----BEGIN DATA-----
* AA==
* -----END DATA-----`);
* ```
*/
export const pemBlocks = (text) => {
const out = [];
for (const m of text.matchAll(pemRE)) {
const tag = m[1].trim();
const b64 = m[2].trim();
if (!tag || !b64)
continue;
out.push({ tag, b64, der: base64.decode(b64.replace(/\s+/g, '')) });
}
return out;
};
const onePem = (text, tag) => {
const all = pemBlocks(text);
if (!all.length)
throw new Error('no PEM blocks found');
if (!tag)
return all[0];
const hit = all.find((i) => i.tag === tag);
if (!hit)
throw new Error(`no PEM block with tag=${tag}`);
return hit;
};
const bytesNum = (bytes) => BigInt(`0x${bytesToHex(bytes) || '0'}`);
const explicitCurve = (data) => {
if (!data || typeof data !== 'object')
return;
const d = data;
const fieldId = d.fieldId;
const info = fieldId?.info;
const curve = d.curve;
if (info?.TAG !== 'primeField' || typeof info.data !== 'bigint')
return;
if (!(curve?.a instanceof Uint8Array) || !(curve?.b instanceof Uint8Array))
return;
if (!(d.base instanceof Uint8Array) || typeof d.order !== 'bigint')
return;
if (d.cofactor !== undefined && typeof d.cofactor !== 'bigint')
return;
return {
fieldId: { info: { TAG: 'primeField', data: info.data } },
curve: { a: curve.a, b: curve.b },
base: d.base,
order: d.order,
cofactor: d.cofactor,
};
};
const explicitCurveName = (data) => {
const d = explicitCurve(data);
if (!d)
return;
// OpenSSL can serialize a standard EC key with explicit domain parameters while the
// matching cert/SPKI keeps the named-curve OID, so normalize equivalent parameters here.
for (const curve in CurveOID) {
const name = curve;
const known = CMS_ALG[name].ec.Point.CURVE();
if (d.fieldId.info.data !== known.p)
continue;
if (bytesNum(d.curve.a) !== known.a || bytesNum(d.curve.b) !== known.b)
continue;
if (d.order !== known.n)
continue;
if (d.cofactor !== undefined && d.cofactor !== known.h)
continue;
const base = CMS_ALG[name].ec.Point.BASE;
if (!equalBytes(d.base, base.toBytes(false)) && !equalBytes(d.base, base.toBytes(true)))
continue;
return name;
}
return;
};
const ecParamCurve = (d) => {
if (d.TAG === 'namedCurve')
return curveOID(d.data);
if (d.TAG === 'implicitCurve')
return 'OID:implicitCurve';
return explicitCurveName(d.data) || 'OID:specifiedCurve';
};
const spkiCurve = (k) => {
if (k.algorithm.info.TAG !== 'EC')
throw new Error(`expected EC SPKI key, got ${k.algorithm.info.TAG}`);
return ecParamCurve(k.algorithm.info.data);
};
// treeshake: these shared X.509 helpers survive through property reads unless the declaration itself is pure.
const SpkiKey = /* @__PURE__ */ (() => DERUtils.SPKI)();
const CMS_ALG = {
'P-256': { ec: p256, sigOid: '1.2.840.10045.4.3.2', hash: sha256 },
'P-384': { ec: p384, sigOid: '1.2.840.10045.4.3.3', hash: sha384 },
'P-521': { ec: p521, sigOid: '1.2.840.10045.4.3.4', hash: sha512 },
brainpoolP256r1: {
ec: brainpoolP256r1,
sigOid: '1.2.840.10045.4.3.2',
hash: sha256,
},
brainpoolP384r1: {
ec: brainpoolP384r1,
sigOid: '1.2.840.10045.4.3.3',
hash: sha384,
},
brainpoolP512r1: {
ec: brainpoolP512r1,
sigOid: '1.2.840.10045.4.3.4',
hash: sha512,
},
Ed25519: {
ed: ed25519,
sigOid: '1.3.101.112',
hash: sha512,
},
Ed448: {
ed: ed448,
sigOid: '1.3.101.113',
hash: sha512,
},
};
// RFC 5754 section 2: this absent-or-NULL parameters rule applies to SHA-2
// AlgorithmIdentifiers specifically, so this set is intentionally SHA2-only
// (not a generic all-hashes OID table).
const SHA2_OID = {
'2.16.840.1.101.3.4.2.4': true,
'2.16.840.1.101.3.4.2.1': true,
'2.16.840.1.101.3.4.2.2': true,
'2.16.840.1.101.3.4.2.3': true,
};
const ASN1_NULL = /* @__PURE__ */ Uint8Array.from([0x05, 0x00]);
const digestAlgParamsOk = (a) => {
const oid = algOID(a.algorithm);
if (!(oid in SHA2_OID))
return true;
const p = a.params ? TLVNodeCodec.encode(a.params) : undefined;
return !p || equalBytes(p, ASN1_NULL);
};
const digestAlgEqual = (a, b) => {
const aOid = algOID(a.algorithm);
const bOid = algOID(b.algorithm);
if (aOid !== bOid)
return false;
if (!digestAlgParamsOk(a) || !digestAlgParamsOk(b))
return false;
const aParams = a.params ? TLVNodeCodec.encode(a.params) : undefined;
const bParams = b.params ? TLVNodeCodec.encode(b.params) : undefined;
if (aOid in SHA2_OID)
return true;
if (!aParams || !bParams)
return !aParams && !bParams;
return equalBytes(aParams, bParams);
};
const ecCurve = (curve) => CMS_ALG[curve].ec;
const isSignCurve = (curve) => curve in CMS_ALG && 'ec' in CMS_ALG[curve];
const CMS_ALG_BY_SIG_OID = /* @__PURE__ */ (() => Object.fromEntries(Object.values(CMS_ALG).map((v) => [v.sigOid, v])))();
const CMS_HASH_BY_OID = /* @__PURE__ */ (() => Object.fromEntries([sha256, sha384, sha512].map((h) => [hashOid(h), h])))();
const HASH_NAME_TO_OID = /* @__PURE__ */ Object.fromEntries(
/* @__PURE__ */ Object.entries({ sha224, sha256, sha384, sha512 }).map(([name, h]) => [
name,
hashOid(h),
]));
const ALG_NAME_TO_OID = /* @__PURE__ */ (() => ({
ecPublicKey: '1.2.840.10045.2.1',
'ecdsa-with-SHA256': CMS_ALG['P-256'].sigOid,
'ecdsa-with-SHA384': CMS_ALG['P-384'].sigOid,
'ecdsa-with-SHA512': CMS_ALG['P-521'].sigOid,
Ed25519: CMS_ALG.Ed25519.sigOid,
Ed448: CMS_ALG.Ed448.sigOid,
...HASH_NAME_TO_OID,
}))();
const ALG_OID_TO_NAME = /* @__PURE__ */ Object.fromEntries(
/* @__PURE__ */ Object.entries(ALG_NAME_TO_OID).map(([k, v]) => [v, k]));
const algOID = (v) => oidValue(ALG_NAME_TO_OID, v, 'algorithm');
const pkcs8Attrs = (k) => k.attributes?.map((raw) => PKCS8Attr.decode(raw));
const pkcs8FromPem = (pem, der) => {
const key = DERUtils.PKCS8.decode(der);
const t = key.algorithm.info.TAG;
if (t === 'rsaEncryption') {
if (key.privateKey.TAG !== 'raw')
throw new Error('RSA PKCS#8: expected raw private key payload');
return {
pem,
der,
attributes: pkcs8Attrs(key),
key,
rsa: DERUtils.RSAPrivateKey.decode(key.privateKey.data),
};
}
return { pem, der, attributes: pkcs8Attrs(key), key };
};
const pkcs8SignKey = (k) => {
const tag = k.algorithm.info.TAG;
if (tag === 'EC') {
const curve = ecParamCurve(k.algorithm.info.data);
if (k.privateKey.TAG !== 'struct')
throw new Error('EC PKCS#8: expected structured ECPrivateKey payload');
const s = k.privateKey.data;
if (s.parameters && ecParamCurve(s.parameters) !== curve)
throw new Error('EC PKCS#8: algorithm and key parameters mismatch');
return { kind: 'EC', curve, secretKey: s.privateKey, publicKey: k.publicKey || s.publicKey };
}
if (tag === 'Ed25519' || tag === 'Ed448') {
if (k.privateKey.TAG !== 'raw')
throw new Error(`${tag} PKCS#8: expected raw private key payload`);
return {
kind: tag,
secretKey: k.privateKey.data,
publicKey: k.publicKey || CMS_ALG[tag].ed.getPublicKey(k.privateKey.data),
};
}
throw new Error(`expected EC/Ed PKCS#8 key, got ${tag}`);
};
const certItem = (der, opts = {}) => X509C.Certificate.decode(berView(der, opts).der);
const certSpkiKey = (spki) => SpkiKey.decode(X509SPKI.encode(spki));
const matchCertKey = (cert, key) => {
const k = certSpkiKey(cert.tbs.spki);
const tag = k.algorithm.info.TAG;
if (tag === 'EC') {
if (key.key.algorithm.info.TAG !== 'EC')
return false;
const curve = spkiCurve(k);
if (!isSignCurve(curve))
return false;
const kk = pkcs8SignKey(key.key);
if (kk.kind !== 'EC' || curve !== kk.curve)
return false;
const cmp = ecCurve(curve).getPublicKey(kk.secretKey, false);
const cmpC = ecCurve(curve).getPublicKey(kk.secretKey, true);
return equalBytes(k.publicKey, cmp) || equalBytes(k.publicKey, cmpC);
}
if (tag === 'Ed25519' || tag === 'Ed448') {
if (key.key.algorithm.info.TAG !== tag)
return false;
const kk = pkcs8SignKey(key.key);
if (kk.kind !== tag)
return false;
return (equalBytes(k.publicKey, kk.publicKey) ||
equalBytes(k.publicKey, CMS_ALG[tag].ed.getPublicKey(kk.secretKey)));
}
throw new Error('matchCertKey supports EC/Ed keys only');
};
const berView = (src, opts = {}) => DERUtils.BER.decode(src, { allowBER: !!opts.allowBER });
const ASN1 = /* @__PURE__ */ (() => DERUtils.ASN1)();
const DERLen = P.wrap({
encodeStream(w, len) {
if (!Number.isSafeInteger(len) || len < 0)
throw new Error(`expected non-negative length, got ${len}`);
if (len < 0x80)
return w.byte(len);
const a = [];
for (let n = len; n > 0; n >>= 8)
a.unshift(n & 0xff);
w.byte(0x80 | a.length);
w.bytes(Uint8Array.from(a));
},
decodeStream(r) {
const a = r.byte();
if (a < 0x80)
return a;
const n = a & 0x7f;
if (!n)
throw new Error('DER indefinite length is not supported');
const lb = r.bytes(n);
let len = 0;
for (const b of lb)
len = (len << 8) | b;
if (len < 0x80)
throw new Error('DER non-minimal length encoding');
return len;
},
});
const TLV = P.struct({ tag: P.U8, value: P.bytes(DERLen) });
const TLVNodeCodec = P.wrap({
encodeStream(w, n) {
const value = n.children
? concatBytes(...n.children.map((i) => TLVNodeCodec.encode(i)))
: hexToBytes(n.valueHex || '');
w.bytes(TLV.encode({ tag: n.tag, value }));
},
decodeStream(r) {
const t = TLV.decodeStream(r);
if (t.tag & 0x20) {
const items = [];
let at = 0;
while (at < t.value.length) {
const src = t.value.slice(at);
if (src.length < 2)
throw new Error('constructed TLV child truncated');
const lb = src[1];
if (lb < 0x80) {
const total = 2 + lb;
items.push(TLVNodeCodec.decode(src.slice(0, total)));
at += total;
continue;
}
const n = lb & 0x7f;
if (!n)
throw new Error('DER indefinite length is not supported');
if (src.length < 2 + n)
throw new Error('constructed TLV child length truncated');
let len = 0;
for (let i = 0; i < n; i++)
len = (len << 8) | src[2 + i];
if (len < 0x80)
throw new Error('DER non-minimal length encoding');
const total = 2 + n + len;
items.push(TLVNodeCodec.decode(src.slice(0, total)));
at += total;
}
if (at !== t.value.length)
throw new Error('constructed TLV child decode mismatch');
return { tag: t.tag, children: items };
}
return { tag: t.tag, valueHex: bytesToHex(t.value) };
},
});
// Encoded ASN.1 ANY passthrough: consume exactly one TLV from stream and keep its canonical bytes.
// This cannot be `P.bytes(null)` (greedy, would eat the rest of parent structure) and cannot be
// plain schema decode because many ANY values stay unresolved until OID-specific dispatch later.
const RawTLV = /* @__PURE__ */ P.wrap({
encodeStream(w, v) {
const t = TLV.decode(v);
w.bytes(TLV.encode(t));
},
decodeStream(r) {
return TLV.encode(TLV.decodeStream(r));
},
});
const ASCII = /* @__PURE__ */ P.apply(/* @__PURE__ */ P.bytes(null), {
encode: (bytes) => {
let out = '';
for (let i = 0; i < bytes.length; i++) {
const c = bytes[i];
if (c > 0x7f)
throw new Error(`bytes contain non-ASCII value 0x${c.toString(16)} at position ${i}`);
out += String.fromCharCode(c);
}
return out;
},
decode: asciiToBytes,
});
const tagged = (tag, inner) => {
const coder = P.wrap({
encodeStream(w, v) {
w.bytes(TLV.encode({ tag, value: inner.encode(v) }));
},
decodeStream(r) {
const t = TLV.decodeStream(r);
if (t.tag !== tag)
throw new Error(`expected tag 0x${tag.toString(16)}, got 0x${t.tag.toString(16)}`);
return inner.decode(t.value);
},
});
return { tagByte: tag, tagBytes: [tag], constructed: 0, inner, ...coder };
};
const UTCTime = /* @__PURE__ */ tagged(0x17, ASCII);
const GeneralizedTime = /* @__PURE__ */ tagged(0x18, ASCII);
const Time =
/* @__PURE__ */ ASN1.choice({ utc: UTCTime, generalized: GeneralizedTime });
// RFC 5280 section 4.1.2.5.1 and 4.1.2.5.2: cert validity uses Zulu time and fixed second precision.
const TimeRE = /^(\d{2}|\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})Z$/;
const X509Time = {
decode: (der) => {
const t = Time.decode(der);
const m = TimeRE.exec(t.data);
if (!m)
throw new Error(`expected X509 time YY|YYYYMMDDHHMMSSZ, got ${t.data}`);
const yRaw = m[1];
if (t.TAG === 'utc' && yRaw.length !== 2)
throw new Error(`expected UTCTime year=2 digits, got ${t.data}`);
if (t.TAG === 'generalized' && yRaw.length !== 4)
throw new Error(`expected GeneralizedTime year=4 digits, got ${t.data}`);
const yNum = Number(yRaw);
const y = t.TAG === 'utc' ? (yNum >= 50 ? 1900 + yNum : 2000 + yNum) : yNum;
const mo = Number(m[2]);
const d = Number(m[3]);
const h = Number(m[4]);
const mi = Number(m[5]);
const s = Number(m[6]);
if (mo < 1 || mo > 12)
throw new Error(`expected month 01..12, got ${m[2]}`);
if (d < 1 || d > 31)
throw new Error(`expected day 01..31, got ${m[3]}`);
if (h > 23)
throw new Error(`expected hour 00..23, got ${m[4]}`);
if (mi > 59)
throw new Error(`expected minute 00..59, got ${m[5]}`);
if (s > 59)
throw new Error(`expected second 00..59, got ${m[6]}`);
const ms = Date.UTC(y, mo - 1, d, h, mi, s);
const dt = new Date(ms);
// RFC 5280 section 4.1.2.5: certificate time fields are exact UTC calendar components and must not roll over.
if (dt.getUTCFullYear() !== y ||
dt.getUTCMonth() + 1 !== mo ||
dt.getUTCDate() !== d ||
dt.getUTCHours() !== h ||
dt.getUTCMinutes() !== mi ||
dt.getUTCSeconds() !== s)
throw new Error(`invalid calendar date in X509 time: ${t.data}`);
return Math.floor(ms / 1000);
},
encode: (ts) => {
if (!Number.isFinite(ts))
throw new Error(`expected finite timestamp, got ${ts}`);
const d = new Date(Math.floor(ts) * 1000);
const pad2 = (n) => `${n}`.padStart(2, '0');
const pad4 = (n) => `${n}`.padStart(4, '0');
const y = d.getUTCFullYear();
const text = y >= 1950 && y <= 2049
? `${pad2(y % 100)}${pad2(d.getUTCMonth() + 1)}${pad2(d.getUTCDate())}${pad2(d.getUTCHours())}${pad2(d.getUTCMinutes())}${pad2(d.getUTCSeconds())}Z`
: `${pad4(y)}${pad2(d.getUTCMonth() + 1)}${pad2(d.getUTCDate())}${pad2(d.getUTCHours())}${pad2(d.getUTCMinutes())}${pad2(d.getUTCSeconds())}Z`;
return (y >= 1950 && y <= 2049 ? UTCTime : GeneralizedTime).encode(text);
},
};
const timeEpoch = (time) => X509Time.decode(Time.encode(time));
const PKCS8Attr = /* @__PURE__ */ (() => ASN1.sequence({ oid: ASN1.OID, values: ASN1.set(RawTLV) }))();
// RFC 5280 section 4.1.1.2 and RFC 5652 sections 10.1.1/10.1.2: AlgorithmIdentifier parameters are OPTIONAL.
// `params` keeps parsed ASN.1 ANY TLV when present; `undefined` means absent.
const HasTail = /* @__PURE__ */ P.wrap({
encodeStream() { },
decodeStream(r) {
return !!r.leftBytes;
},
});
const AlgorithmIdentifier = /* @__PURE__ */ (() => P.apply(
/* @__PURE__ */ ASN1.sequence({
algorithm: ASN1.OID,
params: /* @__PURE__ */ P.optional(HasTail, TLVNodeCodec),
}), {
encode: (x) => ({
algorithm: oidName(ALG_OID_TO_NAME, x.algorithm),
params: x.params,
}),
decode: (x) => ({
algorithm: algOID(x.algorithm),
params: x.params,
}),
}))();
const IA5 = /* @__PURE__ */ tagged(0x16, ASCII);
const UTF8_DECODER = /* @__PURE__ */ new TextDecoder('utf-8', { fatal: true });
const UTF8_ENCODER = /* @__PURE__ */ new TextEncoder();
const UTF8String = /* @__PURE__ */ tagged(0x0c,
/* @__PURE__ */ P.apply(/* @__PURE__ */ P.bytes(null), {
// X.509 UTF8String must be valid UTF-8; reject malformed byte sequences.
encode: (b) => UTF8_DECODER.decode(b),
decode: (s) => UTF8_ENCODER.encode(s),
}));
const PrintableString = /* @__PURE__ */ tagged(0x13,
/* @__PURE__ */ P.validate(ASCII, (s) => {
if (!/^[A-Za-z0-9 '()+,./:=?-]*$/.test(s))
throw new Error(`invalid PrintableString: ${JSON.stringify(s)}`);
return s;
}));
// TeletexString (T61String) is treated as a byte-preserving 0x00..0xff mapping for interoperability.
// Strict T.61 character-set semantics are intentionally not enforced here.
const TeletexString = /* @__PURE__ */ tagged(0x14,
/* @__PURE__ */ P.apply(/* @__PURE__ */ P.bytes(null), {
encode: (b) => {
let out = '';
for (let i = 0; i < b.length; i++)
out += String.fromCharCode(b[i]);
return out;
},
decode: (s) => {
const out = new Uint8Array(s.length);
for (let i = 0; i < s.length; i++) {
const c = s.charCodeAt(i);
if (c > 0xff)
throw new Error(`expected latin1 character, got U+${c.toString(16).toUpperCase()}`);
out[i] = c;
}
return out;
},
}));
const VisibleString = /* @__PURE__ */ tagged(0x1a, ASCII);
const NumericString = /* @__PURE__ */ tagged(0x12,
/* @__PURE__ */ P.validate(ASCII, (s) => {
if (!/^[0-9 ]*$/.test(s))
throw new Error(`invalid NumericString: ${JSON.stringify(s)}`);
return s;
}));
const BMPString = /* @__PURE__ */ tagged(0x1e,
/* @__PURE__ */ P.apply(/* @__PURE__ */ P.bytes(null), {
encode: (b) => {
if (b.length % 2)
throw new Error('BMPString length must be even');
let out = '';
for (let i = 0; i < b.length; i += 2)
out += String.fromCharCode((b[i] << 8) | b[i + 1]);
return out;
},
decode: (s) => {
const out = new Uint8Array(s.length * 2);
for (let i = 0; i < s.length; i++) {
const c = s.charCodeAt(i);
out[i * 2] = c >>> 8;
out[i * 2 + 1] = c & 0xff;
}
return out;
},
}));
const NameString = /* @__PURE__ */ ASN1.choice({
utf8: UTF8String,
printable: PrintableString,
teletex: TeletexString,
ia5: IA5,
bmp: BMPString,
visible: VisibleString,
numeric: NumericString,
});
const ATTR_NAME_OID = {
'2.5.4.3': 'commonName',
'2.5.4.7': 'localityName',
'2.5.4.32': 'owner',
'2.5.4.42': 'givenName',
'2.5.4.106': 'otherName',
};
const QC_STATEMENT_OID = {
'0.4.0.1862.1.1': 'etsiQcCompliance',
};
const CERT_ANY_TAG = {
bool: 0x01,
int: 0x02,
oid: 0x06,
octet: 0x04,
utc: 0x17,
generalized: 0x18,
};
const CertAnyCodec = /* @__PURE__ */ P.apply(TLVNodeCodec, {
encode: (n) => {
const der = TLVNodeCodec.encode(n);
if (n.tag === CERT_ANY_TAG.bool)
return { TAG: 'bool', data: ASN1Bool.decode(der) };
if (n.tag === CERT_ANY_TAG.int)
return { TAG: 'int', data: ASN1.Integer.decode(der) };
if (n.tag === CERT_ANY_TAG.oid) {
const oid = ASN1.OID.decode(der);
return { TAG: 'oid', data: { oid, name: ATTR_NAME_OID[oid] } };
}
if (n.tag === CERT_ANY_TAG.octet)
return { TAG: 'octet', data: ASN1.OctetString.decode(der) };
if (n.tag === CERT_ANY_TAG.utc || n.tag === CERT_ANY_TAG.generalized)
return { TAG: 'time', data: Time.decode(der) };
if (n.tag === UTF8String.tagByte ||
n.tag === PrintableString.tagByte ||
n.tag === TeletexString.tagByte ||
n.tag === IA5.tagByte ||
n.tag === BMPString.tagByte ||
n.tag === VisibleString.tagByte ||
n.tag === NumericString.tagByte)
return { TAG: 'text', data: NameString.decode(der) };
return { TAG: 'raw', data: n };
},
decode: (x) => {
if (x.TAG === 'raw')
return x.data;
if (x.TAG === 'text')
return TLVNodeCodec.decode(NameString.encode(x.data));
if (x.TAG === 'oid')
return TLVNodeCodec.decode(ASN1.OID.encode(x.data.oid));
if (x.TAG === 'int')
return TLVNodeCodec.decode(ASN1.Integer.encode(x.data));
if (x.TAG === 'bool')
return TLVNodeCodec.decode(ASN1Bool.encode(x.data));
if (x.TAG === 'time')
return TLVNodeCodec.decode(Time.encode(x.data));
return TLVNodeCodec.decode(ASN1.OctetString.encode(x.data));
},
});
const NameAttr = /* @__PURE__ */ (() => ASN1.sequence({ oid: ASN1.OID, value: NameString }))();
const X509Name = /* @__PURE__ */ ASN1.sequence({
rdns: /* @__PURE__ */ P.array(null, /* @__PURE__ */ ASN1.set(NameAttr)),
});
const X509Validity = /* @__PURE__ */ ASN1.sequence({ notBefore: Time, notAfter: Time });
// RFC 5912 (PKIX1Explicit-2009): Extension.
const X509Ext = /* @__PURE__ */ (() => ASN1.sequence({ oid: ASN1.OID, rest: /* @__PURE__ */ P.bytes(null) }))();
// RFC 5912 (PKIX1Explicit-2009): SubjectPublicKeyInfo.
const X509SPKI = /* @__PURE__ */ (() => ASN1.sequence({
algorithm: AlgorithmIdentifier,
publicKey: ASN1.BitString,
}))();
// RFC 5912 (PKIX1Explicit-2009): TBSCertificate.
const X509TBSCertificate = /* @__PURE__ */ (() => ASN1.sequence({
version: /* @__PURE__ */ ASN1.optional(/* @__PURE__ */ ASN1.explicit(0, ASN1.Integer)),
serial: ASN1.Integer,
signature: AlgorithmIdentifier,
issuer: X509Name,
validity: X509Validity,
subject: X509Name,
spki: X509SPKI,
issuerUniqueID: /* @__PURE__ */ ASN1.optional(/* @__PURE__ */ ASN1.implicit(1, ASN1.BitString)),
subjectUniqueID: /* @__PURE__ */ ASN1.optional(
/* @__PURE__ */ ASN1.implicit(2, ASN1.BitString)),
extensions: /* @__PURE__ */ ASN1.optional(
/* @__PURE__ */ ASN1.explicit(3,
/* @__PURE__ */ ASN1.sequence({ list: /* @__PURE__ */ P.array(null, X509Ext) }))),
}))();
// RFC 5912 (PKIX1Explicit-2009): Certificate.
const X509Certificate = /* @__PURE__ */ (() => ASN1.sequence({
tbs: X509TBSCertificate,
sigAlg: AlgorithmIdentifier,
sig: ASN1.BitString,
}))();
const X509C = /* @__PURE__ */ (() => ({
Name: X509Name,
TBSCertificate: X509TBSCertificate,
Certificate: X509Certificate,
}))();
// RFC 5652 section 10.2.2: CertificateChoices.
const CMSCertificateChoices = /* @__PURE__ */ (() => ASN1.choice({
certificate: X509C.Certificate,
extendedCertificate: tagged(0xa0, P.bytes(null)),
// RFC 5652 section 12.2: ACv1 module; parsed as opaque branch and not consumed by signer-cert selection.
v1AttrCert: tagged(0xa1, P.bytes(null)),
v2AttrCert: tagged(0xa2, P.bytes(null)),
other: tagged(0xa3, P.bytes(null)),
}))();
// RFC 5652 section 10.2.1: RevocationInfoChoice and OtherRevocationInfoFormat.
const CMSCertificateList = /* @__PURE__ */ (() => ASN1.sequence({
tbsCertList: RawTLV,
signatureAlgorithm: AlgorithmIdentifier,
signatureValue: ASN1.BitString,
}))();
const CMSOtherRevocationInfoFormat = /* @__PURE__ */ (() => ASN1.sequence({ format: ASN1.OID, info: RawTLV }))();
const CMSRevocationInfoChoice = /* @__PURE__ */ (() => ASN1.choice({
crl: CMSCertificateList,
other: ASN1.implicit(1, CMSOtherRevocationInfoFormat),
}))();
// RFC 5652 sections 10.1.1 and 10.1.2: DigestAlgorithmIdentifier/SignatureAlgorithmIdentifier ::= AlgorithmIdentifier.
// RFC 5652 section 5.3: Attribute ::= SEQUENCE { attrType OBJECT IDENTIFIER, attrValues SET OF AttributeValue }.
const CMSAttribute = /* @__PURE__ */ (() => P.validate(ASN1.sequence({ oid: ASN1.OID, values: ASN1.set(RawTLV) }), (a) => {
// RFC 5652 section 11.1: content-type attrValues is SET SIZE (1) OF AttributeValue.
// RFC 5652 section 11.2: message-digest attrValues is SET SIZE (1) OF AttributeValue.
// RFC 5652 section 11.3: signing-time attrValues is SET SIZE (1) OF AttributeValue.
const name = CMS_SIGNED_ATTR_NAME[a.oid] || '';
if (name && a.values.length !== 1)
throw new Error(`${name} attribute must have exactly one value, got ${a.values.length}`);
return a;
}))();
// RFC 5652 section 10.2.4 (used by section 5.3 SignerIdentifier): IssuerAndSerialNumber.
const CMSIssuerAndSerial = /* @__PURE__ */ (() => ASN1.sequence({
issuer: X509C.Name,
serial: ASN1.Integer,
}))();
// RFC 5652 section 5.3: SignerIdentifier (IssuerAndSerialNumber / SubjectKeyIdentifier).
const CMSSignerIdentifier = /* @__PURE__ */ (() => ASN1.choice({
issuerSerial: CMSIssuerAndSerial,
subjectKeyIdentifier: ASN1.implicit(0, ASN1.OctetString),
}))();
// RFC 5652 section 5.3: SignerInfo.
const CMSSignerInfo = /* @__PURE__ */ (() => ASN1.sequence({
version: ASN1.Integer,
sid: CMSSignerIdentifier,
digestAlg: AlgorithmIdentifier,
signedAttrs: ASN1.optional(ASN1.implicit(0, ASN1.set(CMSAttribute))),
signatureAlg: AlgorithmIdentifier,
signature: ASN1.OctetString,
unsignedAttrs: ASN1.optional(ASN1.implicit(1, ASN1.set(CMSAttribute))),
}))();
// RFC 5652 section 5.2: EncapsulatedContentInfo.
const CMSEncapContentInfo = /* @__PURE__ */ (() => ASN1.sequence({
eContentType: ASN1.OID,
eContent: ASN1.optional(ASN1.explicit(0, ASN1.OctetString)),
}))();
// RFC 5652 section 5.1: SignedData.
const CMSSignedData = /* @__PURE__ */ (() => ASN1.sequence({
version: ASN1.Integer,
digestAlgorithms: ASN1.set(AlgorithmIdentifier),
encapContentInfo: CMSEncapContentInfo,
// RFC 5652 section 10.2.3: CertificateSet ::= SET OF CertificateChoices.
certificates: ASN1.optional(ASN1.implicit(0, ASN1.set(CMSCertificateChoices))),
crls: ASN1.optional(ASN1.implicit(1, ASN1.set(CMSRevocationInfoChoice))),
signerInfos: ASN1.set(CMSSignerInfo),
}))();
const CMS_CONTENT_TYPE_NAME_TO_OID = {
data: '1.2.840.113549.1.7.1',
signedData: '1.2.840.113549.1.7.2',
envelopedData: '1.2.840.113549.1.7.3',
};
const CMS_CONTENT_TYPE_OID_TO_NAME = /* @__PURE__ */ (() => Object.fromEntries(Object.entries(CMS_CONTENT_TYPE_NAME_TO_OID).map(([k, v]) => [v, k])))();
const cmsContentTypeOID = (v) => oidValue(CMS_CONTENT_TYPE_NAME_TO_OID, v, 'CMS contentType');
// RFC 5652 section 3: ContentInfo.
const CMSContentInfo = /* @__PURE__ */ (() => P.apply(ASN1.sequence({
contentType: ASN1.OID,
content: ASN1.explicit(0, RawTLV),
}), {
encode: (x) => ({
contentType: oidName(CMS_CONTENT_TYPE_OID_TO_NAME, x.contentType),
content: x.content,
}),
decode: (x) => ({
contentType: cmsContentTypeOID(x.contentType),
content: x.content,
}),
}))();
const CMSX = /* @__PURE__ */ (() => ({
AlgorithmIdentifier: AlgorithmIdentifier,
Attribute: CMSAttribute,
SignerInfo: CMSSignerInfo,
SignedData: CMSSignedData,
ContentInfo: CMSContentInfo,
}))();
// micro-packed coders for full X.509 cert decode/encode, same exposure style as DERUtils in convert.ts
/**
* Low-level X.509 coders used by the higher-level APIs.
* @example
* Use the low-level coders when you need to encode or decode individual X.509 structures.
* ```ts
* import { CERTUtils } from 'micro-key-producer/x509.js';
* CERTUtils.Name.encode({
* rdns: [[{ oid: '2.5.4.3', value: { TAG: 'utf8', data: 'example.com' } }]],
* });
* ```
*/
export const CERTUtils = /* @__PURE__ */ (() => ({
Name: X509C.Name,
TBSCertificate: X509C.TBSCertificate,
Certificate: X509C.Certificate,
}))();
const ASN1BoolInner = /* @__PURE__ */ P.wrap({
encodeStream(w, v) {
w.byte(v ? 0xff : 0x00);
},
decodeStream(r) {
const b = r.byte();
if (!r.isEnd())
throw new Error('BOOLEAN length must be 1');
return b !== 0;
},
});
const ASN1Bool = /* @__PURE__ */ (() => ({
tagByte: 0x01,
tagBytes: [0x01],
constructed: 0,
inner: ASN1BoolInner,
...P.wrap({
encodeStream(w, v) {
w.bytes(Uint8Array.from([0x01, 0x01, v ? 0xff : 0x00]));
},
decodeStream(r) {
const t = RawTLV.decodeStream(r);
if (t.length !== 3 || t[0] !== 0x01 || t[1] !== 0x01)
throw new Error('DER BOOLEAN must be 01 01 xx');
return t[2] !== 0;
},
}),
}))();
const ASN1BitStringInner = /* @__PURE__ */ P.struct({
unused: P.U8,
bytes: /* @__PURE__ */ P.bytes(null),
});
const ASN1BitStringRaw = /* @__PURE__ */ (() => ({
tagByte: 0x03,
tagBytes: [0x03],
constructed: 0,
inner: ASN1BitStringInner,
...P.wrap({
encodeStream(w, v) {
w.bytes(TLV.encode({ tag: 0x03, value: ASN1BitStringInner.encode(v) }));
},
decodeStream(r) {
const t = TLV.decodeStream(r);
if (t.tag !== 0x03)
throw new Error('expected BIT STRING');
const d = ASN1BitStringInner.decode(t.value);
if (d.unused > 7)
throw new Error(`BIT STRING invalid unused bits: ${d.unused}`);
return d;
},
}),
}))();
// Generic IP coders (not ASN.1-specific): bytes <-> textual address.
const IPv4 = /* @__PURE__ */ P.apply(/* @__PURE__ */ P.bytes(4), {
encode: (b) => `${b[0]}.${b[1]}.${b[2]}.${b[3]}`,
decode: (s) => {
const p = s.split('.');
if (p.length !== 4)
throw new Error(`invalid IPv4 address ${s}`);
const out = new Uint8Array(4);
for (let i = 0; i < 4; i++) {
if (!/^[0-9]+$/.test(p[i]))
throw new Error(`invalid IPv4 address ${s}`);
const n = Number(p[i]);
if (!Number.isInteger(n) || n < 0 || n > 255)
throw new Error(`invalid IPv4 address ${s}`);
out[i] = n;
}
return out;
},
});
// Generic IP coders (not ASN.1-specific): bytes <-> textual address.
const IPv6 = /* @__PURE__ */ P.apply(/* @__PURE__ */ P.bytes(16), {
encode: (b) => {
const w = new Array(8);
for (let i = 0; i < 8; i++)
w[i] = (b[i * 2] << 8) | b[i * 2 + 1];
let bestAt = -1;
let bestLen = 0;
for (let i = 0; i < 8;) {
if (w[i] !== 0) {
i++;
continue;
}
let j = i;
while (j < 8 && w[j] === 0)
j++;
const len = j - i;
if (len > bestLen && len > 1) {
bestLen = len;
bestAt = i;
}
i = j;
}
const hexw = w.map((x) => x.toString(16));
if (bestAt < 0)
return hexw.join(':');
const left = hexw.slice(0, bestAt).join(':');
const right = hexw.slice(bestAt + bestLen).join(':');
if (!left && !right)
return '::';
if (!left)
return `::${right}`;
if (!right)
return `${left}::`;
return `${left}::${right}`;
},
decode: (s) => {
if (s.includes(':::'))
throw new Error(`invalid IPv6 address ${s}`);
if ((s.match(/::/g) || []).length > 1)
throw new Error(`invalid IPv6 address ${s}`);
const [l, r] = s.split('::');
const lp = l ? l.split(':').filter((i) => i.length) : [];
const rp = r !== undefined && r ? r.split(':').filter((i) => i.length) : [];
if (!lp.every((i) => /^[0-9a-fA-F]{1,4}$/.test(i)) ||
!rp.every((i) => /^[0-9a-fA-F]{1,4}$/.test(i)))
throw new Error(`invalid IPv6 address ${s}`);
const total = lp.length + rp.length;
if (!((s.includes('::') && total <= 8) || (!s.includes('::') && total === 8)))
throw new Error(`invalid IPv6 address ${s}`);
const mid = s.includes('::') ? new Array(8 - total).fill('0') : [];
const words = [...lp, ...mid, ...rp];
if (words.length !== 8)
throw new Error(`invalid IPv6 address ${s}`);
const out = new Uint8Array(16);
for (let i = 0; i < 8; i++) {
const n = Number.parseInt(words[i], 16);
if (!Number.isFinite(n) || n < 0 || n > 0xffff)
throw new Error(`invalid IPv6 address ${s}`);
out[i * 2] = n >>> 8;
out[i * 2 + 1] = n & 0xff;
}
return out;
},
});
const IPAddress = /* @__PURE__ */ tagged(0x87,
/* @__PURE__ */ P.apply(/* @__PURE__ */ P.bytes(null), {
encode: (b) => {
if (b.length === 4)
return IPv4.decode(b);
if (b.length === 16)
return IPv6.decode(b);
return `hex:${bytesToHex(b)}`;
},
decode: (s) => {
if (s.startsWith('hex:'))
return hexToBytes(s.slice(4));
if (s.includes('.'))
return IPv4.encode(s);
if (s.includes(':'))
return IPv6.encode(s);
throw new Error(`invalid SAN iPAddress ${s}`);
},
}));
const ExtOtherName = /* @__PURE__ */ (() => ASN1.sequence({
type: ASN1.OID,
value: /* @__PURE__ */ ASN1.explicit(0, TLVNodeCodec),
}))();
// RFC 5280 section 4.2.1.6: GeneralName.
const ExtGeneralName = /* @__PURE__ */ (() => ASN1.choice({
otherName: /* @__PURE__ */ ASN1.implicit(0, ExtOtherName),
rfc822Name: /* @__PURE__ */ ASN1.implicit(1, IA5),
dNSName: /* @__PURE__ */ ASN1.implicit(2, IA5),
x400Address: /* @__PURE__ */ ASN1.implicit(3, ASN1.OctetString),
directoryName: /* @__PURE__ */ ASN1.explicit(4, X509Name),
ediPartyName: /* @__PURE__ */ ASN1.implicit(5, ASN1.OctetString),
uniformResourceIdentifier: /* @__PURE__ */ ASN1.implicit(6, IA5),
iPAddress: IPAddress,
registeredID: /* @__PURE__ */ ASN1.implicit(8, ASN1.OID),
}))();
const extNonEmpty = (coder, name, item) => P.validate(coder, (x) => {
if (!x.list.length)
throw new Error(`${name} must contain at least one ${item}`);
return x;
});
const ExtGeneralNames = /* @__PURE__ */ ASN1.sequence({
list: /* @__PURE__ */ P.array(null, ExtGeneralName),
});
// RFC 5280 section 4.2.1.6: subjectAltName uses GeneralNames SIZE (1..MAX).
const ExtSAN = /* @__PURE__ */ extNonEmpty(ExtGeneralNames, 'subjectAltName', 'GeneralName');
// RFC 5280 section 4.2.1.7: issuerAltName uses GeneralNames SIZE (1..MAX).
const ExtIAN = /* @__PURE__ */ extNonEmpty(ExtGeneralNames, 'issuerAltName', 'GeneralName');
// RFC 5280 section 4.2.1.1: AuthorityKeyIdentifier.
const ExtAKI = /* @__PURE__ */ (() => ASN1.sequence({
keyIdentifier: ASN1.optional(ASN1.implicit(0, ASN1.OctetString)),
authorityCertIssuer: ASN1.optional(ASN1.implicit(1, ExtGeneralNames)),
authorityCertSerialNumber: ASN1.optional(ASN1.implicit(2, ASN1.Integer)),
}))();
const ExtAccessInfo = /* @__PURE__ */ (() => ASN1.sequence({
list: P.array(null, ASN1.sequence({ method: ASN1.OID, location: ExtGeneralName })),
}))();
// RFC 5280 section 4.2.2.1: AuthorityInfoAccessSyntax is SEQUENCE SIZE (1..MAX) OF AccessDescription.
const ExtAIA = /* @__PURE__ */ extNonEmpty(ExtAccessInfo, 'authorityInfoAccess', 'AccessDescription');
// RFC 5280 section 4.2.2.2: SubjectInfoAccessSyntax is SEQUENCE SIZE (1..MAX) OF AccessDescription.
const ExtSIA = /* @__PURE__ */ extNonEmpty(ExtAccessInfo, 'subjectInfoAccess', 'AccessDescription');
// RFC 3820 section 3.8: ProxyCertInfo extension.
const OctetsHex = /* @__PURE__ */ tagged(0x04,
/* @__PURE__ */ P.apply(/* @__PURE__ */ P.bytes(null), hex));
const PROXY_POLICY_INHERIT_ALL = '1.3.6.1.5.5.7.21.1';
const PROXY_POLICY_INDEPENDENT = '1.3.6.1.5.5.7.21.2';
const ExtProxyCertInfo = /* @__PURE__ */ (() => ASN1.sequence({
pathLen: ASN1.optional(ASN1.Integer),
policy: ASN1.sequence({ language: ASN1.OID, policy: ASN1.optional(OctetsHex) }),
}))();
const ExtProxyCertInfoChecked = /* @__PURE__ */ P.validate(ExtProxyCertInfo, (x) => {
// RFC 3820 section 3.8.1: pCPathLenConstraint is INTEGER (0..MAX) when present.
if (x.pathLen !== undefined && x.pathLen < 0n)
throw new Error('proxyCertInfo pCPathLenConstraint must be >= 0');
// RFC 3820 section 3.8.2: inheritAll/independent MUST NOT carry policy bytes.
if ((x.policy.language === PROXY_POLICY_INHERIT_ALL ||
x.policy.language === PROXY_POLICY_INDEPENDENT) &&
x.policy.policy !== undefined)
throw new Error('proxyCertInfo policy MUST be absent for inheritAll/independent policy language');
return x;
});
// RFC 7633 section 4.1 + IANA TLS extension registry: Features are TLS extension identifiers (uint16 space).
const ExtTLSFeature = /* @__PURE__ */ (() => P.validate(ASN1.sequence({ list: P.array(null, ASN1.Integer) }), (x) => {
for (const f of x.list) {
if (f < 0n || f > 65535n)
throw new Error(`tlsFeature value must be in 0..65535, got ${f}`);
}
return x;
}))();
const SCTItem = /* @__PURE__ */ (() => P.struct({
version: P.U8,
logID: P.bytes(32),
timestamp: P.U64BE,
extensions: P.apply(P.bytes(P.U16BE), hex),
hash: P.U8,
signatureAlgorithm: P.U8,
signature: P.bytes(P.U16BE),
}))();
const SCTListInner = /* @__PURE__ */ (() => P.validate(P.apply(P.bytes(null), {
encode: (b) =>
// RFC 6962 section 3.3: X.509 extension carries SignedCertificateTimestampList inside ASN.1 OCTET STRING.
P.prefix(P.U16BE, P.array(null, P.prefix(P.U16BE, SCTItem))).decode(b.length && b[0] === 0x04 ? ASN1.OctetString.decode(b) : b),
decode: (v) => P.prefix(P.U16BE, P.array(null, P.prefix(P.U16BE, SCTItem))).encode(v),
}), (x) => {
// RFC 6962 section 3.3: SignedCertificateTimestampList.sct_list is <1..2^16-1>.
if (!x.length)
throw new Error('sct list must contain at least one SerializedSCT');
// RFC 6962 section 3.2: sct_version for v1 is 0.
for (const sct of x) {
if (sct.version !== 0)
throw new Error(`sct_version must be v1 (0), got ${sct.version}`);
}
return x;
}))();
// RFC 5280 section 4.2.1.13: DistributionPointName.
const ExtDistributionPointName = /* @__PURE__ */ (() => ASN1.choice({
fullName: ASN1.implicit(0, ExtGeneralNames),
nameRelativeToCRLIssuer: ASN1.implicit(1, ASN1.set(NameAttr)),
}))();
// RFC 5280 section 4.2.1.13: DistributionPoint and CRLDistributionPoints.
const ExtCRLDP = /* @__PURE__ */ (() => P.validate(ASN1.sequence({
list: P.array(null, ASN1.sequence({
distributionPoint: ASN1.optional(ASN1.explicit(0, ExtDistributionPointName)),
reasons: ASN1.optional(ASN1.implicit(1, ASN1BitStringRaw)),
cRLIssuer: ASN1.optional(ASN1.implicit(2, ExtGeneralNames)),
})),
}), (x) => {
// RFC 5280 section 4.2.1.13: CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint.
if (!x.list.length)
throw new Error('cRLDistributionPoints must contain at least one DistributionPoint');
// RFC 5280 section 4.2.1.13: DistributionPoint MUST NOT contain only reasons;
// either distributionPoint or cRLIssuer must be present.
for (const dp of x.list) {
if (!dp.distributionPoint && !dp.cRLIssuer) {
throw new Error('DistributionPoint must include distributionPoint or cRLIssuer');
}
}
return x;
}))();
const oidSet = (map) => new Set(Object.values(map).map((v) => v[0]));
const oidDecode = (coder, set) => {
return (id, val) => set.has(id) ? coder.decode(concatBytes(ASN1.OID.encode(id), val)) : undefined;
};
const DisplayText = /* @__PURE__ */ ASN1.choice({
utf8: UTF8String,
ia5: IA5,
visible: VisibleString,
bmp: BMPString,
});
const PolicyNoticeRef = /* @__PURE__ */ (() => ASN1.sequence({
organization: DisplayText,
numbers: /* @__PURE__ */ ASN1.sequence({ list: /* @__PURE__ */ P.array(null, ASN1.Integer) }),
}))();
const PolicyQualifierInfoRaw = /* @__PURE__ */ (() => ASN1.sequence({ oid: ASN1.OID, value: RawTLV }))();
const PolicyQualifierUserNotice = /* @__PURE__ */ P.apply(
/* @__PURE__ */ ASN1.sequence({
noticeRef: /* @__PURE__ */ ASN1.optional(PolicyNoticeRef),
explicitText: /* @__PURE__ */ P.optional(HasTail, DisplayText),
}), {
encode: (n) => ({
noticeRef: n.noticeRef
? {
organization: {
tag: n.noticeRef.organization.TAG,
text: n.noticeRef.organization.data,
},
numbers: n.noticeRef.numbers.list.map((v) => Number(v)),
}
: undefined,
explicitText: n.explicitText
? { tag: n.explicitText.TAG, text: n.explicitText.data }
: undefined,
}),
decode: (d) => ({
noticeRef: d.noticeRef
? {
organization: {
TAG: d.noticeRef.organization.tag,
data: d.noticeRef.organization.text,
},
numbers: { list: d.noticeRef.numbers.map((n) => BigInt(n)) },
}
: undefined,
explicitText: d.explicitText
? { TAG: d.explicitText.tag, data: d.explicitText.text }
: undefined,
}),
});
const PolicyQualifierKnownMap = {
cps: ['1.3.6.1.5.5.7.2.1', IA5],
userNotice: ['1.3.6.1.5.5.7.2.2', PolicyQualifierUserNotice],
};
const PolicyQualifierByOID = /* @__PURE__ */ (() => ({
[PolicyQualifierKnownMap.cps[0]]: { TAG: 'cps', coder: PolicyQualifierKnownMap.cps[1] },
[PolicyQualifierKnownMap.userNotice[0]]: {
TAG: 'userNotice',
coder: PolicyQualifierKnownMap.userNotice[1],
},
}))();
const ExtPolicyQualifierInfo = /* @__PURE__ */ P.apply(PolicyQualifierInfoRaw, {
encode: (x) => {
const d = PolicyQualifierByOID[x.oid];
if (d)
return { TAG: d.TAG, data: d.coder.decode(x.value) };
return { TAG: 'unknown', data: { oid: x.oid, value: TLVNodeCodec.decode(x.value) } };
},
decode: (q) => {
if (q.TAG === 'unknown')
return { oid: q.data.oid, value: TLVNodeCodec.encode(q.data.value) };
if (q.TAG === 'cps')
return {
oid: PolicyQualifierKnownMap.cps[0],
value: PolicyQualifierKnownMap.cps[1].encode(q.data),
};
return {
oid: PolicyQualifierKnownMap.userNotice[0],
value: PolicyQualifierKnownMap.userNotice[1].encode(q.data),
};
},
});
// RFC 5280 section 4.2.1.4: CertificatePolicies.
const ExtPolicies = /* @__PURE__ */ (() => P.validate(ASN1.sequence({
list: P.array(null, ASN1.sequence({
policy: ASN1.OID,
qualifiers: ASN1.optional(ASN1.sequence({ list: P.array(null, ExtPolicyQualifierInfo) })),
})),
}), (x) => {
// RFC 5280 section 4.2.1.4: certificatePolicies and policyQualifiers are SIZE (1..MAX).
if (!x.list.length)
throw new Error('certificatePolicies must contain at least one PolicyInformation');
for (const p of x.list) {
if (p.qualifiers && !p.qualifiers.list.length)
throw new Error('policyQualifiers must contain at least one PolicyQualifierInfo when present');
}
return x;
}))();
const ExtGeneralSubtree = /* @__PURE__ */ (() => ASN1.sequence({
base: ExtGeneralName,
minimum: /* @__PURE__ */ ASN1.optional(/* @__PURE__ */ ASN1.implicit(0, ASN1.Integer)),
maximum: /* @__PURE__ */ ASN1.optional(/* @__PURE__ */ ASN1.implicit(1, ASN1.Integer)),
}))();
// RFC 5280 section 4.2.1.10: NameConstraints.
const ExtNameConstraints = /* @__PURE__ */ (() => P.validate(ASN1.sequence({
permitted: ASN1.optional(ASN1.explicit(0, ASN1.sequence({ list: P.array(null, ExtGeneralSubtree) }))),
excluded: ASN1.optional(ASN1.explicit(1, ASN1.sequence({ list: P.array(null, ExtGeneralSubtree) }))),
}), (x) => {
// RFC 5280 section 4.2.1.10: empty NameConstraints sequence is forbidden.
if (!x.permitted && !x.excluded)
throw new Error('nameConstraints must contain permittedSubtrees or excludedSubtrees');
// RFC 5280 section 4.2.1.10: GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree.
if (x.permitted && !x.permitted.list.length)
throw new Error('nameConstraints permittedSubtrees must contain at least one GeneralSubtree');
if (x.excluded && !x.excluded.list.length)
throw new Error('nameConstraints excludedSubtrees must contain at least one GeneralSubtree');
// RFC 5280 profile requirement: minimum MUST be 0 and maximum MUST be absent.
const all = [...(x.permitted?.list || []), ...(x.excluded?.list || [])];
for (const g of all) {
if (g.maximum !== undefined)
throw new Error('nameConstraints GeneralSubtree.maximum is not supported by this profile');
if (g.minimum !== undefined && g.minimum !== 0n)
throw new Error('nameConstraints GeneralSubtree.minimum must be 0 in this profile');
}
return x;
}))();
const ExtSubjectDirectoryAttributes = /* @__PURE__ */ (() => P.apply(P.validate(ASN1.sequence({
list: P.array(null, ASN1.sequence({ type: ASN1.OID, values: ASN1.set(CertAnyCodec) })),
}), (x) => {
// RFC 5280 section 4.2.1.8: SubjectDirectoryAttributes is SEQUENCE SIZE (1..MAX) OF Attribute.
if (!x.list.length)
throw new Error('subjectDirectoryAttributes must contain at least one attribute');
// Attribute syntax (X.501, used by RFC 5280) requires SET SIZE (1..MAX) OF AttributeValue.
for (const a of x.list) {
if (!a.values.length)
throw new Error('subjectDirectoryAttributes attribute values must contain at least one value');
}
return x;
}), {
encode: (x) => ({
list: x.list.map((i) => ({
type: i.type,
typeName: ATTR_NAME_OID[i.type],
values: i.values,
})),
}),
decode: (x) => ({
list: x.list.map((i) => ({ type: i.type, values: i.values })),
}),
}))();
const ExtPrivateKeyUsagePeriod = /* @__PURE__ */ (() => ASN1.sequence({
notBefore: ASN1.optional(ASN1.implicit(0, GeneralizedTime)),
notAfter: ASN1.optional(ASN1.implicit(1, GeneralizedTime)),
}))();
const ExtIssuingDistributionPoint = /* @__PURE__ */ (() => ASN1.sequence({
distributionPoint: ASN1.optional(ASN1.explicit(0, ExtDistributionPointName)),
onlyContainsUserCerts: ASN1.optional(ASN1.implicit(1, ASN1Bool)),
onlyContainsCACerts: ASN1.optional(ASN1.implicit(2, ASN1Bool)),
onlySomeReasons: ASN1.optional(ASN1.implicit(3, ASN1BitStringRaw)),
indirectCRL: ASN1.optional(ASN1.implicit(4, ASN1Bool)),
onlyContainsAttributeCerts: ASN1.optional(ASN1.implicit(5, ASN1Bool)),
}))();
const ExtPolicyMappings = /* @__PURE__ */ (() => ASN1.sequence({
list: /* @__PURE__ */ P.array(null,
/* @__PURE__ */ ASN1.sequence({
issuerDomainPolicy: ASN1.OID,
subjectDomainPolicy: ASN1.OID,
})),
}))();
const POLICY_ANY = '2.5.29.32.0';
const ExtPolicyMappingsChecked = /* @__PURE__ */ (() => P.validate(ExtPolicyMappings, (x) => {
// RFC 5280 section 4.2.1.5: policyMappings is SIZE (1..MAX), and either side MUST NOT be anyPolicy.
if (!x.list.length)
throw new Error('policyMappings must contain at least one mapping');
for (const m of x.list) {
if (m.issuerDomainPolicy === POLICY_ANY || m.subjectDomainPolicy === POLICY_ANY)