@blamejs/pki 0.1.29 → 0.1.31
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 +23 -1
- package/lib/asn1-der.js +8 -31
- package/lib/cbor-det.js +5 -31
- package/lib/ct.js +2 -13
- package/lib/est.js +15 -6
- package/lib/framework-error.js +4 -3
- package/lib/guard-all.js +45 -0
- package/lib/guard-bytes.js +60 -0
- package/lib/guard-crypto.js +34 -0
- package/lib/guard-limits.js +56 -0
- package/lib/guard-name.js +53 -0
- package/lib/guard-range.js +69 -0
- package/lib/guard-text.js +66 -0
- package/lib/jose.js +13 -18
- package/lib/merkle.js +3 -12
- package/lib/oid.js +2 -1
- package/lib/path-validate.js +4 -9
- package/lib/schema-all.js +2 -2
- package/lib/schema-attrcert.js +3 -3
- package/lib/schema-cmp.js +5 -6
- package/lib/schema-crmf.js +3 -3
- package/lib/schema-csrattrs.js +1 -1
- package/lib/schema-pkcs12.js +11 -17
- package/lib/schema-pkix.js +32 -39
- package/lib/webcrypto.js +8 -25
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/jose.js
CHANGED
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
var constants = require("./constants");
|
|
39
39
|
var webcrypto = require("./webcrypto").webcrypto;
|
|
40
40
|
var frameworkError = require("./framework-error");
|
|
41
|
+
var guard = require("./guard-all");
|
|
41
42
|
|
|
42
43
|
var JoseError = frameworkError.JoseError;
|
|
43
44
|
function E(code, message, cause) { return new JoseError(code, message, cause); }
|
|
@@ -52,7 +53,7 @@ var B64U_ALPHABET = /^[A-Za-z0-9_-]*$/;
|
|
|
52
53
|
* @primitive pki.jose.base64url.encode
|
|
53
54
|
* @signature pki.jose.base64url.encode(bytes) -> string
|
|
54
55
|
* @since 0.1.25
|
|
55
|
-
* @status
|
|
56
|
+
* @status stable
|
|
56
57
|
* @spec RFC 7515, RFC 4648
|
|
57
58
|
* @related pki.jose.base64url.decode
|
|
58
59
|
*
|
|
@@ -71,7 +72,7 @@ function b64uEncode(bytes) {
|
|
|
71
72
|
* @primitive pki.jose.base64url.decode
|
|
72
73
|
* @signature pki.jose.base64url.decode(text) -> Buffer
|
|
73
74
|
* @since 0.1.25
|
|
74
|
-
* @status
|
|
75
|
+
* @status stable
|
|
75
76
|
* @spec RFC 7515, RFC 4648, RFC 8555
|
|
76
77
|
* @related pki.jose.base64url.encode
|
|
77
78
|
*
|
|
@@ -213,7 +214,7 @@ function _jsonReader(str) {
|
|
|
213
214
|
* @primitive pki.jose.parseJson
|
|
214
215
|
* @signature pki.jose.parseJson(input) -> value
|
|
215
216
|
* @since 0.1.25
|
|
216
|
-
* @status
|
|
217
|
+
* @status stable
|
|
217
218
|
* @spec RFC 7515, RFC 8259
|
|
218
219
|
* @related pki.jose.base64url.decode
|
|
219
220
|
*
|
|
@@ -228,17 +229,11 @@ function _jsonReader(str) {
|
|
|
228
229
|
* pki.jose.parseJson('{"a":1}'); // -> { a: 1 }
|
|
229
230
|
*/
|
|
230
231
|
function parseJson(input) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
} else if (typeof input === "string") {
|
|
237
|
-
if (Buffer.byteLength(input, "utf8") > LIMITS.JSON_MAX_BYTES) throw E("jose/too-large", "the JSON document exceeds the size cap");
|
|
238
|
-
str = input;
|
|
239
|
-
} else {
|
|
240
|
-
throw E("jose/bad-input", "parseJson requires a Buffer or string");
|
|
241
|
-
}
|
|
232
|
+
// guard.text.decode caps the raw byte length BEFORE the (strict, fatal) UTF-8
|
|
233
|
+
// decode, so an oversized document is rejected before it is materialized.
|
|
234
|
+
var str = guard.text.decode(input, LIMITS.JSON_MAX_BYTES, JoseError, {
|
|
235
|
+
charset: "utf-8", fatal: true, tooLarge: "jose/too-large", badDecode: "jose/bad-json", badInput: "jose/bad-input", label: "the JSON document",
|
|
236
|
+
});
|
|
242
237
|
return _jsonReader(str);
|
|
243
238
|
}
|
|
244
239
|
|
|
@@ -413,7 +408,7 @@ var PRIVATE_JWK_MEMBERS = ["d", "p", "q", "dp", "dq", "qi", "k", "priv"];
|
|
|
413
408
|
* @primitive pki.jose.assertPublicJwk
|
|
414
409
|
* @signature pki.jose.assertPublicJwk(jwk) -> jwk
|
|
415
410
|
* @since 0.1.25
|
|
416
|
-
* @status
|
|
411
|
+
* @status stable
|
|
417
412
|
* @spec RFC 7517, RFC 7518
|
|
418
413
|
* @related pki.jose.sign, pki.jose.thumbprint
|
|
419
414
|
*
|
|
@@ -440,7 +435,7 @@ function assertPublicJwk(jwk) {
|
|
|
440
435
|
* @primitive pki.jose.verify
|
|
441
436
|
* @signature pki.jose.verify(jws, opts) -> Promise<{ header, payload }>
|
|
442
437
|
* @since 0.1.25
|
|
443
|
-
* @status
|
|
438
|
+
* @status stable
|
|
444
439
|
* @spec RFC 7515, RFC 7518, RFC 8555
|
|
445
440
|
* @related pki.jose.sign, pki.jose.parseJson
|
|
446
441
|
*
|
|
@@ -493,7 +488,7 @@ async function verify(jws, opts) {
|
|
|
493
488
|
* @primitive pki.jose.sign
|
|
494
489
|
* @signature pki.jose.sign(opts) -> Promise<{ protected, payload, signature }>
|
|
495
490
|
* @since 0.1.25
|
|
496
|
-
* @status
|
|
491
|
+
* @status stable
|
|
497
492
|
* @spec RFC 7515, RFC 7518, RFC 8555
|
|
498
493
|
* @related pki.jose.verify
|
|
499
494
|
*
|
|
@@ -579,7 +574,7 @@ var THUMBPRINT_MEMBERS = {
|
|
|
579
574
|
* @primitive pki.jose.thumbprint
|
|
580
575
|
* @signature pki.jose.thumbprint(jwk) -> Promise<string>
|
|
581
576
|
* @since 0.1.25
|
|
582
|
-
* @status
|
|
577
|
+
* @status stable
|
|
583
578
|
* @spec RFC 7638, RFC 8037, RFC 9964
|
|
584
579
|
* @related pki.jose.verify
|
|
585
580
|
*
|
package/lib/merkle.js
CHANGED
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
var nodeCrypto = require("node:crypto");
|
|
45
45
|
var constants = require("./constants");
|
|
46
46
|
var frameworkError = require("./framework-error");
|
|
47
|
+
var guard = require("./guard-all");
|
|
47
48
|
|
|
48
49
|
var MerkleError = frameworkError.MerkleError;
|
|
49
50
|
|
|
@@ -58,17 +59,7 @@ function _sha256(buf) {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
function _toBuffer(v, field) {
|
|
61
|
-
|
|
62
|
-
throw new MerkleError("merkle/bad-input", field + " must be a Buffer or Uint8Array");
|
|
63
|
-
}
|
|
64
|
-
try {
|
|
65
|
-
// A zero-copy view over the same bytes; this also surfaces a detached
|
|
66
|
-
// backing ArrayBuffer (a transferred / structuredClone'd view) as a typed
|
|
67
|
-
// error here rather than a raw TypeError from a later concat / digest.
|
|
68
|
-
return Buffer.from(v.buffer, v.byteOffset, v.byteLength);
|
|
69
|
-
} catch (e) {
|
|
70
|
-
throw new MerkleError("merkle/bad-input", field + " is not a usable byte view", e);
|
|
71
|
-
}
|
|
62
|
+
return guard.bytes.view(v, MerkleError, "merkle/bad-input", field);
|
|
72
63
|
}
|
|
73
64
|
|
|
74
65
|
function _node32(v, field) {
|
|
@@ -113,7 +104,7 @@ function _proofNodes(proof) {
|
|
|
113
104
|
|
|
114
105
|
// Constant-time equality of two already-validated equal-length 32-byte buffers.
|
|
115
106
|
function _ctEq(a, b) {
|
|
116
|
-
return
|
|
107
|
+
return guard.crypto.constantTimeEqual(a, b);
|
|
117
108
|
}
|
|
118
109
|
|
|
119
110
|
/**
|
package/lib/oid.js
CHANGED
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
|
|
36
36
|
var asn1 = require("./asn1-der");
|
|
37
37
|
var frameworkError = require("./framework-error");
|
|
38
|
+
var guard = require("./guard-all");
|
|
38
39
|
|
|
39
40
|
var OidError = frameworkError.OidError;
|
|
40
41
|
|
|
@@ -454,7 +455,7 @@ function fromArcs(arcs) {
|
|
|
454
455
|
// one namespace when they have a dotted string in hand.
|
|
455
456
|
function toDER(dotted) { _assertDotted(dotted, "toDER"); return asn1.build.oid(dotted); }
|
|
456
457
|
function fromDER(input) {
|
|
457
|
-
var buf =
|
|
458
|
+
var buf = guard.bytes.view(input, OidError, "oid/bad-input", "fromDER");
|
|
458
459
|
var node = asn1.decode(buf);
|
|
459
460
|
return asn1.read.oid(node);
|
|
460
461
|
}
|
package/lib/path-validate.js
CHANGED
|
@@ -37,6 +37,7 @@ var asn1 = require("./asn1-der");
|
|
|
37
37
|
var schema = require("./schema-engine");
|
|
38
38
|
var x509 = require("./schema-x509");
|
|
39
39
|
var crl = require("./schema-crl");
|
|
40
|
+
var guard = require("./guard-all");
|
|
40
41
|
var constants = require("./constants");
|
|
41
42
|
|
|
42
43
|
var PathError = errors.PathError;
|
|
@@ -226,14 +227,11 @@ function resolveRsaPss(paramsBytes) {
|
|
|
226
227
|
// verifier binds to the salt length the certificate states, so a value
|
|
227
228
|
// that would round is not verifiable material (no real salt exceeds the
|
|
228
229
|
// modulus size, let alone this).
|
|
229
|
-
|
|
230
|
-
saltLength = Number(sl);
|
|
230
|
+
saltLength = guard.range.uint31(sl, E, "path/unsupported-algorithm", "RSASSA-PSS saltLength");
|
|
231
231
|
} else if (f.tagNumber === 3) {
|
|
232
232
|
// Compared for equality with 1 below -- bound before conversion so an
|
|
233
233
|
// oversized value cannot round on its way to the comparison.
|
|
234
|
-
|
|
235
|
-
if (tf < 0n || tf > 2147483647n) throw E("path/unsupported-algorithm", "unsupported RSASSA-PSS trailerField " + tf.toString());
|
|
236
|
-
trailer = Number(tf);
|
|
234
|
+
trailer = guard.range.uint31(asn1.read.integer(f.children[0]), E, "path/unsupported-algorithm", "RSASSA-PSS trailerField");
|
|
237
235
|
}
|
|
238
236
|
});
|
|
239
237
|
if (hash === null) throw E("path/unsupported-algorithm", "RSASSA-PSS hashAlgorithm must be stated explicitly (the SHA-1 default is rejected)");
|
|
@@ -347,10 +345,7 @@ function normalizeAttrValue(v) {
|
|
|
347
345
|
// Reject embedded NUL / control bytes so a truncation attack (CVE-2009-2408)
|
|
348
346
|
// cannot make two different names compare equal.
|
|
349
347
|
if (typeof v !== "string") return v;
|
|
350
|
-
|
|
351
|
-
var c = v.charCodeAt(i);
|
|
352
|
-
if (c === 0 || (c < 0x20 && c !== 0x09)) throw E("path/name-chaining", "distinguished name contains an embedded control byte (CVE-2009-2408)");
|
|
353
|
-
}
|
|
348
|
+
guard.name.assertNoControlBytes(v, E, "path/name-chaining", "distinguished name");
|
|
354
349
|
return v.trim().replace(/\s+/g, " ").toLowerCase();
|
|
355
350
|
}
|
|
356
351
|
|
package/lib/schema-all.js
CHANGED
|
@@ -249,7 +249,7 @@ var FORMATS = [
|
|
|
249
249
|
* @primitive pki.schema.all
|
|
250
250
|
* @signature pki.schema.all() -> string[]
|
|
251
251
|
* @since 0.1.7
|
|
252
|
-
* @status
|
|
252
|
+
* @status stable
|
|
253
253
|
* @spec RFC 5280
|
|
254
254
|
* @related pki.schema.parse
|
|
255
255
|
*
|
|
@@ -264,7 +264,7 @@ function all() { return FORMATS.map(function (f) { return f.name; }); }
|
|
|
264
264
|
* @primitive pki.schema.parse
|
|
265
265
|
* @signature pki.schema.parse(input) -> parsed
|
|
266
266
|
* @since 0.1.7
|
|
267
|
-
* @status
|
|
267
|
+
* @status stable
|
|
268
268
|
* @spec RFC 5280
|
|
269
269
|
* @related pki.schema.x509, pki.schema.all
|
|
270
270
|
*
|
package/lib/schema-attrcert.js
CHANGED
|
@@ -299,7 +299,7 @@ var ATTRIBUTE_CERTIFICATE = pkix.signedEnvelope(NS, ACINFO, {
|
|
|
299
299
|
* @primitive pki.schema.attrcert.parse
|
|
300
300
|
* @signature pki.schema.attrcert.parse(input) -> attributeCertificate
|
|
301
301
|
* @since 0.1.14
|
|
302
|
-
* @status
|
|
302
|
+
* @status stable
|
|
303
303
|
* @spec RFC 5755, X.509
|
|
304
304
|
* @related pki.schema.parse, pki.schema.x509.parse
|
|
305
305
|
*
|
|
@@ -348,7 +348,7 @@ function parseV1(input) {
|
|
|
348
348
|
* @primitive pki.schema.attrcert.pemDecode
|
|
349
349
|
* @signature pki.schema.attrcert.pemDecode(text, label?) -> Buffer
|
|
350
350
|
* @since 0.1.14
|
|
351
|
-
* @status
|
|
351
|
+
* @status stable
|
|
352
352
|
* @spec RFC 7468, RFC 5755
|
|
353
353
|
* @related pki.schema.attrcert.parse
|
|
354
354
|
*
|
|
@@ -367,7 +367,7 @@ function pemDecode(text, label) { return pkix.pemDecode(text, label === null ? n
|
|
|
367
367
|
* @primitive pki.schema.attrcert.pemEncode
|
|
368
368
|
* @signature pki.schema.attrcert.pemEncode(der, label?) -> string
|
|
369
369
|
* @since 0.1.23
|
|
370
|
-
* @status
|
|
370
|
+
* @status stable
|
|
371
371
|
* @spec RFC 7468, RFC 5755
|
|
372
372
|
* @related pki.schema.attrcert.pemDecode
|
|
373
373
|
*
|
package/lib/schema-cmp.js
CHANGED
|
@@ -42,6 +42,7 @@ var pkix = require("./schema-pkix.js");
|
|
|
42
42
|
var cms = require("./schema-cms.js");
|
|
43
43
|
var crmf = require("./schema-crmf.js");
|
|
44
44
|
var frameworkError = require("./framework-error.js");
|
|
45
|
+
var guard = require("./guard-all.js");
|
|
45
46
|
|
|
46
47
|
var CmpError = frameworkError.CmpError;
|
|
47
48
|
var PemError = frameworkError.PemError;
|
|
@@ -99,9 +100,7 @@ var PKI_FAILURE_INFO = schema.decode(function (n, ctx) {
|
|
|
99
100
|
// PollRep checkAfter is a delay in seconds -- negative poisons scheduling, and
|
|
100
101
|
// the value surfaces as an exact number or not at all.
|
|
101
102
|
var CHECK_AFTER = schema.decode(function (n, ctx) {
|
|
102
|
-
|
|
103
|
-
if (v < 0n || v > 2147483647n) throw ctx.E("cmp/bad-poll-rep", "checkAfter must be a non-negative delay in seconds (RFC 9810 sec. 5.3.22)");
|
|
104
|
-
return Number(v);
|
|
103
|
+
return guard.range.uint31(asn1.read.integer(n), ctx.E, "cmp/bad-poll-rep", "checkAfter delay in seconds (RFC 9810 sec. 5.3.22)");
|
|
105
104
|
});
|
|
106
105
|
|
|
107
106
|
// A raw certificate / CRL / publication-info element -- surfaced byte-exact for
|
|
@@ -841,7 +840,7 @@ var PKI_MESSAGE = schema.seq([
|
|
|
841
840
|
* @primitive pki.schema.cmp.parse
|
|
842
841
|
* @signature pki.schema.cmp.parse(input) -> pkiMessage
|
|
843
842
|
* @since 0.1.19
|
|
844
|
-
* @status
|
|
843
|
+
* @status stable
|
|
845
844
|
* @spec RFC 9810, RFC 9483, RFC 9481
|
|
846
845
|
* @related pki.schema.parse, pki.schema.crmf.parse, pki.schema.cms.parse
|
|
847
846
|
*
|
|
@@ -895,7 +894,7 @@ var parse = pkix.makeParser({
|
|
|
895
894
|
* @primitive pki.schema.cmp.pemDecode
|
|
896
895
|
* @signature pki.schema.cmp.pemDecode(text, label?) -> Buffer
|
|
897
896
|
* @since 0.1.19
|
|
898
|
-
* @status
|
|
897
|
+
* @status stable
|
|
899
898
|
* @spec RFC 7468, RFC 9810
|
|
900
899
|
* @related pki.schema.cmp.parse
|
|
901
900
|
*
|
|
@@ -912,7 +911,7 @@ function pemDecode(text, label) { return pkix.pemDecode(text, label || "CMP", Pe
|
|
|
912
911
|
* @primitive pki.schema.cmp.pemEncode
|
|
913
912
|
* @signature pki.schema.cmp.pemEncode(der, label?) -> string
|
|
914
913
|
* @since 0.1.19
|
|
915
|
-
* @status
|
|
914
|
+
* @status stable
|
|
916
915
|
* @spec RFC 7468
|
|
917
916
|
* @related pki.schema.cmp.pemDecode
|
|
918
917
|
*
|
package/lib/schema-crmf.js
CHANGED
|
@@ -455,7 +455,7 @@ var CERT_REQ_MESSAGES = schema.seqOf(CERT_REQ_MSG, {
|
|
|
455
455
|
* @primitive pki.schema.crmf.parse
|
|
456
456
|
* @signature pki.schema.crmf.parse(input) -> certReqMessages
|
|
457
457
|
* @since 0.1.17
|
|
458
|
-
* @status
|
|
458
|
+
* @status stable
|
|
459
459
|
* @spec RFC 4211
|
|
460
460
|
* @related pki.schema.parse, pki.schema.csr.parse
|
|
461
461
|
*
|
|
@@ -496,7 +496,7 @@ var parse = pkix.makeParser({
|
|
|
496
496
|
* @primitive pki.schema.crmf.pemDecode
|
|
497
497
|
* @signature pki.schema.crmf.pemDecode(text, label?) -> Buffer
|
|
498
498
|
* @since 0.1.17
|
|
499
|
-
* @status
|
|
499
|
+
* @status stable
|
|
500
500
|
* @spec RFC 7468, RFC 4211
|
|
501
501
|
* @related pki.schema.crmf.parse
|
|
502
502
|
*
|
|
@@ -513,7 +513,7 @@ function pemDecode(text, label) { return pkix.pemDecode(text, label || null, Pem
|
|
|
513
513
|
* @primitive pki.schema.crmf.pemEncode
|
|
514
514
|
* @signature pki.schema.crmf.pemEncode(der, label) -> string
|
|
515
515
|
* @since 0.1.23
|
|
516
|
-
* @status
|
|
516
|
+
* @status stable
|
|
517
517
|
* @spec RFC 7468, RFC 4211
|
|
518
518
|
* @related pki.schema.crmf.pemDecode
|
|
519
519
|
*
|
package/lib/schema-csrattrs.js
CHANGED
|
@@ -320,7 +320,7 @@ var CSR_ATTRS = schema.seqOf(ATTR_OR_OID, {
|
|
|
320
320
|
* @primitive pki.schema.csrattrs.parse
|
|
321
321
|
* @signature pki.schema.csrattrs.parse(der) -> { items }
|
|
322
322
|
* @since 0.1.24
|
|
323
|
-
* @status
|
|
323
|
+
* @status stable
|
|
324
324
|
* @spec RFC 8951, RFC 9908, RFC 7030
|
|
325
325
|
* @related pki.schema.parse, pki.est.buildEnrollAttributes
|
|
326
326
|
*
|
package/lib/schema-pkcs12.js
CHANGED
|
@@ -46,6 +46,7 @@ var pkix = require("./schema-pkix.js");
|
|
|
46
46
|
var cms = require("./schema-cms.js");
|
|
47
47
|
var pkcs8 = require("./schema-pkcs8.js");
|
|
48
48
|
var frameworkError = require("./framework-error.js");
|
|
49
|
+
var guard = require("./guard-all.js");
|
|
49
50
|
|
|
50
51
|
var Pkcs12Error = frameworkError.Pkcs12Error;
|
|
51
52
|
var PemError = frameworkError.PemError;
|
|
@@ -102,16 +103,10 @@ var PBKDF2_PARAMS = schema.seq([
|
|
|
102
103
|
if (!m.fields.keyLength.present) {
|
|
103
104
|
throw ctx.E("pkcs12/bad-mac-data", "PBMAC1 PBKDF2-params must carry keyLength (RFC 9579 sec. 5)");
|
|
104
105
|
}
|
|
105
|
-
//
|
|
106
|
-
// would round silently
|
|
107
|
-
var
|
|
108
|
-
|
|
109
|
-
throw ctx.E("pkcs12/bad-mac-data", "PBKDF2 iterationCount must be a positive integer within the iteration-count range");
|
|
110
|
-
}
|
|
111
|
-
var keyLength = m.fields.keyLength.value;
|
|
112
|
-
if (keyLength < 1n || keyLength > 2147483647n) {
|
|
113
|
-
throw ctx.E("pkcs12/bad-mac-data", "PBKDF2 keyLength must be a positive integer within the key-length range");
|
|
114
|
-
}
|
|
106
|
+
// guard.range.positiveInt31 bounds + narrows each counter atomically -- a
|
|
107
|
+
// value past the bound would round silently and hand a verifier wrong inputs.
|
|
108
|
+
var iterationCount = guard.range.positiveInt31(m.fields.iterationCount.value, ctx.E, "pkcs12/bad-mac-data", "PBKDF2 iterationCount");
|
|
109
|
+
var keyLength = guard.range.positiveInt31(m.fields.keyLength.value, ctx.E, "pkcs12/bad-mac-data", "PBKDF2 keyLength");
|
|
115
110
|
var prf = m.fields.prf.present ? m.fields.prf.value.result : null;
|
|
116
111
|
// X.690 sec. 11.5 -- a DEFAULT-valued component must be omitted from a DER
|
|
117
112
|
// encoding, and the prf DEFAULT is algid-hmacWithSHA1: hmacWithSHA1 with
|
|
@@ -124,8 +119,8 @@ var PBKDF2_PARAMS = schema.seq([
|
|
|
124
119
|
}
|
|
125
120
|
return {
|
|
126
121
|
salt: m.fields.salt.value,
|
|
127
|
-
iterationCount:
|
|
128
|
-
keyLength:
|
|
122
|
+
iterationCount: iterationCount,
|
|
123
|
+
keyLength: keyLength,
|
|
129
124
|
prfOid: prf ? prf.oid : OID_HMAC_SHA1,
|
|
130
125
|
prfName: prf ? prf.name : "hmacWithSHA1",
|
|
131
126
|
};
|
|
@@ -172,8 +167,7 @@ var MAC_DATA = schema.seq([
|
|
|
172
167
|
if (it.present) {
|
|
173
168
|
var v = it.value;
|
|
174
169
|
if (v === 1n) throw ctx.E("pkcs12/bad-mac-iterations", "iterations equal to its DEFAULT 1 must be omitted (X.690 sec. 11.5)");
|
|
175
|
-
|
|
176
|
-
iterations = Number(v);
|
|
170
|
+
iterations = guard.range.positiveInt31(v, ctx.E, "pkcs12/bad-mac-iterations", "MacData iterations");
|
|
177
171
|
}
|
|
178
172
|
var di = m.fields.mac.value.result;
|
|
179
173
|
var pbmac1 = null;
|
|
@@ -529,7 +523,7 @@ function _buildBag(rec, state, ctx) {
|
|
|
529
523
|
* @primitive pki.schema.pkcs12.parse
|
|
530
524
|
* @signature pki.schema.pkcs12.parse(input) -> pfx
|
|
531
525
|
* @since 0.1.18
|
|
532
|
-
* @status
|
|
526
|
+
* @status stable
|
|
533
527
|
* @spec RFC 7292, RFC 9579
|
|
534
528
|
* @defends ASN.1-parser-DoS (CWE-400)
|
|
535
529
|
* @related pki.schema.parse, pki.schema.pkcs8.parse, pki.schema.cms.parse
|
|
@@ -577,7 +571,7 @@ var parse = pkix.makeParser({
|
|
|
577
571
|
* @primitive pki.schema.pkcs12.pemDecode
|
|
578
572
|
* @signature pki.schema.pkcs12.pemDecode(text, label?) -> Buffer
|
|
579
573
|
* @since 0.1.18
|
|
580
|
-
* @status
|
|
574
|
+
* @status stable
|
|
581
575
|
* @spec RFC 7468, RFC 7292
|
|
582
576
|
* @related pki.schema.pkcs12.parse
|
|
583
577
|
*
|
|
@@ -594,7 +588,7 @@ function pemDecode(text, label) { return pkix.pemDecode(text, label || "PKCS12",
|
|
|
594
588
|
* @primitive pki.schema.pkcs12.pemEncode
|
|
595
589
|
* @signature pki.schema.pkcs12.pemEncode(der, label?) -> string
|
|
596
590
|
* @since 0.1.18
|
|
597
|
-
* @status
|
|
591
|
+
* @status stable
|
|
598
592
|
* @spec RFC 7468
|
|
599
593
|
* @related pki.schema.pkcs12.pemDecode
|
|
600
594
|
*
|
package/lib/schema-pkix.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
var asn1 = require("./asn1-der");
|
|
17
17
|
var constants = require("./constants");
|
|
18
18
|
var schema = require("./schema-engine");
|
|
19
|
+
var guard = require("./guard-all");
|
|
19
20
|
// ct is a leaf format module (requires only the codec / oid / constants / error
|
|
20
21
|
// core, never pkix) so the SCT-list extension VALUE decoder registers here as a
|
|
21
22
|
// registry row, not a switch. The one-directional edge keeps the layering acyclic.
|
|
@@ -35,16 +36,13 @@ var PEM_RE = /-----BEGIN ([A-Z0-9 ]+)-----([\s\S]*?)-----END \1-----/;
|
|
|
35
36
|
// lenient Buffer.from would otherwise let several distinct PEM texts alias one
|
|
36
37
|
// DER (PEM-layer malleability) and silently drop trailing content bits.
|
|
37
38
|
function pemDecode(text, label, PemError) {
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
if (typeof text !== "string") throw new PemError("pem/bad-input", "pemDecode expects a string or Buffer");
|
|
47
|
-
if (text.length > constants.LIMITS.PEM_MAX_BYTES) throw new PemError("pem/too-large", "PEM input exceeds size cap");
|
|
39
|
+
// guard.text.decode caps the raw byte length BEFORE the latin1 string copy:
|
|
40
|
+
// converting first would allocate a full-size string for input the cap is
|
|
41
|
+
// about to reject, and a buffer above Node's max string length would escape as
|
|
42
|
+
// an untyped ERR_STRING_TOO_LONG instead of pem/too-large.
|
|
43
|
+
text = guard.text.decode(text, constants.LIMITS.PEM_MAX_BYTES, PemError, {
|
|
44
|
+
charset: "latin1", tooLarge: "pem/too-large", badInput: "pem/bad-input", label: "PEM input",
|
|
45
|
+
});
|
|
48
46
|
var m = PEM_RE.exec(text);
|
|
49
47
|
if (!m) throw new PemError("pem/no-block", "no PEM block found");
|
|
50
48
|
if (label && m[1] !== label) throw new PemError("pem/label-mismatch", "expected " + JSON.stringify(label) + " block, got " + JSON.stringify(m[1]));
|
|
@@ -64,12 +62,9 @@ function pemDecode(text, label, PemError) {
|
|
|
64
62
|
// base64 body is canonical (the pemDecode rule); returns an array of DER buffers.
|
|
65
63
|
function pemDecodeAll(text, label, PemError) {
|
|
66
64
|
label = label || "CERTIFICATE";
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
if (typeof text !== "string") throw new PemError("pem/bad-input", "pemDecodeAll expects a string or Buffer");
|
|
72
|
-
if (text.length > constants.LIMITS.PEM_MAX_BYTES) throw new PemError("pem/too-large", "PEM input exceeds size cap");
|
|
65
|
+
text = guard.text.decode(text, constants.LIMITS.PEM_MAX_BYTES, PemError, {
|
|
66
|
+
charset: "latin1", tooLarge: "pem/too-large", badInput: "pem/bad-input", label: "PEM input",
|
|
67
|
+
});
|
|
73
68
|
var re = /-----BEGIN ([A-Z0-9 ]+)-----([\s\S]*?)-----END \1-----/g;
|
|
74
69
|
var blocks = [];
|
|
75
70
|
var lastEnd = 0, m;
|
|
@@ -107,9 +102,12 @@ function pemEncode(der, label, PemError) {
|
|
|
107
102
|
// Uint8Array is taken as bytes. opts: { pemLabel, PemError, ErrorClass, prefix }.
|
|
108
103
|
function coerceToDer(input, opts) {
|
|
109
104
|
if (typeof input === "string") return pemDecode(input, opts.pemLabel, opts.PemError);
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
105
|
+
if (Buffer.isBuffer(input) || input instanceof Uint8Array) {
|
|
106
|
+
// Route the DER Buffer / Uint8Array through the shared byte guard so a
|
|
107
|
+
// detached backing store fails closed here for EVERY format that composes
|
|
108
|
+
// this boundary (x509/crl/csr/pkcs8/cms/pkcs12), not per-format.
|
|
109
|
+
var buf = guard.bytes.view(input, opts.ErrorClass, opts.prefix + "/bad-input", "parse");
|
|
110
|
+
return _isPemArmor(buf) ? pemDecode(buf, opts.pemLabel, opts.PemError) : buf;
|
|
113
111
|
}
|
|
114
112
|
throw new opts.ErrorClass(opts.prefix + "/bad-input", "parse expects a DER Buffer or a PEM string");
|
|
115
113
|
}
|
|
@@ -502,12 +500,9 @@ function generalName(ns, opts) {
|
|
|
502
500
|
if (constructed) throw ctx.E(code, "GeneralName [" + t + "] must be primitive (X.690 sec. 10.2)");
|
|
503
501
|
if (GN_IA5[t]) {
|
|
504
502
|
if (n.content.length === 0) throw ctx.E(code, "GeneralName [" + t + "] must be a non-empty IA5String");
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
// (CVE-2009-2408 class) downstream, so reject it at decode.
|
|
509
|
-
if (n.content[i] < 0x20 || n.content[i] > 0x7e) throw ctx.E(code, "GeneralName [" + t + "] must be a printable IA5String (no control bytes)");
|
|
510
|
-
}
|
|
503
|
+
// 7-bit IA5, no C0/DEL control byte -- an embedded NUL/control in a
|
|
504
|
+
// dNSName/rfc822Name/URI enables a name-truncation bypass (CVE-2009-2408).
|
|
505
|
+
guard.name.assertPrintableIa5(n.content, ctx.E, code, "GeneralName [" + t + "]");
|
|
511
506
|
if (decodeValue) value = n.content.toString("latin1");
|
|
512
507
|
} else if (t === 7) {
|
|
513
508
|
if (subtreeBase) {
|
|
@@ -621,11 +616,10 @@ function certExtensionDecoders(ns) {
|
|
|
621
616
|
if (kids[i] && kids[i].tagClass === "universal" && kids[i].tagNumber === _T.INTEGER) {
|
|
622
617
|
if (!cA) throw ns.E(C, "BasicConstraints pathLenConstraint is only permitted when cA is TRUE (RFC 5280 sec. 4.2.1.9)");
|
|
623
618
|
var pl = readInt(kids[i], C, "pathLenConstraint");
|
|
624
|
-
//
|
|
625
|
-
//
|
|
626
|
-
//
|
|
627
|
-
|
|
628
|
-
pathLen = Number(pl); i++;
|
|
619
|
+
// guard.range.uint31 bounds the decoded BigInt and narrows atomically -- a
|
|
620
|
+
// value past the safe-integer range would otherwise round silently and be
|
|
621
|
+
// compared as a path-length ceiling.
|
|
622
|
+
pathLen = guard.range.uint31(pl, ns.E, C, "BasicConstraints pathLenConstraint (RFC 5280 sec. 4.2.1.9)"); i++;
|
|
629
623
|
}
|
|
630
624
|
if (i !== kids.length) throw ns.E(C, "BasicConstraints has unexpected trailing fields");
|
|
631
625
|
return { cA: cA, pathLenConstraint: pathLen };
|
|
@@ -748,11 +742,11 @@ function certExtensionDecoders(ns) {
|
|
|
748
742
|
pcLastTag = k.tagNumber;
|
|
749
743
|
var v;
|
|
750
744
|
try { v = asn1.read.integerImplicit(k, k.tagNumber); } catch (e) { throw ns.E(C, "PolicyConstraints field must be an INTEGER", e); }
|
|
751
|
-
//
|
|
752
|
-
// range would round silently)
|
|
753
|
-
|
|
754
|
-
if (k.tagNumber === 0) rep =
|
|
755
|
-
else if (k.tagNumber === 1) ipm =
|
|
745
|
+
// guard.range.uint31 bounds + narrows atomically (a skip count past the
|
|
746
|
+
// safe-integer range would round silently).
|
|
747
|
+
var skip = guard.range.uint31(v, ns.E, C, "PolicyConstraints skip count (RFC 5280 sec. 4.2.1.11)");
|
|
748
|
+
if (k.tagNumber === 0) rep = skip;
|
|
749
|
+
else if (k.tagNumber === 1) ipm = skip;
|
|
756
750
|
else throw ns.E(C, "PolicyConstraints has an unexpected field [" + k.tagNumber + "]");
|
|
757
751
|
});
|
|
758
752
|
return { requireExplicitPolicy: rep, inhibitPolicyMapping: ipm };
|
|
@@ -764,10 +758,9 @@ function certExtensionDecoders(ns) {
|
|
|
764
758
|
var n = decodeTop(buf, C, "InhibitAnyPolicy");
|
|
765
759
|
if (n.tagClass !== "universal" || n.tagNumber !== _T.INTEGER) throw ns.E(C, "InhibitAnyPolicy must be an INTEGER (RFC 5280 sec. 4.2.1.14)");
|
|
766
760
|
var v = readInt(n, C, "InhibitAnyPolicy");
|
|
767
|
-
//
|
|
768
|
-
// range would round silently)
|
|
769
|
-
|
|
770
|
-
return Number(v);
|
|
761
|
+
// guard.range.uint31 bounds + narrows atomically (a skip count past the
|
|
762
|
+
// safe-integer range would round silently).
|
|
763
|
+
return guard.range.uint31(v, ns.E, C, "InhibitAnyPolicy skip count (RFC 5280 sec. 4.2.1.14)");
|
|
771
764
|
}
|
|
772
765
|
|
|
773
766
|
// subjectAltName / issuerAltName ::= GeneralNames -- decoded values surfaced.
|
package/lib/webcrypto.js
CHANGED
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
|
|
36
36
|
var nodeCrypto = require("node:crypto");
|
|
37
37
|
var frameworkError = require("./framework-error");
|
|
38
|
+
var guard = require("./guard-all");
|
|
38
39
|
|
|
39
40
|
// Single-owner error class -- co-located with its module (framework-error
|
|
40
41
|
// stays the cross-module home; this is webcrypto-private). withCause: a
|
|
@@ -47,21 +48,7 @@ var MAX_RANDOM_BYTES = 65536;
|
|
|
47
48
|
// ---- value helpers ---------------------------------------------------
|
|
48
49
|
|
|
49
50
|
function _toBuf(data, who) {
|
|
50
|
-
|
|
51
|
-
// A Buffer is itself an ArrayBuffer view, so it is handled by the view arm --
|
|
52
|
-
// NOT a `Buffer.isBuffer` fast-path that would return it as-is. A detached
|
|
53
|
-
// backing ArrayBuffer (a transferred / structuredClone'd Buffer, view, or
|
|
54
|
-
// ArrayBuffer) has had its bytes removed and reads as zero-length; the
|
|
55
|
-
// fast-path would silently hash / sign EMPTY (a fail-OPEN). Always re-view
|
|
56
|
-
// through Buffer.from so a detached input fails closed here as a typed error.
|
|
57
|
-
if (isAb || ArrayBuffer.isView(data)) {
|
|
58
|
-
try {
|
|
59
|
-
return isAb ? Buffer.from(data) : Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
|
60
|
-
} catch (e) {
|
|
61
|
-
throw new WebCryptoError("webcrypto/data", (who || "input") + ": input is not a usable byte source (detached backing buffer?)", e);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
throw new WebCryptoError("webcrypto/data", (who || "input") + ": expected BufferSource (ArrayBuffer / TypedArray / Buffer)");
|
|
51
|
+
return guard.bytes.source(data, WebCryptoError, "webcrypto/data", who || "input");
|
|
65
52
|
}
|
|
66
53
|
|
|
67
54
|
function _toArrayBuffer(buf) {
|
|
@@ -354,7 +341,7 @@ SubtleCrypto.prototype.verify = async function verify(algorithm, key, signature,
|
|
|
354
341
|
// in constant time. The length check leaks nothing a constant-time compare
|
|
355
342
|
// would protect; the secret-dependent byte comparison is timingSafeEqual.
|
|
356
343
|
var digest = hm.digest();
|
|
357
|
-
return
|
|
344
|
+
return guard.crypto.constantTimeEqual(digest, sig);
|
|
358
345
|
}
|
|
359
346
|
throw new WebCryptoError("webcrypto/not-supported", "verify: unsupported algorithm " + JSON.stringify(name));
|
|
360
347
|
};
|
|
@@ -729,15 +716,11 @@ Crypto.prototype.getRandomValues = function getRandomValues(typedArray) {
|
|
|
729
716
|
if (typedArray.byteLength > MAX_RANDOM_BYTES) {
|
|
730
717
|
throw new WebCryptoError("webcrypto/data", "getRandomValues: byteLength exceeds " + MAX_RANDOM_BYTES);
|
|
731
718
|
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
} catch (e) {
|
|
738
|
-
throw new WebCryptoError("webcrypto/data", "getRandomValues: byte view is not usable (detached backing buffer?)", e);
|
|
739
|
-
}
|
|
740
|
-
nodeCrypto.randomFillSync(view);
|
|
719
|
+
// Re-view the (already integer-TypedArray-validated) output buffer via
|
|
720
|
+
// guard.bytes.source -- any TypedArray, not just Uint8Array -- so a detached
|
|
721
|
+
// backing store fails closed here; the returned Buffer aliases typedArray, so
|
|
722
|
+
// randomFillSync writes through to the caller's array.
|
|
723
|
+
nodeCrypto.randomFillSync(guard.bytes.source(typedArray, WebCryptoError, "webcrypto/data", "getRandomValues"));
|
|
741
724
|
return typedArray;
|
|
742
725
|
};
|
|
743
726
|
|
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:2ed2aa23-5194-4472-96f3-c4013f134834",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-07-
|
|
8
|
+
"timestamp": "2026-07-11T16:09:44.713Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/pki@0.1.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.1.31",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.1.
|
|
25
|
+
"version": "0.1.31",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/pki@0.1.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.1.31",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/pki@0.1.
|
|
57
|
+
"ref": "@blamejs/pki@0.1.31",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|