@blamejs/pki 0.5.25 → 0.5.26
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.
- package/CHANGELOG.md +30 -0
- package/README.md +1 -1
- package/lib/cmp-build.js +185 -5
- package/lib/cmp-session.js +753 -11
- package/lib/guard-identifier.js +36 -0
- package/lib/guard-name.js +90 -1
- package/lib/oid.js +55 -2
- package/lib/path-validate.js +15 -27
- package/lib/schema-cmp.js +100 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/guard-identifier.js
CHANGED
|
@@ -738,6 +738,41 @@ function _notAnOptionsBag(v) {
|
|
|
738
738
|
// error and says so. A Buffer is excluded because it is an object and is never an options bag --
|
|
739
739
|
// passing one is an argument-order mistake, which is worth naming rather than treating as empty.
|
|
740
740
|
//
|
|
741
|
+
// A built-in exotic whose data lives in internal slots rather than own enumerable keys, recognized by
|
|
742
|
+
// identity so a cross-realm instance is caught too. `util.types` covers each such kind; an array is not
|
|
743
|
+
// among them and is checked separately by the caller.
|
|
744
|
+
function _isBuiltinExotic(v) {
|
|
745
|
+
var t = util.types;
|
|
746
|
+
return t.isDate(v) || t.isMap(v) || t.isSet(v) || t.isWeakMap(v) || t.isWeakSet(v) || t.isRegExp(v) ||
|
|
747
|
+
t.isPromise(v) || t.isArrayBuffer(v) || t.isSharedArrayBuffer(v) || t.isArrayBufferView(v) ||
|
|
748
|
+
t.isBoxedPrimitive(v) || t.isNativeError(v) || t.isArgumentsObject(v) || t.isGeneratorObject(v);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// assertPlainRecord(v, E, code, label): v MUST be a plain data record -- a non-null object that is not an
|
|
752
|
+
// array, a Buffer, a Proxy, or a built-in exotic (a Date, Map, Set, boxed primitive, ...). A caller's
|
|
753
|
+
// `typeof v === "object"` gate admits an array or a Date, which then passes assertKnownKeys with no own
|
|
754
|
+
// key to reject and reads as a keyless record -- every field `undefined` and silently defaulted. This
|
|
755
|
+
// door refuses that shape, so a malformed container is a refusal rather than a request assembled from
|
|
756
|
+
// defaults. Composes BEFORE assertKnownKeys, which only
|
|
757
|
+
// judges the keys that are present, not whether the value is a record at all.
|
|
758
|
+
//
|
|
759
|
+
// The prototype is NOT consulted, so a record built by a caller's own class, or one from another realm
|
|
760
|
+
// (a vm context, a worker) whose prototype is a different Object.prototype, is accepted -- it holds its
|
|
761
|
+
// fields as own enumerable properties like any record. Only a built-in exotic that keeps its data in
|
|
762
|
+
// internal slots (a Date, Map, Set, boxed primitive, ...), an array (index-keyed), a Buffer, or a Proxy
|
|
763
|
+
// is refused, each recognized by identity through `util.types` / `Array.isArray` so a cross-realm
|
|
764
|
+
// instance is caught where an `instanceof` or a prototype check would miss it.
|
|
765
|
+
//
|
|
766
|
+
// @enforced-by behavioral -- a plain-record refusal has no single rename-proof code shape worth a
|
|
767
|
+
// tree-wide detector; the RED vectors (an array, a Date, and a Proxy at each routed entry point, and a
|
|
768
|
+
// cross-realm/subclass record that must still be ACCEPTED) are the guard.
|
|
769
|
+
function assertPlainRecord(v, E, code, label) {
|
|
770
|
+
_requireFactory(E);
|
|
771
|
+
if (v === null || typeof v !== "object" || _isNodeBuffer(v) || _isArray(v)) throw E(code, label + " must be a plain object");
|
|
772
|
+
_refuseUnenumerable(v, E, code, label); // a Proxy's reported keys need not match what it answers
|
|
773
|
+
if (_isBuiltinExotic(v)) throw E(code, label + " must be a plain object (a built-in exotic such as a Date or Map is refused)");
|
|
774
|
+
}
|
|
775
|
+
|
|
741
776
|
// @enforced-by behavioral -- the defect is an ordering, which has no rename-proof code shape; the
|
|
742
777
|
// RED vector (a verb handed `false` accepting it as `{}`) is the guard.
|
|
743
778
|
function optionsObject(opts, E, code, label) {
|
|
@@ -856,6 +891,7 @@ function _settle(opts, E, code, label) {
|
|
|
856
891
|
module.exports = intrinsic.freeze({
|
|
857
892
|
assertCanonicalOid: assertCanonicalOid,
|
|
858
893
|
assertKnownKeys: assertKnownKeys,
|
|
894
|
+
assertPlainRecord: assertPlainRecord,
|
|
859
895
|
optionsObject: optionsObject,
|
|
860
896
|
refuseAccessorFields: refuseAccessorFields,
|
|
861
897
|
// Exported so a caller that forwards an options bag copies the same surface this module
|
package/lib/guard-name.js
CHANGED
|
@@ -53,6 +53,12 @@ var _String = String;
|
|
|
53
53
|
// matches, and it is captured for the same reason as the rest: what a guard does is settled before
|
|
54
54
|
// a caller runs, or it is settled by the caller.
|
|
55
55
|
var _isArray = Array.isArray;
|
|
56
|
+
// Byte identity and the shape test that gates it, both captured for the reason above: a
|
|
57
|
+
// distribution-point comparison asks whether two encodings are the same bytes, and an object
|
|
58
|
+
// supplying its own `equals` would be answering that question about itself.
|
|
59
|
+
var _isBuffer = intrinsic.isBuffer;
|
|
60
|
+
var _bufferEquals = intrinsic.bufferEquals;
|
|
61
|
+
var _getOwnPropertyDescriptor = intrinsic.getOwnPropertyDescriptor;
|
|
56
62
|
|
|
57
63
|
// assertNoControlBytes(str, E, code, label) -> str | throws E(code, ...)
|
|
58
64
|
// DirectoryString policy (a DN attribute value): reject NUL and C0 control bytes
|
|
@@ -185,6 +191,89 @@ function dnEqual(rdnsA, rdnsB, E, code, label) {
|
|
|
185
191
|
return true;
|
|
186
192
|
}
|
|
187
193
|
|
|
194
|
+
// dpnCorresponds(a, b, E, code, label) -> boolean. Do two DistributionPointNames name the
|
|
195
|
+
// same CRL distribution point? RFC 5280 sec. 6.3.3(b)(2)(i) asks it of a certificate's
|
|
196
|
+
// DistributionPoint against a shard CRL's issuingDistributionPoint, and RFC 9483 sec. 4.3.4
|
|
197
|
+
// asks it of the dpn a CMP crlUpdate requested against the IDP of the CRL that came back.
|
|
198
|
+
// The answer is "at least one name in common", compared by BYTE-IDENTICAL DER.
|
|
199
|
+
//
|
|
200
|
+
// The comparison key is pinned by sec. 5.2.5: "The identical encoding MUST be used in the
|
|
201
|
+
// distributionPoint fields of the certificate and the CRL". This is therefore the one name
|
|
202
|
+
// comparison in the toolkit that must NOT canonicalize. dnEqual's sec. 7.1 rule applied here
|
|
203
|
+
// would equate two encodings the spec keeps distinct, and a CRL would be accepted as covering
|
|
204
|
+
// a point it was never published for. Whole-set equality is the opposite error: a point
|
|
205
|
+
// published under several names (a URI beside a directoryName) shares one of them with a
|
|
206
|
+
// reference that lists only that one, and demanding all would reject its own CRL.
|
|
207
|
+
//
|
|
208
|
+
// Mixed forms never correspond, with fullName on one side and nameRelativeToCRLIssuer on the
|
|
209
|
+
// other. Deciding that would mean resolving an RDN fragment against a CRL issuer DN this
|
|
210
|
+
// comparison is not given, so the answer is no.
|
|
211
|
+
//
|
|
212
|
+
// @enforced-by behavioral -- the rule's identity is carried by the kind strings, which the
|
|
213
|
+
// pattern walk strips before matching, so it has no rename-proof code shape. The RED vectors
|
|
214
|
+
// in guard-name.test.js and the crlUpdate vectors in cmp-session.test.js are the guard.
|
|
215
|
+
function dpnCorresponds(a, b, E, code, label) {
|
|
216
|
+
var x = _reduceDpn(a, E, code, label), y = _reduceDpn(b, E, code, label);
|
|
217
|
+
if (x.kind !== y.kind) return false;
|
|
218
|
+
if (x.kind === "rdn") return _bufferEquals(x.bytes, y.bytes);
|
|
219
|
+
for (var i = 0; i < x.names.length; i++) {
|
|
220
|
+
for (var j = 0; j < y.names.length; j++) {
|
|
221
|
+
if (_bufferEquals(x.names[i], y.names[j])) return true;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// The comparand, checked and REDUCED to the values that were checked. The reason _assertSequence
|
|
228
|
+
// gives applies here in full: a comparison handed the wrong shape must refuse at the call site
|
|
229
|
+
// instead of answering. Reading `names` off a value that has none would run a loop over
|
|
230
|
+
// `i < undefined` zero times and report "no correspondence", which reads as a clean refusal of a
|
|
231
|
+
// CRL and hides that nothing was compared; reading it off a sparse array would walk to
|
|
232
|
+
// Array.prototype for the missing index and compare a name the reference never carried.
|
|
233
|
+
//
|
|
234
|
+
// Every field must be the comparand's OWN DATA property, and its value is taken FROM the property
|
|
235
|
+
// descriptor. That is one reflective read per field, so the value the shape check validated is the
|
|
236
|
+
// value the comparison is answered from: no second read is left for an accessor, or a Proxy trap,
|
|
237
|
+
// to answer differently, and a field the decoder never produces (inherited, or reached through a
|
|
238
|
+
// getter) is refused. Buffers, not caller byte-likes, for the same reason: the comparison is byte
|
|
239
|
+
// identity, and an object answering `equals` or `length` for itself would be deciding the question
|
|
240
|
+
// asked of it.
|
|
241
|
+
function _reduceDpn(d, E, code, label) {
|
|
242
|
+
function refuse(why) { return E(code, "cannot compare " + label + ": " + why); }
|
|
243
|
+
function ownValue(o, k) {
|
|
244
|
+
// FromPropertyDescriptor hands back a fresh ordinary object, so `value` here is the value the
|
|
245
|
+
// object itself reported for that key, including through a Proxy's own-descriptor trap.
|
|
246
|
+
var desc = _getOwnPropertyDescriptor(o, k);
|
|
247
|
+
if (!desc || !_hasOwn(desc, "value")) throw refuse("a distribution-point comparison requires " + k + " to be the comparand's own value (RFC 5280 sec. 6.3.3 compares decoded names)");
|
|
248
|
+
return desc.value;
|
|
249
|
+
}
|
|
250
|
+
if (!d || typeof d !== "object") {
|
|
251
|
+
throw refuse("a distribution-point comparison requires the decoded DistributionPointName on both sides (kind fullName or rdn)");
|
|
252
|
+
}
|
|
253
|
+
var kind = ownValue(d, "kind");
|
|
254
|
+
if (kind !== "fullName" && kind !== "rdn") throw refuse("a DistributionPointName is fullName or nameRelativeToCRLIssuer, and this is neither");
|
|
255
|
+
if (kind === "rdn") {
|
|
256
|
+
var bytes = ownValue(d, "bytes");
|
|
257
|
+
if (!_isBuffer(bytes)) throw refuse("a nameRelativeToCRLIssuer distribution point must carry its encoded bytes");
|
|
258
|
+
return { kind: kind, bytes: bytes };
|
|
259
|
+
}
|
|
260
|
+
var names = ownValue(d, "names");
|
|
261
|
+
if (!_isArray(names)) throw refuse("a fullName distribution point must carry a sequence of encoded GeneralNames");
|
|
262
|
+
// The length is read once, into the bound the copy runs to. A hole and an inherited element are
|
|
263
|
+
// the same refusal as any other missing own value: indexing past a hole walks to Array.prototype,
|
|
264
|
+
// where a planted numeric property supplies a name the reference never carried.
|
|
265
|
+
var len = names.length;
|
|
266
|
+
// Indexed assignment, never a push: the accumulator must not be built by a method a caller can
|
|
267
|
+
// replace, since what it collects is what the comparison is answered from.
|
|
268
|
+
var taken = [];
|
|
269
|
+
for (var i = 0; i < len; i++) {
|
|
270
|
+
var n = ownValue(names, i);
|
|
271
|
+
if (!_isBuffer(n)) throw refuse("every fullName GeneralName must be its encoded bytes");
|
|
272
|
+
taken[i] = n;
|
|
273
|
+
}
|
|
274
|
+
return { kind: kind, names: taken };
|
|
275
|
+
}
|
|
276
|
+
|
|
188
277
|
// escapeControlBytes(str) -> str. The render-side sibling of assertNoControlBytes:
|
|
189
278
|
// where a name string must still be DISPLAYED best-effort (a human-readable report,
|
|
190
279
|
// a log line) instead of rejected, neutralize every C0 control byte and DEL by
|
|
@@ -316,7 +405,7 @@ function lowerAscii(s) {
|
|
|
316
405
|
// call, so a writable export would let a caller replace the check rather than pass it.
|
|
317
406
|
module.exports = intrinsic.freeze({
|
|
318
407
|
assertNoControlBytes: assertNoControlBytes, assertPrintableIa5: assertPrintableIa5,
|
|
319
|
-
dnEqual: dnEqual, rdnEqual: rdnEqual, emailEqual: emailEqual,
|
|
408
|
+
dnEqual: dnEqual, rdnEqual: rdnEqual, emailEqual: emailEqual, dpnCorresponds: dpnCorresponds,
|
|
320
409
|
escapeControlBytes: escapeControlBytes, escapeDnValue: escapeDnValue,
|
|
321
410
|
lowerAscii: lowerAscii,
|
|
322
411
|
});
|
package/lib/oid.js
CHANGED
|
@@ -234,8 +234,11 @@ var FAMILIES = {
|
|
|
234
234
|
// id-pkip arc (id-pkix 5). id-regCtrl (id-pkip 1) names the control types a
|
|
235
235
|
// CertRequest carries; id-regInfo (id-pkip 2) names the registration-info types.
|
|
236
236
|
// The parser surfaces each control/info value raw, keyed by these names.
|
|
237
|
+
// altCertTemplate (7) and the two keySpec controls algId (11) / rsaKeyLen (12) are the RFC 9480
|
|
238
|
+
// sec. 2.16 additions; a CertReqTemplate response states its key requirements with the latter two.
|
|
237
239
|
regCtrl: { base: [1, 3, 6, 1, 5, 5, 7, 5, 1], of: {
|
|
238
|
-
regToken: 1, authenticator: 2, pkiPublicationInfo: 3, pkiArchiveOptions: 4, oldCertID: 5, protocolEncrKey: 6
|
|
240
|
+
regToken: 1, authenticator: 2, pkiPublicationInfo: 3, pkiArchiveOptions: 4, oldCertID: 5, protocolEncrKey: 6,
|
|
241
|
+
altCertTemplate: 7, algId: 11, rsaKeyLen: 12 } },
|
|
239
242
|
regInfo: { base: [1, 3, 6, 1, 5, 5, 7, 5, 2], of: { utf8Pairs: 1, certReq: 2 } },
|
|
240
243
|
|
|
241
244
|
// PKIX extended key purposes (id-kp, RFC 5280 sec. 4.2.1.12). timeStamping is required,
|
|
@@ -376,6 +379,9 @@ var FAMILIES = {
|
|
|
376
379
|
// SignerInfo carries a MAC-based proof instead. Registered so a decoder can
|
|
377
380
|
// name it and refuse it deliberately.
|
|
378
381
|
"id-alg-noSignature": 2,
|
|
382
|
+
// RSASSA-PSS with SHAKE (RFC 8692): RSA signature algorithms on the PKIX arc.
|
|
383
|
+
// Registered so a consumer can name them -- e.g. the CMP keySpec RSA exclusion.
|
|
384
|
+
"id-RSASSA-PSS-SHAKE128": 30, "id-RSASSA-PSS-SHAKE256": 31,
|
|
379
385
|
"id-alg-xmss-hashsig": 34, "id-alg-xmssmt-hashsig": 35,
|
|
380
386
|
// Composite ML-DSA signature algorithms (draft-ietf-lamps-pq-composite-sigs
|
|
381
387
|
// sec. 6): a PQ ML-DSA paired with a traditional RSA / ECDSA / EdDSA so the
|
|
@@ -432,7 +438,49 @@ var FAMILIES = {
|
|
|
432
438
|
|
|
433
439
|
// OIW Secsig SHA-1 (1.3.14.3.2.26) -- the most common OCSP CertID hashAlgorithm;
|
|
434
440
|
// nistHash covers only SHA-256 and above.
|
|
435
|
-
|
|
441
|
+
// OIW Secsig arc (1.3.14.3.2). id-SHA1 (.26) plus every RSA identifier the OIW SIG assigned under the
|
|
442
|
+
// arc: md4WithRSA (.2), md5WithRSA (.3), md4WithRSAEncryption (.4), rsaSignature (.11, the ISO 9796 RSA
|
|
443
|
+
// scheme), mdc2WithRSASignature (.14), shaWithRSAEncryption (.15), rsaKeyTransport (.22), and the
|
|
444
|
+
// md2/md5/sha1-WithRSASignature set (.24/.25/.29) -- pre-PKCS#1 RSA algorithms still seen in old certs.
|
|
445
|
+
// This is the arc's complete RSA membership per the OIW SIG assignments. Registered so a consumer can
|
|
446
|
+
// name them -- e.g. the CMP keySpec RSA exclusion, which recognizes RSA wherever it is standardized.
|
|
447
|
+
// The arc is shared with DES, DSA, DH, and the hashes, so no prefix isolates RSA; each is named.
|
|
448
|
+
oiwSecsig: { base: [1, 3, 14, 3, 2], of: {
|
|
449
|
+
sha1: 26,
|
|
450
|
+
"md4WithRSA": 2, "md5WithRSA": 3, "md4WithRSAEncryption": 4, "rsaSignature": 11,
|
|
451
|
+
"mdc2WithRSASignature": 14, "shaWithRSAEncryption": 15, "rsaKeyTransport": 22,
|
|
452
|
+
"md2WithRSASignature": 24, "md5WithRSASignature": 25, "sha1WithRSASignature": 29 } },
|
|
453
|
+
|
|
454
|
+
// TeleTrusT (BSI) RSA signature arc (1.3.36.3.3.1) -- RSA signatures with SHA-1 and the RIPEMD family.
|
|
455
|
+
// The arc is RSA-dedicated, so a consumer can classify the whole family by its prefix; one member is
|
|
456
|
+
// registered here so that prefix can be derived from the registry rather than a dotted-decimal literal.
|
|
457
|
+
teletrustRsaSig: { base: [1, 3, 36, 3, 3, 1], of: { "rsaSignatureWithripemd160": 2 } },
|
|
458
|
+
|
|
459
|
+
// X.509 directory encryptionAlgorithm arc (2.5.8.1). id-ea-rsa (.1) is the X.509 `rsa` public-key
|
|
460
|
+
// identifier, distinct from PKCS#1 rsaEncryption. Registered so a consumer can name it -- e.g. the
|
|
461
|
+
// CMP keySpec RSA exclusion, which must recognize RSA wherever it is standardized.
|
|
462
|
+
x509EncAlg: { base: [2, 5, 8, 1], of: { "rsa": 1 } },
|
|
463
|
+
|
|
464
|
+
// X.500 directory signatureAlgorithm arc (2.5.8.3). Its RSA members are sqMod-nWithRSA (.1, the
|
|
465
|
+
// ITU-T X.509 / ISO 9594-8 square-mod-n-with-RSA signature) and mdc2WithRSA (.100, MDC-2-with-RSA).
|
|
466
|
+
// Registered so the CMP keySpec RSA exclusion can name them; the arc is not RSA-dedicated, so each
|
|
467
|
+
// member is named individually rather than prefix-matched.
|
|
468
|
+
x509SigAlg: { base: [2, 5, 8, 3], of: { "sqMod-nWithRSA": 1, "mdc2WithRSA": 100 } },
|
|
469
|
+
|
|
470
|
+
// Chinese GM/T (SCA) SM algorithm arc (1.2.156.10197.1). Its one RSA member is sm3WithRSAEncryption
|
|
471
|
+
// (.504, a PKCS#1 v1.5 RSA signature over an SM3 digest); sm2 (.301), sm3 (.401), and SM2-with-SM3
|
|
472
|
+
// (.501) on the arc are not RSA. Registered so the CMP keySpec RSA exclusion can name it -- the arc is
|
|
473
|
+
// mixed, so the RSA member is named individually rather than prefix-matched.
|
|
474
|
+
smAlg: { base: [1, 2, 156, 10197, 1], of: { "sm3WithRSAEncryption": 504 } },
|
|
475
|
+
|
|
476
|
+
// BSI TR-03110 EAC Terminal Authentication RSA arc (0.4.0.127.0.7.2.2.2.1, id-TA-RSA). Every child is an
|
|
477
|
+
// RSA signature scheme -- PKCS#1 v1.5 or PSS over SHA-1/256/512 -- and the sibling id-TA-ECDSA arc
|
|
478
|
+
// (0.4.0.127.0.7.2.2.2.2) is EC, so this arc is RSA-dedicated. The members are registered so a consumer
|
|
479
|
+
// can derive the arc prefix from the registry rather than a dotted-decimal literal and classify the
|
|
480
|
+
// whole family, including a variant BSI adds later, by that prefix.
|
|
481
|
+
bsiTaRsa: { base: [0, 4, 0, 127, 0, 7, 2, 2, 2, 1], of: {
|
|
482
|
+
"id-TA-RSA-v1-5-SHA-1": 1, "id-TA-RSA-v1-5-SHA-256": 2, "id-TA-RSA-PSS-SHA-1": 3,
|
|
483
|
+
"id-TA-RSA-PSS-SHA-256": 4, "id-TA-RSA-v1-5-SHA-512": 5, "id-TA-RSA-PSS-SHA-512": 6 } },
|
|
436
484
|
|
|
437
485
|
// NIST AES content-encryption algorithms (arc 2.16.840.1.101.3.4.1) -- the
|
|
438
486
|
// modern PBES2 / CMS content ciphers a PKCS#12 or EnvelopedData names.
|
|
@@ -451,6 +499,11 @@ var FAMILIES = {
|
|
|
451
499
|
// sets .35-.46, each pairing a parameter set with its message-digest hash. The
|
|
452
500
|
// parameters MUST be absent for every one of them (enforced via paramsMustBeAbsent).
|
|
453
501
|
nistSig: { base: [2, 16, 840, 1, 101, 3, 4, 3], of: {
|
|
502
|
+
// RSASSA-PKCS1-v1_5 with SHA-3 (NIST CSOR): RSA signature algorithms on the NIST
|
|
503
|
+
// signature arc, alongside the ML-DSA / SLH-DSA sets. Registered so a consumer can
|
|
504
|
+
// name them -- e.g. the CMP keySpec check that must recognize RSA to exclude it.
|
|
505
|
+
"id-rsassa-pkcs1-v1_5-with-sha3-224": 13, "id-rsassa-pkcs1-v1_5-with-sha3-256": 14,
|
|
506
|
+
"id-rsassa-pkcs1-v1_5-with-sha3-384": 15, "id-rsassa-pkcs1-v1_5-with-sha3-512": 16,
|
|
454
507
|
"id-ml-dsa-44": 17, "id-ml-dsa-65": 18, "id-ml-dsa-87": 19,
|
|
455
508
|
"id-slh-dsa-sha2-128s": 20, "id-slh-dsa-sha2-128f": 21,
|
|
456
509
|
"id-slh-dsa-sha2-192s": 22, "id-slh-dsa-sha2-192f": 23,
|
package/lib/path-validate.js
CHANGED
|
@@ -1695,38 +1695,23 @@ function decodeIdp(ext) {
|
|
|
1695
1695
|
}
|
|
1696
1696
|
|
|
1697
1697
|
// RFC 5280 sec. 6.3.3(b)(2)(i): find the certificate DistributionPoint that
|
|
1698
|
-
// CORRESPONDS to a shard CRL's IDP distribution point name
|
|
1699
|
-
//
|
|
1700
|
-
//
|
|
1701
|
-
//
|
|
1702
|
-
//
|
|
1703
|
-
// a
|
|
1704
|
-
//
|
|
1705
|
-
//
|
|
1706
|
-
//
|
|
1707
|
-
//
|
|
1708
|
-
// the issuer DN is not attempted (fail closed). Per sec. 6.3.3(b)(1), a DP
|
|
1709
|
-
// naming a cRLIssuer participates only when that cRLIssuer is the certificate
|
|
1710
|
-
// issuer itself: the checker only consults CRLs issued BY the certificate
|
|
1711
|
-
// issuer and rejects indirect CRLs, so a DP delegated to another CRL issuer
|
|
1712
|
-
// is out of play. Returns the matched DP (its `reasons` feeds the coarse
|
|
1713
|
-
// reason-mask rule) or null.
|
|
1698
|
+
// CORRESPONDS to a shard CRL's IDP distribution point name. The correspondence
|
|
1699
|
+
// rule itself -- at least one name in common, compared by BYTE-IDENTICAL DER,
|
|
1700
|
+
// mixed name forms never corresponding -- is guard.name.dpnCorresponds, which
|
|
1701
|
+
// is also what a CMP crlUpdate answer is held to, so one definition decides
|
|
1702
|
+
// whether a CRL speaks for a point wherever the question is asked. Per sec.
|
|
1703
|
+
// 6.3.3(b)(1), a DP naming a cRLIssuer participates only when that cRLIssuer
|
|
1704
|
+
// is the certificate issuer itself: the checker only consults CRLs issued BY
|
|
1705
|
+
// the certificate issuer and rejects indirect CRLs, so a DP delegated to
|
|
1706
|
+
// another CRL issuer is out of play. Returns the matched DP (its `reasons`
|
|
1707
|
+
// feeds the coarse reason-mask rule) or null.
|
|
1714
1708
|
function correspondingCertDp(idpDpn, certDPs, issuerRdns) {
|
|
1715
1709
|
if (!idpDpn || !certDPs) return null;
|
|
1716
1710
|
for (var i = 0; i < certDPs.length; i++) {
|
|
1717
1711
|
var dp = certDPs[i];
|
|
1718
1712
|
if (!dp.distributionPoint) continue;
|
|
1719
1713
|
if (dp.cRLIssuer && !crlIssuerNamesIssuer(dp.cRLIssuer, issuerRdns)) continue;
|
|
1720
|
-
|
|
1721
|
-
if (idpDpn.kind === "fullName" && cdpn.kind === "fullName") {
|
|
1722
|
-
for (var a = 0; a < idpDpn.names.length; a++) {
|
|
1723
|
-
for (var c = 0; c < cdpn.names.length; c++) {
|
|
1724
|
-
if (idpDpn.names[a].equals(cdpn.names[c])) return dp;
|
|
1725
|
-
}
|
|
1726
|
-
}
|
|
1727
|
-
} else if (idpDpn.kind === "rdn" && cdpn.kind === "rdn") {
|
|
1728
|
-
if (idpDpn.bytes.equals(cdpn.bytes)) return dp;
|
|
1729
|
-
}
|
|
1714
|
+
if (guard.name.dpnCorresponds(idpDpn, dp.distributionPoint, E, "path/bad-idp", "a CRL distribution point")) return dp;
|
|
1730
1715
|
}
|
|
1731
1716
|
return null;
|
|
1732
1717
|
}
|
|
@@ -2332,7 +2317,10 @@ var ocspCore = ocspVerify.makeOcspVerify({
|
|
|
2332
2317
|
// of it on the public pki.path surface (index.js exports the whole path-validate module).
|
|
2333
2318
|
cmpVerify.setEngine({ verifyWithSpki: _verifyWithSpki, build: build, validate: validate });
|
|
2334
2319
|
// pki.cmp.session validates the ISSUED leaf certificate (its signature + chain) through the same engine.
|
|
2335
|
-
|
|
2320
|
+
// `verifyWithSpki` so a root CA key update's rollover signatures are checked by the same signature
|
|
2321
|
+
// engine (with its EdDSA low-order-point and algorithm-confusion gates) that verifies a message's
|
|
2322
|
+
// protection, rather than a second, weaker check inside the session.
|
|
2323
|
+
cmpSession.setEngine({ build: build, validate: validate, toAnchor: toAnchor, coerceCert: coerceCert, verifyWithSpki: _verifyWithSpki });
|
|
2336
2324
|
// pki.cms.verify chains a SignedData's signer certificate to the anchors the CALLER named, so its
|
|
2337
2325
|
// `trusted` is decided by this one path engine rather than a second, weaker walk of its own.
|
|
2338
2326
|
// `toAnchor` so cms.verify can validate the caller's anchors ONCE at entry, before any signer is
|
package/lib/schema-cmp.js
CHANGED
|
@@ -836,6 +836,99 @@ var PKI_MESSAGE = schema.seq([
|
|
|
836
836
|
},
|
|
837
837
|
});
|
|
838
838
|
|
|
839
|
+
// ---- support-message infoValue structures (RFC 9480 sec. 2.14-2.17) -----
|
|
840
|
+
//
|
|
841
|
+
// A genm / genp carries its payload as an ANY, so the InfoTypeAndValue leaf above
|
|
842
|
+
// surfaces it as raw bytes. These are the strict decoders for the four values RFC 9483
|
|
843
|
+
// sec. 4.3 defines, each a standalone parser so a malformed payload is refused with the
|
|
844
|
+
// same caps, minimal-encoding and trailing-byte rules the message itself is held to.
|
|
845
|
+
// The CMP module is EXPLICIT TAGS, so every context tag here wraps its value.
|
|
846
|
+
|
|
847
|
+
// CACertsValue ::= SEQUENCE SIZE (1..MAX) OF CMPCertificate. An absent infoValue is how
|
|
848
|
+
// "no CA certificate is available" is said (sec. 4.3.1), so an EMPTY sequence is malformed
|
|
849
|
+
// rather than a second spelling of the same answer.
|
|
850
|
+
var CA_CERTS_VALUE = schema.seqOf(rawSequence("cmp/bad-info-value"), {
|
|
851
|
+
min: 1, code: "cmp/bad-info-value", what: "CACertsValue",
|
|
852
|
+
build: function (m) { return m.items.map(function (it) { return it.value; }); },
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
// RootCaKeyUpdateContent ::= SEQUENCE { newWithNew CMPCertificate,
|
|
856
|
+
// newWithOld [0] CMPCertificate OPTIONAL, oldWithNew [1] CMPCertificate OPTIONAL }
|
|
857
|
+
// (RFC 9480 sec. 2.15). Both optionals are OPTIONAL in the syntax; RFC 9483 sec. 4.3.2
|
|
858
|
+
// requires newWithOld of a response, which the caller of this decoder enforces.
|
|
859
|
+
var ROOT_CA_KEY_UPDATE = schema.seq([
|
|
860
|
+
schema.field("newWithNew", rawSequence("cmp/bad-info-value")),
|
|
861
|
+
schema.trailing([
|
|
862
|
+
{ tag: 0, name: "newWithOld", schema: rawSequence("cmp/bad-info-value"), explicit: true, emptyCode: "cmp/bad-info-value" },
|
|
863
|
+
{ tag: 1, name: "oldWithNew", schema: rawSequence("cmp/bad-info-value"), explicit: true, emptyCode: "cmp/bad-info-value" },
|
|
864
|
+
], { minTag: 0, maxTag: 1, unexpectedCode: "cmp/bad-info-value", orderCode: "cmp/bad-info-value" }),
|
|
865
|
+
], {
|
|
866
|
+
assert: "sequence", code: "cmp/bad-info-value", what: "RootCaKeyUpdateContent",
|
|
867
|
+
build: function (m) {
|
|
868
|
+
return {
|
|
869
|
+
newWithNew: m.fields.newWithNew.value,
|
|
870
|
+
newWithOld: m.fields.newWithOld.present ? m.fields.newWithOld.value : null,
|
|
871
|
+
oldWithNew: m.fields.oldWithNew.present ? m.fields.oldWithNew.value : null,
|
|
872
|
+
};
|
|
873
|
+
},
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
// AttributeTypeAndValue ::= SEQUENCE { type OBJECT IDENTIFIER, value ANY }, the element of
|
|
877
|
+
// the Controls that carries a CertReqTemplate keySpec (RFC 9480 sec. 2.16). The value stays
|
|
878
|
+
// raw here: which syntax it must have depends on the type, which sec. 4.3.3 constrains.
|
|
879
|
+
var ATTR_TYPE_AND_VALUE = schema.seq([
|
|
880
|
+
schema.field("type", schema.oidLeaf()),
|
|
881
|
+
schema.field("value", schema.any()),
|
|
882
|
+
], {
|
|
883
|
+
assert: "sequence", code: "cmp/bad-info-value", what: "AttributeTypeAndValue",
|
|
884
|
+
build: function (m, ctx) {
|
|
885
|
+
var t = m.fields.type.value;
|
|
886
|
+
return { type: t, name: ctx.oid.name(t) || null, value: m.fields.value.value.bytes };
|
|
887
|
+
},
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
// CertReqTemplateContent ::= SEQUENCE { certTemplate CertTemplate, keySpec Controls OPTIONAL }
|
|
891
|
+
// (RFC 9480 sec. 2.16). keySpec is untagged, so it is recognized by its universal SEQUENCE tag
|
|
892
|
+
// following the CertTemplate, which is itself IMPLICIT [tagged] by the CRMF module.
|
|
893
|
+
var CERT_REQ_TEMPLATE_VALUE = schema.seq([
|
|
894
|
+
schema.field("certTemplate", CERT_TEMPLATE_LEAF),
|
|
895
|
+
schema.optional("keySpec", schema.seqOf(ATTR_TYPE_AND_VALUE, {
|
|
896
|
+
min: 1, code: "cmp/bad-info-value", what: "CertReqTemplate keySpec",
|
|
897
|
+
build: function (m) { return m.items.map(function (it) { return it.value.result; }); },
|
|
898
|
+
}), { whenUniversal: [TAGS.SEQUENCE] }),
|
|
899
|
+
], {
|
|
900
|
+
assert: "sequence", code: "cmp/bad-info-value", what: "CertReqTemplateValue",
|
|
901
|
+
build: function (m) {
|
|
902
|
+
return {
|
|
903
|
+
certTemplate: m.fields.certTemplate.value,
|
|
904
|
+
keySpec: m.fields.keySpec.present ? m.fields.keySpec.value.result : null,
|
|
905
|
+
};
|
|
906
|
+
},
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
// CRLsValue ::= SEQUENCE SIZE (1..MAX) OF CertificateList (RFC 9480 sec. 2.17).
|
|
910
|
+
var CRLS_VALUE = schema.seqOf(rawSequence("cmp/bad-info-value"), {
|
|
911
|
+
min: 1, code: "cmp/bad-info-value", what: "CRLsValue",
|
|
912
|
+
build: function (m) { return m.items.map(function (it) { return it.value; }); },
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
function _valueParser(what, topSchema) {
|
|
916
|
+
return pkix.makeParser({ pemLabel: "CMP", PemError: PemError, ErrorClass: CmpError, prefix: "cmp", what: what, topSchema: topSchema, ns: NS });
|
|
917
|
+
}
|
|
918
|
+
// @internal -- pki.cmp.session reads a support-message response through these.
|
|
919
|
+
var readCaCerts = _valueParser("CACertsValue", CA_CERTS_VALUE);
|
|
920
|
+
var readRootCaKeyUpdate = _valueParser("RootCaKeyUpdateContent", ROOT_CA_KEY_UPDATE);
|
|
921
|
+
var readCertReqTemplate = _valueParser("CertReqTemplateValue", CERT_REQ_TEMPLATE_VALUE);
|
|
922
|
+
var readCrls = _valueParser("CRLsValue", CRLS_VALUE);
|
|
923
|
+
// A CertReqTemplate keySpec states a key requirement as an AlgorithmIdentifier (RFC 9480 sec. 2.16),
|
|
924
|
+
// which is the shared sub-schema every format already reads one with: `SEQUENCE { algorithm OID,
|
|
925
|
+
// parameters ANY OPTIONAL }` and nothing after it. Reading it through the same definition is what
|
|
926
|
+
// keeps a third field from passing as a well-formed one.
|
|
927
|
+
var readAlgorithmIdentifier = _valueParser("AlgorithmIdentifier", ALG_ID);
|
|
928
|
+
// A bare Name, so a caller-encoded issuer can be read back into the RDN sequence that guard-name's
|
|
929
|
+
// canonical comparison takes. pki.cmp.session binds a returned CRL to the issuer it asked for.
|
|
930
|
+
var readName = _valueParser("Name", pkix.name(NS));
|
|
931
|
+
|
|
839
932
|
/**
|
|
840
933
|
* @primitive pki.schema.cmp.parse
|
|
841
934
|
* @signature pki.schema.cmp.parse(input) -> pkiMessage
|
|
@@ -970,4 +1063,11 @@ module.exports = {
|
|
|
970
1063
|
pemDecode: pemDecode,
|
|
971
1064
|
pemEncode: pemEncode,
|
|
972
1065
|
matches: matches,
|
|
1066
|
+
// @internal -- the RFC 9483 sec. 4.3 support-message values, read strictly from a genp.
|
|
1067
|
+
readCaCerts: readCaCerts,
|
|
1068
|
+
readRootCaKeyUpdate: readRootCaKeyUpdate,
|
|
1069
|
+
readCertReqTemplate: readCertReqTemplate,
|
|
1070
|
+
readCrls: readCrls,
|
|
1071
|
+
readAlgorithmIdentifier: readAlgorithmIdentifier,
|
|
1072
|
+
readName: readName,
|
|
973
1073
|
};
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:af65ad7a-ef35-4bc4-8aef-24a43cb8ece0",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-08-
|
|
8
|
+
"timestamp": "2026-08-23T00:05:35.940Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/pki@0.5.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.5.26",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.5.
|
|
25
|
+
"version": "0.5.26",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/pki@0.5.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.5.26",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/pki@0.5.
|
|
57
|
+
"ref": "@blamejs/pki@0.5.26",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|