@blamejs/pki 0.5.21 → 0.5.23
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 +28 -1
- package/lib/acme.js +15 -10
- package/lib/asn1-der.js +6 -2
- package/lib/attrcert-sign.js +10 -5
- package/lib/cmc-build.js +6 -2
- package/lib/cmc-verify.js +6 -2
- package/lib/cmp-verify.js +3 -3
- package/lib/cms-verify.js +1 -1
- package/lib/crl-sign.js +10 -5
- package/lib/ct.js +6 -1
- package/lib/est.js +7 -3
- package/lib/hpke.js +5 -1
- package/lib/http-digest.js +13 -3
- package/lib/path-validate.js +9 -9
- package/lib/pki-build.js +10 -6
- package/lib/schema-attrcert.js +5 -1
- package/lib/schema-crl.js +5 -1
- package/lib/schema-ocsp.js +7 -2
- package/lib/schema-pkix.js +6 -1
- package/lib/tls-cert-compress.js +5 -1
- package/lib/trust.js +5 -1
- package/lib/webauthn-mds.js +5 -1
- package/lib/webauthn.js +6 -2
- package/lib/x509-sign.js +18 -7
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,34 @@ All notable changes to `@blamejs/pki` are documented here. The format
|
|
|
4
4
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this
|
|
5
5
|
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
-
## v0.5.
|
|
7
|
+
## v0.5.23 — 2026-08-21
|
|
8
|
+
|
|
9
|
+
The tables that decide which status codes, revocation reasons, trust bits and extensions this toolkit recognizes now answer from an operation taken at load, and an unsupported HTTP Digest algorithm is refused rather than read off a prototype.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Eighteen further modules -- ACME, EST, CMC, WebAuthn and its metadata reader, Certificate Transparency, trust-store ingest, TLS certificate compression, HPKE, HTTP Digest, the CRL and attribute-certificate signers, and the OCSP, CRL, attribute-certificate and shared PKIX schemas -- are held to the live-read rule, each declaring the reads it still carries as a count that can only fall.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- An HTTP Digest challenge naming an unsupported algorithm is refused with a typed error. The algorithm registry was an ordinary object, so a name matching any member of `Object.prototype` answered the supported-algorithm gate with a value the registry never held: the refusal was skipped and the call ended in an untyped `TypeError`. The registry now carries a null prototype.
|
|
18
|
+
- OCSP response statuses and revocation reasons, CRL reason codes, CCADB trust bits, attribute-certificate critical extensions and object-digest types, Certificate Transparency log states, and the repeated-parameter check on an HTTP Digest challenge all decide membership through a captured operation, so replacing the runtime's own membership test after load cannot widen what they accept.
|
|
19
|
+
|
|
20
|
+
## v0.5.22 — 2026-08-20
|
|
21
|
+
|
|
22
|
+
The list tests that decide whether a caller's argument is refused, and which form an extension spec is in, now answer from operations taken at load rather than read when they run.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- The DER codec, the PKIX builders and the certificate signer are held to the live-read rule, with the reads each still carries declared as a count that can only fall. Certification-path validation's declared count falls in this release as its list doors convert.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- The DER builder refuses a non-list of children under a captured test. Every structure this toolkit encodes passes through that door, and it was the busiest live read in the library by two orders of magnitude.
|
|
31
|
+
- `pki.x509.sign` picks which form the caller's extension spec is in with a captured test. That choice decides which fields are read and therefore what is signed into the certificate: answering wrongly sends an object down the list arm, and the extensions it carries are never seen. The same test decides whether the spec asserts keyCertSign, which is what makes the issued certificate a CA.
|
|
32
|
+
- The name, key-usage and GeneralNames builders refuse a non-list argument under a captured test, as do certification-path validation's own list doors.
|
|
33
|
+
|
|
34
|
+
## v0.5.21 — 2026-08-20
|
|
8
35
|
|
|
9
36
|
The check that holds the crypto engine to load-time captures could be satisfied around, so nine modules had taken the safe primitive at one site and were never held to it anywhere else.
|
|
10
37
|
|
package/lib/acme.js
CHANGED
|
@@ -49,6 +49,11 @@ var frameworkError = require("./framework-error");
|
|
|
49
49
|
var httpTransport = require("./http-transport");
|
|
50
50
|
var retryAfter = require("./http-retry-after");
|
|
51
51
|
var guard = require("./guard-all");
|
|
52
|
+
var intrinsic = require("./guard-intrinsic");
|
|
53
|
+
// Membership is asked through an operation taken at load. Written out, the question reads two
|
|
54
|
+
// replaceable properties to answer one thing, and either answering wrongly changes whether a field
|
|
55
|
+
// counts as present in a document this client is deciding how to act on.
|
|
56
|
+
var _hasOwn = intrinsic.hasOwn;
|
|
52
57
|
var pkgVersion = require("../package.json").version;
|
|
53
58
|
|
|
54
59
|
var AcmeError = frameworkError.AcmeError;
|
|
@@ -211,7 +216,7 @@ function _validate(kind, obj) {
|
|
|
211
216
|
var spec = SPECS[kind];
|
|
212
217
|
for (var f = 0; f < spec.length; f++) {
|
|
213
218
|
var field = spec[f];
|
|
214
|
-
var present =
|
|
219
|
+
var present = _hasOwn(obj, field.name);
|
|
215
220
|
var required = field.required || (field.requiredWhen && field.requiredWhen(obj));
|
|
216
221
|
if (!present) {
|
|
217
222
|
if (required) throw E("acme/missing-field", "an ACME " + kind + " is missing the required field " + JSON.stringify(field.name));
|
|
@@ -383,9 +388,9 @@ var ERROR_NAMESPACE = "urn:ietf:params:acme:error:";
|
|
|
383
388
|
*/
|
|
384
389
|
function validateProblem(obj) {
|
|
385
390
|
if (!_isObject(obj)) throw E("acme/bad-problem", "a problem document must be a JSON object");
|
|
386
|
-
if (
|
|
391
|
+
if (_hasOwn(obj, "identifier")) throw E("acme/bad-problem", "a top-level problem document must not carry an identifier (RFC 8555 sec. 6.7.1)");
|
|
387
392
|
if (!_isString(obj.type) || obj.type.indexOf(ERROR_NAMESPACE) !== 0) throw E("acme/bad-problem", "an ACME problem type must be in the " + ERROR_NAMESPACE + " namespace (RFC 8555 sec. 6.7)");
|
|
388
|
-
if (
|
|
393
|
+
if (_hasOwn(obj, "subproblems")) {
|
|
389
394
|
if (!Array.isArray(obj.subproblems)) throw E("acme/bad-problem", "subproblems must be an array");
|
|
390
395
|
for (var i = 0; i < obj.subproblems.length; i++) {
|
|
391
396
|
var sub = obj.subproblems[i];
|
|
@@ -393,7 +398,7 @@ function validateProblem(obj) {
|
|
|
393
398
|
// A subproblem identifier reflects a submitted order identifier, which MAY be a
|
|
394
399
|
// wildcard (a rejectedIdentifier for a *.example.org order), so it is validated
|
|
395
400
|
// with the order-identifier rule, not the stricter authorization one.
|
|
396
|
-
if (
|
|
401
|
+
if (_hasOwn(sub, "identifier")) _validateOrderIdentifier(sub.identifier);
|
|
397
402
|
}
|
|
398
403
|
}
|
|
399
404
|
return obj;
|
|
@@ -455,7 +460,7 @@ function validate(kind, obj) {
|
|
|
455
460
|
*/
|
|
456
461
|
function identify(obj) {
|
|
457
462
|
if (!_isObject(obj)) return "unknown";
|
|
458
|
-
var has = function (k) { return
|
|
463
|
+
var has = function (k) { return _hasOwn(obj, k); };
|
|
459
464
|
if (has("protected") && has("signature") && _isString(obj.protected) && _isString(obj.signature)) return "jws";
|
|
460
465
|
if (_isString(obj.type) && obj.type.indexOf(ERROR_NAMESPACE) === 0) return "problem";
|
|
461
466
|
if (has("newNonce") && has("newAccount")) return "directory";
|
|
@@ -677,8 +682,8 @@ async function verifyTlsAlpn01(certDer, token, accountJwk, identifier) {
|
|
|
677
682
|
// `url`, and either `kid` (an account URL) or `jwk` (an embedded public JWK).
|
|
678
683
|
function _outerHeader(o) {
|
|
679
684
|
var h = { alg: o.alg, nonce: o.nonce, url: o.url };
|
|
680
|
-
if (
|
|
681
|
-
if (
|
|
685
|
+
if (_hasOwn(o, "kid")) h.kid = o.kid;
|
|
686
|
+
if (_hasOwn(o, "jwk")) h.jwk = o.jwk;
|
|
682
687
|
return h;
|
|
683
688
|
}
|
|
684
689
|
function _payloadBuf(obj) {
|
|
@@ -1113,8 +1118,8 @@ function revokeCert(o) {
|
|
|
1113
1118
|
return _promised(function () {
|
|
1114
1119
|
if (!_isObject(o) || !Buffer.isBuffer(o.certificate)) throw E("acme/bad-input", "revokeCert requires a DER certificate Buffer (opts.certificate)");
|
|
1115
1120
|
x509.parse(o.certificate); // structural validation of the target
|
|
1116
|
-
var hasKid =
|
|
1117
|
-
var hasJwk =
|
|
1121
|
+
var hasKid = _hasOwn(o, "kid");
|
|
1122
|
+
var hasJwk = _hasOwn(o, "jwk");
|
|
1118
1123
|
if (hasKid === hasJwk) throw E("acme/bad-input", "revokeCert must be signed with EXACTLY ONE of the account kid or the certificate jwk (RFC 8555 sec. 7.6)");
|
|
1119
1124
|
var payload = { certificate: jose.base64url.encode(o.certificate) };
|
|
1120
1125
|
if (o.reason !== undefined) {
|
|
@@ -1675,7 +1680,7 @@ function _parseLinkValue(raw) {
|
|
|
1675
1680
|
// unresolvable target fails closed as acme/bad-link. Scoped to the alternate relation the download consumes.
|
|
1676
1681
|
function _parseLinkAlternates(headers, base) {
|
|
1677
1682
|
var raw = null;
|
|
1678
|
-
for (var k in headers) { if (
|
|
1683
|
+
for (var k in headers) { if (_hasOwn(headers, k) && k.toLowerCase() === "link") { raw = headers[k]; break; } }
|
|
1679
1684
|
if (raw == null) return [];
|
|
1680
1685
|
var baseUrl = new URL(base), baseOrigin = baseUrl.origin, baseHref = baseUrl.href; // the download URL
|
|
1681
1686
|
var fields = Array.isArray(raw) ? raw : [raw], out = [], seen = Object.create(null), totalBytes = 0;
|
package/lib/asn1-der.js
CHANGED
|
@@ -34,6 +34,10 @@
|
|
|
34
34
|
var constants = require("./constants");
|
|
35
35
|
var frameworkError = require("./framework-error");
|
|
36
36
|
var guard = require("./guard-all");
|
|
37
|
+
var intrinsic = require("./guard-intrinsic");
|
|
38
|
+
// The list test at each builder door decides whether a caller's argument is refused. Read at the
|
|
39
|
+
// moment it runs, a replacement answering true admits a non-list and the door it guards is gone.
|
|
40
|
+
var _isArray = intrinsic.isArray;
|
|
37
41
|
|
|
38
42
|
var Asn1Error = frameworkError.Asn1Error;
|
|
39
43
|
var OidError = frameworkError.OidError;
|
|
@@ -966,7 +970,7 @@ var build = {
|
|
|
966
970
|
// encoder composes, so the minimal-encoding rule lives once (the CHECK side is
|
|
967
971
|
// schema.assertMinimalNamedBits). No bits asserted -> the empty BIT STRING.
|
|
968
972
|
namedBitString: function (positions) {
|
|
969
|
-
if (!
|
|
973
|
+
if (!_isArray(positions)) throw new Asn1Error("asn1/bad-bit-string", "namedBitString requires an array of bit positions");
|
|
970
974
|
var hi = -1, i, p;
|
|
971
975
|
for (i = 0; i < positions.length; i++) {
|
|
972
976
|
p = positions[i];
|
|
@@ -1059,7 +1063,7 @@ function _intContent(v, who) {
|
|
|
1059
1063
|
}
|
|
1060
1064
|
|
|
1061
1065
|
function _asBufferArray(arr, who) {
|
|
1062
|
-
if (!
|
|
1066
|
+
if (!_isArray(arr)) throw new Asn1Error("asn1/bad-children", who + ": expected an array of TLV buffers");
|
|
1063
1067
|
return arr.map(function (b) { return _asBuffer(b, who); });
|
|
1064
1068
|
}
|
|
1065
1069
|
|
package/lib/attrcert-sign.js
CHANGED
|
@@ -33,6 +33,11 @@ var attrcert = require("./schema-attrcert");
|
|
|
33
33
|
var x509 = require("./schema-x509");
|
|
34
34
|
var signScheme = require("./sign-scheme");
|
|
35
35
|
var guard = require("./guard-all");
|
|
36
|
+
var intrinsic = require("./guard-intrinsic");
|
|
37
|
+
// Membership is asked through an operation taken at load. Written out, the question reads two
|
|
38
|
+
// replaceable properties to answer one thing, and either answering true admits a name the table
|
|
39
|
+
// never carried -- here, an unrecognized critical extension counted as one this profile supports.
|
|
40
|
+
var _hasOwn = intrinsic.hasOwn;
|
|
36
41
|
var pkix = require("./schema-pkix");
|
|
37
42
|
var schema = require("./schema-engine");
|
|
38
43
|
require("./path-validate"); // side-effect: path-validate injects its signature engine into attrcert-verify at load
|
|
@@ -373,10 +378,10 @@ function _buildAttributes(attrSpec) {
|
|
|
373
378
|
// carrying, so BOTH extension forms meet it -- the object form and the pre-encoded array form, which
|
|
374
379
|
// is the one that can name a pointer at all. `seen` is the OID-keyed set each branch already builds.
|
|
375
380
|
function _assertRevocationSchemesExclusive(seen) {
|
|
376
|
-
if (!
|
|
381
|
+
if (!_hasOwn(seen, O("noRevAvail"))) return;
|
|
377
382
|
var names = Object.keys(REVOCATION_POINTERS);
|
|
378
383
|
for (var i = 0; i < names.length; i++) {
|
|
379
|
-
if (
|
|
384
|
+
if (_hasOwn(seen, names[i])) {
|
|
380
385
|
throw _err("attrcert/bad-input", "an attribute certificate must not carry both noRevAvail and a " +
|
|
381
386
|
REVOCATION_POINTERS[names[i]] + " revocation pointer (RFC 5755 sec. 6)");
|
|
382
387
|
}
|
|
@@ -762,7 +767,7 @@ function _unsupportedCritical(ac) {
|
|
|
762
767
|
if (e.critical !== true) continue;
|
|
763
768
|
// Own-property membership, so an extension OID that happens to name something on Object.prototype
|
|
764
769
|
// cannot read as supported.
|
|
765
|
-
if (!
|
|
770
|
+
if (!_hasOwn(SUPPORTED_CRITICAL, e.oid)) return e.oid;
|
|
766
771
|
// A supported OID whose value this build could not interpret is still unusable.
|
|
767
772
|
if (e.decoded === null || e.decoded === undefined || e.decoded.opaque === true) return e.oid;
|
|
768
773
|
}
|
|
@@ -887,7 +892,7 @@ function _fixVerifyOptions(opts) {
|
|
|
887
892
|
var revocationStatus = null;
|
|
888
893
|
if (o.revocationStatus !== undefined) {
|
|
889
894
|
if (typeof o.revocationStatus !== "string" ||
|
|
890
|
-
!
|
|
895
|
+
!_hasOwn(REVOCATION_STATUS, o.revocationStatus)) {
|
|
891
896
|
throw _err("attrcert/bad-input", 'pki.attrcert.verify opts.revocationStatus must be "notRevoked" or "revoked"');
|
|
892
897
|
}
|
|
893
898
|
revocationStatus = o.revocationStatus;
|
|
@@ -916,7 +921,7 @@ function _revocationScheme(ac) {
|
|
|
916
921
|
var e = ac.extensions[i];
|
|
917
922
|
if (e.name === "noRevAvail") out.noRevAvail = true;
|
|
918
923
|
var p = REVOCATION_POINTERS[e.oid];
|
|
919
|
-
if (p !== undefined &&
|
|
924
|
+
if (p !== undefined && _hasOwn(REVOCATION_POINTERS, e.oid)) out.pointer = p;
|
|
920
925
|
}
|
|
921
926
|
return out;
|
|
922
927
|
}
|
package/lib/cmc-build.js
CHANGED
|
@@ -33,6 +33,10 @@ var cmcFmt = require("./schema-cmc"); // to read the assembled message back bef
|
|
|
33
33
|
var oid = require("./oid");
|
|
34
34
|
var cmsSign = require("./cms-sign");
|
|
35
35
|
var guard = require("./guard-all");
|
|
36
|
+
var intrinsic = require("./guard-intrinsic");
|
|
37
|
+
// Own-key membership through an operation taken at load. Written out, the question reads two
|
|
38
|
+
// replaceable properties to answer one thing, and here it decides which controls a request carries.
|
|
39
|
+
var _hasOwn = intrinsic.hasOwn;
|
|
36
40
|
var frameworkError = require("./framework-error");
|
|
37
41
|
|
|
38
42
|
var CmcError = frameworkError.CmcError;
|
|
@@ -710,7 +714,7 @@ function _copySigners(signer, owned) {
|
|
|
710
714
|
if (!s || typeof s !== "object") return s;
|
|
711
715
|
var out = {}, k;
|
|
712
716
|
for (k in s) {
|
|
713
|
-
if (!
|
|
717
|
+
if (!_hasOwn(s, k)) continue;
|
|
714
718
|
out[k] = copyValue(s[k], k === "key");
|
|
715
719
|
}
|
|
716
720
|
return out;
|
|
@@ -725,7 +729,7 @@ function _copySigners(signer, owned) {
|
|
|
725
729
|
if (!v || typeof v !== "object" || typeof v.type === "string") return v; // CryptoKey / scalar
|
|
726
730
|
var c = {}, ck;
|
|
727
731
|
for (ck in v) {
|
|
728
|
-
if (!
|
|
732
|
+
if (!_hasOwn(v, ck)) continue;
|
|
729
733
|
var cv = v[ck];
|
|
730
734
|
c[ck] = (Buffer.isBuffer(cv) || cv instanceof Uint8Array) ? _own(Buffer.from(cv), isSecret) : cv;
|
|
731
735
|
}
|
package/lib/cmc-verify.js
CHANGED
|
@@ -25,6 +25,10 @@ var cmc = require("./schema-cmc");
|
|
|
25
25
|
var cmsVerify = require("./cms-verify"); // PD14: the carrier's signature MUST be verified
|
|
26
26
|
var cmsDecrypt = require("./cms-decrypt"); // ... and an AuthenticatedData carrier by its MAC
|
|
27
27
|
var guard = require("./guard-all");
|
|
28
|
+
var intrinsic = require("./guard-intrinsic");
|
|
29
|
+
// Own-key membership through an operation taken at load. Written out, the question reads two
|
|
30
|
+
// replaceable properties to answer one thing, and here it decides which controls a response carries.
|
|
31
|
+
var _hasOwn = intrinsic.hasOwn;
|
|
28
32
|
var frameworkError = require("./framework-error");
|
|
29
33
|
|
|
30
34
|
var CmcError = frameworkError.CmcError;
|
|
@@ -428,7 +432,7 @@ function _copyAnyBytes(v) {
|
|
|
428
432
|
|
|
429
433
|
function _snapshotSent(sent) {
|
|
430
434
|
var out = {}, k;
|
|
431
|
-
for (k in sent) { if (
|
|
435
|
+
for (k in sent) { if (_hasOwn(sent, k)) out[k] = sent[k]; }
|
|
432
436
|
// Normalized through the SAME authoring guard pki.cmc.build puts an authored
|
|
433
437
|
// integer through. A Transaction Identifier is an unbounded INTEGER on the wire,
|
|
434
438
|
// so one above Number.MAX_SAFE_INTEGER has already been rounded by the time it
|
|
@@ -458,7 +462,7 @@ function _snapshotSent(sent) {
|
|
|
458
462
|
if (out.recipient && typeof out.recipient === "object") {
|
|
459
463
|
var r = {}, rk;
|
|
460
464
|
for (rk in out.recipient) {
|
|
461
|
-
if (!
|
|
465
|
+
if (!_hasOwn(out.recipient, rk)) continue;
|
|
462
466
|
r[rk] = _copyAnyBytes(out.recipient[rk]);
|
|
463
467
|
}
|
|
464
468
|
out.recipient = r;
|
package/lib/cmp-verify.js
CHANGED
|
@@ -935,9 +935,9 @@ function _fixTime(v) {
|
|
|
935
935
|
}
|
|
936
936
|
function _fixVerifyOptions(opts, made) {
|
|
937
937
|
if (opts == null) opts = {}; // ONLY null / undefined -> the default empty opts (a falsy false/0/"" is a bad config, not a default)
|
|
938
|
-
// Through the toolkit's own options door
|
|
939
|
-
//
|
|
940
|
-
//
|
|
938
|
+
// Through the toolkit's own options door rather than a pair of local field checks. The door
|
|
939
|
+
// REFUSES an options bag whose fields are accessors, and refuses one that changes which fields it
|
|
940
|
+
// carries while they are read; a local check on each field it needs can do neither.
|
|
941
941
|
//
|
|
942
942
|
// That is what closes this whole class rather than one member of it. An accessor is caller code
|
|
943
943
|
// running inside the call, before the verb has done anything, and from there it can rewrite the
|
package/lib/cms-verify.js
CHANGED
|
@@ -829,7 +829,7 @@ function _cloneAnchorValue(v, depth) {
|
|
|
829
829
|
if (Array.isArray(v)) return v.map(function (e) { return _cloneAnchorValue(e, depth + 1); });
|
|
830
830
|
var out = {}, k;
|
|
831
831
|
for (k in v) {
|
|
832
|
-
if (!
|
|
832
|
+
if (!intrinsic.hasOwn(v, k)) continue;
|
|
833
833
|
out[k] = _cloneAnchorValue(v[k], depth + 1);
|
|
834
834
|
}
|
|
835
835
|
return out;
|
package/lib/crl-sign.js
CHANGED
|
@@ -29,6 +29,11 @@ var crlSchema = require("./schema-crl");
|
|
|
29
29
|
var x509Schema = require("./schema-x509");
|
|
30
30
|
var signScheme = require("./sign-scheme");
|
|
31
31
|
var guard = require("./guard-all");
|
|
32
|
+
var intrinsic = require("./guard-intrinsic");
|
|
33
|
+
// Membership is asked through an operation taken at load. Written out, the question reads two
|
|
34
|
+
// replaceable properties to answer one thing, and either of them answering true admits a name the
|
|
35
|
+
// table never carried -- here, a reason code the CRL profile does not define.
|
|
36
|
+
var _hasOwn = intrinsic.hasOwn;
|
|
32
37
|
var frameworkError = require("./framework-error");
|
|
33
38
|
var pkix = require("./schema-pkix");
|
|
34
39
|
var schema = require("./schema-engine");
|
|
@@ -164,10 +169,10 @@ function _resolveReason(reason, isDelta) {
|
|
|
164
169
|
var codeNum;
|
|
165
170
|
if (typeof reason === "number") codeNum = reason;
|
|
166
171
|
else if (typeof reason === "string") {
|
|
167
|
-
if (!
|
|
172
|
+
if (!_hasOwn(REASON_BY_NAME, reason)) throw _err("crl/bad-reason-code", "unknown CRLReason " + JSON.stringify(reason));
|
|
168
173
|
codeNum = REASON_BY_NAME[reason];
|
|
169
174
|
} else throw _err("crl/bad-reason-code", "reason must be a CRLReason name or number");
|
|
170
|
-
if (!
|
|
175
|
+
if (!_hasOwn(CRL_REASON, String(codeNum))) throw _err("crl/bad-reason-code", "undefined or reserved CRLReason " + codeNum + " (RFC 5280 sec. 5.3.1)");
|
|
171
176
|
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)");
|
|
172
177
|
return codeNum;
|
|
173
178
|
}
|
|
@@ -284,7 +289,7 @@ function _buildCrlExtensions(spec, ctx) {
|
|
|
284
289
|
// fixed criticality (assertValidExtension rejects an explicit critical=FALSE, so 3 children == critical,
|
|
285
290
|
// 2 == non-critical) AND a well-formed value of the right type (INTEGER for cRLNumber/deltaCRLIndicator,
|
|
286
291
|
// else a SEQUENCE), never emitted opaque.
|
|
287
|
-
if (
|
|
292
|
+
if (_hasOwn(REQUIRED_CRITICALITY, extnId)) {
|
|
288
293
|
if ((node.children.length === 3) !== REQUIRED_CRITICALITY[extnId]) {
|
|
289
294
|
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") + ")");
|
|
290
295
|
}
|
|
@@ -387,13 +392,13 @@ function _buildRevoked(entryList, isDelta) {
|
|
|
387
392
|
// RFC 5280 sec. 5.3 -- a recognized entry extension is held to the profile even via the escape hatch:
|
|
388
393
|
// its fixed criticality AND a well-formed value of the right type (reasonCode an ENUMERATED in the
|
|
389
394
|
// legal set, invalidityDate a GeneralizedTime).
|
|
390
|
-
if (
|
|
395
|
+
if (_hasOwn(REQUIRED_ENTRY_CRITICALITY, extnId)) {
|
|
391
396
|
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") + ")");
|
|
392
397
|
var einner = _extInner(xnode, oid.name(extnId) || extnId);
|
|
393
398
|
if (extnId === O("reasonCode")) {
|
|
394
399
|
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)");
|
|
395
400
|
var rc = asn1.read.enumerated(einner);
|
|
396
|
-
if (!
|
|
401
|
+
if (!_hasOwn(CRL_REASON, rc.toString())) throw _err("crl/bad-reason-code", "pre-encoded reasonCode " + rc + " is undefined or reserved (RFC 5280 sec. 5.3.1)");
|
|
397
402
|
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)");
|
|
398
403
|
} else if (extnId === O("invalidityDate")) {
|
|
399
404
|
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)");
|
package/lib/ct.js
CHANGED
|
@@ -44,6 +44,11 @@ var asn1 = require("./asn1-der.js");
|
|
|
44
44
|
var constants = require("./constants.js");
|
|
45
45
|
var frameworkError = require("./framework-error.js");
|
|
46
46
|
var guard = require("./guard-all.js");
|
|
47
|
+
var intrinsic = require("./guard-intrinsic.js");
|
|
48
|
+
// Own-key membership through an operation taken at load. Written out, the question reads two
|
|
49
|
+
// replaceable properties to answer one thing, and here it decides whether a log state is one this
|
|
50
|
+
// toolkit trusts -- answering true for a name the table never carried trusts an untrusted log.
|
|
51
|
+
var _hasOwn = intrinsic.hasOwn;
|
|
47
52
|
var ByteReader = require("./byte-reader.js");
|
|
48
53
|
var ByteWriter = require("./byte-writer.js");
|
|
49
54
|
var oid = require("./oid.js");
|
|
@@ -650,7 +655,7 @@ var LOG_STATE_TRUST = { pending: "no", qualified: "yes", usable: "yes", readonly
|
|
|
650
655
|
function _parseLogState(state) {
|
|
651
656
|
if (state == null || typeof state !== "object") throw _ctErr("ct/bad-state", "a CT log entry is missing its state");
|
|
652
657
|
var keys = Object.keys(state);
|
|
653
|
-
if (keys.length !== 1 || !
|
|
658
|
+
if (keys.length !== 1 || !_hasOwn(LOG_STATE_TRUST, keys[0])) {
|
|
654
659
|
throw _ctErr("ct/bad-state", "a CT log state must carry exactly one recognized member (pending/qualified/usable/readonly/retired/rejected)");
|
|
655
660
|
}
|
|
656
661
|
var name = keys[0], member = state[name];
|
package/lib/est.js
CHANGED
|
@@ -72,6 +72,10 @@ var OID_CMC_DATA_RETURN = oid.byName("id-cmc-dataReturn");
|
|
|
72
72
|
var crmfFmt = require("./schema-crmf");
|
|
73
73
|
var frameworkError = require("./framework-error");
|
|
74
74
|
var guard = require("./guard-all");
|
|
75
|
+
var intrinsic = require("./guard-intrinsic");
|
|
76
|
+
// Own-key membership through an operation taken at load: written out it reads two replaceable
|
|
77
|
+
// properties to answer one question, and here it decides which fields of a response are copied.
|
|
78
|
+
var _hasOwn = intrinsic.hasOwn;
|
|
75
79
|
var httpTransport = require("./http-transport");
|
|
76
80
|
var httpDigest = require("./http-digest");
|
|
77
81
|
var retryAfter = require("./http-retry-after");
|
|
@@ -1592,7 +1596,7 @@ function _assertAgrees(name, supplied, carried) {
|
|
|
1592
1596
|
function _copyRecipient(r) {
|
|
1593
1597
|
if (!r || typeof r !== "object") return r;
|
|
1594
1598
|
var out = {}, k;
|
|
1595
|
-
for (k in r) { if (
|
|
1599
|
+
for (k in r) { if (_hasOwn(r, k)) out[k] = _copyBytes(r[k]); }
|
|
1596
1600
|
return out;
|
|
1597
1601
|
}
|
|
1598
1602
|
|
|
@@ -1606,10 +1610,10 @@ function _copyRecipient(r) {
|
|
|
1606
1610
|
// transport function, a tls config).
|
|
1607
1611
|
function _shallowCopy(o) {
|
|
1608
1612
|
var out = {}, k;
|
|
1609
|
-
for (k in o) { if (
|
|
1613
|
+
for (k in o) { if (_hasOwn(o, k)) out[k] = o[k]; }
|
|
1610
1614
|
if (out.auth && typeof out.auth === "object") {
|
|
1611
1615
|
var a = {}, ak;
|
|
1612
|
-
for (ak in out.auth) { if (
|
|
1616
|
+
for (ak in out.auth) { if (_hasOwn(out.auth, ak)) a[ak] = out.auth[ak]; }
|
|
1613
1617
|
out.auth = a;
|
|
1614
1618
|
}
|
|
1615
1619
|
return out;
|
package/lib/hpke.js
CHANGED
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
var nodeCrypto = require("crypto");
|
|
28
28
|
var frameworkError = require("./framework-error");
|
|
29
29
|
var guard = require("./guard-all");
|
|
30
|
+
var intrinsic = require("./guard-intrinsic");
|
|
31
|
+
// Own-key membership through an operation taken at load. Written out, the question reads two
|
|
32
|
+
// replaceable properties to answer one thing, and here it decides which suite parameters are read.
|
|
33
|
+
var _hasOwn = intrinsic.hasOwn;
|
|
30
34
|
|
|
31
35
|
var HpkeError = frameworkError.HpkeError;
|
|
32
36
|
function _err(code, message, cause) { return new HpkeError(code, message, cause); }
|
|
@@ -423,7 +427,7 @@ function _mode(opts, keys, who) {
|
|
|
423
427
|
guard.identifier.assertKnownKeys(opts, keys, _err, "hpke/bad-input", function (k) {
|
|
424
428
|
var other = (keys === _SETUP_S_KEYS ? _SETUP_R_KEYS : _SETUP_S_KEYS);
|
|
425
429
|
return "unknown HPKE " + who + " option " + JSON.stringify(k) +
|
|
426
|
-
(
|
|
430
|
+
(_hasOwn(other, k)
|
|
427
431
|
? " -- that option belongs to the other end of the exchange, where it would authenticate or seed; here it would do nothing"
|
|
428
432
|
: "") + " -- accepted: " + Object.keys(keys).sort().join(", ");
|
|
429
433
|
});
|
package/lib/http-digest.js
CHANGED
|
@@ -14,14 +14,24 @@
|
|
|
14
14
|
|
|
15
15
|
var crypto = require("crypto");
|
|
16
16
|
var constants = require("./constants");
|
|
17
|
+
var intrinsic = require("./guard-intrinsic");
|
|
18
|
+
// Own-key membership through an operation taken at load. Written out, the question reads two
|
|
19
|
+
// replaceable properties to answer one thing, and here it decides whether an auth-param repeats --
|
|
20
|
+
// answering false lets the second copy of a nonce overwrite the first.
|
|
21
|
+
var _hasOwn = intrinsic.hasOwn;
|
|
17
22
|
|
|
18
23
|
// Algorithm registry, a data row and not a switch: rank orders the RFC 7616 sec. 3.7
|
|
19
24
|
// "most secure the client can use" selection (SHA-512-256 > SHA-256 > MD5); an unknown algorithm ranks 0.
|
|
20
|
-
|
|
25
|
+
//
|
|
26
|
+
// Null prototype, because the gate on an unknown algorithm is `if (!ALGS[name])`. With an ordinary
|
|
27
|
+
// object literal that question is asked of the prototype chain too, so an algorithm named for any
|
|
28
|
+
// member of Object.prototype answers with a truthy value the table never carried: the
|
|
29
|
+
// unsupported-algorithm refusal is skipped and the descriptor's own fields read as undefined.
|
|
30
|
+
var ALGS = intrinsic.assign(intrinsic.create(null), {
|
|
21
31
|
"SHA-512-256": { hash: "sha512-256", rank: 3, sess: false }, "SHA-512-256-SESS": { hash: "sha512-256", rank: 3, sess: true },
|
|
22
32
|
"SHA-256": { hash: "sha256", rank: 2, sess: false }, "SHA-256-SESS": { hash: "sha256", rank: 2, sess: true },
|
|
23
33
|
"MD5": { hash: "md5", rank: 1, sess: false }, "MD5-SESS": { hash: "md5", rank: 1, sess: true },
|
|
24
|
-
};
|
|
34
|
+
});
|
|
25
35
|
var DEFAULT_CODES = {
|
|
26
36
|
unsupportedAlgorithm: "digest/unsupported-algorithm", weakAlgorithm: "digest/weak-algorithm",
|
|
27
37
|
noQop: "digest/no-qop", badChallenge: "digest/bad-challenge",
|
|
@@ -110,7 +120,7 @@ function _parseParams(paramText, E, code) {
|
|
|
110
120
|
var rawVal = seg.slice(eq + 1).replace(/^\s+|\s+$/g, "");
|
|
111
121
|
var quoted = rawVal.charAt(0) === "\"";
|
|
112
122
|
if (quoted && !_closedQuote(rawVal)) throw E(code, "an unterminated or trailing-garbage Digest quoted-string (RFC 7230 sec. 3.2.6)");
|
|
113
|
-
if (
|
|
123
|
+
if (_hasOwn(map, key)) throw E(code, "repeated Digest auth-param " + JSON.stringify(key));
|
|
114
124
|
var value = quoted ? _unq(rawVal) : rawVal;
|
|
115
125
|
// Reject a control octet in any value (CR / LF / NUL / ...): it is a MUST-reject per RFC 7230 qdtext AND a
|
|
116
126
|
// header-injection sink -- an unescaped CR/LF reflected into the outgoing Authorization would split the header.
|
package/lib/path-validate.js
CHANGED
|
@@ -771,7 +771,7 @@ function isSubtreeBaseValid(tag, base) {
|
|
|
771
771
|
case 1: case 2: case 6: return typeof base === "string"; // rfc822Name / dNSName / URI
|
|
772
772
|
case 7: return (Buffer.isBuffer(base) || base instanceof Uint8Array) &&
|
|
773
773
|
(base.length === 8 || base.length === 32); // iPAddress: address + mask
|
|
774
|
-
case 4: return base !== null && typeof base === "object" && !!base.rdns &&
|
|
774
|
+
case 4: return base !== null && typeof base === "object" && !!base.rdns && intrinsic.isArray(base.rdns); // directoryName
|
|
775
775
|
default: return base !== undefined;
|
|
776
776
|
}
|
|
777
777
|
}
|
|
@@ -782,7 +782,7 @@ function isSubtreeBaseValid(tag, base) {
|
|
|
782
782
|
// disabling the constraint the caller configured -- so it throws instead.
|
|
783
783
|
function checkedSubtreeSeeds(list, optName) {
|
|
784
784
|
if (list === undefined || list === null) return [];
|
|
785
|
-
if (!
|
|
785
|
+
if (!intrinsic.isArray(list)) throw E("path/bad-input", "validate: opts." + optName + " must be an array of { tag, base } subtree entries");
|
|
786
786
|
return list.map(function (st) {
|
|
787
787
|
if (!st || typeof st !== "object" || !Number.isInteger(st.tag) || st.tag < 0 || st.tag > 8 || !isSubtreeBaseValid(st.tag, st.base)) {
|
|
788
788
|
throw E("path/bad-input", "validate: opts." + optName + " entries must be { tag: <GeneralName tag number 0..8>, base: <that form's constraint value> }");
|
|
@@ -1217,7 +1217,7 @@ async function validate(path, opts) {
|
|
|
1217
1217
|
guard.identifier.assertKnownKeys(opts, _VALIDATE_OPTS, E, "path/bad-input",
|
|
1218
1218
|
"pki.path.validate has an unknown option. The anchor here is `trustAnchor`, singular; " +
|
|
1219
1219
|
"pki.path.build takes `trustAnchors`. The unknown option was: ");
|
|
1220
|
-
if (!
|
|
1220
|
+
if (!intrinsic.isArray(path)) throw E("path/bad-input", "validate: path must be an array of certificates");
|
|
1221
1221
|
// Bound the per-cert asymmetric-verify work BEFORE parsing an untrusted bundle
|
|
1222
1222
|
// (the policy-tree cap guards CVE-2023-0464-style blow-up; this guards linear
|
|
1223
1223
|
// crypto amplification from an oversized path). Entry-point tier: throw.
|
|
@@ -1242,7 +1242,7 @@ async function validate(path, opts) {
|
|
|
1242
1242
|
// Membership is tested with indexOf, so a raw string here would be consulted
|
|
1243
1243
|
// as a SUBSTRING match -- validated to an array of strings instead.
|
|
1244
1244
|
if (opts.userInitialPolicySet !== undefined) {
|
|
1245
|
-
var uipsOk =
|
|
1245
|
+
var uipsOk = intrinsic.isArray(opts.userInitialPolicySet) && opts.userInitialPolicySet.length > 0 &&
|
|
1246
1246
|
guard.list.allMatch(opts.userInitialPolicySet, function (p) { return typeof p === "string" && p.length > 0; });
|
|
1247
1247
|
if (!uipsOk) throw E("path/bad-input", "validate: opts.userInitialPolicySet must be a non-empty array of policy OID strings");
|
|
1248
1248
|
// Each entry is compared (indexOf) against canonical decoder output, so a
|
|
@@ -2643,7 +2643,7 @@ function assertAnchorConstraints(ta, checkPurpose) {
|
|
|
2643
2643
|
function resolvePurposeOpts(opts) {
|
|
2644
2644
|
var requiredEku = null;
|
|
2645
2645
|
if (opts.requiredEku !== undefined) {
|
|
2646
|
-
if (!
|
|
2646
|
+
if (!intrinsic.isArray(opts.requiredEku) || opts.requiredEku.length === 0) {
|
|
2647
2647
|
throw E("path/bad-input", "validate: opts.requiredEku must be a non-empty array of key-purpose OID names or dotted OID strings");
|
|
2648
2648
|
}
|
|
2649
2649
|
requiredEku = opts.requiredEku.map(function (p) {
|
|
@@ -2679,7 +2679,7 @@ function toAnchor(entry) {
|
|
|
2679
2679
|
// (name matching), publicKey bytes and the algorithm OID (the sec. 6.1.4 key
|
|
2680
2680
|
// hand-off). trustAnchors is a caller option, so a malformed tuple is a config
|
|
2681
2681
|
// error and fails closed at entry, not a downstream no-path / soft verdict.
|
|
2682
|
-
if (!
|
|
2682
|
+
if (!intrinsic.isArray(entry.name.rdns) || !Buffer.isBuffer(entry.publicKey) || typeof entry.algorithm !== "string") {
|
|
2683
2683
|
throw E("path/bad-input", "build: a trustAnchor tuple must be { name: { rdns: [...] }, publicKey: Buffer, algorithm: OID string }");
|
|
2684
2684
|
}
|
|
2685
2685
|
return entry;
|
|
@@ -2972,7 +2972,7 @@ async function build(leaf, opts) {
|
|
|
2972
2972
|
|
|
2973
2973
|
var poolInput = opts.candidates !== undefined ? opts.candidates : opts.intermediates;
|
|
2974
2974
|
if (poolInput === undefined) poolInput = [];
|
|
2975
|
-
if (!
|
|
2975
|
+
if (!intrinsic.isArray(poolInput)) throw E("path/bad-input", "build: opts.candidates must be an array of certificates");
|
|
2976
2976
|
// A pool larger than the absolute ceiling is rejected at entry (bounds the
|
|
2977
2977
|
// parse work on an untrusted bundle) -- distinct from the per-search tick
|
|
2978
2978
|
// budget below, which an operator may set lower to cap the DFS.
|
|
@@ -2987,7 +2987,7 @@ async function build(leaf, opts) {
|
|
|
2987
2987
|
var poolDerKeys = new Set();
|
|
2988
2988
|
for (var pdk = 0; pdk < pool.length; pdk++) intrinsic.setAdd(poolDerKeys, certDerKey(pool[pdk]));
|
|
2989
2989
|
|
|
2990
|
-
if (!
|
|
2990
|
+
if (!intrinsic.isArray(opts.trustAnchors) || opts.trustAnchors.length === 0) throw E("path/bad-input", "build: opts.trustAnchors must be a non-empty array of anchor tuples or root certificates");
|
|
2991
2991
|
var anchors = opts.trustAnchors.map(toAnchor);
|
|
2992
2992
|
|
|
2993
2993
|
guard.time.assertValid(opts.time, E, "path/bad-input", "build: opts.time (the validity-window check date, forwarded to validate)");
|
|
@@ -3081,7 +3081,7 @@ async function build(leaf, opts) {
|
|
|
3081
3081
|
// supplied and `opts.softFail` answers for it. Own membership decides only whether a name is
|
|
3082
3082
|
// build-only, which is a question about this module's own table and never about the caller.
|
|
3083
3083
|
Object.keys(_VALIDATE_OPTS).forEach(function (k) {
|
|
3084
|
-
if (
|
|
3084
|
+
if (intrinsic.hasOwn(BUILD_ONLY_OPT, k)) return;
|
|
3085
3085
|
if (k in opts) forwarded[k] = opts[k];
|
|
3086
3086
|
});
|
|
3087
3087
|
function validateOpts(anchor) {
|
package/lib/pki-build.js
CHANGED
|
@@ -14,6 +14,10 @@ var asn1 = require("./asn1-der");
|
|
|
14
14
|
var schema = require("./schema-engine");
|
|
15
15
|
var compositeSig = require("./composite-sig");
|
|
16
16
|
var guard = require("./guard-all");
|
|
17
|
+
var intrinsic = require("./guard-intrinsic");
|
|
18
|
+
// Each of these doors refuses a caller argument that is not a list. Read live, a replacement
|
|
19
|
+
// answering true lets a non-list through the check written to stop it.
|
|
20
|
+
var _isArray = intrinsic.isArray;
|
|
17
21
|
var oid = require("./oid");
|
|
18
22
|
var ipUtils = require("./ip-utils");
|
|
19
23
|
var nodeCrypto = require("crypto");
|
|
@@ -83,7 +87,7 @@ function makeBuilder(ctx) {
|
|
|
83
87
|
function encodeName(spec) {
|
|
84
88
|
if (Buffer.isBuffer(spec)) { assertValidNameDer(spec); return spec; }
|
|
85
89
|
if (typeof spec === "string") spec = [{ commonName: spec }];
|
|
86
|
-
if (!
|
|
90
|
+
if (!_isArray(spec)) throw E("bad-name", "a name must be a string, an array of RDNs, or raw Name DER");
|
|
87
91
|
return b.sequence(spec.map(encodeRdn));
|
|
88
92
|
}
|
|
89
93
|
// Full validation of a raw Name DER: walk it through the exact RDNSequence parser the decoder uses.
|
|
@@ -161,7 +165,7 @@ function makeBuilder(ctx) {
|
|
|
161
165
|
|
|
162
166
|
// ---- extension-value encoders (the inverse of certExtensionDecoders) ----
|
|
163
167
|
function extKeyUsage(names) {
|
|
164
|
-
if (!
|
|
168
|
+
if (!_isArray(names) || !names.length) throw E("bad-input", "keyUsage must assert at least one bit (RFC 5280 sec. 4.2.1.3)");
|
|
165
169
|
var positions = names.map(function (n) {
|
|
166
170
|
var pos = KU_BIT[n];
|
|
167
171
|
if (pos == null) throw E("bad-input", "unknown keyUsage bit " + JSON.stringify(n));
|
|
@@ -187,7 +191,7 @@ function makeBuilder(ctx) {
|
|
|
187
191
|
throw E("bad-input", "unknown " + label + " " + JSON.stringify(n) + " (expected a registered name or a dotted-decimal OID)");
|
|
188
192
|
}
|
|
189
193
|
function extExtKeyUsage(names) {
|
|
190
|
-
if (!
|
|
194
|
+
if (!_isArray(names) || !names.length) throw E("bad-input", "extendedKeyUsage must list at least one KeyPurposeId");
|
|
191
195
|
return b.sequence(names.map(function (n) { return b.oid(_resolveOid(n, "extendedKeyUsage purpose")); }));
|
|
192
196
|
}
|
|
193
197
|
function validateBcSpec(bc) {
|
|
@@ -214,14 +218,14 @@ function makeBuilder(ctx) {
|
|
|
214
218
|
// RoleSyntax.roleAuthority [0], IetfAttrSyntax.policyAuthority [0]); the [n] node's children ARE the
|
|
215
219
|
// GeneralName members, no inner SEQUENCE wrapper.
|
|
216
220
|
function encodeGeneralNames(entries, implicitTag) {
|
|
217
|
-
if (!
|
|
221
|
+
if (!_isArray(entries) || !entries.length) throw E("bad-input", "a GeneralNames must carry at least one GeneralName");
|
|
218
222
|
var members = entries.map(encodeGeneralName);
|
|
219
223
|
if (implicitTag == null) return b.sequence(members);
|
|
220
224
|
return b.contextConstructed(implicitTag, Buffer.concat(members));
|
|
221
225
|
}
|
|
222
226
|
function extSan(entries) { return encodeGeneralNames(entries); }
|
|
223
227
|
function extCertPolicies(names) {
|
|
224
|
-
if (!
|
|
228
|
+
if (!_isArray(names) || !names.length) throw E("bad-input", "certificatePolicies must list at least one policy OID");
|
|
225
229
|
var seen = {};
|
|
226
230
|
return b.sequence(names.map(function (n) {
|
|
227
231
|
var pOid = _resolveOid(n, "certificate policy");
|
|
@@ -301,7 +305,7 @@ function makeBuilder(ctx) {
|
|
|
301
305
|
};
|
|
302
306
|
function requestedExtensions(extSpec, spki) {
|
|
303
307
|
var EXT_DECODERS = ctx.EXT_DECODERS;
|
|
304
|
-
if (
|
|
308
|
+
if (_isArray(extSpec)) {
|
|
305
309
|
if (!extSpec.length) throw E("bad-input", "the requested extensions list must carry at least one extension");
|
|
306
310
|
var seen = {};
|
|
307
311
|
return b.sequence(extSpec.map(function (e, i) {
|
package/lib/schema-attrcert.js
CHANGED
|
@@ -39,6 +39,10 @@ var schema = require("./schema-engine");
|
|
|
39
39
|
var pkix = require("./schema-pkix");
|
|
40
40
|
var oid = require("./oid");
|
|
41
41
|
var guard = require("./guard-all");
|
|
42
|
+
var intrinsic = require("./guard-intrinsic");
|
|
43
|
+
// Own-key membership through an operation taken at load. Written out, the question reads two
|
|
44
|
+
// replaceable properties to answer one thing, and here it decides which object-digest types resolve.
|
|
45
|
+
var _hasOwn = intrinsic.hasOwn;
|
|
42
46
|
var frameworkError = require("./framework-error");
|
|
43
47
|
|
|
44
48
|
var AttrCertError = frameworkError.AttrCertError;
|
|
@@ -69,7 +73,7 @@ var ODT_NAMES = constants.NAMES.OBJECT_DIGEST_TYPE;
|
|
|
69
73
|
var DIGESTED_OBJECT_TYPE = schema.decode(function (n, ctx) {
|
|
70
74
|
var v = asn1.read.enumerated(n);
|
|
71
75
|
var k = v.toString();
|
|
72
|
-
if (!
|
|
76
|
+
if (!_hasOwn(ODT_NAMES, k)) {
|
|
73
77
|
throw ctx.E("attrcert/bad-digested-object-type", "digestedObjectType " + k + " is not a defined value (RFC 5755 sec. 4.1)");
|
|
74
78
|
}
|
|
75
79
|
// RFC 5755 sec. 7.3 -- a conformant AC MUST NOT use otherObjectTypes(2); the digested
|
package/lib/schema-crl.js
CHANGED
|
@@ -28,6 +28,10 @@ var schema = require("./schema-engine");
|
|
|
28
28
|
var pkix = require("./schema-pkix");
|
|
29
29
|
var oid = require("./oid");
|
|
30
30
|
var frameworkError = require("./framework-error");
|
|
31
|
+
var intrinsic = require("./guard-intrinsic");
|
|
32
|
+
// Own-key membership through an operation taken at load. Written out, the question reads two
|
|
33
|
+
// replaceable properties to answer one thing, and here it decides which CRLReason values are defined.
|
|
34
|
+
var _hasOwn = intrinsic.hasOwn;
|
|
31
35
|
|
|
32
36
|
var CrlError = frameworkError.CrlError;
|
|
33
37
|
var PemError = frameworkError.PemError;
|
|
@@ -77,7 +81,7 @@ function decodeExt(ext) {
|
|
|
77
81
|
// read.enumerated asserts the ENUMERATED tag (a bare INTEGER is rejected) --
|
|
78
82
|
// the codec owns the tag check, so this reader need not repeat it.
|
|
79
83
|
var reason = asn1.read.enumerated(asn1.decode(ext.value));
|
|
80
|
-
if (!
|
|
84
|
+
if (!_hasOwn(CRL_REASONS, reason.toString())) throw new Error("undefined CRLReason " + reason.toString());
|
|
81
85
|
value = Number(reason);
|
|
82
86
|
} else if (ext.oid === OID_INVALIDITY_DATE) { // invalidityDate ::= GeneralizedTime
|
|
83
87
|
var n = asn1.decode(ext.value);
|
package/lib/schema-ocsp.js
CHANGED
|
@@ -39,6 +39,11 @@ var pkix = require("./schema-pkix");
|
|
|
39
39
|
var oid = require("./oid");
|
|
40
40
|
var constants = require("./constants");
|
|
41
41
|
var frameworkError = require("./framework-error");
|
|
42
|
+
var intrinsic = require("./guard-intrinsic");
|
|
43
|
+
// Own-key membership through an operation taken at load. Written out, the question reads two
|
|
44
|
+
// replaceable properties to answer one thing, and here it decides which response statuses and
|
|
45
|
+
// revocation reasons are defined -- an admitted name names a status RFC 6960 never assigned.
|
|
46
|
+
var _hasOwn = intrinsic.hasOwn;
|
|
42
47
|
|
|
43
48
|
var OcspError = frameworkError.OcspError;
|
|
44
49
|
var PemError = frameworkError.PemError;
|
|
@@ -85,7 +90,7 @@ var GENERALIZED_TIME = pkix.generalizedTime(NS, { code: "ocsp/bad-time", message
|
|
|
85
90
|
var OCSP_RESPONSE_STATUS = schema.decode(function (n, ctx) {
|
|
86
91
|
var v = asn1.read.enumerated(n);
|
|
87
92
|
var key = v.toString();
|
|
88
|
-
if (!
|
|
93
|
+
if (!_hasOwn(STATUS_NAMES, key)) throw ctx.E("ocsp/bad-response-status", "undefined OCSPResponseStatus " + key + " (RFC 6960 sec. 4.2.1)");
|
|
89
94
|
return { code: Number(v), name: STATUS_NAMES[key] };
|
|
90
95
|
});
|
|
91
96
|
|
|
@@ -93,7 +98,7 @@ var OCSP_RESPONSE_STATUS = schema.decode(function (n, ctx) {
|
|
|
93
98
|
var CRL_REASON = schema.decode(function (n, ctx) {
|
|
94
99
|
var v = asn1.read.enumerated(n);
|
|
95
100
|
var key = v.toString();
|
|
96
|
-
if (!
|
|
101
|
+
if (!_hasOwn(CRL_REASONS, key)) throw ctx.E("ocsp/bad-revocation-reason", "undefined CRLReason " + key + " (RFC 5280 sec. 5.3.1)");
|
|
97
102
|
return CRL_REASONS[key];
|
|
98
103
|
});
|
|
99
104
|
|
package/lib/schema-pkix.js
CHANGED
|
@@ -17,6 +17,11 @@ var asn1 = require("./asn1-der");
|
|
|
17
17
|
var constants = require("./constants");
|
|
18
18
|
var schema = require("./schema-engine");
|
|
19
19
|
var guard = require("./guard-all");
|
|
20
|
+
var intrinsic = require("./guard-intrinsic");
|
|
21
|
+
// Own-key membership through an operation taken at load. Written out, the question reads two
|
|
22
|
+
// replaceable properties to answer one thing, and here it decides which version numbers a format
|
|
23
|
+
// accepts -- shared by every format module, so one admitted name widens all of them at once.
|
|
24
|
+
var _hasOwn = intrinsic.hasOwn;
|
|
20
25
|
// ct is a leaf format module (requires only the codec / oid / constants / error
|
|
21
26
|
// core, never pkix) so the SCT-list extension VALUE decoder registers here as a
|
|
22
27
|
// registry row, not a switch. The one-directional edge keeps the layering acyclic.
|
|
@@ -174,7 +179,7 @@ function makeNS(prefix, ErrorClass, oidModule) {
|
|
|
174
179
|
function versionReader(ns, accept) {
|
|
175
180
|
return schema.decode(function (n) {
|
|
176
181
|
var key = asn1.read.integer(n).toString();
|
|
177
|
-
if (
|
|
182
|
+
if (_hasOwn(accept, key)) return accept[key];
|
|
178
183
|
throw ns.E(ns.prefix + "/bad-version", "unsupported version " + key);
|
|
179
184
|
});
|
|
180
185
|
}
|
package/lib/tls-cert-compress.js
CHANGED
|
@@ -52,6 +52,10 @@
|
|
|
52
52
|
|
|
53
53
|
var C = require("./constants");
|
|
54
54
|
var guard = require("./guard-all");
|
|
55
|
+
var intrinsic = require("./guard-intrinsic");
|
|
56
|
+
// Own-key membership through an operation taken at load. Written out, the question reads two
|
|
57
|
+
// replaceable properties to answer one thing, and here it decides which algorithms are supported.
|
|
58
|
+
var _hasOwn = intrinsic.hasOwn;
|
|
55
59
|
var frameworkError = require("./framework-error");
|
|
56
60
|
var validator = require("./validator-all");
|
|
57
61
|
var ByteWriter = require("./byte-writer");
|
|
@@ -88,7 +92,7 @@ var LEVEL_OPT = {
|
|
|
88
92
|
// guard on this runtime. An algorithm the guard cannot decompress must never be offered
|
|
89
93
|
// or accepted: advertising one would mean accepting a message we then cannot open.
|
|
90
94
|
var SUPPORTED = guard.compress.algorithms().filter(function (n) {
|
|
91
|
-
return
|
|
95
|
+
return _hasOwn(ALG_BY_NAME, n) && typeof COMPRESS[n] === "function";
|
|
92
96
|
});
|
|
93
97
|
|
|
94
98
|
// The minimum Certificate message size and the widest opaque<..2^24-1> vector, both owned
|
package/lib/trust.js
CHANGED
|
@@ -40,6 +40,10 @@ var constants = require("./constants");
|
|
|
40
40
|
var errors = require("./framework-error");
|
|
41
41
|
var asn1 = require("./asn1-der");
|
|
42
42
|
var guard = require("./guard-all");
|
|
43
|
+
var intrinsic = require("./guard-intrinsic");
|
|
44
|
+
// Own-key membership through an operation taken at load. Written out, the question reads two
|
|
45
|
+
// replaceable properties to answer one thing, and here it decides which trust bits an anchor carries.
|
|
46
|
+
var _hasOwn = intrinsic.hasOwn;
|
|
43
47
|
var x509 = require("./schema-x509");
|
|
44
48
|
|
|
45
49
|
var TrustError = errors.TrustError;
|
|
@@ -604,7 +608,7 @@ function _trustBits(cell) {
|
|
|
604
608
|
var t = tok.trim();
|
|
605
609
|
if (t === "") return;
|
|
606
610
|
var l = t.toLowerCase();
|
|
607
|
-
if (!
|
|
611
|
+
if (!_hasOwn(_TRUST_BIT_TOKENS, l)) {
|
|
608
612
|
throw E("trust/bad-csv", "unrecognized Trust Bits token " + JSON.stringify(t));
|
|
609
613
|
}
|
|
610
614
|
var p = _TRUST_BIT_TOKENS[l];
|
package/lib/webauthn-mds.js
CHANGED
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
var frameworkError = require("./framework-error");
|
|
23
23
|
var x509 = require("./schema-x509");
|
|
24
24
|
var guard = require("./guard-all");
|
|
25
|
+
var intrinsic = require("./guard-intrinsic");
|
|
26
|
+
// Own-key membership through an operation taken at load. Written out, the question reads two
|
|
27
|
+
// replaceable properties to answer one thing, and here it decides which status rows an entry carries.
|
|
28
|
+
var _hasOwn = intrinsic.hasOwn;
|
|
25
29
|
var jose = require("./jose");
|
|
26
30
|
var rfc3339 = require("./rfc3339");
|
|
27
31
|
var constants = require("./constants");
|
|
@@ -348,7 +352,7 @@ function _verifyMetadataBlob(blob, opts) {
|
|
|
348
352
|
// reader that skips the list accepts a token on terms it never met. Nothing here processes an
|
|
349
353
|
// extension parameter, so any name is unprocessed, including a standard name, which sec. 4.1.11
|
|
350
354
|
// forbids listing at all.
|
|
351
|
-
if (
|
|
355
|
+
if (_hasOwn(header, "crit")) {
|
|
352
356
|
var crit = header.crit;
|
|
353
357
|
if (!Array.isArray(crit) || crit.length === 0) {
|
|
354
358
|
throw _err("webauthn/bad-metadata-blob", "the metadata BLOB header crit must be a non-empty array (RFC 7515 sec. 4.1.11)");
|
package/lib/webauthn.js
CHANGED
|
@@ -34,6 +34,10 @@ var constants = require("./constants");
|
|
|
34
34
|
var validator = require("./validator-all");
|
|
35
35
|
var edwardsPoint = require("./edwards-point");
|
|
36
36
|
var guard = require("./guard-all");
|
|
37
|
+
var intrinsic = require("./guard-intrinsic");
|
|
38
|
+
// Own-key membership through an operation taken at load: written out it reads two replaceable
|
|
39
|
+
// properties to answer one question, and either answering true admits a name the table never had.
|
|
40
|
+
var _hasOwn = intrinsic.hasOwn;
|
|
37
41
|
var jose = require("./jose");
|
|
38
42
|
var mds = require("./webauthn-mds");
|
|
39
43
|
var nodeCrypto = require("crypto");
|
|
@@ -2220,7 +2224,7 @@ function _snapshotAssertion(input) {
|
|
|
2220
2224
|
if (!_isPlainObject(input)) throw _err("webauthn/bad-input", "pki.webauthn.verifyAssertion takes an options object");
|
|
2221
2225
|
guard.identifier.assertKnownKeys(input, _ASSERT_OPTS, _err, "webauthn/bad-input", "verifyAssertion input has an unknown key ");
|
|
2222
2226
|
var out = {}, k;
|
|
2223
|
-
for (k in input) { if (
|
|
2227
|
+
for (k in input) { if (_hasOwn(input, k)) out[k] = input[k]; }
|
|
2224
2228
|
// The SAME set of byte forms the arguments accept. A snapshot list narrower than the accept list
|
|
2225
2229
|
// leaves whatever falls in the gap aliased across the yield -- which is the one input a widened
|
|
2226
2230
|
// accept list newly admits, so the two must be written from one predicate.
|
|
@@ -2237,7 +2241,7 @@ function _snapshotAssertion(input) {
|
|
|
2237
2241
|
} else if (_isPlainObject(out.credentialPublicKey)) {
|
|
2238
2242
|
var key = {}, kk;
|
|
2239
2243
|
for (kk in out.credentialPublicKey) {
|
|
2240
|
-
if (!
|
|
2244
|
+
if (!_hasOwn(out.credentialPublicKey, kk)) continue;
|
|
2241
2245
|
// The read itself can fault: a stored key is a caller-supplied object, so a field may be an
|
|
2242
2246
|
// accessor, and one that THROWS would escape as a raw error from the very copy whose job is
|
|
2243
2247
|
// to make the descriptor stop being the caller's. The copy is the boundary, so the fault is
|
package/lib/x509-sign.js
CHANGED
|
@@ -27,6 +27,11 @@ var oid = require("./oid");
|
|
|
27
27
|
var x509 = require("./schema-x509");
|
|
28
28
|
var signScheme = require("./sign-scheme");
|
|
29
29
|
var guard = require("./guard-all");
|
|
30
|
+
var intrinsic = require("./guard-intrinsic");
|
|
31
|
+
// This test picks which FORM the caller's extension spec is in, so it decides which fields are read
|
|
32
|
+
// and therefore what is signed into the certificate. Read live, a replacement answering true sends
|
|
33
|
+
// an object down the list arm and the extensions it carries are never seen.
|
|
34
|
+
var _isArray = intrinsic.isArray;
|
|
30
35
|
var frameworkError = require("./framework-error");
|
|
31
36
|
var schema = require("./schema-engine");
|
|
32
37
|
var pkix = require("./schema-pkix");
|
|
@@ -112,7 +117,7 @@ function _akiKeyId(val, ctx) {
|
|
|
112
117
|
// Extension DER buffers). Enforces the RFC 5280 CA cross-field gates, then emits a deterministic order.
|
|
113
118
|
function _buildExtensions(extSpec, ctx) {
|
|
114
119
|
if (extSpec == null) return [];
|
|
115
|
-
if (
|
|
120
|
+
if (_isArray(extSpec)) {
|
|
116
121
|
// Validate each pre-encoded extension, reject a duplicate extnID (RFC 5280 sec. 4.2 allows at most one
|
|
117
122
|
// instance of an extension), and decode basicConstraints + keyUsage to apply the same CA
|
|
118
123
|
// cross-field rules the object form enforces (below), so the array escape hatch cannot bypass them.
|
|
@@ -161,7 +166,7 @@ function _buildExtensions(extSpec, ctx) {
|
|
|
161
166
|
if (bc != null) _validateBcSpec(bc);
|
|
162
167
|
var caTrue = !!(bc && bc.cA === true);
|
|
163
168
|
var ku = extSpec.keyUsage;
|
|
164
|
-
var assertsKeyCertSign =
|
|
169
|
+
var assertsKeyCertSign = _isArray(ku) && ku.indexOf("keyCertSign") >= 0;
|
|
165
170
|
// RFC 5280 sec. 4.2.1.3 -- keyCertSign requires basicConstraints cA=TRUE.
|
|
166
171
|
if (assertsKeyCertSign && !caTrue) throw _err("x509/bad-input", "keyUsage keyCertSign requires basicConstraints cA=TRUE (RFC 5280 sec. 4.2.1.3)");
|
|
167
172
|
// RFC 5280 sec. 4.2.1.9 -- pathLenConstraint requires cA=TRUE AND keyCertSign.
|
|
@@ -217,7 +222,7 @@ function _assertIssuerIsCa(issuerCert) {
|
|
|
217
222
|
// pre-encoded array extensions form (used to honor a supplied issuer's pathLenConstraint).
|
|
218
223
|
function _issuedCaInfo(extSpec) {
|
|
219
224
|
if (extSpec == null) return { cA: false, pathLen: null };
|
|
220
|
-
if (!
|
|
225
|
+
if (!_isArray(extSpec)) {
|
|
221
226
|
var bc = extSpec.basicConstraints;
|
|
222
227
|
return { cA: !!(bc && bc.cA === true), pathLen: bc && bc.pathLen != null ? Number(bc.pathLen) : null };
|
|
223
228
|
}
|
|
@@ -234,7 +239,7 @@ function _issuedCaInfo(extSpec) {
|
|
|
234
239
|
// is decoded to look for an Extension whose extnID is subjectAltName with a TRUE critical flag.
|
|
235
240
|
function _hasCriticalSan(extSpec) {
|
|
236
241
|
if (extSpec == null) return false;
|
|
237
|
-
if (!
|
|
242
|
+
if (!_isArray(extSpec)) return !!extSpec.subjectAltName;
|
|
238
243
|
var sanOid = O("subjectAltName");
|
|
239
244
|
for (var i = 0; i < extSpec.length; i++) {
|
|
240
245
|
var n = asn1.decode(_reqDer(extSpec[i], "extension"));
|
|
@@ -362,17 +367,23 @@ function _sign(spec, issuer, opts) {
|
|
|
362
367
|
if (guard.time.instantOf(spec.notBefore) > guard.time.instantOf(spec.notAfter)) throw _err("x509/bad-input", "notBefore must not be after notAfter (RFC 5280 sec. 4.1.2.5)");
|
|
363
368
|
var validityDer = b.sequence([_timeDer(spec.notBefore, "notBefore"), _timeDer(spec.notAfter, "notAfter")]);
|
|
364
369
|
|
|
365
|
-
|
|
370
|
+
// Read ONCE. Three separate consumers test which form this spec is in -- the encoder, the
|
|
371
|
+
// empty-subject rule below and the path-length rule under it -- and each answering for its own
|
|
372
|
+
// read is three chances to answer differently about one certificate. The door deep-copies the
|
|
373
|
+
// spec, so today they cannot disagree; taking the value once means they still cannot if the
|
|
374
|
+
// shape of that door ever changes.
|
|
375
|
+
var extSpec = spec.extensions;
|
|
376
|
+
var exts = _buildExtensions(extSpec, { spki: spki, issuerSpki: issuerSpki, issuerCert: issuerCert, subjectEmpty: subjectEmpty });
|
|
366
377
|
// RFC 5280 sec. 4.1.2.6 -- an empty subject requires a critical subjectAltName (recognized in both the
|
|
367
378
|
// object form and a pre-encoded array-form extension).
|
|
368
|
-
if (subjectEmpty && !_hasCriticalSan(
|
|
379
|
+
if (subjectEmpty && !_hasCriticalSan(extSpec)) {
|
|
369
380
|
throw _err("x509/bad-input", "an empty subject requires a critical subjectAltName (RFC 5280 sec. 4.1.2.6)");
|
|
370
381
|
}
|
|
371
382
|
// Honor a supplied issuer's pathLenConstraint (RFC 5280 sec. 4.2.1.9): a CA certificate issued below
|
|
372
383
|
// it consumes one unit of path length, so pathLen=0 forbids issuing a CA below it, and an issued CA's
|
|
373
384
|
// own pathLenConstraint must leave room within the issuer's remaining depth.
|
|
374
385
|
if (issuerPathLen != null) {
|
|
375
|
-
var issued = _issuedCaInfo(
|
|
386
|
+
var issued = _issuedCaInfo(extSpec);
|
|
376
387
|
// A self-issued CA certificate (subject == issuer, e.g. a CA key rollover) does NOT consume path
|
|
377
388
|
// length (RFC 5280 sec. 6.1 counts only non-self-issued intermediates), so it is permitted even at
|
|
378
389
|
// pathLen 0. Compare canonically (sec. 7.1) via the parsed RDN sequences.
|
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:efde95e7-f09e-4942-88b0-af9566cae79a",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-08-
|
|
8
|
+
"timestamp": "2026-08-21T04:42:14.069Z",
|
|
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.23",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.5.
|
|
25
|
+
"version": "0.5.23",
|
|
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.23",
|
|
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.23",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|