UNPKG

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
/*! 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)