@blamejs/pki 0.3.8 → 0.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,574 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+
5
+ /**
6
+ * @module pki.crl
7
+ * @nav Signing
8
+ * @title CRLs
9
+ * @intro The X.509 CRL producing side. `pki.crl.sign` builds a `TBSCertList`, signs it, and emits a
10
+ * `CertificateList` (RFC 5280 sec. 5) that `pki.schema.crl.parse`, `pki.path.crlChecker`, and OpenSSL
11
+ * all accept -- over any signature algorithm the toolkit registry resolves: RSA (PKCS#1 v1.5 / PSS),
12
+ * ECDSA, EdDSA, ML-DSA, SLH-DSA, and the composite (hybrid) arms. `pki.crl.verify` checks a CRL
13
+ * signature through the one path-validation signature engine, and `pki.crl.isRevoked` looks a serial up
14
+ * in a parsed CRL. Parsing lives at `pki.schema.crl.parse`.
15
+ * @spec RFC 5280
16
+ * @card Build, sign, and verify an X.509 CRL (RFC 5280 sec. 5) over any registry algorithm.
17
+ */
18
+ //
19
+ // The signature matrix comes from the shared sign-scheme resolver (the same registry pki.x509.sign /
20
+ // pki.cms.sign drive), so a new algorithm is a registry row, never a branch here. The TBSCertList DER is
21
+ // assembled through the canonical asn1.build.* layer + the shared pki-build producing primitives; the
22
+ // strict schema-crl decoder round-trips it and OpenSSL cross-checks it. VERIFY composes the ONE
23
+ // path-validate.verifyCrlSignature engine pki.path.crlChecker uses -- there is no second, weaker verifier.
24
+
25
+ var asn1 = require("./asn1-der");
26
+ var oid = require("./oid");
27
+ var crlSchema = require("./schema-crl");
28
+ var x509Schema = require("./schema-x509");
29
+ var signScheme = require("./sign-scheme");
30
+ var guard = require("./guard-all");
31
+ var frameworkError = require("./framework-error");
32
+ var pkix = require("./schema-pkix");
33
+ var pkiBuild = require("./pki-build");
34
+ var constants = require("./constants");
35
+ require("./path-validate"); // side-effect: path-validate injects its signature engine into crl-verify at load
36
+ var crlVerify = require("./crl-verify");
37
+
38
+ var CrlError = frameworkError.CrlError;
39
+ var NS = pkix.makeNS("crl", CrlError, oid);
40
+ var NAME_SCHEMA = pkix.name(NS);
41
+ var SPKI_SCHEMA = pkix.spki(NS);
42
+ var b = asn1.build;
43
+ var TAGS = asn1.TAGS;
44
+
45
+ // Two error factories (x509-sign pattern): `_err` takes a full crl/* code; `_signE` prepends the domain so
46
+ // the shared sign-scheme resolver/signer faults keep crl/* codes. Both are FACTORIES (guard.* and
47
+ // resolveSignScheme invoke them as E(code, msg) with no `new`).
48
+ function _err(code, message, cause) { return new CrlError(code, message, cause); }
49
+ function _signE(kind, message, cause) { return new CrlError("crl/" + kind, message, cause); }
50
+ function O(n) { return oid.byName(n); }
51
+
52
+ // The shared PKIX producing primitives (lib/pki-build.js), bound to the crl namespace so they keep the
53
+ // frozen crl/* codes (Hard rule #5 -- one encode definition, no per-format hand-roll).
54
+ var _b = pkiBuild.makeBuilder({ ErrorClass: CrlError, prefix: "crl", O: O, NS: NS, NAME_SCHEMA: NAME_SCHEMA, SPKI_SCHEMA: SPKI_SCHEMA, EXT_DECODERS: {} });
55
+ var _encodeName = _b.encodeName, _isEmptyName = _b.isEmptyName, _reqDer = _b.reqDer,
56
+ _assertValidSpki = _b.assertValidSpki, _assertValidExtension = _b.assertValidExtension,
57
+ _timeDer = _b.timeDer, _ext = _b.ext, _extAki = _b.extAki, _spkiKeyId = _b.spkiKeyId,
58
+ _serialInteger = _b.serialInteger, _encodeGeneralName = _b.encodeGeneralName,
59
+ _certLikeFromSpki = _b.certLikeFromSpki, _assertSignatureVerifies = _b.assertSignatureVerifies;
60
+ // The RFC 5280 sec. 4.2.1 extension value decoders (shared with pki.schema.x509.parse), used to confirm an
61
+ // issuer certificate's keyUsage asserts cRLSign before a CRL is signed under it.
62
+ var EXT_DECODERS = pkix.certExtensionDecoders(NS).byOid;
63
+
64
+ var OID_SKI = O("subjectKeyIdentifier");
65
+
66
+ // CRLReason value<->name (RFC 5280 sec. 5.3.1). The value->name table is the frozen constants source;
67
+ // build the name->value reverse once at load. Value 7 is unused/reserved and absent from the table.
68
+ var CRL_REASON = constants.NAMES.CRL_REASON;
69
+ var REASON_BY_NAME = {};
70
+ Object.keys(CRL_REASON).forEach(function (v) { REASON_BY_NAME[CRL_REASON[v]] = Number(v); });
71
+
72
+ var KNOWN_CRL_EXT_KEYS = { authorityKeyIdentifier: 1, issuingDistributionPoint: 1, deltaCRLIndicator: 1, freshestCRL: 1, authorityInfoAccess: 1 };
73
+ var KNOWN_IDP_KEYS = { fullName: 1, onlyContainsUserCerts: 1, onlyContainsCACerts: 1, onlyContainsAttributeCerts: 1, indirectCRL: 1 };
74
+
75
+ // RFC 5280 sec. 5.2 -- the profile-fixed criticality of each recognized CRL extension. cRLNumber (5.2.3),
76
+ // authorityKeyIdentifier (5.2.1), freshestCRL (5.2.6), and authorityInfoAccess (5.2.7) MUST be non-critical;
77
+ // issuingDistributionPoint (5.2.5) and deltaCRLIndicator (5.2.4) MUST be critical. Enforced on the object
78
+ // form by construction AND on the pre-encoded escape hatch, so the hatch cannot bypass the profile.
79
+ var REQUIRED_CRITICALITY = {};
80
+ [["cRLNumber", false], ["authorityKeyIdentifier", false], ["freshestCRL", false], ["authorityInfoAccess", false],
81
+ ["issuingDistributionPoint", true], ["deltaCRLIndicator", true]].forEach(function (r) { REQUIRED_CRITICALITY[O(r[0])] = r[1]; });
82
+
83
+ // RFC 5280 sec. 5.3 -- the profile-fixed criticality of each recognized crlEntryExtension: reasonCode (5.3.1)
84
+ // and invalidityDate (5.3.2) MUST be non-critical; certificateIssuer (5.3.3) MUST be critical.
85
+ var REQUIRED_ENTRY_CRITICALITY = {};
86
+ [["reasonCode", false], ["invalidityDate", false]].forEach(function (r) { REQUIRED_ENTRY_CRITICALITY[O(r[0])] = r[1]; });
87
+
88
+ // certificateIssuer (indirect CRLs) is deferred: pki.path.crlChecker skips a CRL carrying any critical entry
89
+ // extension other than reasonCode (it does not track the per-entry issuer of an indirect CRL), so a CRL
90
+ // signed with certificateIssuer would verify but never be authoritative for revocation. Reject it -- on both
91
+ // the object form and the pre-encoded hatch -- until the checker processes indirect CRLs.
92
+ var _CERT_ISSUER_DEFERRED = "certificateIssuer (indirect CRLs) is not yet supported -- pki.path.crlChecker skips a CRL carrying any critical entry extension other than reasonCode, so an indirect CRL would never be authoritative for revocation. Re-enabled once the checker processes certificateIssuer / indirect CRLs.";
93
+ // The IDP indirectCRL flag marks the whole CRL as indirect, which crlChecker skips outright -- deferred for
94
+ // the same reason as certificateIssuer, on both the object form and the pre-encoded hatch.
95
+ var _INDIRECT_IDP_DEFERRED = "issuingDistributionPoint indirectCRL (indirect CRLs) is not yet supported -- pki.path.crlChecker skips a CRL whose IDP marks it indirect, so it would never be authoritative for revocation. Re-enabled once the checker processes indirect CRLs.";
96
+
97
+ // ---- shared numeric / reason / key-id helpers ------------------------------
98
+
99
+ // A non-negative INTEGER (0..MAX) of at most 20 content octets -- cRLNumber (sec. 5.2.3) and
100
+ // deltaCRLIndicator baseCRLNumber (sec. 5.2.4). b.integer has no size cap of its own (unlike serialInteger).
101
+ function _boundedInteger(v, label) {
102
+ var n;
103
+ if (typeof v === "bigint") n = v;
104
+ else if (typeof v === "number") { if (!Number.isSafeInteger(v)) throw _err("crl/bad-crl-number", label + " number must be a safe integer (pass a BigInt, hex string, or Buffer for a larger value)"); n = BigInt(v); }
105
+ else if (typeof v === "string") { try { n = BigInt(v); } catch (e) { throw _err("crl/bad-crl-number", label + " string must be a decimal or 0x-hex integer", e); } }
106
+ else if (Buffer.isBuffer(v)) { n = v.length ? BigInt("0x" + v.toString("hex")) : 0n; }
107
+ else throw _err("crl/bad-crl-number", label + " must be a BigInt, integer, hex string, or Buffer");
108
+ if (n < 0n) throw _err("crl/bad-crl-number", label + " must be non-negative (INTEGER 0..MAX, RFC 5280 sec. 5.2.3)");
109
+ var tlv = b.integer(n);
110
+ if (asn1.decode(tlv).content.length > 20) throw _err("crl/bad-crl-number", label + " must not exceed 20 octets (RFC 5280 sec. 5.2.3)");
111
+ return { tlv: tlv, value: n };
112
+ }
113
+
114
+ // Decode a pre-encoded Extension's extnValue (the trailing OCTET STRING's content) to one well-formed DER
115
+ // node, or reject -- so a recognized extension's value is validated on the escape hatch, not emitted opaque.
116
+ function _extInner(node, label) {
117
+ try { return asn1.decode(asn1.read.octetString(node.children[node.children.length - 1])); }
118
+ catch (e) { throw _err("crl/bad-input", "pre-encoded " + label + " extension value is not valid DER", e); }
119
+ }
120
+ // A pre-encoded cRLNumber / baseCRLNumber value: a non-negative INTEGER of at most 20 octets, or reject.
121
+ function _requireBoundedInt(inner, label) {
122
+ if (inner.tagClass !== "universal" || inner.tagNumber !== TAGS.INTEGER) throw _err("crl/bad-crl-number", "pre-encoded " + label + " value must be an INTEGER (RFC 5280 sec. 5.2.3 / 5.2.4)");
123
+ var v = asn1.read.integer(inner);
124
+ if (v < 0n) throw _err("crl/bad-crl-number", label + " must be non-negative (RFC 5280 sec. 5.2.3)");
125
+ if (inner.content.length > 20) throw _err("crl/bad-crl-number", label + " must not exceed 20 octets (RFC 5280 sec. 5.2.3)");
126
+ return v;
127
+ }
128
+
129
+ // Resolve a CRLReason to its numeric code: validate membership, reject 7/undefined, gate removeFromCRL(8)
130
+ // to a delta CRL. unspecified(0) is returned so the caller OMITS it (SHOULD be absent, sec. 5.3.1).
131
+ function _resolveReason(reason, isDelta) {
132
+ var codeNum;
133
+ if (typeof reason === "number") codeNum = reason;
134
+ else if (typeof reason === "string") {
135
+ if (!Object.prototype.hasOwnProperty.call(REASON_BY_NAME, reason)) throw _err("crl/bad-reason-code", "unknown CRLReason " + JSON.stringify(reason));
136
+ codeNum = REASON_BY_NAME[reason];
137
+ } else throw _err("crl/bad-reason-code", "reason must be a CRLReason name or number");
138
+ if (!Object.prototype.hasOwnProperty.call(CRL_REASON, String(codeNum))) throw _err("crl/bad-reason-code", "undefined or reserved CRLReason " + codeNum + " (RFC 5280 sec. 5.3.1)");
139
+ if (codeNum === 8 && !isDelta) throw _err("crl/bad-reason-code", "removeFromCRL(8) may appear only in a delta CRL (RFC 5280 sec. 5.3.1)");
140
+ return codeNum;
141
+ }
142
+
143
+ // The AKI keyIdentifier from the issuer: an explicit Buffer, or true -> the issuer cert's subjectKeyIdentifier,
144
+ // else the SHA-1 of the issuer SPKI (RFC 5280 sec. 5.2.1 key-identifier method).
145
+ function _akiKeyId(val, ctx) {
146
+ if (Buffer.isBuffer(val)) return val;
147
+ if (val === true) {
148
+ if (ctx.issuerCert) {
149
+ var ski = (ctx.issuerCert.extensions || []).filter(function (e) { return e.oid === OID_SKI; })[0];
150
+ if (ski) { try { return asn1.read.octetString(asn1.decode(ski.value)); } catch (_e) { /* fall through to re-derive from the issuer SPKI */ } }
151
+ }
152
+ return _spkiKeyId(ctx.issuerSpki);
153
+ }
154
+ throw _err("crl/bad-input", "authorityKeyIdentifier must be true (auto-derive from the issuer) or a Buffer key id");
155
+ }
156
+
157
+ // ---- CRL extension value encoders (sec. 5.2) -------------------------------
158
+
159
+ // IssuingDistributionPoint (sec. 5.2.5): critical; MUST NOT DER-encode to an empty SEQUENCE; at most one
160
+ // scope boolean TRUE; onlyContainsAttributeCerts MUST be FALSE; DEFAULT-FALSE booleans omitted.
161
+ function _idpValue(idp) {
162
+ if (!idp || typeof idp !== "object" || Array.isArray(idp) || Buffer.isBuffer(idp)) throw _err("crl/bad-idp", "issuingDistributionPoint must be an object");
163
+ Object.keys(idp).forEach(function (k) { if (!KNOWN_IDP_KEYS[k]) throw _err("crl/bad-idp", "unknown issuingDistributionPoint field " + JSON.stringify(k) + " (pass a pre-encoded Extension DER via the extensions array for an exotic field like onlySomeReasons)"); });
164
+ if (idp.onlyContainsAttributeCerts === true) throw _err("crl/bad-idp", "onlyContainsAttributeCerts=TRUE is not permitted for a conforming CRL issuer (RFC 5280 sec. 5.2.5)");
165
+ var children = [];
166
+ if (idp.fullName != null) {
167
+ var entries = Array.isArray(idp.fullName) ? idp.fullName : [idp.fullName];
168
+ if (!entries.length) throw _err("crl/bad-idp", "issuingDistributionPoint fullName must carry at least one GeneralName");
169
+ // distributionPoint [0] { fullName [0] IMPLICIT GeneralNames }
170
+ children.push(b.contextConstructed(0, b.contextConstructed(0, Buffer.concat(entries.map(_encodeGeneralName)))));
171
+ }
172
+ var scopeTrue = 0;
173
+ if (idp.onlyContainsUserCerts === true) { children.push(b.contextPrimitive(1, Buffer.from([0xff]))); scopeTrue++; }
174
+ if (idp.onlyContainsCACerts === true) { children.push(b.contextPrimitive(2, Buffer.from([0xff]))); scopeTrue++; }
175
+ if (idp.indirectCRL === true) throw _err("crl/bad-idp", _INDIRECT_IDP_DEFERRED);
176
+ if (scopeTrue > 1) throw _err("crl/bad-idp", "at most one of onlyContainsUserCerts / onlyContainsCACerts may be TRUE (RFC 5280 sec. 5.2.5)");
177
+ if (!children.length) throw _err("crl/bad-idp", "issuingDistributionPoint MUST NOT be empty (RFC 5280 sec. 5.2.5)");
178
+ return b.sequence(children);
179
+ }
180
+
181
+ // Validate a pre-encoded IssuingDistributionPoint value against the same RFC 5280 sec. 5.2.5 profile the
182
+ // object form enforces: MUST NOT be empty; at most one of onlyContainsUserCerts [1] / onlyContainsCACerts [2]
183
+ // TRUE; onlyContainsAttributeCerts [5] MUST be FALSE; indirectCRL [4] deferred (crlChecker skips indirect CRLs).
184
+ function _validatePreEncodedIdp(inner) {
185
+ if (!inner.children || !inner.children.length) throw _err("crl/bad-idp", "pre-encoded issuingDistributionPoint MUST NOT be empty (RFC 5280 sec. 5.2.5)");
186
+ var scopeTrue = 0;
187
+ inner.children.forEach(function (c) {
188
+ if (c.tagClass !== "context") throw _err("crl/bad-idp", "pre-encoded issuingDistributionPoint members must be context-tagged (RFC 5280 sec. 5.2.5)");
189
+ var isTrue = c.content != null && c.content.length > 0 && c.content[c.content.length - 1] !== 0x00; // a present DEFAULT-FALSE boolean is TRUE
190
+ if (c.tagNumber === 4) throw _err("crl/bad-idp", _INDIRECT_IDP_DEFERRED); // indirectCRL
191
+ if (c.tagNumber === 5 && isTrue) throw _err("crl/bad-idp", "onlyContainsAttributeCerts=TRUE is not permitted for a conforming CRL issuer (RFC 5280 sec. 5.2.5)");
192
+ if ((c.tagNumber === 1 || c.tagNumber === 2) && isTrue) scopeTrue++;
193
+ });
194
+ if (scopeTrue > 1) throw _err("crl/bad-idp", "at most one of onlyContainsUserCerts / onlyContainsCACerts may be TRUE (RFC 5280 sec. 5.2.5)");
195
+ }
196
+
197
+ // freshestCRL / CRLDistributionPoints (sec. 5.2.6): SEQUENCE OF DistributionPoint carrying only
198
+ // distributionPoint (reasons + cRLIssuer omitted). Accepts an array of GeneralName entries (one DP's
199
+ // fullName) or an array of { fullName } distribution points.
200
+ function _freshestValue(spec) {
201
+ if (!Array.isArray(spec) || !spec.length) throw _err("crl/bad-input", "freshestCRL must be a non-empty array of GeneralNames (or { fullName } distribution points)");
202
+ var dps = spec.every(function (e) { return e && typeof e === "object" && !Buffer.isBuffer(e) && Object.keys(e).length === 1 && e.fullName != null; })
203
+ ? spec.map(function (dp) { return dp.fullName; })
204
+ : [spec];
205
+ return b.sequence(dps.map(function (fullName) {
206
+ var entries = Array.isArray(fullName) ? fullName : [fullName];
207
+ if (!entries.length) throw _err("crl/bad-input", "a freshestCRL distribution point must carry at least one GeneralName (GeneralNames is SIZE(1..MAX), RFC 5280 sec. 4.2.1.13)");
208
+ return b.sequence([b.contextConstructed(0, b.contextConstructed(0, Buffer.concat(entries.map(_encodeGeneralName))))]);
209
+ }));
210
+ }
211
+
212
+ // authorityInfoAccess (sec. 5.2.7): SEQUENCE OF AccessDescription, caIssuers-only. Accepts an array of
213
+ // GeneralName entries, each a caIssuers accessLocation.
214
+ function _aiaValue(spec) {
215
+ if (!Array.isArray(spec) || !spec.length) throw _err("crl/bad-input", "authorityInfoAccess must be a non-empty array of caIssuers GeneralNames");
216
+ var caIssuers = O("caIssuers");
217
+ return b.sequence(spec.map(function (gn) { return b.sequence([b.oid(caIssuers), _encodeGeneralName(gn)]); }));
218
+ }
219
+
220
+ // The crlExtensions [0] set. Fixed per-extension criticality (sec. 5.2); at most one instance of each
221
+ // (sec. 5.2). `spec.extensions` is an object of the recognized keys OR an array of pre-encoded Extension
222
+ // DER (escape hatch for an extension the object form does not model). Returns { exts, isDelta }.
223
+ function _buildCrlExtensions(spec, ctx) {
224
+ var out = [], seen = {}, isDelta = false;
225
+ function push(oidName, critical, valueDer) {
226
+ var id = O(oidName);
227
+ if (seen[id]) throw _err("crl/bad-input", "duplicate CRL extension " + oidName + " (RFC 5280 sec. 5.2 -- at most one instance)");
228
+ seen[id] = true;
229
+ out.push(_ext(id, critical, valueDer));
230
+ }
231
+ var crlNumberVal = null;
232
+ if (spec.crlNumber != null) { var cn = _boundedInteger(spec.crlNumber, "cRLNumber"); crlNumberVal = cn.value; push("cRLNumber", false, cn.tlv); }
233
+ var ext = spec.extensions;
234
+ if (ext == null) return { exts: out, isDelta: isDelta };
235
+ if (Array.isArray(ext)) {
236
+ var arrBase = null, arrCrlNum = null;
237
+ ext.forEach(function (e, i) {
238
+ var der = _reqDer(e, "extension");
239
+ _assertValidExtension(der, i);
240
+ var node = asn1.decode(der);
241
+ var extnId = asn1.read.oid(node.children[0]);
242
+ if (seen[extnId]) throw _err("crl/bad-input", "duplicate extension " + extnId + " (RFC 5280 sec. 5.2)");
243
+ seen[extnId] = true;
244
+ // RFC 5280 sec. 5.2 -- a recognized CRL extension is held to the profile even via the escape hatch: its
245
+ // fixed criticality (assertValidExtension rejects an explicit critical=FALSE, so 3 children == critical,
246
+ // 2 == non-critical) AND a well-formed value of the right type (INTEGER for cRLNumber/deltaCRLIndicator,
247
+ // else a SEQUENCE), never emitted opaque.
248
+ if (Object.prototype.hasOwnProperty.call(REQUIRED_CRITICALITY, extnId)) {
249
+ if ((node.children.length === 3) !== REQUIRED_CRITICALITY[extnId]) {
250
+ throw _err("crl/bad-input", "pre-encoded " + (oid.name(extnId) || extnId) + " extension has the wrong criticality (RFC 5280 sec. 5.2 requires it " + (REQUIRED_CRITICALITY[extnId] ? "critical" : "non-critical") + ")");
251
+ }
252
+ var inner = _extInner(node, oid.name(extnId) || extnId);
253
+ if (extnId === O("cRLNumber")) arrCrlNum = _requireBoundedInt(inner, "cRLNumber");
254
+ else if (extnId === O("deltaCRLIndicator")) arrBase = _requireBoundedInt(inner, "deltaCRLIndicator baseCRLNumber");
255
+ else {
256
+ if (inner.tagClass !== "universal" || inner.tagNumber !== TAGS.SEQUENCE) throw _err("crl/bad-input", "pre-encoded " + (oid.name(extnId) || extnId) + " extension value must be a SEQUENCE (RFC 5280 sec. 5.2)");
257
+ // A pre-encoded IssuingDistributionPoint is held to the same sec. 5.2.5 profile as the object form.
258
+ if (extnId === O("issuingDistributionPoint")) _validatePreEncodedIdp(inner);
259
+ }
260
+ }
261
+ if (extnId === O("deltaCRLIndicator")) isDelta = true;
262
+ out.push(b.raw(der));
263
+ });
264
+ // RFC 5280 sec. 5.2.3 / 5.2.4 -- a delta CRL MUST carry a cRLNumber greater than its baseCRLNumber, whether
265
+ // that cRLNumber is pre-encoded here or supplied via spec.crlNumber; the escape hatch is held to the same rule.
266
+ if (isDelta) {
267
+ // RFC 5280 sec. 5.2.6 -- freshestCRL MUST NOT appear in a delta CRL, including via the pre-encoded hatch.
268
+ if (seen[O("freshestCRL")]) throw _err("crl/bad-input", "freshestCRL MUST NOT appear in a delta CRL (RFC 5280 sec. 5.2.6)");
269
+ var eff = crlNumberVal != null ? crlNumberVal : arrCrlNum;
270
+ if (eff == null) throw _err("crl/bad-input", "a delta CRL MUST include a cRLNumber (RFC 5280 sec. 5.2.3 / 5.2.4)");
271
+ if (arrBase != null && eff <= arrBase) throw _err("crl/bad-crl-number", "a delta CRL's cRLNumber MUST be greater than its baseCRLNumber (RFC 5280 sec. 5.2.4)");
272
+ }
273
+ return { exts: out, isDelta: isDelta };
274
+ }
275
+ if (typeof ext !== "object") throw _err("crl/bad-input", "extensions must be an object or an array of pre-encoded Extension DER");
276
+ Object.keys(ext).forEach(function (k) { if (!KNOWN_CRL_EXT_KEYS[k]) throw _err("crl/bad-input", "unknown CRL extension " + JSON.stringify(k) + " in the extensions spec; pass a pre-encoded Extension DER via the array form"); });
277
+ isDelta = ext.deltaCRLIndicator != null;
278
+ if (ext.authorityKeyIdentifier != null) push("authorityKeyIdentifier", false, _extAki(_akiKeyId(ext.authorityKeyIdentifier, ctx)));
279
+ if (ext.issuingDistributionPoint != null) push("issuingDistributionPoint", true, _idpValue(ext.issuingDistributionPoint));
280
+ if (ext.deltaCRLIndicator != null) {
281
+ var base = _boundedInteger(ext.deltaCRLIndicator, "deltaCRLIndicator baseCRLNumber");
282
+ // RFC 5280 sec. 5.2.3 / 5.2.4 -- a conforming delta CRL MUST carry a cRLNumber (sec. 5.2.3 requires it in
283
+ // every CRL), and that cRLNumber MUST be greater than the BaseCRLNumber it is built against (sec. 5.2.4).
284
+ if (crlNumberVal == null) throw _err("crl/bad-input", "a delta CRL MUST include a cRLNumber (set spec.crlNumber) (RFC 5280 sec. 5.2.3 / 5.2.4)");
285
+ if (crlNumberVal <= base.value) throw _err("crl/bad-crl-number", "a delta CRL's cRLNumber (" + crlNumberVal + ") MUST be greater than its baseCRLNumber (" + base.value + ") (RFC 5280 sec. 5.2.4)");
286
+ push("deltaCRLIndicator", true, base.tlv);
287
+ }
288
+ if (ext.freshestCRL != null) {
289
+ if (isDelta) throw _err("crl/bad-input", "freshestCRL MUST NOT appear in a delta CRL (RFC 5280 sec. 5.2.6)");
290
+ push("freshestCRL", false, _freshestValue(ext.freshestCRL));
291
+ }
292
+ if (ext.authorityInfoAccess != null) push("authorityInfoAccess", false, _aiaValue(ext.authorityInfoAccess));
293
+ return { exts: out, isDelta: isDelta };
294
+ }
295
+
296
+ // The revokedCertificates entries (sec. 5.1.2.6 / 5.3). Each entry: serial + revocationDate + optional
297
+ // crlEntryExtensions (reasonCode ENUMERATED, invalidityDate GeneralizedTime-only, certificateIssuer, or a
298
+ // pre-encoded escape hatch). Returns { entries, anyExt } -- anyExt forces v2 (sec. 5.1.2.1).
299
+ function _buildRevoked(entryList, isDelta) {
300
+ if (!Array.isArray(entryList)) throw _err("crl/bad-input", "revoked must be an array of revoked-certificate entries");
301
+ var anyExt = false, seenSerials = {};
302
+ var entries = entryList.map(function (e, idx) {
303
+ if (!e || typeof e !== "object" || Buffer.isBuffer(e)) throw _err("crl/bad-input", "each revoked entry must be an object");
304
+ if (e.serialNumber == null) throw _err("crl/bad-input", "revoked entry [" + idx + "] requires a serialNumber");
305
+ var serialTlv = _serialInteger(e.serialNumber);
306
+ // RFC 5280 sec. 5.1.2.6 -- a serial number identifies a certificate uniquely, so a CRL MUST NOT list the
307
+ // same serial twice; dedup on the DER content (the same value the parser surfaces as serialNumberHex).
308
+ var serialKey = asn1.decode(serialTlv).content.toString("hex");
309
+ if (seenSerials[serialKey]) throw _err("crl/bad-input", "revoked entry [" + idx + "] duplicates a serial number already listed in this CRL (RFC 5280 sec. 5.1.2.6)");
310
+ seenSerials[serialKey] = true;
311
+ var children = [serialTlv, _timeDer(e.revocationDate, "revocationDate")];
312
+ var entryExts = [], seen = {};
313
+ function pushE(oidName, critical, valueDer) {
314
+ var id = O(oidName);
315
+ if (seen[id]) throw _err("crl/bad-input", "duplicate entry extension " + oidName + " in revoked entry [" + idx + "]");
316
+ seen[id] = true;
317
+ entryExts.push(_ext(id, critical, valueDer));
318
+ }
319
+ if (e.reason != null) {
320
+ var codeNum = _resolveReason(e.reason, isDelta);
321
+ if (codeNum !== 0) pushE("reasonCode", false, b.enumerated(BigInt(codeNum))); // unspecified(0) omitted (sec. 5.3.1)
322
+ }
323
+ if (e.invalidityDate != null) {
324
+ guard.time.assertValid(e.invalidityDate, _err, "crl/bad-input", "invalidityDate");
325
+ // sec. 5.3.2 -- ALWAYS GeneralizedTime (never the UTCTime cutover), no fractional seconds.
326
+ pushE("invalidityDate", false, b.generalizedTime(e.invalidityDate));
327
+ }
328
+ if (e.certificateIssuer != null) throw _err("crl/bad-input", _CERT_ISSUER_DEFERRED);
329
+ if (e.extensions != null) {
330
+ if (!Array.isArray(e.extensions)) throw _err("crl/bad-input", "revoked entry [" + idx + "] extensions must be an array of pre-encoded Extension DER");
331
+ e.extensions.forEach(function (x, j) {
332
+ var der = _reqDer(x, "entry extension");
333
+ _assertValidExtension(der, j);
334
+ var xnode = asn1.decode(der);
335
+ var extnId = asn1.read.oid(xnode.children[0]);
336
+ if (seen[extnId]) throw _err("crl/bad-input", "duplicate entry extension " + extnId + " in revoked entry [" + idx + "]");
337
+ seen[extnId] = true;
338
+ // certificateIssuer (indirect CRLs) is not usable end-to-end yet (crlChecker skips such a CRL); reject it.
339
+ if (extnId === O("certificateIssuer")) throw _err("crl/bad-input", _CERT_ISSUER_DEFERRED);
340
+ // RFC 5280 sec. 5.3 -- a recognized entry extension is held to the profile even via the escape hatch:
341
+ // its fixed criticality AND a well-formed value of the right type (reasonCode an ENUMERATED in the
342
+ // legal set, invalidityDate a GeneralizedTime).
343
+ if (Object.prototype.hasOwnProperty.call(REQUIRED_ENTRY_CRITICALITY, extnId)) {
344
+ if ((xnode.children.length === 3) !== REQUIRED_ENTRY_CRITICALITY[extnId]) throw _err("crl/bad-input", "pre-encoded entry extension " + (oid.name(extnId) || extnId) + " has the wrong criticality (RFC 5280 sec. 5.3 requires it " + (REQUIRED_ENTRY_CRITICALITY[extnId] ? "critical" : "non-critical") + ")");
345
+ var einner = _extInner(xnode, oid.name(extnId) || extnId);
346
+ if (extnId === O("reasonCode")) {
347
+ if (einner.tagClass !== "universal" || einner.tagNumber !== TAGS.ENUMERATED) throw _err("crl/bad-reason-code", "pre-encoded reasonCode value must be an ENUMERATED (RFC 5280 sec. 5.3.1)");
348
+ var rc = asn1.read.enumerated(einner);
349
+ if (!Object.prototype.hasOwnProperty.call(CRL_REASON, rc.toString())) throw _err("crl/bad-reason-code", "pre-encoded reasonCode " + rc + " is undefined or reserved (RFC 5280 sec. 5.3.1)");
350
+ if (rc === 8n && !isDelta) throw _err("crl/bad-reason-code", "removeFromCRL(8) may appear only in a delta CRL (RFC 5280 sec. 5.3.1)");
351
+ } else if (extnId === O("invalidityDate")) {
352
+ if (einner.tagClass !== "universal" || einner.tagNumber !== TAGS.GENERALIZED_TIME) throw _err("crl/bad-input", "pre-encoded invalidityDate value must be a GeneralizedTime (RFC 5280 sec. 5.3.2)");
353
+ try { asn1.read.time(einner); } catch (e) { throw _err("crl/bad-input", "pre-encoded invalidityDate is not a well-formed GeneralizedTime (RFC 5280 sec. 5.3.2)", e); }
354
+ }
355
+ }
356
+ entryExts.push(b.raw(der));
357
+ });
358
+ }
359
+ if (entryExts.length) { anyExt = true; children.push(b.sequence(entryExts)); }
360
+ return b.sequence(children);
361
+ });
362
+ return { entries: entries, anyExt: anyExt };
363
+ }
364
+
365
+ // ---- the primitives --------------------------------------------------------
366
+
367
+ function _parseIssuerCert(cert) {
368
+ var parsed = (Buffer.isBuffer(cert) || typeof cert === "string") ? x509Schema.parse(cert) : cert;
369
+ if (!parsed || !parsed.tbsBytes || !parsed.subjectPublicKeyInfo) throw _err("crl/bad-input", "issuer.cert must be a certificate DER/PEM or a parsed certificate");
370
+ return parsed;
371
+ }
372
+
373
+ // RFC 5280 sec. 4.2.1.3 -- a certificate whose key signs CRLs asserts the cRLSign keyUsage bit. When the
374
+ // issuer certificate carries a keyUsage extension it MUST include cRLSign, or the CRL it signs is rejected
375
+ // by a conforming relying party (pki.path.crlChecker included). Mirrors x509-sign's keyCertSign gate; a
376
+ // certificate with no keyUsage extension is unrestricted.
377
+ function _assertIssuerCanSignCrl(issuerCert) {
378
+ var kuExt = (issuerCert.extensions || []).filter(function (e) { return e.oid === O("keyUsage"); })[0];
379
+ if (!kuExt) return;
380
+ var ku;
381
+ try { ku = EXT_DECODERS[O("keyUsage")](kuExt.value); }
382
+ catch (e) { if (e instanceof CrlError) throw e; throw _err("crl/bad-input", "the issuer certificate keyUsage is malformed", e); }
383
+ if (ku.cRLSign !== true) throw _err("crl/bad-input", "the issuer certificate keyUsage does not assert cRLSign -- it cannot sign CRLs (RFC 5280 sec. 4.2.1.3)");
384
+ }
385
+
386
+ function _sign(spec, issuer, opts) {
387
+ opts = opts || {};
388
+ if (!spec || typeof spec !== "object" || Buffer.isBuffer(spec)) throw _err("crl/bad-input", "the CRL spec must be an object");
389
+ issuer = issuer || {};
390
+ if (issuer.key == null) throw _err("crl/bad-input", "a signing key (issuer.key, a PKCS#8 private key) is required");
391
+
392
+ // Resolve the issuer name + signing-key SPKI. `{ cert }` supplies both; else `{ name|spec.issuer, publicKey }`.
393
+ var issuerDer, issuerSpki, issuerCert = null;
394
+ if (issuer.cert != null) {
395
+ issuerCert = _parseIssuerCert(issuer.cert);
396
+ _assertIssuerCanSignCrl(issuerCert);
397
+ issuerDer = pkiBuild.tbsNameField(issuerCert, "subject");
398
+ issuerSpki = issuerCert.subjectPublicKeyInfo.bytes;
399
+ } else {
400
+ issuerSpki = _reqDer(issuer.publicKey, "issuer.publicKey (the issuer SPKI DER)");
401
+ _assertValidSpki(issuerSpki, "issuer.publicKey");
402
+ var dnSource = issuer.name != null ? issuer.name : spec.issuer;
403
+ if (dnSource == null) throw _err("crl/bad-issuer", "an issuer distinguished name is required (issuer.name or spec.issuer) when no issuer.cert is given");
404
+ issuerDer = _encodeName(dnSource);
405
+ }
406
+ // RFC 5280 sec. 5.1.2.3 -- the issuer MUST be a non-empty distinguished name.
407
+ if (_isEmptyName(issuerDer)) throw _err("crl/bad-issuer", "issuer must be a non-empty distinguished name (RFC 5280 sec. 5.1.2.3)");
408
+
409
+ // thisUpdate (required) + nextUpdate (optional; MUST NOT precede thisUpdate). _timeDer validates each date
410
+ // and applies the RFC 5280 sec. 5.1.2.4/.5 UTCTime<=2049-else-GeneralizedTime cutover.
411
+ if (spec.thisUpdate == null) throw _err("crl/bad-input", "thisUpdate is required (RFC 5280 sec. 5.1.2.4)");
412
+ var thisU = _timeDer(spec.thisUpdate, "thisUpdate");
413
+ var nextU = null;
414
+ if (spec.nextUpdate != null) {
415
+ nextU = _timeDer(spec.nextUpdate, "nextUpdate");
416
+ // allow:nan-date-comparison-unguarded -- both operands are guard.time.assertValid'd via _timeDer on the
417
+ // two lines above (thisUpdate + nextUpdate), so an Invalid Date throws before this comparison.
418
+ if (spec.nextUpdate.getTime() < spec.thisUpdate.getTime()) throw _err("crl/bad-input", "nextUpdate must not be before thisUpdate (RFC 5280 sec. 5.1.2.5)");
419
+ }
420
+
421
+ var extResult = _buildCrlExtensions(spec, { issuerCert: issuerCert, issuerSpki: issuerSpki });
422
+ var crlExts = extResult.exts;
423
+ var revoked = spec.revoked != null ? _buildRevoked(spec.revoked, extResult.isDelta) : { entries: [], anyExt: false };
424
+ // RFC 5280 sec. 5.1.2.1 -- v2 iff any CRL or entry extension is present, else v1 (version omitted).
425
+ var version = (crlExts.length || revoked.anyExt) ? 2 : 1;
426
+
427
+ // The signature scheme resolves from the SIGNING key's SPKI algorithm (the whole registry, for free).
428
+ var scheme = signScheme.resolveSignScheme(_certLikeFromSpki(issuerSpki), { combinedRsaSig: true, pss: opts.pss, digestAlgorithm: opts.digestAlgorithm }, true, _signE);
429
+
430
+ var tbsChildren = [];
431
+ if (version === 2) tbsChildren.push(b.integer(1n)); // bare INTEGER(1), NOT a [0] EXPLICIT tag like a certificate
432
+ tbsChildren.push(scheme.sigAlgId); // signature == signatureAlgorithm (sec. 5.1.1.2), single source
433
+ tbsChildren.push(issuerDer);
434
+ tbsChildren.push(thisU);
435
+ if (nextU) tbsChildren.push(nextU);
436
+ if (revoked.entries.length) tbsChildren.push(b.sequence(revoked.entries)); // omit the field when empty (sec. 5.1.2.6)
437
+ if (crlExts.length) tbsChildren.push(b.explicit(0, b.sequence(crlExts))); // crlExtensions [0] EXPLICIT
438
+ var tbsDer = b.sequence(tbsChildren);
439
+
440
+ return signScheme.signOverTbs(scheme, issuer.key, tbsDer, _signE).then(function (sig) {
441
+ // The signature MUST verify under the issuer public key before returning, or the CRL would be rejected
442
+ // downstream (composite returns a promise; the classical/PQC path throws synchronously on a mismatch).
443
+ return Promise.resolve(_assertSignatureVerifies(tbsDer, sig, issuerSpki, scheme)).then(function () {
444
+ var crlDer = b.sequence([tbsDer, scheme.sigAlgId, b.bitString(sig, 0)]);
445
+ return opts.pem ? crlSchema.pemEncode(crlDer, "X509 CRL") : crlDer;
446
+ });
447
+ }, function (e) {
448
+ if (e instanceof CrlError) throw e;
449
+ throw _err("crl/bad-input", "signing the CRL failed -- the signing key does not match the resolved algorithm or is invalid", e);
450
+ });
451
+ }
452
+
453
+ /**
454
+ * @primitive pki.crl.sign
455
+ * @signature pki.crl.sign(spec, issuer, opts?) -> Promise<Buffer|string>
456
+ * @since 0.3.9
457
+ * @status experimental
458
+ * @spec RFC 5280 sec. 5, RFC 9882, RFC 9814
459
+ * @defends crl-forgery (CWE-347)
460
+ * @related pki.schema.crl.parse, pki.crl.verify, pki.path.crlChecker
461
+ *
462
+ * Build, sign, and DER-encode an X.509 certificate revocation list. `spec` describes the CRL --
463
+ * `thisUpdate` / `nextUpdate` (`Date`s), an optional `crlNumber`, a `revoked` array (each entry a
464
+ * `serialNumber` + `revocationDate` with an optional `reason` or `invalidityDate`),
465
+ * and an optional `extensions` object (`authorityKeyIdentifier`, `issuingDistributionPoint`,
466
+ * `deltaCRLIndicator`, `freshestCRL`, `authorityInfoAccess`) or an array of pre-encoded `Extension` DER.
467
+ * `issuer` is the signing side: `{ cert, key }` takes the issuer DN + SPKI from a CA certificate;
468
+ * `{ name, publicKey, key }` (or `spec.issuer` + `{ publicKey, key }`) supplies them explicitly. The
469
+ * signature algorithm is resolved from the signing key, so every algorithm the toolkit signs with (RSA
470
+ * PKCS#1 v1.5 / PSS, ECDSA, EdDSA, ML-DSA, SLH-DSA, composite) is available without a per-algorithm branch.
471
+ *
472
+ * The version is derived from the field set (v2 when any CRL or entry extension is present, else v1). The
473
+ * outer `signatureAlgorithm` is emitted from the same source as `tbsCertList.signature` (sec. 5.1.1.2); an
474
+ * empty revocation list omits `revokedCertificates` rather than emitting an empty SEQUENCE (sec. 5.1.2.6);
475
+ * `reasonCode` is an ENUMERATED and `invalidityDate` is always GeneralizedTime (sec. 5.3.1/5.3.2);
476
+ * per-extension criticality is fixed by the RFC; and the produced signature is verified under the issuer
477
+ * key before return. A violation throws a typed `CrlError`.
478
+ *
479
+ * @opts
480
+ * - `pem` (boolean) -- return a PEM `X509 CRL` string instead of DER.
481
+ * - `pss` (boolean) -- sign an RSA key with RSASSA-PSS rather than PKCS#1 v1.5.
482
+ * - `digestAlgorithm` (string) -- override the message digest where the algorithm permits a choice.
483
+ * @example
484
+ * var der = await pki.crl.sign({
485
+ * thisUpdate: new Date("2026-01-01T00:00:00Z"), nextUpdate: new Date("2026-02-01T00:00:00Z"),
486
+ * crlNumber: 7n,
487
+ * revoked: [{ serialNumber: 0x1234n, revocationDate: new Date("2026-01-15T00:00:00Z"), reason: "keyCompromise" }],
488
+ * extensions: { authorityKeyIdentifier: true },
489
+ * }, { cert: signerCertDer, key: signerKeyPkcs8 });
490
+ * pki.schema.crl.parse(der).revokedCertificates[0].serialNumberHex; // "1234"
491
+ */
492
+ function sign(spec, issuer, opts) { return Promise.resolve().then(function () { return _sign(spec, issuer, opts); }); }
493
+
494
+ function _coerceCrl(crl) {
495
+ if (Buffer.isBuffer(crl) || typeof crl === "string") return crlSchema.parse(crl);
496
+ if (crl && typeof crl === "object" && crl.tbsBytes && crl.signatureValue && crl.signatureAlgorithm) return crl;
497
+ throw _err("crl/bad-input", "crl must be a CRL DER Buffer, a PEM string, or a parsed CRL (from pki.schema.crl.parse)");
498
+ }
499
+
500
+ function _resolveIssuerSpki(issuer) {
501
+ if (issuer == null) throw _err("crl/bad-input", "an issuer is required to verify a CRL");
502
+ if (Buffer.isBuffer(issuer)) { _assertValidSpki(issuer, "issuer SPKI"); return issuer; }
503
+ if (issuer.cert != null) return _parseIssuerCert(issuer.cert).subjectPublicKeyInfo.bytes;
504
+ if (issuer.publicKey != null) { var spki = _reqDer(issuer.publicKey, "issuer.publicKey"); _assertValidSpki(spki, "issuer.publicKey"); return spki; }
505
+ if (issuer.subjectPublicKeyInfo && issuer.subjectPublicKeyInfo.bytes) return issuer.subjectPublicKeyInfo.bytes; // a parsed certificate
506
+ throw _err("crl/bad-input", "issuer must be { cert }, { publicKey } (SPKI DER), or a raw SPKI Buffer");
507
+ }
508
+
509
+ /**
510
+ * @primitive pki.crl.verify
511
+ * @signature pki.crl.verify(crl, issuer) -> Promise<boolean>
512
+ * @since 0.3.9
513
+ * @status experimental
514
+ * @spec RFC 5280 sec. 5.1.1.3, RFC 9814
515
+ * @defends crl-signature-bypass (CWE-347)
516
+ * @related pki.crl.sign, pki.path.crlChecker, pki.schema.crl.parse
517
+ *
518
+ * Verify a CRL's signature over its exact parsed `tbsCertList` bytes under the issuer public key. `crl`
519
+ * is a DER `Buffer`, a PEM string, or a parsed CRL; `issuer` is `{ cert }` (DER/PEM/parsed), `{ publicKey }`
520
+ * (SPKI DER), or a raw SPKI `Buffer`. Verification composes the one path-validation signature engine
521
+ * `pki.path.crlChecker` uses -- the same algorithm-confusion (RFC 9814 sec. 4 key-OID == sig-OID) and
522
+ * EdDSA low-order-point gates -- so there is no second, weaker CRL verifier. It fails closed to `false` on
523
+ * any resolution, import, or verification fault; malformed input throws a typed `CrlError`. This checks the
524
+ * signature only -- issuer authorization, currency, and distribution-point scope are `pki.path.crlChecker`.
525
+ *
526
+ * @example
527
+ * var ok = await pki.crl.verify(crlDer, { publicKey: signerSpki }); // true / false
528
+ */
529
+ function verify(crl, issuer) { return Promise.resolve().then(function () { return _verify(crl, issuer); }); }
530
+ function _verify(crl, issuer) {
531
+ var parsed = _coerceCrl(crl);
532
+ var spki = _resolveIssuerSpki(issuer);
533
+ return crlVerify.verifyCrlSignature(parsed, spki);
534
+ }
535
+
536
+ function _serialHexOf(serial) {
537
+ var v;
538
+ if (typeof serial === "bigint") v = serial;
539
+ else if (typeof serial === "number") { if (!Number.isSafeInteger(serial)) throw _err("crl/bad-input", "serialNumber number must be a safe integer (pass a BigInt, hex string, or Buffer)"); v = BigInt(serial); }
540
+ else if (typeof serial === "string") { try { v = BigInt(serial); } catch (e) { throw _err("crl/bad-input", "serialNumber string must be a decimal or 0x-hex integer", e); } }
541
+ else if (Buffer.isBuffer(serial)) { v = serial.length ? BigInt("0x" + serial.toString("hex")) : 0n; }
542
+ else throw _err("crl/bad-input", "serialNumber must be a BigInt, integer, hex string, or Buffer");
543
+ if (v <= 0n) throw _err("crl/bad-input", "serialNumber must be a positive integer");
544
+ // Match schema-crl's serialNumberHex: the DER INTEGER content octets (preserving sign padding).
545
+ return asn1.decode(b.integer(v)).content.toString("hex");
546
+ }
547
+
548
+ /**
549
+ * @primitive pki.crl.isRevoked
550
+ * @signature pki.crl.isRevoked(crl, serialNumber) -> entry | null
551
+ * @since 0.3.9
552
+ * @status experimental
553
+ * @spec RFC 5280 sec. 5.1.2.6
554
+ * @related pki.crl.verify, pki.schema.crl.parse
555
+ *
556
+ * Look a certificate serial number up in a CRL's `revokedCertificates` list. `crl` is a DER `Buffer`, a
557
+ * PEM string, or a parsed CRL; `serialNumber` is a `BigInt`, a safe integer, a decimal / `0x`-hex string,
558
+ * or a magnitude `Buffer`. Returns the matching revoked-certificate entry (`{ serialNumber, serialNumberHex,
559
+ * revocationDate, crlEntryExtensions }`) or `null` when the serial is not listed. A structural lookup only --
560
+ * it does NOT verify the CRL signature or its currency; call `pki.crl.verify` / `pki.path.crlChecker` for that.
561
+ *
562
+ * @example
563
+ * pki.crl.isRevoked(crlDer, 0x1234n) ? "revoked" : "not listed";
564
+ */
565
+ function isRevoked(crl, serialNumber) {
566
+ var parsed = _coerceCrl(crl);
567
+ var hex = _serialHexOf(serialNumber);
568
+ for (var i = 0; i < parsed.revokedCertificates.length; i++) {
569
+ if (parsed.revokedCertificates[i].serialNumberHex === hex) return parsed.revokedCertificates[i];
570
+ }
571
+ return null;
572
+ }
573
+
574
+ module.exports = { sign: sign, verify: verify, isRevoked: isRevoked };
@@ -0,0 +1,27 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ //
5
+ // @internal -- the ONE CRL signature-verify seam, shared by pki.path.crlChecker and pki.crl.verify so both
6
+ // route through the SAME signature engine (algorithm-confusion + EdDSA low-order + composite gates) and no
7
+ // second, weaker CRL verifier can drift. path-validate owns that engine (_verifyWithSpki) and injects it
8
+ // here at its module load (setEngine). Keeping the seam in this internal module -- rather than on
9
+ // path-validate's module.exports -- keeps it OFF the public pki.path surface (it takes the path-internal
10
+ // SubjectPublicKeyInfo bytes, not a documented issuer shape). NOT wired into index.js; reached only by require.
11
+
12
+ var guard = require("./guard-all");
13
+
14
+ var _engine = null; // path-validate's _verifyWithSpki, injected at path-validate's module load via setEngine.
15
+
16
+ function setEngine(verifyWithSpki) { _engine = verifyWithSpki; }
17
+
18
+ // Verify a parsed CRL's signature over its raw tbsCertList bytes under the issuer SubjectPublicKeyInfo DER.
19
+ // Fail-closed to false on a non-octet-aligned signature or any engine fault (the engine never throws out).
20
+ function verifyCrlSignature(crl, spkiBytes) {
21
+ // _engine is injected by path-validate at its module load (setEngine); pki.crl.sign requires path-validate
22
+ // for exactly that side-effect, so the engine is always set before a verify runs.
23
+ if (!guard.crypto.isOctetAligned(crl.signatureValue)) return Promise.resolve(false); // non-octet-aligned signature
24
+ return _engine(crl.signatureAlgorithm, crl.signatureValue.bytes, spkiBytes, crl.tbsBytes);
25
+ }
26
+
27
+ module.exports = { setEngine: setEngine, verifyCrlSignature: verifyCrlSignature };
@@ -318,6 +318,15 @@ var WebauthnError = defineClass("WebauthnError", { withCause: true });
318
318
  // on malformed input -- a linter surveys a corpus that includes malformed members,
319
319
  // so hostile bytes surface a `fatal` finding (lint/unparseable) and return a report.
320
320
  var LintError = defineClass("LintError", { withCause: true });
321
+ // KeyError -- the pki.key key-material lifecycle domain: a bad export/import
322
+ // input (not a CryptoKey / DER / PEM), an unsupported key or PBES2 algorithm, a
323
+ // malformed EncryptedPrivateKeyInfo / PBES2 parameter set, an attacker-controlled
324
+ // PBKDF2 salt or iteration count over its cap, a version<=>publicKey mismatch on a
325
+ // produced OneAsymmetricKey, or a failed PBES2 decryption. A MAC-less PBES2-CBC
326
+ // decrypt collapses every post-derivation failure (wrong password, valid pad but
327
+ // not a PrivateKeyInfo) into ONE uniform key/decrypt-failed so it is not a padding
328
+ // oracle; the structural pre-derivation faults stay distinct and typed.
329
+ var KeyError = defineClass("KeyError", { withCause: true });
321
330
 
322
331
  module.exports = {
323
332
  PkiError: PkiError,
@@ -355,4 +364,5 @@ module.exports = {
355
364
  JoseError: JoseError,
356
365
  AcmeError: AcmeError,
357
366
  TrustError: TrustError,
367
+ KeyError: KeyError,
358
368
  };