@blamejs/pki 0.1.18 → 0.1.19
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 +22 -0
- package/README.md +2 -1
- package/lib/framework-error.js +7 -0
- package/lib/oid.js +15 -0
- package/lib/path-validate.js +4 -0
- package/lib/schema-all.js +20 -2
- package/lib/schema-attrcert.js +6 -6
- package/lib/schema-cmp.js +928 -0
- package/lib/schema-cms.js +10 -13
- package/lib/schema-crl.js +2 -2
- package/lib/schema-crmf.js +62 -28
- package/lib/schema-csr.js +4 -4
- package/lib/schema-engine.js +46 -4
- package/lib/schema-ocsp.js +8 -10
- package/lib/schema-pkcs12.js +7 -8
- package/lib/schema-pkcs8.js +6 -7
- package/lib/schema-pkix.js +27 -12
- package/lib/schema-tsp.js +10 -17
- package/lib/schema-x509.js +3 -3
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/schema-cms.js
CHANGED
|
@@ -132,12 +132,11 @@ function rawElement(item) {
|
|
|
132
132
|
// [3], a v2AttrCert [2], a v1AttrCert [1]; a RevocationInfoChoice `other` is [1]),
|
|
133
133
|
// the SignerInfo versions, and whether the content type is id-data.
|
|
134
134
|
function _expectedSignedDataVersion(certificates, crls, signerInfos, eContentType) {
|
|
135
|
-
function
|
|
136
|
-
var
|
|
137
|
-
var otherCrl = crls.some(function (c) { return ctx(c, 1); });
|
|
135
|
+
var otherCert = certificates.some(function (c) { return schema.isContext(c, 3); });
|
|
136
|
+
var otherCrl = crls.some(function (c) { return schema.isContext(c, 1); });
|
|
138
137
|
if (otherCert || otherCrl) return 5;
|
|
139
|
-
if (certificates.some(function (c) { return
|
|
140
|
-
if (certificates.some(function (c) { return
|
|
138
|
+
if (certificates.some(function (c) { return schema.isContext(c, 2); })) return 4; // v2AttrCert [2]
|
|
139
|
+
if (certificates.some(function (c) { return schema.isContext(c, 1); }) || // v1AttrCert [1]
|
|
141
140
|
signerInfos.some(function (si) { return si.version === 3; }) ||
|
|
142
141
|
eContentType !== OID_DATA) return 3;
|
|
143
142
|
return 1;
|
|
@@ -528,12 +527,11 @@ var ORIGINATOR_INFO = schema.seq([
|
|
|
528
527
|
// [2]; a crl `other` is [1]; a pwri or ori forces v3; all-ktri-IAS with no
|
|
529
528
|
// originatorInfo/unprotectedAttrs is v0; everything else v2).
|
|
530
529
|
function _expectedEnvelopedDataVersion(originatorInfo, recipientInfos, hasUnprotectedAttrs) {
|
|
531
|
-
function ctx(el, n) { return el.tagClass === "context" && el.tagNumber === n; }
|
|
532
530
|
var hasOrig = !!originatorInfo;
|
|
533
531
|
var certs = hasOrig ? originatorInfo.certs : [];
|
|
534
532
|
var crls = hasOrig ? originatorInfo.crls : [];
|
|
535
|
-
if (hasOrig && (certs.some(function (c) { return
|
|
536
|
-
if ((hasOrig && certs.some(function (c) { return
|
|
533
|
+
if (hasOrig && (certs.some(function (c) { return schema.isContext(c, 3); }) || crls.some(function (c) { return schema.isContext(c, 1); }))) return 4;
|
|
534
|
+
if ((hasOrig && certs.some(function (c) { return schema.isContext(c, 2); })) ||
|
|
537
535
|
recipientInfos.some(function (r) { return r.type === "pwri" || r.type === "ori"; })) return 3;
|
|
538
536
|
if (!hasOrig && !hasUnprotectedAttrs &&
|
|
539
537
|
recipientInfos.every(function (r) { return r.type === "ktri" && r.ridType === "issuerAndSerialNumber"; })) return 0;
|
|
@@ -676,11 +674,10 @@ function pemEncode(der, label) { return pkix.pemEncode(der, label || "CMS", PemE
|
|
|
676
674
|
// registry order.
|
|
677
675
|
function matches(root) {
|
|
678
676
|
var TAGS = asn1.TAGS;
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
if (!k
|
|
682
|
-
if (!(k[
|
|
683
|
-
if (!(k[1].tagClass === "context" && k[1].tagNumber === 0 && k[1].children)) return false;
|
|
677
|
+
var k = pkix.rootSequenceChildren(root, 2, 2);
|
|
678
|
+
if (!k) return false;
|
|
679
|
+
if (!schema.isUniversal(k[0], TAGS.OBJECT_IDENTIFIER)) return false;
|
|
680
|
+
if (!(schema.isContext(k[1], 0) && k[1].children)) return false;
|
|
684
681
|
return true;
|
|
685
682
|
}
|
|
686
683
|
|
package/lib/schema-crl.js
CHANGED
|
@@ -238,10 +238,10 @@ function matches(root) {
|
|
|
238
238
|
// version, then signature (SEQUENCE) + issuer (SEQUENCE); the next element is
|
|
239
239
|
// thisUpdate (Time) for a CRL, Validity (SEQUENCE) for a certificate.
|
|
240
240
|
var i = 0;
|
|
241
|
-
if (
|
|
241
|
+
if (schema.isUniversal(tbs.children[i], TAGS.INTEGER)) i++;
|
|
242
242
|
i += 2; // signature + issuer
|
|
243
243
|
var pos = tbs.children[i];
|
|
244
|
-
return
|
|
244
|
+
return schema.isUniversalOneOf(pos, [TAGS.UTC_TIME, TAGS.GENERALIZED_TIME]);
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
module.exports = {
|
package/lib/schema-crmf.js
CHANGED
|
@@ -296,30 +296,27 @@ var CERT_TEMPLATE = schema.seq([
|
|
|
296
296
|
assert: "sequence", code: "crmf/bad-cert-template", what: "CertTemplate",
|
|
297
297
|
build: function (m, ctx) {
|
|
298
298
|
var f = m.fields;
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
// CA
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
var caAssigned = ["serialNumber", "signingAlg", "issuerUID", "subjectUID"];
|
|
307
|
-
for (var i = 0; i < caAssigned.length; i++) {
|
|
308
|
-
if (f[caAssigned[i]].present) {
|
|
309
|
-
throw ctx.E("crmf/bad-cert-template", "CertTemplate " + caAssigned[i] + " MUST be omitted — it is CA-assigned or deprecated (RFC 4211 §5)");
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
// version MUST be 2 (v3) when supplied; SHOULD be omitted (RFC 4211 §5).
|
|
299
|
+
// version MUST be 2 (v3) when supplied; SHOULD be omitted (RFC 4211 §5). This
|
|
300
|
+
// is a structural CertTemplate rule (it holds wherever the template appears);
|
|
301
|
+
// the CA-assigned-field omission rule is REQUEST-context-specific and lives in
|
|
302
|
+
// CERT_REQUEST.build, because the same CertTemplate structure identifies an
|
|
303
|
+
// existing certificate — serialNumber and issuer present — inside a CMP
|
|
304
|
+
// RevDetails (RFC 9810 §5.3.9). Every field is surfaced so both the request
|
|
305
|
+
// omission check and the revocation consumer read them off one result.
|
|
313
306
|
if (f.version.present && f.version.value !== 2n) {
|
|
314
307
|
throw ctx.E("crmf/bad-version", "CertTemplate version MUST be 2 (v3) if supplied (RFC 4211 §5)");
|
|
315
308
|
}
|
|
316
309
|
return {
|
|
317
|
-
version:
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
310
|
+
version: f.version.present ? f.version.value : null,
|
|
311
|
+
serialNumber: f.serialNumber.present ? f.serialNumber.value : null,
|
|
312
|
+
signingAlg: f.signingAlg.present ? f.signingAlg.value.result : null,
|
|
313
|
+
issuer: f.issuer.present ? f.issuer.value : null,
|
|
314
|
+
validity: f.validity.present ? f.validity.value.result : null,
|
|
315
|
+
subject: f.subject.present ? f.subject.value : null,
|
|
316
|
+
publicKey: f.publicKey.present ? f.publicKey.value.result : null,
|
|
317
|
+
issuerUID: f.issuerUID.present ? f.issuerUID.value : null,
|
|
318
|
+
subjectUID: f.subjectUID.present ? f.subjectUID.value : null,
|
|
319
|
+
extensions: f.extensions.present ? f.extensions.value.result : null,
|
|
323
320
|
};
|
|
324
321
|
},
|
|
325
322
|
});
|
|
@@ -334,11 +331,32 @@ var CERT_REQUEST = schema.seq([
|
|
|
334
331
|
schema.optional("controls", CONTROLS, { whenUniversal: [TAGS.SEQUENCE] }),
|
|
335
332
|
], {
|
|
336
333
|
assert: "sequence", code: "crmf/bad-cert-request", what: "CertRequest",
|
|
337
|
-
build: function (m) {
|
|
334
|
+
build: function (m, ctx) {
|
|
335
|
+
var tpl = m.fields.certTemplate.value.result;
|
|
336
|
+
// RFC 4211 §5 — serialNumber, signingAlg, issuerUID, and subjectUID MUST be
|
|
337
|
+
// omitted from a REQUEST's CertTemplate: serialNumber and signingAlg are
|
|
338
|
+
// assigned by the CA, and the UID pair is deprecated. A requester must not
|
|
339
|
+
// dictate a CA-assigned value (a requester-chosen serialNumber is a real
|
|
340
|
+
// hazard), so a request template that sets any of them is rejected
|
|
341
|
+
// fail-closed. The check lives HERE, not in CERT_TEMPLATE, because the same
|
|
342
|
+
// structure legitimately carries serialNumber/issuer in a CMP RevDetails.
|
|
343
|
+
// A cross-certification request (ccr) is the ONE exception: RFC 9810 Appendix
|
|
344
|
+
// D.6 profiles its template with signingAlg PRESENT (the requesting CA states
|
|
345
|
+
// the algorithm it wants the cross-certificate signed with), so the caller
|
|
346
|
+
// relaxes that single field via ctx.allowSigningAlg; serialNumber and the UID
|
|
347
|
+
// pair stay forbidden.
|
|
348
|
+
var caAssigned = ctx.allowSigningAlg
|
|
349
|
+
? ["serialNumber", "issuerUID", "subjectUID"]
|
|
350
|
+
: ["serialNumber", "signingAlg", "issuerUID", "subjectUID"];
|
|
351
|
+
for (var i = 0; i < caAssigned.length; i++) {
|
|
352
|
+
if (tpl[caAssigned[i]] !== null) {
|
|
353
|
+
throw ctx.E("crmf/bad-cert-template", "CertTemplate " + caAssigned[i] + " MUST be omitted — it is CA-assigned or deprecated (RFC 4211 §5)");
|
|
354
|
+
}
|
|
355
|
+
}
|
|
338
356
|
return {
|
|
339
357
|
certReqId: m.fields.certReqId.value,
|
|
340
358
|
certReqIdHex: m.fields.certReqId.node.content.toString("hex"),
|
|
341
|
-
certTemplate:
|
|
359
|
+
certTemplate: tpl,
|
|
342
360
|
controls: m.fields.controls.present ? m.fields.controls.value.result : null,
|
|
343
361
|
certReqBytes: m.node.bytes, // the exact CertRequest TLV the POP signature covers (§4.1)
|
|
344
362
|
};
|
|
@@ -466,20 +484,36 @@ function pemDecode(text, label) { return pkix.pemDecode(text, label || null, Pem
|
|
|
466
484
|
// ocsp-request: an OCSPRequest's tbsRequest never leads with the INTEGER-then-
|
|
467
485
|
// SEQUENCE pair (its children[0] is a context [0] version or a requestList).
|
|
468
486
|
function matches(root) {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
487
|
+
var k = pkix.rootSequenceChildren(root, 1);
|
|
488
|
+
if (!k) return false;
|
|
489
|
+
var msg = k[0];
|
|
490
|
+
if (!(msg.children && schema.isUniversal(msg, TAGS.SEQUENCE) && msg.children.length >= 1)) return false;
|
|
472
491
|
var certReq = msg.children[0];
|
|
473
|
-
if (!certReq.children
|
|
492
|
+
if (!(certReq.children && schema.isUniversal(certReq, TAGS.SEQUENCE) && certReq.children.length >= 2)) return false;
|
|
474
493
|
var id = certReq.children[0], tpl = certReq.children[1];
|
|
475
|
-
return
|
|
476
|
-
|
|
494
|
+
return schema.isUniversal(id, TAGS.INTEGER) &&
|
|
495
|
+
schema.isUniversal(tpl, TAGS.SEQUENCE) && !!tpl.children;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Validate a bare, already-decoded CertReqMessages / CertTemplate node — for a
|
|
499
|
+
// composer that carries these structures inside its own envelope (an RFC 9810
|
|
500
|
+
// CMP PKIBody's ir/cr/kur/krr/ccr arms; a RevDetails.certDetails). NS-bound to
|
|
501
|
+
// crmf so an interior failure throws CrmfError with a crmf/* code, never the
|
|
502
|
+
// composer's class wearing a foreign code. Same contract as the CMS walkers.
|
|
503
|
+
// opts.allowSigningAlg relaxes the request-template signingAlg-omission rule for
|
|
504
|
+
// the CMP cross-certification (ccr) arm only (RFC 9810 Appendix D.6).
|
|
505
|
+
function walkCertReqMessages(node, opts) {
|
|
506
|
+
var ctx = (opts && opts.allowSigningAlg) ? Object.assign({}, NS, { allowSigningAlg: true }) : NS;
|
|
507
|
+
return schema.walk(CERT_REQ_MESSAGES, node, ctx).result;
|
|
477
508
|
}
|
|
509
|
+
function walkCertTemplate(node) { return schema.walk(CERT_TEMPLATE, node, NS).result; }
|
|
478
510
|
|
|
479
511
|
module.exports = {
|
|
480
512
|
parse: parse,
|
|
481
513
|
pemDecode: pemDecode,
|
|
482
514
|
matches: matches,
|
|
515
|
+
walkCertReqMessages: walkCertReqMessages,
|
|
516
|
+
walkCertTemplate: walkCertTemplate,
|
|
483
517
|
// The top schema, exposed (not on the curated pki.schema.crmf surface) so a
|
|
484
518
|
// round-trip test can prove schema.encode(schema, v) -> decode -> parse recovers
|
|
485
519
|
// v — the invariant that structurally validates the IMPLICIT/EXPLICIT tag handling.
|
package/lib/schema-csr.js
CHANGED
|
@@ -171,10 +171,10 @@ function matches(root) {
|
|
|
171
171
|
if (!cri) return false;
|
|
172
172
|
var k = cri.children;
|
|
173
173
|
if (k.length !== 4) return false;
|
|
174
|
-
return
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
174
|
+
return schema.isUniversal(k[0], TAGS.INTEGER) &&
|
|
175
|
+
schema.isUniversal(k[1], TAGS.SEQUENCE) &&
|
|
176
|
+
schema.isUniversal(k[2], TAGS.SEQUENCE) &&
|
|
177
|
+
schema.isContext(k[3], 0);
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
module.exports = {
|
package/lib/schema-engine.js
CHANGED
|
@@ -160,10 +160,10 @@ function optional(name, schema, opts) {
|
|
|
160
160
|
var match = opts.whenAny
|
|
161
161
|
? function () { return true; }
|
|
162
162
|
: opts.whenUniversal
|
|
163
|
-
? function (n) { return n
|
|
163
|
+
? function (n) { return isUniversalOneOf(n, opts.whenUniversal); }
|
|
164
164
|
: opts.tags
|
|
165
|
-
? function (n) { return n
|
|
166
|
-
: function (n) { return n
|
|
165
|
+
? function (n) { return isContextOneOf(n, opts.tags); }
|
|
166
|
+
: function (n) { return isContext(n, opts.tag); };
|
|
167
167
|
return { fkind: "optional", name: name, schema: schema, tag: opts.tag, match: match,
|
|
168
168
|
explicit: !!opts.explicit, emptyCode: opts.emptyCode, hasDefault: ("default" in opts), def: opts.default };
|
|
169
169
|
}
|
|
@@ -587,6 +587,45 @@ function embeddedDer(schema, bytes, ctx, opts) {
|
|
|
587
587
|
return walk(schema, node, ctx);
|
|
588
588
|
}
|
|
589
589
|
|
|
590
|
+
// X.690 §11.2.2 — a NamedBitList BIT STRING (KeyUsage, PKIFailureInfo, and the
|
|
591
|
+
// like) MUST drop every trailing zero bit under DER, giving one canonical
|
|
592
|
+
// encoding per value: an empty value carries 0 unused bits; a non-empty value's
|
|
593
|
+
// last content octet is non-zero AND its lowest USED bit (at position
|
|
594
|
+
// `unusedBits`) is set. `fail(message)` throws the caller's typed domain error,
|
|
595
|
+
// so the reject code stays per-format. Centralized so no format re-derives — and
|
|
596
|
+
// silently diverges on — the minimal-encoding rule (a DER-canonicalization
|
|
597
|
+
// bypass class); see the named-bitlist-minimal-encoding-inlined gate.
|
|
598
|
+
// ASN.1 node tag predicates — the per-node `tagClass`/`tagNumber` test every
|
|
599
|
+
// format's matches() detector (and the engine's own optional/choice walkers)
|
|
600
|
+
// would otherwise re-inline. Centralized so a detector composes one predicate
|
|
601
|
+
// instead of hand-rolling the comparison (and drifting); see the
|
|
602
|
+
// detector-reinlines-root-tag-guard gate.
|
|
603
|
+
function isUniversal(node, tagNumber) {
|
|
604
|
+
return !!node && node.tagClass === "universal" && node.tagNumber === tagNumber;
|
|
605
|
+
}
|
|
606
|
+
function isContext(node, tagNumber) {
|
|
607
|
+
return !!node && node.tagClass === "context" && node.tagNumber === tagNumber;
|
|
608
|
+
}
|
|
609
|
+
function isUniversalOneOf(node, tagNumbers) {
|
|
610
|
+
return !!node && node.tagClass === "universal" && tagNumbers.indexOf(node.tagNumber) !== -1;
|
|
611
|
+
}
|
|
612
|
+
function isContextOneOf(node, tagNumbers) {
|
|
613
|
+
return !!node && node.tagClass === "context" && tagNumbers.indexOf(node.tagNumber) !== -1;
|
|
614
|
+
}
|
|
615
|
+
function isContextInRange(node, min, max) {
|
|
616
|
+
return !!node && node.tagClass === "context" && node.tagNumber >= min && node.tagNumber <= max;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function assertMinimalNamedBits(unusedBits, bytes, fail) {
|
|
620
|
+
if (bytes.length === 0) {
|
|
621
|
+
if (unusedBits !== 0) fail("an empty NamedBitList must encode with 0 unused bits (X.690 §11.2.2)");
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
var last = bytes[bytes.length - 1];
|
|
625
|
+
if (last === 0) fail("a NamedBitList must not carry a trailing all-zero octet (X.690 §11.2.2)");
|
|
626
|
+
if (((last >> unusedBits) & 1) !== 1) fail("a NamedBitList must drop all trailing zero bits (X.690 §11.2.2)");
|
|
627
|
+
}
|
|
628
|
+
|
|
590
629
|
module.exports = {
|
|
591
630
|
// structural
|
|
592
631
|
seq: seq, field: field, optional: optional, explicit: explicit, trailing: trailing,
|
|
@@ -597,5 +636,8 @@ module.exports = {
|
|
|
597
636
|
implicitNull: implicitNull, implicitInteger: implicitInteger,
|
|
598
637
|
any: any, decode: decode, time: time,
|
|
599
638
|
// engine
|
|
600
|
-
walk: walk, encode: encode, embeddedDer: embeddedDer,
|
|
639
|
+
walk: walk, encode: encode, embeddedDer: embeddedDer, assertMinimalNamedBits: assertMinimalNamedBits,
|
|
640
|
+
// node tag predicates
|
|
641
|
+
isUniversal: isUniversal, isContext: isContext,
|
|
642
|
+
isUniversalOneOf: isUniversalOneOf, isContextOneOf: isContextOneOf, isContextInRange: isContextInRange,
|
|
601
643
|
};
|
package/lib/schema-ocsp.js
CHANGED
|
@@ -489,11 +489,10 @@ function pemDecode(text, label) { return pkix.pemDecode(text, label || "OCSP RES
|
|
|
489
489
|
// unconditionally exclusive.
|
|
490
490
|
function matchesResponse(root) {
|
|
491
491
|
var T = asn1.TAGS;
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
if (k
|
|
495
|
-
if (
|
|
496
|
-
if (k.length === 2 && !(k[1].tagClass === "context" && k[1].tagNumber === 0 && k[1].children)) return false;
|
|
492
|
+
var k = pkix.rootSequenceChildren(root, 1, 2);
|
|
493
|
+
if (!k) return false;
|
|
494
|
+
if (!schema.isUniversal(k[0], T.ENUMERATED)) return false;
|
|
495
|
+
if (k.length === 2 && !(schema.isContext(k[1], 0) && k[1].children)) return false;
|
|
497
496
|
return true;
|
|
498
497
|
}
|
|
499
498
|
|
|
@@ -503,11 +502,10 @@ function matchesResponse(root) {
|
|
|
503
502
|
// signed-envelope trio), while an OCSPRequest is 1-2 — so it is excluded by arity.
|
|
504
503
|
function matchesRequest(root) {
|
|
505
504
|
var T = asn1.TAGS;
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
if (k.
|
|
509
|
-
if (
|
|
510
|
-
if (k.length === 2 && !(k[1].tagClass === "context" && k[1].tagNumber === 0 && k[1].children)) return false;
|
|
505
|
+
var k = pkix.rootSequenceChildren(root, 1, 2);
|
|
506
|
+
if (!k) return false;
|
|
507
|
+
if (!(schema.isUniversal(k[0], T.SEQUENCE) && k[0].children)) return false;
|
|
508
|
+
if (k.length === 2 && !(schema.isContext(k[1], 0) && k[1].children)) return false;
|
|
511
509
|
return true;
|
|
512
510
|
}
|
|
513
511
|
|
package/lib/schema-pkcs12.js
CHANGED
|
@@ -595,15 +595,14 @@ function pemEncode(der, label) { return pkix.pemEncode(der, label || "PKCS12", P
|
|
|
595
595
|
// a shape a PrivateKeyInfo's AlgorithmIdentifier never presents) and
|
|
596
596
|
// children[2] (a SEQUENCE MacData or absent, never PKCS#8's OCTET STRING).
|
|
597
597
|
function matches(root) {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
if (!k
|
|
601
|
-
if (!(k[0].tagClass === "universal" && k[0].tagNumber === TAGS.INTEGER)) return false;
|
|
598
|
+
var k = pkix.rootSequenceChildren(root, 2, 3);
|
|
599
|
+
if (!k) return false;
|
|
600
|
+
if (!schema.isUniversal(k[0], TAGS.INTEGER)) return false;
|
|
602
601
|
var ci = k[1];
|
|
603
|
-
if (!ci.children
|
|
604
|
-
if (!(ci.children[0]
|
|
605
|
-
if (!(
|
|
606
|
-
if (k.length === 3 && !(
|
|
602
|
+
if (!(ci.children && schema.isUniversal(ci, TAGS.SEQUENCE) && ci.children.length === 2)) return false;
|
|
603
|
+
if (!schema.isUniversal(ci.children[0], TAGS.OBJECT_IDENTIFIER)) return false;
|
|
604
|
+
if (!(schema.isContext(ci.children[1], 0) && ci.children[1].children)) return false;
|
|
605
|
+
if (k.length === 3 && !(schema.isUniversal(k[2], TAGS.SEQUENCE) && k[2].children)) return false;
|
|
607
606
|
return true;
|
|
608
607
|
}
|
|
609
608
|
|
package/lib/schema-pkcs8.js
CHANGED
|
@@ -179,15 +179,14 @@ function pemEncode(der, label) { return pkix.pemEncode(der, label || "PRIVATE KE
|
|
|
179
179
|
// order.
|
|
180
180
|
function matches(root) {
|
|
181
181
|
var TAGS = asn1.TAGS;
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (!k
|
|
185
|
-
if (!(k[
|
|
186
|
-
if (!(k[
|
|
187
|
-
if (!(k[2].tagClass === "universal" && k[2].tagNumber === TAGS.OCTET_STRING)) return false;
|
|
182
|
+
var k = pkix.rootSequenceChildren(root, 3, 5);
|
|
183
|
+
if (!k) return false;
|
|
184
|
+
if (!schema.isUniversal(k[0], TAGS.INTEGER)) return false;
|
|
185
|
+
if (!schema.isUniversal(k[1], TAGS.SEQUENCE)) return false;
|
|
186
|
+
if (!schema.isUniversal(k[2], TAGS.OCTET_STRING)) return false;
|
|
188
187
|
var last = -1;
|
|
189
188
|
for (var i = 3; i < k.length; i++) {
|
|
190
|
-
if (k[i]
|
|
189
|
+
if (!schema.isContextInRange(k[i], 0, 1) || k[i].tagNumber <= last) return false;
|
|
191
190
|
last = k[i].tagNumber;
|
|
192
191
|
}
|
|
193
192
|
return true;
|
package/lib/schema-pkix.js
CHANGED
|
@@ -392,7 +392,10 @@ function certExtensionDecoders(ns) {
|
|
|
392
392
|
if (kids[i] && kids[i].tagClass === "universal" && kids[i].tagNumber === _T.INTEGER) {
|
|
393
393
|
if (!cA) throw ns.E(C, "BasicConstraints pathLenConstraint is only permitted when cA is TRUE (RFC 5280 §4.2.1.9)");
|
|
394
394
|
var pl = readInt(kids[i], C, "pathLenConstraint");
|
|
395
|
-
|
|
395
|
+
// Bounded before Number() narrowing — a value past the safe-integer range
|
|
396
|
+
// would round silently and be compared as a path-length ceiling, so it is
|
|
397
|
+
// exact-or-rejected (the same rule the RSASSA-PSS/PKCS#12 counters follow).
|
|
398
|
+
if (pl < 0n || pl > 2147483647n) throw ns.E(C, "BasicConstraints pathLenConstraint must be a non-negative integer within range");
|
|
396
399
|
pathLen = Number(pl); i++;
|
|
397
400
|
}
|
|
398
401
|
if (i !== kids.length) throw ns.E(C, "BasicConstraints has unexpected trailing fields");
|
|
@@ -413,13 +416,8 @@ function certExtensionDecoders(ns) {
|
|
|
413
416
|
var anyBit = false;
|
|
414
417
|
for (var z = 0; z < bs.bytes.length; z++) { if (bs.bytes[z] !== 0) { anyBit = true; break; } }
|
|
415
418
|
if (!anyBit) throw ns.E(C, "KeyUsage must assert at least one bit (RFC 5280 §4.2.1.3)");
|
|
416
|
-
// KeyUsage is a NamedBitList
|
|
417
|
-
|
|
418
|
-
// (at position unusedBits) must be set — one canonical encoding per value.
|
|
419
|
-
var last = bs.bytes[bs.bytes.length - 1];
|
|
420
|
-
if (last === 0 || ((last >> bs.unusedBits) & 1) !== 1) {
|
|
421
|
-
throw ns.E(C, "KeyUsage must use the minimal DER NamedBitList encoding (no trailing zero bits, X.690 §11.2.2)");
|
|
422
|
-
}
|
|
419
|
+
// KeyUsage is a NamedBitList — enforce the shared X.690 §11.2.2 minimal-DER rule.
|
|
420
|
+
schema.assertMinimalNamedBits(bs.unusedBits, bs.bytes, function (m) { throw ns.E(C, m); });
|
|
423
421
|
var out = {};
|
|
424
422
|
KU_BITS.forEach(function (nm, bit) {
|
|
425
423
|
var byte = bit >> 3, mask = 0x80 >> (bit & 7);
|
|
@@ -532,7 +530,9 @@ function certExtensionDecoders(ns) {
|
|
|
532
530
|
pcLastTag = k.tagNumber;
|
|
533
531
|
var v;
|
|
534
532
|
try { v = asn1.read.integerImplicit(k, k.tagNumber); } catch (e) { throw ns.E(C, "PolicyConstraints field must be an INTEGER", e); }
|
|
535
|
-
|
|
533
|
+
// Bounded before Number() narrowing (a skip count past the safe-integer
|
|
534
|
+
// range would round silently) — exact-or-rejected.
|
|
535
|
+
if (v < 0n || v > 2147483647n) throw ns.E(C, "PolicyConstraints skip count must be a non-negative integer within range");
|
|
536
536
|
if (k.tagNumber === 0) rep = Number(v);
|
|
537
537
|
else if (k.tagNumber === 1) ipm = Number(v);
|
|
538
538
|
else throw ns.E(C, "PolicyConstraints has an unexpected field [" + k.tagNumber + "]");
|
|
@@ -546,7 +546,9 @@ function certExtensionDecoders(ns) {
|
|
|
546
546
|
var n = decodeTop(buf, C, "InhibitAnyPolicy");
|
|
547
547
|
if (n.tagClass !== "universal" || n.tagNumber !== _T.INTEGER) throw ns.E(C, "InhibitAnyPolicy must be an INTEGER (RFC 5280 §4.2.1.14)");
|
|
548
548
|
var v = readInt(n, C, "InhibitAnyPolicy");
|
|
549
|
-
|
|
549
|
+
// Bounded before Number() narrowing (a skip count past the safe-integer
|
|
550
|
+
// range would round silently) — exact-or-rejected.
|
|
551
|
+
if (v < 0n || v > 2147483647n) throw ns.E(C, "InhibitAnyPolicy skip count must be a non-negative integer within range");
|
|
550
552
|
return Number(v);
|
|
551
553
|
}
|
|
552
554
|
|
|
@@ -725,13 +727,25 @@ function attribute(ns) {
|
|
|
725
727
|
// detectors cannot drift on it (the CRL detector historically omitted the
|
|
726
728
|
// tbs-is-universal check this recovers).
|
|
727
729
|
function signedEnvelopeTbs(root) {
|
|
728
|
-
if (!
|
|
730
|
+
if (!schema.isUniversal(root, asn1.TAGS.SEQUENCE)) return null;
|
|
729
731
|
if (!root.children || root.children.length !== 3) return null;
|
|
730
732
|
var tbs = root.children[0];
|
|
731
|
-
if (!tbs.children ||
|
|
733
|
+
if (!tbs.children || !schema.isUniversal(tbs, asn1.TAGS.SEQUENCE)) return null;
|
|
732
734
|
return tbs;
|
|
733
735
|
}
|
|
734
736
|
|
|
737
|
+
// rootSequenceChildren(root, minLen, maxLen) — the shared root-shape guard the
|
|
738
|
+
// format matches() detectors re-inline: returns root.children when root is a
|
|
739
|
+
// NON-EMPTY universal SEQUENCE whose child count is within [minLen, maxLen]
|
|
740
|
+
// (maxLen optional/Infinity), else null. Sibling of signedEnvelopeTbs.
|
|
741
|
+
function rootSequenceChildren(root, minLen, maxLen) {
|
|
742
|
+
if (!schema.isUniversal(root, asn1.TAGS.SEQUENCE) || !root.children) return null;
|
|
743
|
+
var n = root.children.length;
|
|
744
|
+
if (n < (minLen || 0)) return null;
|
|
745
|
+
if (maxLen != null && n > maxLen) return null;
|
|
746
|
+
return root.children;
|
|
747
|
+
}
|
|
748
|
+
|
|
735
749
|
// Every format's `parse` is the shared runParse bound to that format's identity
|
|
736
750
|
// (PEM label, error class, error-code prefix, top-level schema). This returns the
|
|
737
751
|
// bound parser so a format declares its configuration once and never re-writes the
|
|
@@ -785,6 +799,7 @@ module.exports = {
|
|
|
785
799
|
spki: spki,
|
|
786
800
|
makeParser: makeParser,
|
|
787
801
|
signedEnvelopeTbs: signedEnvelopeTbs,
|
|
802
|
+
rootSequenceChildren: rootSequenceChildren,
|
|
788
803
|
signedEnvelope: signedEnvelope,
|
|
789
804
|
attrValueToString: attrValueToString,
|
|
790
805
|
attributeTypeAndValue: attributeTypeAndValue,
|
package/lib/schema-tsp.js
CHANGED
|
@@ -217,13 +217,7 @@ var PKI_STATUS_INFO = schema.seq([
|
|
|
217
217
|
// declared unusedBits must sit exactly below the lowest set bit of the last octet (so
|
|
218
218
|
// the encoding is minimal). The empty value encodes as 0 content octets, 0 unusedBits.
|
|
219
219
|
function _assertMinimalNamedBits(unusedBits, bytes) {
|
|
220
|
-
|
|
221
|
-
if (unusedBits !== 0) throw NS.E("tsp/bad-failinfo", "an empty PKIFailureInfo must encode with 0 unused bits (X.690 §11.2.2)");
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
var last = bytes[bytes.length - 1];
|
|
225
|
-
if (last === 0) throw NS.E("tsp/bad-failinfo", "PKIFailureInfo NamedBitList must not have a trailing all-zero octet (X.690 §11.2.2)");
|
|
226
|
-
if (((last >> unusedBits) & 1) !== 1) throw NS.E("tsp/bad-failinfo", "PKIFailureInfo NamedBitList must have all trailing zero bits removed (X.690 §11.2.2)");
|
|
220
|
+
schema.assertMinimalNamedBits(unusedBits, bytes, function (m) { throw NS.E("tsp/bad-failinfo", m); });
|
|
227
221
|
}
|
|
228
222
|
|
|
229
223
|
// Decode the set NamedBitList bits to their RFC 3161 names (bit 0 = MSB of byte 0).
|
|
@@ -277,7 +271,7 @@ var TIME_STAMP_RESP = schema.seq([
|
|
|
277
271
|
* @primitive pki.schema.tsp.parseTstInfo
|
|
278
272
|
* @signature pki.schema.tsp.parseTstInfo(input) -> tstInfo
|
|
279
273
|
* @since 0.1.13
|
|
280
|
-
* @status
|
|
274
|
+
* @status stable
|
|
281
275
|
* @spec RFC 3161
|
|
282
276
|
* @related pki.schema.tsp.parseToken, pki.schema.tsp.parse
|
|
283
277
|
*
|
|
@@ -299,7 +293,7 @@ var parseTstInfo = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorCl
|
|
|
299
293
|
* @primitive pki.schema.tsp.parse
|
|
300
294
|
* @signature pki.schema.tsp.parse(input) -> timeStampResp
|
|
301
295
|
* @since 0.1.13
|
|
302
|
-
* @status
|
|
296
|
+
* @status stable
|
|
303
297
|
* @spec RFC 3161
|
|
304
298
|
* @related pki.schema.tsp.parseToken, pki.schema.parse
|
|
305
299
|
*
|
|
@@ -320,7 +314,7 @@ var parse = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorClass: Ts
|
|
|
320
314
|
* @primitive pki.schema.tsp.parseToken
|
|
321
315
|
* @signature pki.schema.tsp.parseToken(input) -> tstInfo
|
|
322
316
|
* @since 0.1.13
|
|
323
|
-
* @status
|
|
317
|
+
* @status stable
|
|
324
318
|
* @spec RFC 3161, RFC 5652
|
|
325
319
|
* @related pki.schema.tsp.parse, pki.schema.cms.parse
|
|
326
320
|
*
|
|
@@ -374,7 +368,7 @@ function parseToken(input) {
|
|
|
374
368
|
* @primitive pki.schema.tsp.pemDecode
|
|
375
369
|
* @signature pki.schema.tsp.pemDecode(text, label?) -> Buffer
|
|
376
370
|
* @since 0.1.13
|
|
377
|
-
* @status
|
|
371
|
+
* @status stable
|
|
378
372
|
* @spec RFC 7468, RFC 3161
|
|
379
373
|
* @related pki.schema.tsp.parse
|
|
380
374
|
*
|
|
@@ -392,13 +386,12 @@ function pemDecode(text, label) { return pkix.pemDecode(text, label || null, Pem
|
|
|
392
386
|
// CMS ContentInfo, the INTEGER-first PKCS#8 key, and the exactly-3 signed-envelope
|
|
393
387
|
// trio, so it detects unambiguously regardless of registry order.
|
|
394
388
|
function matches(root) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if (k.length < 1 || k.length > 2) return false;
|
|
389
|
+
var k = pkix.rootSequenceChildren(root, 1, 2);
|
|
390
|
+
if (!k) return false;
|
|
398
391
|
var si = k[0];
|
|
399
|
-
if (!(
|
|
400
|
-
if (!(si.children[0]
|
|
401
|
-
if (k.length === 2 && !(k[1]
|
|
392
|
+
if (!(schema.isUniversal(si, TAGS.SEQUENCE) && si.children && si.children.length >= 1)) return false;
|
|
393
|
+
if (!schema.isUniversal(si.children[0], TAGS.INTEGER)) return false;
|
|
394
|
+
if (k.length === 2 && !schema.isUniversal(k[1], TAGS.SEQUENCE)) return false;
|
|
402
395
|
return true;
|
|
403
396
|
}
|
|
404
397
|
|
package/lib/schema-x509.js
CHANGED
|
@@ -220,12 +220,12 @@ function matches(root) {
|
|
|
220
220
|
var tbs = pkix.signedEnvelopeTbs(root);
|
|
221
221
|
if (!tbs) return false;
|
|
222
222
|
var kids = tbs.children;
|
|
223
|
-
var i = (kids[0]
|
|
223
|
+
var i = schema.isContext(kids[0], 0) ? 1 : 0; // optional [0] version
|
|
224
224
|
var validity = kids[i + 3]; // serial(i), signature(i+1), issuer(i+2), validity(i+3)
|
|
225
|
-
return
|
|
225
|
+
return schema.isUniversal(validity, TAGS.SEQUENCE) &&
|
|
226
226
|
!!validity.children && validity.children.length === 2 &&
|
|
227
227
|
validity.children.every(function (t) {
|
|
228
|
-
return
|
|
228
|
+
return schema.isUniversalOneOf(t, [TAGS.UTC_TIME, TAGS.GENERALIZED_TIME]);
|
|
229
229
|
});
|
|
230
230
|
}
|
|
231
231
|
|
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:c8d0b092-58fa-4019-9da1-b2b636e61608",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-07-
|
|
8
|
+
"timestamp": "2026-07-09T11:21:47.988Z",
|
|
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.1.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.1.19",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.1.
|
|
25
|
+
"version": "0.1.19",
|
|
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.1.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.1.19",
|
|
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.1.
|
|
57
|
+
"ref": "@blamejs/pki@0.1.19",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|