@blamejs/pki 0.5.20 → 0.5.22
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 +34 -0
- package/lib/asn1-der.js +6 -2
- package/lib/cms-decrypt.js +5 -4
- package/lib/cms-encrypt.js +2 -1
- package/lib/cms-sign.js +4 -3
- package/lib/cms-verify.js +2 -1
- package/lib/guard-all.js +5 -2
- package/lib/guard-intrinsic.js +5 -0
- package/lib/ocsp-verify.js +12 -6
- package/lib/path-validate.js +22 -21
- package/lib/pkcs12-build.js +5 -4
- package/lib/pki-build.js +10 -6
- package/lib/sign-scheme.js +38 -20
- package/lib/tsp-sign.js +2 -1
- package/lib/x509-sign.js +18 -7
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,40 @@ 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.22 — 2026-08-21
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- 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.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- 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.
|
|
18
|
+
- `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.
|
|
19
|
+
- 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.
|
|
20
|
+
|
|
21
|
+
## v0.5.21 — 2026-08-20
|
|
22
|
+
|
|
23
|
+
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.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- `guard.intrinsic.bufferEquals`, the captured byte-identity comparison the bindings above are decided with.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- Taking the captures has one spelling again. The guard orchestrator no longer re-exports them, so a module reaches them by importing the module that holds them, and the check reads that off the loaded module graph rather than off the text. This is the enforcement change: a module that adopts one safe primitive is held to the rule everywhere, and no way of writing the import, or of writing something that resembles one, changes the answer.
|
|
32
|
+
- The seven modules the widened check surfaced carry a declared count of remaining reads rather than an exemption. A new live read pushes a module past its figure and fails, and converting reads without lowering the figure fails too, so the number keeps naming the real count. A module reaching zero leaves the list and is held to zero.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- An OCSP response is bound to the certificate it answers for with captured byte comparisons. The CertID says the response concerns this issuer and the responder ID says this key signed it, and both were decided by a `Buffer.prototype.equals` read at call time: replaced after load it reports every hash equal, and a `good` signed by a different CA answers for the certificate under check.
|
|
37
|
+
- The signer's key is matched to its certificate under a captured case fold. WebCrypto algorithm names compare case-insensitively, so the check folds both sides; dispatched live, a replaced fold reported two different algorithm names equal and a key whose algorithm does not match the certificate passed the gate that exists to catch exactly that.
|
|
38
|
+
- `pki.sign` decides the RSASSA-PSS parameters, the digest tables and its key-shape tests on captured operations, so a caller who replaces one after this package loads cannot change which algorithm a signature runs under.
|
|
39
|
+
- A signing key is classified by asking the value what it is rather than by `instanceof` against a global. The old test read the `Uint8Array` binding at call time, and it answered wrongly in both directions on its own terms: a real typed array from another realm was not one, and an object built on the prototype was.
|
|
40
|
+
|
|
7
41
|
## v0.5.20 — 2026-08-20
|
|
8
42
|
|
|
9
43
|
Five verdicts that turned on list membership now decide it by comparison, so replacing an array method after the module loads cannot change what a certificate, a responder or a signed message is found to be.
|
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/cms-decrypt.js
CHANGED
|
@@ -23,6 +23,7 @@ var schemaCms = require("./schema-cms");
|
|
|
23
23
|
var webcrypto = require("./webcrypto");
|
|
24
24
|
var frameworkError = require("./framework-error");
|
|
25
25
|
var guard = require("./guard-all");
|
|
26
|
+
var intrinsic = require("./guard-intrinsic");
|
|
26
27
|
var pbes2 = require("./pbes2");
|
|
27
28
|
var b = asn1.build;
|
|
28
29
|
var subtle = webcrypto.webcrypto.subtle;
|
|
@@ -415,8 +416,8 @@ async function _kemriCek(ri, km) {
|
|
|
415
416
|
// why, by name. An unresolvable OID is left to _openContent too, as the unsupported algorithm it is.
|
|
416
417
|
function _assertContentCipherMode(eci, ct) {
|
|
417
418
|
var oidStr = eci.contentEncryptionAlgorithm.oid;
|
|
418
|
-
var isAead =
|
|
419
|
-
if (!isAead && !
|
|
419
|
+
var isAead = intrinsic.hasOwn(AEAD_ALGS, oidStr);
|
|
420
|
+
if (!isAead && !intrinsic.hasOwn(CONTENT_MODE, oidStr)) return;
|
|
420
421
|
if (ct === "authEnvelopedData") {
|
|
421
422
|
if (isAead) return;
|
|
422
423
|
throw _err("cms/unsupported-algorithm", "contentEncryptionAlgorithm " + oidStr + " is not an authenticated encryption algorithm, which authEnvelopedData requires (RFC 5083 sec. 2)");
|
|
@@ -428,14 +429,14 @@ function _assertContentCipherMode(eci, ct) {
|
|
|
428
429
|
// ---- stage 3: open the content (uniform failure) ---------------------------
|
|
429
430
|
async function _openContent(parsed, eci, cek, ct) {
|
|
430
431
|
var alg = eci.contentEncryptionAlgorithm;
|
|
431
|
-
var keyBits =
|
|
432
|
+
var keyBits = intrinsic.hasOwn(CONTENT_KEYBITS, alg.oid) ? CONTENT_KEYBITS[alg.oid] : 0;
|
|
432
433
|
if (!keyBits) {
|
|
433
434
|
// AES-CCM parses completely here -- registered OIDs, the sec. 3.1 nonce bound, the seven legal
|
|
434
435
|
// ICV lengths -- and is then declined, because AES-GCM is the only AEAD this toolkit implements
|
|
435
436
|
// for content encryption. Saying only "unsupported <oid>" is what an operator also sees for an
|
|
436
437
|
// OID nothing in the toolkit recognizes, and the two call for different things: one is a
|
|
437
438
|
// capability gap to route around, the other is a malformed or exotic message.
|
|
438
|
-
if (
|
|
439
|
+
if (intrinsic.hasOwn(AEAD_ALGS, alg.oid) && AEAD_ALGS[alg.oid] === "ccm") {
|
|
439
440
|
throw _err("cms/unsupported-algorithm", "contentEncryptionAlgorithm " + alg.oid + " is AES-CCM. RFC 5084 sec. 3.1 permits it for this content type; this toolkit implements AES-GCM only for content encryption");
|
|
440
441
|
}
|
|
441
442
|
throw _err("cms/unsupported-algorithm", "unsupported contentEncryptionAlgorithm " + alg.oid);
|
package/lib/cms-encrypt.js
CHANGED
|
@@ -23,6 +23,7 @@ var schemaCms = require("./schema-cms");
|
|
|
23
23
|
var webcrypto = require("./webcrypto");
|
|
24
24
|
var frameworkError = require("./framework-error");
|
|
25
25
|
var guard = require("./guard-all");
|
|
26
|
+
var intrinsic = require("./guard-intrinsic");
|
|
26
27
|
var pbes2 = require("./pbes2");
|
|
27
28
|
var b = asn1.build;
|
|
28
29
|
var subtle = webcrypto.webcrypto.subtle;
|
|
@@ -565,7 +566,7 @@ function _assertAuthAttrs(pairs, forbidden) {
|
|
|
565
566
|
}
|
|
566
567
|
var t;
|
|
567
568
|
try { t = asn1.read.oid(node.children[0]); } catch (e) { throw _err("cms/bad-input", "an authenticated attribute type is not an OBJECT IDENTIFIER", e); }
|
|
568
|
-
if (
|
|
569
|
+
if (intrinsic.hasOwn(seenTypes, t)) throw _err("cms/bad-input", "authenticated attributes must not repeat an attribute type (RFC 5652): " + t);
|
|
569
570
|
seenTypes[t] = 1;
|
|
570
571
|
if (forbidden && t === forbidden.oid) throw _err("cms/bad-input", forbidden.why);
|
|
571
572
|
});
|
package/lib/cms-sign.js
CHANGED
|
@@ -22,6 +22,7 @@ var frameworkError = require("./framework-error");
|
|
|
22
22
|
var webcrypto = require("./webcrypto");
|
|
23
23
|
var signScheme = require("./sign-scheme");
|
|
24
24
|
var guard = require("./guard-all");
|
|
25
|
+
var intrinsic = require("./guard-intrinsic");
|
|
25
26
|
var pkiBuild = require("./pki-build"); // the shared post-sign self-check, among other producing helpers
|
|
26
27
|
var cms = require("./schema-cms");
|
|
27
28
|
|
|
@@ -117,7 +118,7 @@ function _buildSid(cert, useSki) {
|
|
|
117
118
|
function _buildSignedAttrs(pairs) {
|
|
118
119
|
var seenTypes = {};
|
|
119
120
|
var attrs = pairs.map(function (p) {
|
|
120
|
-
if (
|
|
121
|
+
if (intrinsic.hasOwn(seenTypes, p.type)) throw _err("cms/bad-input", "signedAttrs must not repeat an attribute type (RFC 5652 sec. 5.3): " + p.type);
|
|
121
122
|
seenTypes[p.type] = 1;
|
|
122
123
|
return b.sequence([b.oid(p.type), b.set(p.values)]);
|
|
123
124
|
});
|
|
@@ -155,8 +156,8 @@ function _buildUnsignedAttrs(list) {
|
|
|
155
156
|
var pairs = _resolveAttrPairs(list, "an unsigned attribute value");
|
|
156
157
|
var seen = {};
|
|
157
158
|
pairs.forEach(function (p) {
|
|
158
|
-
if (
|
|
159
|
-
if (
|
|
159
|
+
if (intrinsic.hasOwn(UNSIGNED_FORBIDDEN, p.type)) throw _err("cms/bad-input", "the " + UNSIGNED_FORBIDDEN[p.type] + " attribute must not appear as an unsigned attribute (RFC 5652 sec. 11)");
|
|
160
|
+
if (intrinsic.hasOwn(seen, p.type)) throw _err("cms/bad-input", "unsignedAttrs must not repeat an attribute type (RFC 5652 sec. 5.3): " + p.type);
|
|
160
161
|
seen[p.type] = 1;
|
|
161
162
|
});
|
|
162
163
|
var setOf = b.set(pairs.map(function (p) { return b.sequence([b.oid(p.type), b.set(p.values)]); }));
|
package/lib/cms-verify.js
CHANGED
|
@@ -46,6 +46,7 @@ var SLHDSA_BY_OID = signScheme.SLHDSA_BY_OID; // shared SLH-DS
|
|
|
46
46
|
var validator = require("./validator-all");
|
|
47
47
|
var compositeSig = require("./composite-sig");
|
|
48
48
|
var guard = require("./guard-all");
|
|
49
|
+
var intrinsic = require("./guard-intrinsic");
|
|
49
50
|
var frameworkError = require("./framework-error");
|
|
50
51
|
var pkix = require("./schema-pkix");
|
|
51
52
|
|
|
@@ -73,7 +74,7 @@ var DIGEST_HASH = {
|
|
|
73
74
|
// foreign fault and loses this module's own unsupported-algorithm verdict.
|
|
74
75
|
var SIG_HASH = { sha1: "SHA-1", sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" };
|
|
75
76
|
// Is `name` a message-digest algorithm this verifier supports?
|
|
76
|
-
function _supportedDigest(name) { return
|
|
77
|
+
function _supportedDigest(name) { return intrinsic.hasOwn(DIGEST_HASH, name) && !!DIGEST_HASH[name]; }
|
|
77
78
|
// The digest of `content` under the named algorithm, resolved to a Buffer.
|
|
78
79
|
function _computeDigest(name, content) {
|
|
79
80
|
return subtle.digest(DIGEST_HASH[name], content).then(function (d) { return Buffer.from(d); });
|
package/lib/guard-all.js
CHANGED
|
@@ -68,7 +68,11 @@ var compress = require("./guard-compress");
|
|
|
68
68
|
var secret = require("./guard-secret");
|
|
69
69
|
var parsed = require("./guard-parsed");
|
|
70
70
|
var async_ = require("./guard-async");
|
|
71
|
-
|
|
71
|
+
// guard-intrinsic is deliberately NOT re-exported here. Re-exporting it gave taking the captures a
|
|
72
|
+
// second spelling, and the check that holds a module to the discipline could then be satisfied
|
|
73
|
+
// around: a module reached them as `guard.intrinsic.<x>` and never entered scope, so its remaining
|
|
74
|
+
// live reads were never named. With one spelling, a module's own require IS the fact, and the check
|
|
75
|
+
// reads it off the loaded module graph rather than guessing at the text.
|
|
72
76
|
|
|
73
77
|
module.exports = {
|
|
74
78
|
bytes: bytes,
|
|
@@ -88,5 +92,4 @@ module.exports = {
|
|
|
88
92
|
secret: secret,
|
|
89
93
|
parsed: parsed,
|
|
90
94
|
async: async_,
|
|
91
|
-
intrinsic: intrinsic,
|
|
92
95
|
};
|
package/lib/guard-intrinsic.js
CHANGED
|
@@ -55,6 +55,10 @@ var _bufferConcat = Buffer.concat;
|
|
|
55
55
|
var _bufferIsBuffer = Buffer.isBuffer;
|
|
56
56
|
var _bufferByteLength = Buffer.byteLength;
|
|
57
57
|
var _bufferCompare = Buffer.compare;
|
|
58
|
+
// `Buffer.prototype.equals` is the byte-identity test a binding decides on: an OCSP CertID says the
|
|
59
|
+
// response is about this issuer, a responder ID says this key signed it. Dispatched live, one
|
|
60
|
+
// answering true makes a response about a DIFFERENT certificate match the one being asked about.
|
|
61
|
+
var _bufferEquals = uncurry(Buffer.prototype.equals);
|
|
58
62
|
|
|
59
63
|
// The rest of what a guard reads from the runtime while it is deciding something. These are
|
|
60
64
|
// grouped here rather than captured file by file so the set is enumerable in one place and a guard
|
|
@@ -268,6 +272,7 @@ module.exports = {
|
|
|
268
272
|
isBuffer: _bufferIsBuffer,
|
|
269
273
|
byteLength: _bufferByteLength,
|
|
270
274
|
compare: _bufferCompare,
|
|
275
|
+
bufferEquals: _bufferEquals,
|
|
271
276
|
isView: _isView,
|
|
272
277
|
isInteger: _isInteger,
|
|
273
278
|
isSafeInteger: _isSafeInteger,
|
package/lib/ocsp-verify.js
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
|
|
30
30
|
var asn1 = require("./asn1-der");
|
|
31
31
|
var guard = require("./guard-all");
|
|
32
|
+
var intrinsic = require("./guard-intrinsic");
|
|
32
33
|
var oid = require("./oid");
|
|
33
34
|
var x509 = require("./schema-x509");
|
|
34
35
|
var compositeSig = require("./composite-sig");
|
|
@@ -48,11 +49,16 @@ var OID_OCSP_SIGNING = oid.byName("ocspSigning");
|
|
|
48
49
|
// Does this extended-key-usage list assert the given purpose? The answer IS an authorization
|
|
49
50
|
// decision, so it goes through the membership guard rather than a prototype method. See guard-list.
|
|
50
51
|
var _asserts = guard.list.contains;
|
|
52
|
+
// Captured at load. Each of these answers a binding question -- does this response concern the
|
|
53
|
+
// certificate being asked about, did this key sign it -- so a replacement made after this module
|
|
54
|
+
// loaded would decide the answer while every check around it still runs.
|
|
55
|
+
var _bufferFrom = intrinsic.bufferFrom;
|
|
56
|
+
var _bytesEqual = intrinsic.bufferEquals;
|
|
51
57
|
var OID_OCSP_NOCHECK = oid.byName("ocspNoCheck");
|
|
52
58
|
var OID_EKU = oid.byName("extKeyUsage");
|
|
53
59
|
var OID_KEY_USAGE = oid.byName("keyUsage");
|
|
54
60
|
|
|
55
|
-
function ocspDigest(alg, buf) { return subtle.digest(alg, buf).then(function (h) { return
|
|
61
|
+
function ocspDigest(alg, buf) { return subtle.digest(alg, buf).then(function (h) { return _bufferFrom(h); }); }
|
|
56
62
|
|
|
57
63
|
// The subjectPublicKey BIT STRING value (past the unused-bits octet) of an SPKI DER: the exact
|
|
58
64
|
// bytes an OCSP CertID issuerKeyHash / byKey KeyHash hash over (RFC 6960 sec. 4.1.1). Throws on a
|
|
@@ -108,12 +114,12 @@ function makeOcspVerify(deps) {
|
|
|
108
114
|
// 5280 sec. 7.1-equal byte encoding of the validated issuer DN to try.
|
|
109
115
|
async function ocspCertIdMatches(certID, cert, issuerNameCandidates, issuerKeyBits) {
|
|
110
116
|
if (certID.serialNumberHex !== cert.serialNumberHex) return false;
|
|
111
|
-
var hashName =
|
|
117
|
+
var hashName = intrinsic.hasOwn(OCSP_CERTID_HASHES, certID.hashAlgorithm.oid) ? OCSP_CERTID_HASHES[certID.hashAlgorithm.oid] : null;
|
|
112
118
|
if (!hashName) return false;
|
|
113
119
|
var keyHash = await ocspDigest(hashName, issuerKeyBits);
|
|
114
|
-
if (!certID.issuerKeyHash
|
|
120
|
+
if (!_bytesEqual(certID.issuerKeyHash, keyHash)) return false;
|
|
115
121
|
for (var i = 0; i < issuerNameCandidates.length; i++) {
|
|
116
|
-
if (certID.issuerNameHash
|
|
122
|
+
if (_bytesEqual(certID.issuerNameHash, await ocspDigest(hashName, issuerNameCandidates[i]))) return true;
|
|
117
123
|
}
|
|
118
124
|
return false;
|
|
119
125
|
}
|
|
@@ -126,7 +132,7 @@ function makeOcspVerify(deps) {
|
|
|
126
132
|
var matchesIssuer = false;
|
|
127
133
|
try {
|
|
128
134
|
if (rid.byName) matchesIssuer = dnEqual(rid.byName.rdns, cert.issuer.rdns);
|
|
129
|
-
else if (rid.byKey) matchesIssuer = rid.byKey
|
|
135
|
+
else if (rid.byKey) matchesIssuer = _bytesEqual(rid.byKey, await ocspDigest("SHA-1", issuerKeyBits));
|
|
130
136
|
} catch (_e) { matchesIssuer = false; }
|
|
131
137
|
if (matchesIssuer) return issuer.workingPublicKey;
|
|
132
138
|
|
|
@@ -137,7 +143,7 @@ function makeOcspVerify(deps) {
|
|
|
137
143
|
var identifies = false;
|
|
138
144
|
try {
|
|
139
145
|
if (rid.byName) identifies = dnEqual(rid.byName.rdns, rc.subject.rdns);
|
|
140
|
-
else if (rid.byKey) identifies = rid.byKey
|
|
146
|
+
else if (rid.byKey) identifies = _bytesEqual(rid.byKey, await ocspDigest("SHA-1", ocspKeyValue(rc.subjectPublicKeyInfo.bytes)));
|
|
141
147
|
} catch (_e) { identifies = false; }
|
|
142
148
|
if (!identifies) continue;
|
|
143
149
|
// The delegate MUST be issued directly by the CA that issued the target.
|
package/lib/path-validate.js
CHANGED
|
@@ -48,6 +48,7 @@ var cmpVerify = require("./cmp-verify");
|
|
|
48
48
|
var cmsVerify = require("./cms-verify");
|
|
49
49
|
var cmpSession = require("./cmp-session");
|
|
50
50
|
var guard = require("./guard-all");
|
|
51
|
+
var intrinsic = require("./guard-intrinsic");
|
|
51
52
|
var constants = require("./constants");
|
|
52
53
|
var validator = require("./validator-all");
|
|
53
54
|
var cms = require("./schema-cms");
|
|
@@ -770,7 +771,7 @@ function isSubtreeBaseValid(tag, base) {
|
|
|
770
771
|
case 1: case 2: case 6: return typeof base === "string"; // rfc822Name / dNSName / URI
|
|
771
772
|
case 7: return (Buffer.isBuffer(base) || base instanceof Uint8Array) &&
|
|
772
773
|
(base.length === 8 || base.length === 32); // iPAddress: address + mask
|
|
773
|
-
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
|
|
774
775
|
default: return base !== undefined;
|
|
775
776
|
}
|
|
776
777
|
}
|
|
@@ -781,7 +782,7 @@ function isSubtreeBaseValid(tag, base) {
|
|
|
781
782
|
// disabling the constraint the caller configured -- so it throws instead.
|
|
782
783
|
function checkedSubtreeSeeds(list, optName) {
|
|
783
784
|
if (list === undefined || list === null) return [];
|
|
784
|
-
if (!
|
|
785
|
+
if (!intrinsic.isArray(list)) throw E("path/bad-input", "validate: opts." + optName + " must be an array of { tag, base } subtree entries");
|
|
785
786
|
return list.map(function (st) {
|
|
786
787
|
if (!st || typeof st !== "object" || !Number.isInteger(st.tag) || st.tag < 0 || st.tag > 8 || !isSubtreeBaseValid(st.tag, st.base)) {
|
|
787
788
|
throw E("path/bad-input", "validate: opts." + optName + " entries must be { tag: <GeneralName tag number 0..8>, base: <that form's constraint value> }");
|
|
@@ -1108,8 +1109,8 @@ function unrecognizedCriticalExtension(cert, isTarget) {
|
|
|
1108
1109
|
for (var i = 0; i < cert.extensions.length; i++) {
|
|
1109
1110
|
var ext = cert.extensions[i];
|
|
1110
1111
|
if (!ext.critical) continue;
|
|
1111
|
-
if (!
|
|
1112
|
-
if (isTarget &&
|
|
1112
|
+
if (!intrinsic.hasOwn(PROCESSED_EXTENSIONS, ext.oid)) return ext.oid;
|
|
1113
|
+
if (isTarget && intrinsic.hasOwn(TARGET_UNPROCESSED_IF_CRITICAL, ext.oid)) return ext.oid;
|
|
1113
1114
|
}
|
|
1114
1115
|
return null;
|
|
1115
1116
|
}
|
|
@@ -1216,7 +1217,7 @@ async function validate(path, opts) {
|
|
|
1216
1217
|
guard.identifier.assertKnownKeys(opts, _VALIDATE_OPTS, E, "path/bad-input",
|
|
1217
1218
|
"pki.path.validate has an unknown option. The anchor here is `trustAnchor`, singular; " +
|
|
1218
1219
|
"pki.path.build takes `trustAnchors`. The unknown option was: ");
|
|
1219
|
-
if (!
|
|
1220
|
+
if (!intrinsic.isArray(path)) throw E("path/bad-input", "validate: path must be an array of certificates");
|
|
1220
1221
|
// Bound the per-cert asymmetric-verify work BEFORE parsing an untrusted bundle
|
|
1221
1222
|
// (the policy-tree cap guards CVE-2023-0464-style blow-up; this guards linear
|
|
1222
1223
|
// crypto amplification from an oversized path). Entry-point tier: throw.
|
|
@@ -1241,7 +1242,7 @@ async function validate(path, opts) {
|
|
|
1241
1242
|
// Membership is tested with indexOf, so a raw string here would be consulted
|
|
1242
1243
|
// as a SUBSTRING match -- validated to an array of strings instead.
|
|
1243
1244
|
if (opts.userInitialPolicySet !== undefined) {
|
|
1244
|
-
var uipsOk =
|
|
1245
|
+
var uipsOk = intrinsic.isArray(opts.userInitialPolicySet) && opts.userInitialPolicySet.length > 0 &&
|
|
1245
1246
|
guard.list.allMatch(opts.userInitialPolicySet, function (p) { return typeof p === "string" && p.length > 0; });
|
|
1246
1247
|
if (!uipsOk) throw E("path/bad-input", "validate: opts.userInitialPolicySet must be a non-empty array of policy OID strings");
|
|
1247
1248
|
// Each entry is compared (indexOf) against canonical decoder output, so a
|
|
@@ -1492,7 +1493,7 @@ async function validate(path, opts) {
|
|
|
1492
1493
|
anchorPurposeApplied = true;
|
|
1493
1494
|
// The map is the operator's RESTRICTION, so it is asked for its own entry: an inherited
|
|
1494
1495
|
// `true` would grant the purpose on every anchor whose map does not name it.
|
|
1495
|
-
if (!
|
|
1496
|
+
if (!intrinsic.hasOwn(ta.purposes, checkPurpose) || ta.purposes[checkPurpose] !== true) {
|
|
1496
1497
|
checks.push({ name: "purposeTrust", ok: false, code: "path/purpose-not-trusted" }); failed = true;
|
|
1497
1498
|
}
|
|
1498
1499
|
}
|
|
@@ -2633,7 +2634,7 @@ function _hasPurposeScopedMetadata(ta) {
|
|
|
2633
2634
|
}
|
|
2634
2635
|
|
|
2635
2636
|
function assertAnchorConstraints(ta, checkPurpose) {
|
|
2636
|
-
var d = (checkPurpose && ta && ta.distrustAfter &&
|
|
2637
|
+
var d = (checkPurpose && ta && ta.distrustAfter && intrinsic.hasOwn(ta.distrustAfter, checkPurpose))
|
|
2637
2638
|
? ta.distrustAfter[checkPurpose] : null;
|
|
2638
2639
|
if (d == null) return null;
|
|
2639
2640
|
return guard.time.assertValid(d, E, "path/bad-input", "trustAnchor.distrustAfter." + checkPurpose);
|
|
@@ -2642,7 +2643,7 @@ function assertAnchorConstraints(ta, checkPurpose) {
|
|
|
2642
2643
|
function resolvePurposeOpts(opts) {
|
|
2643
2644
|
var requiredEku = null;
|
|
2644
2645
|
if (opts.requiredEku !== undefined) {
|
|
2645
|
-
if (!
|
|
2646
|
+
if (!intrinsic.isArray(opts.requiredEku) || opts.requiredEku.length === 0) {
|
|
2646
2647
|
throw E("path/bad-input", "validate: opts.requiredEku must be a non-empty array of key-purpose OID names or dotted OID strings");
|
|
2647
2648
|
}
|
|
2648
2649
|
requiredEku = opts.requiredEku.map(function (p) {
|
|
@@ -2678,7 +2679,7 @@ function toAnchor(entry) {
|
|
|
2678
2679
|
// (name matching), publicKey bytes and the algorithm OID (the sec. 6.1.4 key
|
|
2679
2680
|
// hand-off). trustAnchors is a caller option, so a malformed tuple is a config
|
|
2680
2681
|
// error and fails closed at entry, not a downstream no-path / soft verdict.
|
|
2681
|
-
if (!
|
|
2682
|
+
if (!intrinsic.isArray(entry.name.rdns) || !Buffer.isBuffer(entry.publicKey) || typeof entry.algorithm !== "string") {
|
|
2682
2683
|
throw E("path/bad-input", "build: a trustAnchor tuple must be { name: { rdns: [...] }, publicKey: Buffer, algorithm: OID string }");
|
|
2683
2684
|
}
|
|
2684
2685
|
return entry;
|
|
@@ -2754,9 +2755,9 @@ function _pushCandidates(frame, scored, stack, counter) {
|
|
|
2754
2755
|
// Loop detection and the dedupe sets below are resource bounds, so they are asked through the
|
|
2755
2756
|
// captured methods: a replaced `Set.prototype.has` answering false never prunes and the build
|
|
2756
2757
|
// walks a cycle until a cap stops it.
|
|
2757
|
-
if (
|
|
2758
|
+
if (intrinsic.setHas(frame.keys, candKey)) continue;
|
|
2758
2759
|
var childKeys = new Set(frame.keys);
|
|
2759
|
-
|
|
2760
|
+
intrinsic.setAdd(childKeys, candKey);
|
|
2760
2761
|
stack.push({ chain: [cand].concat(frame.chain), hop: frame.hop + 1, keys: childKeys });
|
|
2761
2762
|
}
|
|
2762
2763
|
return n;
|
|
@@ -2847,16 +2848,16 @@ async function _fetchAiaIssuers(current, aia) {
|
|
|
2847
2848
|
// IP literals only (already validated above), never a hostname whose resolved address we cannot verify.
|
|
2848
2849
|
if (net.isIP(u.hostname.replace(/^\[(.*)\]$/, "$1")) === 0 && !aia.transportGuardsAddresses) continue;
|
|
2849
2850
|
u.hash = ""; // the fragment is never sent on the wire -> not part of the request/dedupe identity
|
|
2850
|
-
if (
|
|
2851
|
+
if (intrinsic.setHas(aia.fetchedUrls, u.href) || intrinsic.setHas(seenThisCert, u.href)) continue; // dedupe (build-wide OR same-cert) on the normalized URL
|
|
2851
2852
|
if (seenThisCert.size >= aia.maxPerCert) break; // per-cert DISTINCT-url cap, checked BEFORE appending so maxAiaPerCert:0 collects nothing (no fetch at all)
|
|
2852
|
-
|
|
2853
|
+
intrinsic.setAdd(seenThisCert, u.href);
|
|
2853
2854
|
uris.push(u.href);
|
|
2854
2855
|
}
|
|
2855
2856
|
var out = [];
|
|
2856
2857
|
for (var k = 0; k < uris.length; k++) {
|
|
2857
|
-
if (
|
|
2858
|
+
if (intrinsic.setHas(aia.fetchedUrls, uris[k])) continue; // a same-cert normalization-equal duplicate
|
|
2858
2859
|
if (aia.fetches >= aia.maxFetches) break; // total budget reached -> STOP fetching (a SILENT cap, never a throw that denies a buildable path)
|
|
2859
|
-
|
|
2860
|
+
intrinsic.setAdd(aia.fetchedUrls, uris[k]); // mark BEFORE the fetch -> fetched at most once, even on failure
|
|
2860
2861
|
aia.fetches += 1;
|
|
2861
2862
|
var certs;
|
|
2862
2863
|
try { certs = await _aiaFetchOne(uris[k], aia); }
|
|
@@ -2971,7 +2972,7 @@ async function build(leaf, opts) {
|
|
|
2971
2972
|
|
|
2972
2973
|
var poolInput = opts.candidates !== undefined ? opts.candidates : opts.intermediates;
|
|
2973
2974
|
if (poolInput === undefined) poolInput = [];
|
|
2974
|
-
if (!
|
|
2975
|
+
if (!intrinsic.isArray(poolInput)) throw E("path/bad-input", "build: opts.candidates must be an array of certificates");
|
|
2975
2976
|
// A pool larger than the absolute ceiling is rejected at entry (bounds the
|
|
2976
2977
|
// parse work on an untrusted bundle) -- distinct from the per-search tick
|
|
2977
2978
|
// budget below, which an operator may set lower to cap the DFS.
|
|
@@ -2984,9 +2985,9 @@ async function build(leaf, opts) {
|
|
|
2984
2985
|
// repeating CMS returning the same issuer) is appended AT MOST ONCE -- it otherwise inflates the pool, charging
|
|
2985
2986
|
// the candidate budget and the ceiling for each copy before the issuer is ever evaluated.
|
|
2986
2987
|
var poolDerKeys = new Set();
|
|
2987
|
-
for (var pdk = 0; pdk < pool.length; pdk++)
|
|
2988
|
+
for (var pdk = 0; pdk < pool.length; pdk++) intrinsic.setAdd(poolDerKeys, certDerKey(pool[pdk]));
|
|
2988
2989
|
|
|
2989
|
-
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");
|
|
2990
2991
|
var anchors = opts.trustAnchors.map(toAnchor);
|
|
2991
2992
|
|
|
2992
2993
|
guard.time.assertValid(opts.time, E, "path/bad-input", "build: opts.time (the validity-window check date, forwarded to validate)");
|
|
@@ -3146,8 +3147,8 @@ async function build(leaf, opts) {
|
|
|
3146
3147
|
var fetched = await _fetchAiaIssuers(fbCur, aiaCtx); // append newly-fetched issuer(s) to the SHARED pool
|
|
3147
3148
|
for (var fj = 0; fj < fetched.length; fj++) {
|
|
3148
3149
|
var fdk = certDerKey(fetched[fj]);
|
|
3149
|
-
if (
|
|
3150
|
-
|
|
3150
|
+
if (intrinsic.setHas(poolDerKeys, fdk)) continue; // a byte-duplicate (mirror URL / repeating CMS) -> add once, never re-charge the budget/ceiling
|
|
3151
|
+
intrinsic.setAdd(poolDerKeys, fdk);
|
|
3151
3152
|
if (pool.length < poolCeiling) pool.push(fetched[fj]);
|
|
3152
3153
|
}
|
|
3153
3154
|
}
|
package/lib/pkcs12-build.js
CHANGED
|
@@ -48,6 +48,7 @@ var cmsDecrypt = require("./cms-decrypt"); // the @internal decryptEnvelopedDa
|
|
|
48
48
|
var schemaPkcs12 = require("./schema-pkcs12");
|
|
49
49
|
var pkix = require("./schema-pkix");
|
|
50
50
|
var guard = require("./guard-all");
|
|
51
|
+
var intrinsic = require("./guard-intrinsic");
|
|
51
52
|
var frameworkError = require("./framework-error");
|
|
52
53
|
var C = require("./constants");
|
|
53
54
|
|
|
@@ -368,7 +369,7 @@ function _buildBag(bag, opts, depth) {
|
|
|
368
369
|
if (!bag || typeof bag !== "object") throw _err("pkcs12/bad-input", "each bag must be an object with a type");
|
|
369
370
|
// Checked against THIS type's table. An unrecognized type falls through to the switch below,
|
|
370
371
|
// which is the one place that names the permitted set.
|
|
371
|
-
if (
|
|
372
|
+
if (intrinsic.hasOwn(_BAG_KEYS_BY_TYPE, bag.type)) {
|
|
372
373
|
guard.identifier.assertKnownKeys(bag, _BAG_KEYS_BY_TYPE[bag.type], _err, "pkcs12/bad-input", function (k) {
|
|
373
374
|
return "unknown field " + JSON.stringify(k) + " on a " + JSON.stringify(bag.type) +
|
|
374
375
|
" bag; that field is not read for this bag type";
|
|
@@ -588,7 +589,7 @@ async function _buildMacData(macOpts, sharedPassword, authSafeDer) {
|
|
|
588
589
|
function _specForm(spec) {
|
|
589
590
|
var isObject = !!spec && typeof spec === "object" && !Buffer.isBuffer(spec);
|
|
590
591
|
var safeContents = isObject ? spec.safeContents : null;
|
|
591
|
-
var full =
|
|
592
|
+
var full = intrinsic.isArray(safeContents);
|
|
592
593
|
return { isObject: isObject, full: full, safeContents: full ? safeContents : null };
|
|
593
594
|
}
|
|
594
595
|
|
|
@@ -599,7 +600,7 @@ function _normalizeSpec(spec, opts, form) {
|
|
|
599
600
|
// be handed different values.
|
|
600
601
|
var recipientCerts = opts.recipientCerts;
|
|
601
602
|
var enveloped = recipientCerts != null;
|
|
602
|
-
if (enveloped && (!
|
|
603
|
+
if (enveloped && (!intrinsic.isArray(recipientCerts) || !recipientCerts.length)) throw _err("pkcs12/bad-input", "opts.recipientCerts must be a non-empty array of recipient certificates");
|
|
603
604
|
var certBags = [];
|
|
604
605
|
if (spec.cert != null) certBags.push({ type: "cert", cert: spec.cert, friendlyName: spec.friendlyName, localKeyId: spec.localKeyId });
|
|
605
606
|
if (spec.ca != null && !Array.isArray(spec.ca)) throw _err("pkcs12/bad-input", "spec.ca must be an array of certificates");
|
|
@@ -749,7 +750,7 @@ async function _build(spec, opts) {
|
|
|
749
750
|
// array reached _buildMacData as an empty descriptor and produced the DEFAULT MAC. Excluded by
|
|
750
751
|
// the same test opts.integrity already applies, read through the load-time capture so a
|
|
751
752
|
// replaced Array.isArray cannot reopen it.
|
|
752
|
-
if (opts.mac != null && opts.mac !== false && (typeof opts.mac !== "object" ||
|
|
753
|
+
if (opts.mac != null && opts.mac !== false && (typeof opts.mac !== "object" || intrinsic.isArray(opts.mac))) throw _err("pkcs12/bad-input", "opts.mac must be false or a { algorithm, hash, salt, iterations, keyLength } object");
|
|
753
754
|
if (opts.mac != null && opts.mac !== false) {
|
|
754
755
|
// The algorithm selects the derivation, so the table that runs is the one belonging to it.
|
|
755
756
|
// An unrecognized spelling keeps the union; _buildMacData refuses it by name a moment later.
|
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/sign-scheme.js
CHANGED
|
@@ -23,10 +23,28 @@ var webcrypto = require("./webcrypto");
|
|
|
23
23
|
var subtle = webcrypto.webcrypto.subtle;
|
|
24
24
|
var validator = require("./validator-all");
|
|
25
25
|
var guard = require("./guard-all");
|
|
26
|
+
var intrinsic = require("./guard-intrinsic");
|
|
26
27
|
var compositeSig = require("./composite-sig");
|
|
27
28
|
var b = asn1.build;
|
|
28
29
|
function O(name) { return oid.byName(name); }
|
|
29
30
|
|
|
31
|
+
// Captured at load. This module decides which algorithm a signature runs under and whether the
|
|
32
|
+
// caller's key matches the certificate it claims, so an operation read at call time is one the
|
|
33
|
+
// caller answers: a replaced case fold reports two different algorithm names equal, and a replaced
|
|
34
|
+
// `Buffer.isBuffer` admits a key that is not bytes.
|
|
35
|
+
var _BigInt = intrinsic.BigInt;
|
|
36
|
+
var _String = intrinsic.String;
|
|
37
|
+
var _filter = intrinsic.filter;
|
|
38
|
+
var _stringify = intrinsic.stringify;
|
|
39
|
+
var _toUpperCase = intrinsic.toUpperCase;
|
|
40
|
+
var _isBuffer = intrinsic.isBuffer;
|
|
41
|
+
var _bufferFrom = intrinsic.bufferFrom;
|
|
42
|
+
// Asks the value's internal slot rather than `instanceof`, which reads the `Uint8Array` global at
|
|
43
|
+
// call time and answers about whichever constructor is bound then. It also settles the two cases
|
|
44
|
+
// `instanceof` gets wrong on their own terms: a real typed array from another realm is one, and an
|
|
45
|
+
// object merely built on the prototype is not.
|
|
46
|
+
var _isU8 = intrinsic.types.isUint8Array;
|
|
47
|
+
|
|
30
48
|
var HASH = { sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" };
|
|
31
49
|
var NODE_DIGEST = { sha256: "sha256", sha384: "sha384", sha512: "sha512", shake128: "shake128", shake256: "shake256" };
|
|
32
50
|
var PSS_SALT = { "SHA-256": 32, "SHA-384": 48, "SHA-512": 64 };
|
|
@@ -61,9 +79,9 @@ var MLDSA_SUITABLE_DIGEST = {
|
|
|
61
79
|
// legitimate one; the RED vectors (a below-strength digest still refused with its name planted on
|
|
62
80
|
// Object.prototype, on both the sign and verify paths) are the guard.
|
|
63
81
|
function mldsaDigestSuitable(mlName, md) {
|
|
64
|
-
if (!
|
|
82
|
+
if (!intrinsic.hasOwn(MLDSA_SUITABLE_DIGEST, mlName)) return false;
|
|
65
83
|
var row = MLDSA_SUITABLE_DIGEST[mlName];
|
|
66
|
-
return !!row &&
|
|
84
|
+
return !!row && intrinsic.hasOwn(row, md) && !!row[md];
|
|
67
85
|
}
|
|
68
86
|
|
|
69
87
|
var SLHDSA_BY_OID = {};
|
|
@@ -79,7 +97,7 @@ function _algId(name, shape) { return shape === "null" ? b.sequence([b.oid(O(nam
|
|
|
79
97
|
function _pssAlgId(digestName) {
|
|
80
98
|
var hashAlg = b.sequence([b.oid(O(digestName)), b.nullValue()]);
|
|
81
99
|
var mgf = b.sequence([b.oid(O("mgf1")), hashAlg]);
|
|
82
|
-
var params = b.sequence([b.explicit(0, hashAlg), b.explicit(1, mgf), b.explicit(2, b.integer(
|
|
100
|
+
var params = b.sequence([b.explicit(0, hashAlg), b.explicit(1, mgf), b.explicit(2, b.integer(_BigInt(PSS_SALT[HASH[digestName]])))]);
|
|
83
101
|
return b.sequence([b.oid(O("rsassaPss")), params]);
|
|
84
102
|
}
|
|
85
103
|
// The hash an id-RSASSA-PSS SPKI restricts its key to, or null when it restricts none.
|
|
@@ -104,7 +122,7 @@ function _pssHashFromSpki(cert, E) {
|
|
|
104
122
|
if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children) {
|
|
105
123
|
throw E("unsupported-algorithm", "the id-RSASSA-PSS key parameters are not an RSASSA-PSS-params SEQUENCE (RFC 4055 sec. 3.1)");
|
|
106
124
|
}
|
|
107
|
-
var hashField = node.children
|
|
125
|
+
var hashField = _filter(node.children, function (c) { return c.tagClass === "context" && c.tagNumber === 0; })[0];
|
|
108
126
|
// DEFAULT sha1Identifier -- an omitted hashAlgorithm names SHA-1, which this toolkit does not
|
|
109
127
|
// sign or verify with, so it is reported as the pin it is and refused by the caller's own table.
|
|
110
128
|
if (!hashField) return "sha1";
|
|
@@ -150,15 +168,15 @@ function resolveSignScheme(cert, so, noSignedAttrs, E) {
|
|
|
150
168
|
var comp = compositeSig.COMPOSITE_ALGS[keyOid];
|
|
151
169
|
if (comp) {
|
|
152
170
|
if (comp.trad.unsupported) throw E("unsupported-algorithm", "composite " + comp.name + ": " + comp.trad.unsupported);
|
|
153
|
-
if (so.digestAlgorithm && so.digestAlgorithm !== comp.phCms) throw E("bad-input", "composite " + comp.name + " fixes the digestAlgorithm to " + comp.phCms + " (draft-ietf-lamps-cms-composite-sigs sec. 3.4); " +
|
|
171
|
+
if (so.digestAlgorithm && so.digestAlgorithm !== comp.phCms) throw E("bad-input", "composite " + comp.name + " fixes the digestAlgorithm to " + comp.phCms + " (draft-ietf-lamps-cms-composite-sigs sec. 3.4); " + _stringify(so.digestAlgorithm) + " conflicts");
|
|
154
172
|
return { composite: comp, digest: comp.phCms, digestAlgId: _algId(comp.phCms, "absent"), sigAlgId: _algId(comp.name, "absent") };
|
|
155
173
|
}
|
|
156
174
|
if (keyOid === O("rsaEncryption") || keyOid === O("rsassaPss")) {
|
|
157
175
|
var isPssKey = keyOid === O("rsassaPss");
|
|
158
176
|
var pinned = isPssKey ? _pssHashFromSpki(cert, E) : null;
|
|
159
|
-
if (pinned && so.digestAlgorithm && so.digestAlgorithm !== pinned) throw E("bad-input", "the signer key restricts the RSASSA-PSS digest to " + pinned + ", but digestAlgorithm " +
|
|
177
|
+
if (pinned && so.digestAlgorithm && so.digestAlgorithm !== pinned) throw E("bad-input", "the signer key restricts the RSASSA-PSS digest to " + pinned + ", but digestAlgorithm " + _stringify(so.digestAlgorithm) + " was requested");
|
|
160
178
|
var d = so.digestAlgorithm || pinned || "sha256";
|
|
161
|
-
if (!HASH[d]) throw E("unsupported-algorithm", "unsupported RSA digest algorithm " +
|
|
179
|
+
if (!HASH[d]) throw E("unsupported-algorithm", "unsupported RSA digest algorithm " + _stringify(d));
|
|
162
180
|
if (so.pss || isPssKey) return { digest: d, digestAlgId: _algId(d, "absent"), sigAlgId: _pssAlgId(d), imp: { name: "RSA-PSS", hash: HASH[d] }, sign: { name: "RSA-PSS", saltLength: PSS_SALT[HASH[d]] }, ecdsaDer: false };
|
|
163
181
|
var rsaSigAlgId = so.combinedRsaSig ? _algId(RSA_PKCS1_SIG[d], "null") : _algId("rsaEncryption", "null");
|
|
164
182
|
return { digest: d, digestAlgId: _algId(d, "absent"), sigAlgId: rsaSigAlgId, imp: { name: "RSASSA-PKCS1-v1_5", hash: HASH[d] }, sign: { name: "RSASSA-PKCS1-v1_5" }, ecdsaDer: false };
|
|
@@ -170,13 +188,13 @@ function resolveSignScheme(cert, so, noSignedAttrs, E) {
|
|
|
170
188
|
var ec = EC_BY_CURVE_OID[curveOid];
|
|
171
189
|
if (!ec) throw E("unsupported-algorithm", "the signer key is on an unsupported EC curve");
|
|
172
190
|
var de = so.digestAlgorithm || "sha256";
|
|
173
|
-
if (!HASH[de]) throw E("unsupported-algorithm", "unsupported ECDSA digest algorithm " +
|
|
191
|
+
if (!HASH[de]) throw E("unsupported-algorithm", "unsupported ECDSA digest algorithm " + _stringify(de));
|
|
174
192
|
return { digest: de, digestAlgId: _algId(de, "absent"), sigAlgId: _algId(ECDSA_ALG[de], "absent"), imp: { name: "ECDSA", namedCurve: ec.curve }, sign: { name: "ECDSA", hash: HASH[de] }, ecdsaDer: true, coordLen: ec.coordLen };
|
|
175
193
|
}
|
|
176
194
|
if (keyOid === O("Ed25519") || keyOid === O("Ed448")) {
|
|
177
195
|
var name = keyOid === O("Ed25519") ? "Ed25519" : "Ed448";
|
|
178
196
|
var dd = so.digestAlgorithm || (name === "Ed25519" ? "sha512" : "shake256");
|
|
179
|
-
if (!NODE_DIGEST[dd]) throw E("unsupported-algorithm", "unsupported " + name + " digest algorithm " +
|
|
197
|
+
if (!NODE_DIGEST[dd]) throw E("unsupported-algorithm", "unsupported " + name + " digest algorithm " + _stringify(dd));
|
|
180
198
|
return { digest: dd, digestAlgId: _algId(dd, "absent"), sigAlgId: _algId(name, "absent"), imp: { name: name }, sign: { name: name }, ecdsaDer: false };
|
|
181
199
|
}
|
|
182
200
|
if (MLDSA_BY_OID[keyOid]) {
|
|
@@ -186,14 +204,14 @@ function resolveSignScheme(cert, so, noSignedAttrs, E) {
|
|
|
186
204
|
md = "sha512";
|
|
187
205
|
} else {
|
|
188
206
|
md = so.digestAlgorithm || "sha512";
|
|
189
|
-
if (!NODE_DIGEST[md]) throw E("unsupported-algorithm", "unsupported ML-DSA message digest " +
|
|
207
|
+
if (!NODE_DIGEST[md]) throw E("unsupported-algorithm", "unsupported ML-DSA message digest " + _stringify(md));
|
|
190
208
|
if (!mldsaDigestSuitable(mlName, md)) throw E("unsupported-algorithm", "the " + md + " message digest is below the security strength of " + mlName + " (RFC 9882 sec. 3.3)");
|
|
191
209
|
}
|
|
192
210
|
return { digest: md, digestAlgId: _algId(md, "absent"), sigAlgId: _algId(oid.name(keyOid), "absent"), imp: { name: mlName }, sign: { name: mlName }, ecdsaDer: false };
|
|
193
211
|
}
|
|
194
212
|
if (SLHDSA_BY_OID[keyOid]) {
|
|
195
213
|
var slh = SLHDSA_BY_OID[keyOid];
|
|
196
|
-
if (so.digestAlgorithm && so.digestAlgorithm !== slh.digest) throw E("bad-input", "SLH-DSA " + slh.wc + " requires the " + slh.digest + " message digest (RFC 9814 sec. 4); digestAlgorithm " +
|
|
214
|
+
if (so.digestAlgorithm && so.digestAlgorithm !== slh.digest) throw E("bad-input", "SLH-DSA " + slh.wc + " requires the " + slh.digest + " message digest (RFC 9814 sec. 4); digestAlgorithm " + _stringify(so.digestAlgorithm) + " conflicts");
|
|
197
215
|
return { digest: slh.digest, digestAlgId: _algId(slh.digest, "absent"), sigAlgId: _algId(oid.name(keyOid), "absent"), imp: { name: slh.wc }, sign: { name: slh.wc }, ecdsaDer: false };
|
|
198
216
|
}
|
|
199
217
|
throw E("unsupported-algorithm", "unsupported signer key algorithm " + keyOid);
|
|
@@ -204,7 +222,7 @@ function _assertKeyMatchesScheme(key, imp, E) {
|
|
|
204
222
|
var ka = key.algorithm || {};
|
|
205
223
|
// WebCrypto algorithm names are ASCII-case-insensitive (RSASSA-PKCS1-v1_5 and RSASSA-PKCS1-V1_5 are one
|
|
206
224
|
// algorithm), so compare case-folded; a genuine mismatch (e.g. RSASSA-PKCS1-v1_5 vs ECDSA) still fails.
|
|
207
|
-
if (
|
|
225
|
+
if (_toUpperCase(_String(ka.name)) !== _toUpperCase(_String(imp.name))) throw E("bad-input", "the signer CryptoKey algorithm (" + ka.name + ") does not match the certificate's key algorithm (" + imp.name + ")");
|
|
208
226
|
if (imp.hash && (!ka.hash || ka.hash.name !== imp.hash)) throw E("bad-input", "the signer CryptoKey hash (" + (ka.hash && ka.hash.name) + ") does not match the signing digest (" + imp.hash + ")");
|
|
209
227
|
if (imp.namedCurve && ka.namedCurve !== imp.namedCurve) throw E("bad-input", "the signer CryptoKey curve (" + ka.namedCurve + ") does not match the certificate curve (" + imp.namedCurve + ")");
|
|
210
228
|
}
|
|
@@ -212,19 +230,19 @@ function _normPkcs8(k, label, E) {
|
|
|
212
230
|
// A caller's own Buffer is borrowed, not copied, the same rule _importKey states below. Every
|
|
213
231
|
// other form is copied, and neither copy is duplicated further, so a private key never gains a
|
|
214
232
|
// plaintext duplicate this module cannot account for.
|
|
215
|
-
if (
|
|
216
|
-
if (k
|
|
233
|
+
if (_isBuffer(k)) return guard.bytes.view(k, E, "bad-input", label);
|
|
234
|
+
if (_isU8(k)) return guard.bytes.snapshot(k, E, "bad-input", label);
|
|
217
235
|
if (typeof k === "string") { try { return pkcs8.pemDecode(k); } catch (e) { throw E("bad-input", label + " PEM could not be decoded", e); } }
|
|
218
236
|
throw E("bad-input", label + " must be a PKCS#8 DER Buffer, Uint8Array, or PEM string");
|
|
219
237
|
}
|
|
220
238
|
function _normCompositeKeys(key, comp, E) {
|
|
221
|
-
if (!key || typeof key !== "object" ||
|
|
239
|
+
if (!key || typeof key !== "object" || _isBuffer(key) || _isU8(key) || key.mldsa == null || key.trad == null) {
|
|
222
240
|
throw E("bad-input", "a composite " + comp.name + " signer key must be { mldsa: <PKCS#8>, trad: <PKCS#8> }");
|
|
223
241
|
}
|
|
224
242
|
return { mldsa: _normPkcs8(key.mldsa, "the composite ML-DSA component key", E), trad: _normPkcs8(key.trad, "the composite traditional component key", E) };
|
|
225
243
|
}
|
|
226
244
|
function _importKey(key, imp, E) {
|
|
227
|
-
if (key && typeof key === "object" && !
|
|
245
|
+
if (key && typeof key === "object" && !_isBuffer(key) && !(_isU8(key)) && key.type === "private") {
|
|
228
246
|
_assertKeyMatchesScheme(key, imp, E);
|
|
229
247
|
// `key` is documented as taking a CryptoKey, so one created outside this engine signs here too --
|
|
230
248
|
// it is re-imported through this engine, because it carries none of the key material this engine
|
|
@@ -236,8 +254,8 @@ function _importKey(key, imp, E) {
|
|
|
236
254
|
// only thing that reads it. A caller's own Buffer is passed through untouched: they hold a live
|
|
237
255
|
// reference and will use it again, so clearing it would destroy their key.
|
|
238
256
|
var der, owned = false;
|
|
239
|
-
if (
|
|
240
|
-
else if (key
|
|
257
|
+
if (_isBuffer(key)) der = guard.bytes.view(key, E, "bad-input", "the signer private key");
|
|
258
|
+
else if (_isU8(key)) { der = guard.bytes.snapshot(key, E, "bad-input", "the signer private key"); owned = true; }
|
|
241
259
|
else if (typeof key === "string") {
|
|
242
260
|
try { der = pkcs8.pemDecode(key); }
|
|
243
261
|
catch (e) { throw E("bad-input", "the signer PEM private key could not be decoded", e); }
|
|
@@ -260,11 +278,11 @@ function _importKey(key, imp, E) {
|
|
|
260
278
|
// path signs both component keys and returns mldsaSig || tradSig (composite-sig.js owns it).
|
|
261
279
|
function signOverTbs(scheme, key, signedBytes, E) {
|
|
262
280
|
if (scheme.composite) {
|
|
263
|
-
return compositeSig.compositeSign(scheme.composite, _normCompositeKeys(key, scheme.composite, E), signedBytes).then(function (sig) { return
|
|
281
|
+
return compositeSig.compositeSign(scheme.composite, _normCompositeKeys(key, scheme.composite, E), signedBytes).then(function (sig) { return _bufferFrom(sig); });
|
|
264
282
|
}
|
|
265
283
|
return _importKey(key, scheme.imp, E).then(function (priv) {
|
|
266
284
|
return subtle.sign(scheme.sign, priv, signedBytes).then(function (sigRaw) {
|
|
267
|
-
var sig =
|
|
285
|
+
var sig = _bufferFrom(sigRaw);
|
|
268
286
|
if (scheme.ecdsaDer) sig = validator.sig.rawToEcdsaDer(sig, scheme.coordLen);
|
|
269
287
|
return sig;
|
|
270
288
|
});
|
package/lib/tsp-sign.js
CHANGED
|
@@ -25,6 +25,7 @@ var pkiX509 = require("./schema-x509");
|
|
|
25
25
|
var smime = require("./schema-smime");
|
|
26
26
|
var schemaTsp = require("./schema-tsp");
|
|
27
27
|
var guard = require("./guard-all");
|
|
28
|
+
var intrinsic = require("./guard-intrinsic");
|
|
28
29
|
var frameworkError = require("./framework-error");
|
|
29
30
|
|
|
30
31
|
var pkix = require("./schema-pkix");
|
|
@@ -507,7 +508,7 @@ var _BIND_DIGEST = { sha256: "sha256", sha384: "sha384", sha512: "sha512" };
|
|
|
507
508
|
// tsp/* verdict code; a config-shape error throws (the caller's contract).
|
|
508
509
|
function _imprintMatches(mi, data) {
|
|
509
510
|
var name = mi.hashAlgorithm && mi.hashAlgorithm.name;
|
|
510
|
-
var nodeAlg = (name &&
|
|
511
|
+
var nodeAlg = (name && intrinsic.hasOwn(_VERIFY_DIGEST, name)) ? _VERIFY_DIGEST[name] : null;
|
|
511
512
|
if (!nodeAlg) return "tsp/unsupported-algorithm"; // cannot recompute -> never assume a match
|
|
512
513
|
var wantLen = _VERIFY_DIGEST_LEN[name];
|
|
513
514
|
// The token's messageImprint MUST be a full-length digest for its algorithm. A short/empty
|
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:3735feb6-6447-4894-953a-28811d47f2c0",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-08-
|
|
8
|
+
"timestamp": "2026-08-21T03:44:09.554Z",
|
|
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.22",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.5.
|
|
25
|
+
"version": "0.5.22",
|
|
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.22",
|
|
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.22",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|