@blamejs/pki 0.1.30 → 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 +9 -0
- package/lib/guard-all.js +7 -0
- package/lib/guard-bytes.js +3 -0
- package/lib/guard-crypto.js +2 -0
- package/lib/guard-limits.js +7 -0
- package/lib/guard-name.js +53 -0
- package/lib/guard-range.js +69 -0
- package/lib/guard-text.js +3 -0
- package/lib/jose.js +7 -7
- 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 +15 -20
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,15 @@ 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.1.31 — 2026-07-11
|
|
8
|
+
|
|
9
|
+
The DER format cohort and the JOSE surface graduate to stable.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- pki.schema.pkcs12 / attrcert / crmf / cmp / csrattrs (parse, pemDecode, pemEncode), pki.schema.all and pki.schema.parse, and the pki.jose signing / verification / thumbprint / base64url / JSON surface graduate from experimental to stable.
|
|
14
|
+
- The LTS-CALENDAR graduation criterion now states that a settled, well-tested format no mainstream tool implements graduates on the toolkit's own conformance-vector round-trip plus coverage-guided fuzzing, rather than on a harness oracle that does not exist.
|
|
15
|
+
|
|
7
16
|
## v0.1.30 — 2026-07-11
|
|
8
17
|
|
|
9
18
|
Fail-closed hardening of the byte-input and text-decode boundaries.
|
package/lib/guard-all.js
CHANGED
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
// (recursion / allocation DoS defence)
|
|
19
19
|
// guard.crypto.constantTimeEqual -- length-checked constant-time compare
|
|
20
20
|
// (timing side-channel defence)
|
|
21
|
+
// guard.range.int / .uint31 / .positiveInt31
|
|
22
|
+
// -- bound a decoded integer before narrowing
|
|
23
|
+
// to Number (silent-narrowing defence)
|
|
21
24
|
//
|
|
22
25
|
// Each shape is enforced by a codebase-patterns detector: the characteristic
|
|
23
26
|
// token of a guard (the Buffer.from(x.buffer, byteOffset) re-view, the
|
|
@@ -29,10 +32,14 @@ var bytes = require("./guard-bytes");
|
|
|
29
32
|
var text = require("./guard-text");
|
|
30
33
|
var limits = require("./guard-limits");
|
|
31
34
|
var crypto = require("./guard-crypto");
|
|
35
|
+
var range = require("./guard-range");
|
|
36
|
+
var name = require("./guard-name");
|
|
32
37
|
|
|
33
38
|
module.exports = {
|
|
34
39
|
bytes: bytes,
|
|
35
40
|
text: text,
|
|
36
41
|
limits: limits,
|
|
37
42
|
crypto: crypto,
|
|
43
|
+
range: range,
|
|
44
|
+
name: name,
|
|
38
45
|
};
|
package/lib/guard-bytes.js
CHANGED
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
// Accepts a Buffer / Uint8Array -- the DER / CBOR / CT / Merkle input contract.
|
|
28
28
|
// ErrorClass MUST be a withCause PkiError subclass (the raw detach failure is
|
|
29
29
|
// threaded as the cause).
|
|
30
|
+
// @enforced-by guard-shape-reinlined
|
|
31
|
+
// @guard-shape Buffer\.from\(\s*([A-Za-z_$][\w$]*)\.buffer\s*,\s*\1\.byteOffset
|
|
30
32
|
function view(input, ErrorClass, code, label) {
|
|
31
33
|
if (Buffer.isBuffer(input) || input instanceof Uint8Array) {
|
|
32
34
|
try {
|
|
@@ -42,6 +44,7 @@ function view(input, ErrorClass, code, label) {
|
|
|
42
44
|
// Accepts the full W3C BufferSource (Buffer / TypedArray view / raw ArrayBuffer)
|
|
43
45
|
// -- the WebCrypto input contract. A raw ArrayBuffer is copied via Buffer.from,
|
|
44
46
|
// which also throws on a detached backing store.
|
|
47
|
+
// @enforced-by guard-shape-reinlined (the re-view shape is declared on view above)
|
|
45
48
|
function source(input, ErrorClass, code, label) {
|
|
46
49
|
var isAb = input instanceof ArrayBuffer;
|
|
47
50
|
if (isAb || ArrayBuffer.isView(input)) {
|
package/lib/guard-crypto.js
CHANGED
|
@@ -25,6 +25,8 @@ var nodeCrypto = require("node:crypto");
|
|
|
25
25
|
|
|
26
26
|
// constantTimeEqual(a, b) -> boolean. Length-checked, then constant-time over
|
|
27
27
|
// equal lengths. a and b are Buffers.
|
|
28
|
+
// @enforced-by guard-shape-reinlined
|
|
29
|
+
// @guard-shape \.timingSafeEqual\s*\(
|
|
28
30
|
function constantTimeEqual(a, b) {
|
|
29
31
|
return a.length === b.length && nodeCrypto.timingSafeEqual(a, b);
|
|
30
32
|
}
|
package/lib/guard-limits.js
CHANGED
|
@@ -27,6 +27,8 @@ var constants = require("./constants");
|
|
|
27
27
|
// cap(value, key, dflt) -> a validated non-negative integer, or dflt when value
|
|
28
28
|
// is undefined. A non-integer / non-finite / negative value is a config-time
|
|
29
29
|
// TypeError (Number.isInteger already rejects non-numbers, NaN, and Infinity).
|
|
30
|
+
// @enforced-by behavioral -- a config-time cap validator has no rename-proof code
|
|
31
|
+
// shape; the decode-rejects-a-bad-cap RED vectors (asn1/cbor) are the guard.
|
|
30
32
|
function cap(value, key, dflt) {
|
|
31
33
|
if (value === undefined) return dflt;
|
|
32
34
|
if (!Number.isInteger(value) || value < 0) {
|
|
@@ -38,6 +40,11 @@ function cap(value, key, dflt) {
|
|
|
38
40
|
// depthCap(value, key, dflt) -> a validated recursion-depth cap: cap() plus the
|
|
39
41
|
// stack-safe ceiling, so a raised maxDepth cannot drive recursive descent into a
|
|
40
42
|
// raw RangeError past the native frame limit.
|
|
43
|
+
// @enforced-by guard-shape-reinlined
|
|
44
|
+
// @guard-scope file
|
|
45
|
+
// @guard-shape \bopts\.maxDepth\b
|
|
46
|
+
// @guard-shape \bdepth\s*\+\s*1\b
|
|
47
|
+
// @guard-via \.depthCap\s*\(
|
|
41
48
|
function depthCap(value, key, dflt) {
|
|
42
49
|
var n = cap(value, key, dflt);
|
|
43
50
|
if (n > constants.LIMITS.MAX_DECODE_DEPTH_CEILING) {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
//
|
|
5
|
+
// @internal -- no operator-facing namespace. The documented surface is the
|
|
6
|
+
// consumers whose name-string integrity composes this guard (pki.path.validate
|
|
7
|
+
// DN chaining, pki.schema.x509 GeneralName / SAN decode).
|
|
8
|
+
//
|
|
9
|
+
// guard-name -- fail-closed rejection of an embedded control byte in a
|
|
10
|
+
// distinguished-name / SAN string. Two policies over one concern.
|
|
11
|
+
//
|
|
12
|
+
// Defends the name-truncation / display-confusion class (CVE-2009-2408): a NUL
|
|
13
|
+
// or control byte embedded in a decoded name lets an attacker make two different
|
|
14
|
+
// names compare equal (or a UI truncate at the NUL), so a cert issued for
|
|
15
|
+
// "good.example.com\0.evil.com" is treated as "good.example.com". CWE-158
|
|
16
|
+
// (improper neutralization of null byte) / CWE-20. The reject is at decode, so a
|
|
17
|
+
// truncation name never reaches a comparison or a display.
|
|
18
|
+
|
|
19
|
+
// assertNoControlBytes(str, E, code, label) -> str | throws E(code, ...)
|
|
20
|
+
// DirectoryString policy (a DN attribute value): reject NUL and C0 control bytes
|
|
21
|
+
// in a DECODED name string. TAB (0x09) is exempt; printable non-ASCII (a
|
|
22
|
+
// UTF8String CN carries accented / CJK characters) is allowed. `str` is assumed a
|
|
23
|
+
// string (the caller guards typeof). E is the (code, message) typed-error factory.
|
|
24
|
+
// @enforced-by behavioral -- the control-byte reject has no rename-proof code
|
|
25
|
+
// shape distinct from the ASN.1 charset readers; the CVE-2009-2408 RED vectors
|
|
26
|
+
// (a DN string with an embedded NUL / control byte rejects) are the guard.
|
|
27
|
+
function assertNoControlBytes(str, E, code, label) {
|
|
28
|
+
for (var i = 0; i < str.length; i++) {
|
|
29
|
+
var c = str.charCodeAt(i);
|
|
30
|
+
if (c === 0 || (c < 0x20 && c !== 0x09)) {
|
|
31
|
+
throw E(code, label + " contains an embedded control byte (CVE-2009-2408)");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return str;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// assertPrintableIa5(buf, E, code, label) -> buf | throws E(code, ...)
|
|
38
|
+
// IA5String policy (a dNSName / rfc822Name / URI GeneralName): every byte must be
|
|
39
|
+
// printable 7-bit ASCII [0x20, 0x7e] -- an embedded NUL / control byte enables the
|
|
40
|
+
// same name-truncation bypass downstream. `buf` is the raw GeneralName content.
|
|
41
|
+
// @enforced-by behavioral -- the printable-IA5 byte-range reject has no rename-proof
|
|
42
|
+
// code shape distinct from the ASN.1 IA5 reader; the CVE-2009-2408 RED vectors
|
|
43
|
+
// (a SAN with a control byte rejects) are the guard.
|
|
44
|
+
function assertPrintableIa5(buf, E, code, label) {
|
|
45
|
+
for (var i = 0; i < buf.length; i++) {
|
|
46
|
+
if (buf[i] < 0x20 || buf[i] > 0x7e) {
|
|
47
|
+
throw E(code, label + " must be a printable IA5String (no control bytes)");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return buf;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { assertNoControlBytes: assertNoControlBytes, assertPrintableIa5: assertPrintableIa5 };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
//
|
|
5
|
+
// @internal -- no operator-facing namespace. The documented surface is the
|
|
6
|
+
// parsers whose decoded-integer bounds compose this guard (pki.schema.x509 /
|
|
7
|
+
// crl / pkcs12 / cmp, pki.path.validate).
|
|
8
|
+
//
|
|
9
|
+
// guard-range -- fail-closed bound of an untrusted DECODED integer before it is
|
|
10
|
+
// narrowed to a JS Number. Parse-time (Tier-2): the value is malformed content,
|
|
11
|
+
// so it throws the caller's typed PkiError -- distinct from guard-limits, which
|
|
12
|
+
// validates an operator-supplied option at config time (Tier-1, TypeError).
|
|
13
|
+
//
|
|
14
|
+
// Defends the silent-narrowing class (a decoded ASN.1 INTEGER past 2^53 rounds
|
|
15
|
+
// when coerced to a Number, so a caller acting on the result -- a pathLen /
|
|
16
|
+
// skipCerts / saltLength / iteration counter -- acts on the WRONG number;
|
|
17
|
+
// CWE-190 integer overflow / CWE-681 incorrect conversion). The bound and the
|
|
18
|
+
// narrow are ATOMIC here: a caller cannot obtain the Number without the range
|
|
19
|
+
// having been enforced, so the value can never round silently and be acted on.
|
|
20
|
+
|
|
21
|
+
// 2^31 - 1 -- the shared skip-cert / path-length / salt / iteration ceiling.
|
|
22
|
+
// This literal lives ONLY here (a re-inline anywhere else is flagged: the
|
|
23
|
+
// guard-shape walk anchors on the literal). 2^53 - 1 is the safe-narrow ceiling.
|
|
24
|
+
var UINT31_MAX = 2147483647n;
|
|
25
|
+
var SAFE_MAX = 9007199254740991n;
|
|
26
|
+
var SAFE_MIN = -9007199254740991n;
|
|
27
|
+
|
|
28
|
+
// int(value, min, max, E, code, label) -> a Number in [min, max].
|
|
29
|
+
// value : the BigInt result of an ASN.1 INTEGER / ENUMERATED read.
|
|
30
|
+
// min, max : inclusive BigInt bounds.
|
|
31
|
+
// E : the (code, message, cause) typed-error factory in scope at the call
|
|
32
|
+
// site (ns.E / ctx.E / the module-local E) -- guard-range injects the
|
|
33
|
+
// caller's typed error through it, the tier-appropriate currency (a
|
|
34
|
+
// parse boundary carries a factory, not a bare ErrorClass).
|
|
35
|
+
// code : the frozen domain/reason code this field rejects under.
|
|
36
|
+
// label : field phrase (+ optional RFC cite) for the message.
|
|
37
|
+
// @enforced-by guard-shape-reinlined
|
|
38
|
+
// @guard-shape 2147483647n
|
|
39
|
+
function int(value, min, max, E, code, label) {
|
|
40
|
+
// Config-time authoring guard (Tier-1): narrowing to Number is lossless only
|
|
41
|
+
// when the WHOLE range sits inside the safe-integer band [-(2^53-1), 2^53-1].
|
|
42
|
+
// Both ends bind -- a min below the floor rounds a decoded value near it just
|
|
43
|
+
// as a max above the ceiling does. A wider range needs a BigInt-preserving
|
|
44
|
+
// guard, not this one (this is why the uint64 Merkle-coordinate domain must
|
|
45
|
+
// NOT route here).
|
|
46
|
+
if (max > SAFE_MAX) {
|
|
47
|
+
throw new TypeError("guard.range.int: max " + max + " exceeds the safe-integer ceiling; use a BigInt-preserving guard");
|
|
48
|
+
}
|
|
49
|
+
if (min < SAFE_MIN) {
|
|
50
|
+
throw new TypeError("guard.range.int: min " + min + " is below the safe-integer floor; use a BigInt-preserving guard");
|
|
51
|
+
}
|
|
52
|
+
// Parse-time reject (Tier-2): malformed / out-of-range decoded content.
|
|
53
|
+
if (typeof value !== "bigint" || value < min || value > max) {
|
|
54
|
+
throw E(code, label + " must be an integer within " + min + ".." + max);
|
|
55
|
+
}
|
|
56
|
+
return Number(value);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// uint31(value, E, code, label) -> Number in [0, 2^31-1]. The DER-INTEGER
|
|
60
|
+
// non-negative skip/path/salt shape (RFC 5280 / 4211 / 9810).
|
|
61
|
+
// @enforced-by guard-shape-reinlined (shares the 2147483647n shape declared on int)
|
|
62
|
+
function uint31(value, E, code, label) { return int(value, 0n, UINT31_MAX, E, code, label); }
|
|
63
|
+
|
|
64
|
+
// positiveInt31(value, E, code, label) -> Number in [1, 2^31-1]. The PKCS#12
|
|
65
|
+
// PBKDF2 / MAC iteration-count shape (a count of at least one).
|
|
66
|
+
// @enforced-by guard-shape-reinlined (shares the 2147483647n shape declared on int)
|
|
67
|
+
function positiveInt31(value, E, code, label) { return int(value, 1n, UINT31_MAX, E, code, label); }
|
|
68
|
+
|
|
69
|
+
module.exports = { int: int, uint31: uint31, positiveInt31: positiveInt31 };
|
package/lib/guard-text.js
CHANGED
|
@@ -35,6 +35,9 @@ var LATIN1 = "latin1";
|
|
|
35
35
|
// (required only when fatal is set),
|
|
36
36
|
// badInput: code for a non-Buffer/non-string reject,
|
|
37
37
|
// label: human phrase for the message }
|
|
38
|
+
// @enforced-by behavioral -- cap-before-copy has no rename-proof code shape; the
|
|
39
|
+
// over-cap RED vectors + the detached re-view through guard.bytes.view (itself
|
|
40
|
+
// enforced) are the guard.
|
|
38
41
|
function decode(input, maxBytes, ErrorClass, spec) {
|
|
39
42
|
var charset = spec.charset || LATIN1;
|
|
40
43
|
if (Buffer.isBuffer(input)) {
|
package/lib/jose.js
CHANGED
|
@@ -53,7 +53,7 @@ var B64U_ALPHABET = /^[A-Za-z0-9_-]*$/;
|
|
|
53
53
|
* @primitive pki.jose.base64url.encode
|
|
54
54
|
* @signature pki.jose.base64url.encode(bytes) -> string
|
|
55
55
|
* @since 0.1.25
|
|
56
|
-
* @status
|
|
56
|
+
* @status stable
|
|
57
57
|
* @spec RFC 7515, RFC 4648
|
|
58
58
|
* @related pki.jose.base64url.decode
|
|
59
59
|
*
|
|
@@ -72,7 +72,7 @@ function b64uEncode(bytes) {
|
|
|
72
72
|
* @primitive pki.jose.base64url.decode
|
|
73
73
|
* @signature pki.jose.base64url.decode(text) -> Buffer
|
|
74
74
|
* @since 0.1.25
|
|
75
|
-
* @status
|
|
75
|
+
* @status stable
|
|
76
76
|
* @spec RFC 7515, RFC 4648, RFC 8555
|
|
77
77
|
* @related pki.jose.base64url.encode
|
|
78
78
|
*
|
|
@@ -214,7 +214,7 @@ function _jsonReader(str) {
|
|
|
214
214
|
* @primitive pki.jose.parseJson
|
|
215
215
|
* @signature pki.jose.parseJson(input) -> value
|
|
216
216
|
* @since 0.1.25
|
|
217
|
-
* @status
|
|
217
|
+
* @status stable
|
|
218
218
|
* @spec RFC 7515, RFC 8259
|
|
219
219
|
* @related pki.jose.base64url.decode
|
|
220
220
|
*
|
|
@@ -408,7 +408,7 @@ var PRIVATE_JWK_MEMBERS = ["d", "p", "q", "dp", "dq", "qi", "k", "priv"];
|
|
|
408
408
|
* @primitive pki.jose.assertPublicJwk
|
|
409
409
|
* @signature pki.jose.assertPublicJwk(jwk) -> jwk
|
|
410
410
|
* @since 0.1.25
|
|
411
|
-
* @status
|
|
411
|
+
* @status stable
|
|
412
412
|
* @spec RFC 7517, RFC 7518
|
|
413
413
|
* @related pki.jose.sign, pki.jose.thumbprint
|
|
414
414
|
*
|
|
@@ -435,7 +435,7 @@ function assertPublicJwk(jwk) {
|
|
|
435
435
|
* @primitive pki.jose.verify
|
|
436
436
|
* @signature pki.jose.verify(jws, opts) -> Promise<{ header, payload }>
|
|
437
437
|
* @since 0.1.25
|
|
438
|
-
* @status
|
|
438
|
+
* @status stable
|
|
439
439
|
* @spec RFC 7515, RFC 7518, RFC 8555
|
|
440
440
|
* @related pki.jose.sign, pki.jose.parseJson
|
|
441
441
|
*
|
|
@@ -488,7 +488,7 @@ async function verify(jws, opts) {
|
|
|
488
488
|
* @primitive pki.jose.sign
|
|
489
489
|
* @signature pki.jose.sign(opts) -> Promise<{ protected, payload, signature }>
|
|
490
490
|
* @since 0.1.25
|
|
491
|
-
* @status
|
|
491
|
+
* @status stable
|
|
492
492
|
* @spec RFC 7515, RFC 7518, RFC 8555
|
|
493
493
|
* @related pki.jose.verify
|
|
494
494
|
*
|
|
@@ -574,7 +574,7 @@ var THUMBPRINT_MEMBERS = {
|
|
|
574
574
|
* @primitive pki.jose.thumbprint
|
|
575
575
|
* @signature pki.jose.thumbprint(jwk) -> Promise<string>
|
|
576
576
|
* @since 0.1.25
|
|
577
|
-
* @status
|
|
577
|
+
* @status stable
|
|
578
578
|
* @spec RFC 7638, RFC 8037, RFC 9964
|
|
579
579
|
* @related pki.jose.verify
|
|
580
580
|
*
|
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
|
@@ -500,12 +500,9 @@ function generalName(ns, opts) {
|
|
|
500
500
|
if (constructed) throw ctx.E(code, "GeneralName [" + t + "] must be primitive (X.690 sec. 10.2)");
|
|
501
501
|
if (GN_IA5[t]) {
|
|
502
502
|
if (n.content.length === 0) throw ctx.E(code, "GeneralName [" + t + "] must be a non-empty IA5String");
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
// (CVE-2009-2408 class) downstream, so reject it at decode.
|
|
507
|
-
if (n.content[i] < 0x20 || n.content[i] > 0x7e) throw ctx.E(code, "GeneralName [" + t + "] must be a printable IA5String (no control bytes)");
|
|
508
|
-
}
|
|
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 + "]");
|
|
509
506
|
if (decodeValue) value = n.content.toString("latin1");
|
|
510
507
|
} else if (t === 7) {
|
|
511
508
|
if (subtreeBase) {
|
|
@@ -619,11 +616,10 @@ function certExtensionDecoders(ns) {
|
|
|
619
616
|
if (kids[i] && kids[i].tagClass === "universal" && kids[i].tagNumber === _T.INTEGER) {
|
|
620
617
|
if (!cA) throw ns.E(C, "BasicConstraints pathLenConstraint is only permitted when cA is TRUE (RFC 5280 sec. 4.2.1.9)");
|
|
621
618
|
var pl = readInt(kids[i], C, "pathLenConstraint");
|
|
622
|
-
//
|
|
623
|
-
//
|
|
624
|
-
//
|
|
625
|
-
|
|
626
|
-
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++;
|
|
627
623
|
}
|
|
628
624
|
if (i !== kids.length) throw ns.E(C, "BasicConstraints has unexpected trailing fields");
|
|
629
625
|
return { cA: cA, pathLenConstraint: pathLen };
|
|
@@ -746,11 +742,11 @@ function certExtensionDecoders(ns) {
|
|
|
746
742
|
pcLastTag = k.tagNumber;
|
|
747
743
|
var v;
|
|
748
744
|
try { v = asn1.read.integerImplicit(k, k.tagNumber); } catch (e) { throw ns.E(C, "PolicyConstraints field must be an INTEGER", e); }
|
|
749
|
-
//
|
|
750
|
-
// range would round silently)
|
|
751
|
-
|
|
752
|
-
if (k.tagNumber === 0) rep =
|
|
753
|
-
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;
|
|
754
750
|
else throw ns.E(C, "PolicyConstraints has an unexpected field [" + k.tagNumber + "]");
|
|
755
751
|
});
|
|
756
752
|
return { requireExplicitPolicy: rep, inhibitPolicyMapping: ipm };
|
|
@@ -762,10 +758,9 @@ function certExtensionDecoders(ns) {
|
|
|
762
758
|
var n = decodeTop(buf, C, "InhibitAnyPolicy");
|
|
763
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)");
|
|
764
760
|
var v = readInt(n, C, "InhibitAnyPolicy");
|
|
765
|
-
//
|
|
766
|
-
// range would round silently)
|
|
767
|
-
|
|
768
|
-
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)");
|
|
769
764
|
}
|
|
770
765
|
|
|
771
766
|
// subjectAltName / issuerAltName ::= GeneralNames -- decoded values surfaced.
|
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
|
]
|