@blamejs/pki 0.1.29 → 0.1.30
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 +14 -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 +38 -0
- package/lib/guard-bytes.js +57 -0
- package/lib/guard-crypto.js +32 -0
- package/lib/guard-limits.js +49 -0
- package/lib/guard-text.js +63 -0
- package/lib/jose.js +6 -11
- package/lib/merkle.js +3 -12
- package/lib/oid.js +2 -1
- package/lib/schema-pkix.js +17 -19
- package/lib/webcrypto.js +8 -25
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,20 @@ 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.
|
|
7
|
+
## v0.1.30 — 2026-07-11
|
|
8
|
+
|
|
9
|
+
Fail-closed hardening of the byte-input and text-decode boundaries.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- A detached-backed BufferSource (a transferred / structuredClone'd view) passed to any DER format parser -- pki.schema.x509 / crl / csr / pkcs8 / cms / pkcs12 -- fails closed with the format's typed bad-input error at the shared parse-input boundary rather than being decoded as an empty buffer.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- The EST transfer and multipart-mixed decoders enforce their size cap on the raw byte length before decoding the payload to a string, and an HTTP error response body is decoded only up to the prefix shown in the message -- closing a single-input string-allocation amplification where an oversized body was materialized in full before the cap rejected it.
|
|
18
|
+
- pki.oid.fromDER rejects a non-Buffer or detached-backed input with a typed oid/bad-input error instead of a raw TypeError.
|
|
19
|
+
|
|
20
|
+
## v0.1.29 — 2026-07-10
|
|
8
21
|
|
|
9
22
|
A detached-backed BufferSource now fails closed with a typed error at every byte-input boundary.
|
|
10
23
|
|
package/lib/asn1-der.js
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
|
|
33
33
|
var constants = require("./constants");
|
|
34
34
|
var frameworkError = require("./framework-error");
|
|
35
|
+
var guard = require("./guard-all");
|
|
35
36
|
|
|
36
37
|
var Asn1Error = frameworkError.Asn1Error;
|
|
37
38
|
var OidError = frameworkError.OidError;
|
|
@@ -105,19 +106,7 @@ function _className(bits) {
|
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
function _asBuffer(input, who) {
|
|
108
|
-
|
|
109
|
-
// NOT a `Buffer.isBuffer` fast-path that returns it as-is. A detached backing
|
|
110
|
-
// ArrayBuffer (a transferred / structuredClone'd view) reads as zero-length;
|
|
111
|
-
// re-viewing through Buffer.from surfaces it as a typed error here rather than
|
|
112
|
-
// a misleading downstream truncated-DER verdict on an empty buffer.
|
|
113
|
-
if (Buffer.isBuffer(input) || input instanceof Uint8Array) {
|
|
114
|
-
try {
|
|
115
|
-
return Buffer.from(input.buffer, input.byteOffset, input.byteLength);
|
|
116
|
-
} catch (e) {
|
|
117
|
-
throw new Asn1Error("asn1/not-buffer", who + ": input is not a usable byte view (detached backing buffer?)", e);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
throw new Asn1Error("asn1/not-buffer", who + ": expected a Buffer / Uint8Array");
|
|
109
|
+
return guard.bytes.view(input, Asn1Error, "asn1/not-buffer", who);
|
|
121
110
|
}
|
|
122
111
|
|
|
123
112
|
// ---- Decoder --------------------------------------------------------
|
|
@@ -160,27 +149,15 @@ function _asBuffer(input, who) {
|
|
|
160
149
|
* var node = pki.asn1.decode(der);
|
|
161
150
|
* node.tagNumber === pki.asn1.TAGS.SEQUENCE;
|
|
162
151
|
*/
|
|
163
|
-
// A size/depth cap opt: absent means the constants.LIMITS default; a provided
|
|
164
|
-
// value must be a non-negative finite integer. A non-finite cap (NaN, Infinity)
|
|
165
|
-
// silently DISABLES the guard -- every `>` compare against it is false -- so a
|
|
166
|
-
// deeply nested input would run to a bare RangeError instead of the typed
|
|
167
|
-
// asn1/too-deep verdict. A bad cap is a config fault: throw at entry.
|
|
168
|
-
function _capOpt(v, key, dflt) {
|
|
169
|
-
if (v === undefined) return dflt;
|
|
170
|
-
if (typeof v !== "number" || !isFinite(v) || v < 0 || Math.floor(v) !== v) {
|
|
171
|
-
throw new TypeError("decode: " + key + " must be a non-negative integer");
|
|
172
|
-
}
|
|
173
|
-
return v;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
152
|
function decode(input, opts) {
|
|
177
153
|
opts = opts || {};
|
|
178
154
|
var buf = _asBuffer(input, "decode");
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
155
|
+
// A size/depth cap opt: absent means the constants.LIMITS default; a provided
|
|
156
|
+
// value must be a non-negative integer (guard.limits.cap). depthCap also
|
|
157
|
+
// refuses a maxDepth above the stack-safe ceiling so a raised cap cannot drive
|
|
158
|
+
// recursive descent into a bare RangeError instead of the typed asn1/too-deep.
|
|
159
|
+
var maxBytes = guard.limits.cap(opts.maxBytes, "maxBytes", constants.LIMITS.DER_MAX_BYTES);
|
|
160
|
+
var maxDepth = guard.limits.depthCap(opts.maxDepth, "maxDepth", constants.LIMITS.DER_MAX_DEPTH);
|
|
184
161
|
if (buf.length > maxBytes) {
|
|
185
162
|
throw new Asn1Error("asn1/too-large", "input " + buf.length + " bytes exceeds cap " + maxBytes);
|
|
186
163
|
}
|
package/lib/cbor-det.js
CHANGED
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
var constants = require("./constants");
|
|
38
38
|
var frameworkError = require("./framework-error");
|
|
39
39
|
var asn1 = require("./asn1-der");
|
|
40
|
+
var guard = require("./guard-all");
|
|
40
41
|
|
|
41
42
|
var CborError = frameworkError.CborError;
|
|
42
43
|
|
|
@@ -57,31 +58,7 @@ var _utf8 = new TextDecoder("utf-8", { fatal: true });
|
|
|
57
58
|
var _MAX_EPOCH_SECONDS = 8640000000000n;
|
|
58
59
|
|
|
59
60
|
function _asBuffer(input, who) {
|
|
60
|
-
|
|
61
|
-
// -- NOT a `Buffer.isBuffer` fast-path that would return it as-is. A detached
|
|
62
|
-
// backing ArrayBuffer (a transferred / structuredClone'd view) has had its
|
|
63
|
-
// bytes removed and reads as zero-length; the fast-path would hand the byte
|
|
64
|
-
// walk an empty buffer. Always re-view through Buffer.from so a detached input
|
|
65
|
-
// fails closed here as a typed error rather than a raw TypeError deeper in the
|
|
66
|
-
// walk (or a misleading truncated-input verdict).
|
|
67
|
-
if (Buffer.isBuffer(input) || input instanceof Uint8Array) {
|
|
68
|
-
try {
|
|
69
|
-
return Buffer.from(input.buffer, input.byteOffset, input.byteLength);
|
|
70
|
-
} catch (e) {
|
|
71
|
-
throw new CborError("cbor/not-buffer", who + ": input is not a usable byte view (detached backing buffer?)", e);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
throw new CborError("cbor/not-buffer", who + ": expected a Buffer / Uint8Array");
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Config-time cap validation: a bad cap is an authoring bug, so it throws a
|
|
78
|
-
// TypeError (not a CborError) exactly like asn1-der's _capOpt.
|
|
79
|
-
function _capOpt(v, key, dflt) {
|
|
80
|
-
if (v === undefined) return dflt;
|
|
81
|
-
if (typeof v !== "number" || !Number.isInteger(v) || v < 0) {
|
|
82
|
-
throw new TypeError("decode: " + key + " must be a non-negative integer");
|
|
83
|
-
}
|
|
84
|
-
return v;
|
|
61
|
+
return guard.bytes.view(input, CborError, "cbor/not-buffer", who);
|
|
85
62
|
}
|
|
86
63
|
|
|
87
64
|
// Determinism profile -> a ruleset. v1 whitelists only "deterministic"
|
|
@@ -283,12 +260,9 @@ function _decodeItem(buf, start, limit, depth, maxD, rules, state) {
|
|
|
283
260
|
function decode(input, opts) {
|
|
284
261
|
opts = opts || {};
|
|
285
262
|
var buf = _asBuffer(input, "decode");
|
|
286
|
-
var maxBytes =
|
|
287
|
-
var maxDepth =
|
|
288
|
-
|
|
289
|
-
throw new TypeError("decode: maxDepth " + maxDepth + " exceeds the stack-safe ceiling " + constants.LIMITS.MAX_DECODE_DEPTH_CEILING);
|
|
290
|
-
}
|
|
291
|
-
var maxItems = _capOpt(opts.maxItems, "maxItems", constants.LIMITS.CBOR_MAX_ITEMS);
|
|
263
|
+
var maxBytes = guard.limits.cap(opts.maxBytes, "maxBytes", constants.LIMITS.CBOR_MAX_BYTES);
|
|
264
|
+
var maxDepth = guard.limits.depthCap(opts.maxDepth, "maxDepth", constants.LIMITS.CBOR_MAX_DEPTH);
|
|
265
|
+
var maxItems = guard.limits.cap(opts.maxItems, "maxItems", constants.LIMITS.CBOR_MAX_ITEMS);
|
|
292
266
|
var rules = _profile(opts.profile);
|
|
293
267
|
if (buf.length > maxBytes) throw new CborError("cbor/too-large", "input " + buf.length + " bytes exceeds cap " + maxBytes);
|
|
294
268
|
var r = _decodeItem(buf, 0, buf.length, 0, maxDepth, rules, { n: 0, max: maxItems });
|
package/lib/ct.js
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
var asn1 = require("./asn1-der.js");
|
|
42
42
|
var constants = require("./constants.js");
|
|
43
43
|
var frameworkError = require("./framework-error.js");
|
|
44
|
+
var guard = require("./guard-all.js");
|
|
44
45
|
|
|
45
46
|
var CtError = frameworkError.CtError;
|
|
46
47
|
var C = constants;
|
|
@@ -111,19 +112,7 @@ function _peelInner(extValue) {
|
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
function _toBuffer(v, field) {
|
|
114
|
-
|
|
115
|
-
// NOT a `Buffer.isBuffer` fast-path that returns it as-is. A detached backing
|
|
116
|
-
// ArrayBuffer (a transferred / structuredClone'd view) reads as zero-length;
|
|
117
|
-
// re-viewing through Buffer.from surfaces it as a typed error here rather than
|
|
118
|
-
// a misleading downstream bad-DER verdict on an empty buffer.
|
|
119
|
-
if (Buffer.isBuffer(v) || v instanceof Uint8Array) {
|
|
120
|
-
try {
|
|
121
|
-
return Buffer.from(v.buffer, v.byteOffset, v.byteLength);
|
|
122
|
-
} catch (e) {
|
|
123
|
-
throw new CtError("ct/bad-input", field + " is not a usable byte view (detached backing buffer?)", e);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
throw new CtError("ct/bad-input", field + " must be a Buffer or Uint8Array");
|
|
115
|
+
return guard.bytes.view(v, CtError, "ct/bad-input", field);
|
|
127
116
|
}
|
|
128
117
|
|
|
129
118
|
// Parse one SerializedSCT body inside a sub-reader bounded to the element (so a
|
package/lib/est.js
CHANGED
|
@@ -50,6 +50,7 @@ var crl = require("./schema-crl");
|
|
|
50
50
|
var pkcs8 = require("./schema-pkcs8");
|
|
51
51
|
var csr = require("./schema-csr");
|
|
52
52
|
var frameworkError = require("./framework-error");
|
|
53
|
+
var guard = require("./guard-all");
|
|
53
54
|
|
|
54
55
|
var EstError = frameworkError.EstError;
|
|
55
56
|
function E(code, message, cause) { return new EstError(code, message, cause); }
|
|
@@ -128,14 +129,16 @@ function _httpDateMs(s) {
|
|
|
128
129
|
* var roundTripped = pki.est.transferDecode(pki.est.transferEncode(der));
|
|
129
130
|
*/
|
|
130
131
|
function transferDecode(body) {
|
|
131
|
-
var s = Buffer.isBuffer(body) ? body.toString("latin1") : String(body);
|
|
132
132
|
// The pre-decode ceiling: the largest base64 that could yield a DER_MAX_BYTES
|
|
133
133
|
// document, plus a generous line-wrapping allowance (CRLF at 64/76-char lines is
|
|
134
134
|
// ~3%; 1/8 leaves ample margin) so a normally-wrapped near-limit body is not
|
|
135
135
|
// rejected before the real DER_MAX_BYTES limit is enforced on the decode below.
|
|
136
|
+
// guard.text.decode caps the raw byte length BEFORE the latin1 copy.
|
|
136
137
|
var b64Len = Math.ceil(constants.LIMITS.DER_MAX_BYTES * 4 / 3);
|
|
137
138
|
var cap = b64Len + Math.ceil(b64Len / 8) + constants.BYTES.kib(64);
|
|
138
|
-
|
|
139
|
+
var s = guard.text.decode(body, cap, EstError, {
|
|
140
|
+
charset: "latin1", tooLarge: "est/too-large", badInput: "est/bad-input", label: "the EST payload",
|
|
141
|
+
});
|
|
139
142
|
var stripped = s.replace(/[\r\n \t]/g, "");
|
|
140
143
|
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(stripped)) throw E("est/bad-base64", "the EST payload is not RFC 4648 base64 (RFC 8951 sec. 3.1)");
|
|
141
144
|
var der = Buffer.from(stripped, "base64");
|
|
@@ -186,8 +189,9 @@ function _multipartBoundary(contentType) {
|
|
|
186
189
|
function splitMultipartMixed(body, contentType) {
|
|
187
190
|
var boundary = _multipartBoundary(contentType);
|
|
188
191
|
if (!boundary) throw E("est/bad-multipart", "a serverkeygen response must be multipart/mixed with a boundary (RFC 7030 sec. 4.4.2)");
|
|
189
|
-
var text =
|
|
190
|
-
|
|
192
|
+
var text = guard.text.decode(body, constants.LIMITS.DER_MAX_BYTES * 2, EstError, {
|
|
193
|
+
charset: "latin1", tooLarge: "est/too-large", badInput: "est/bad-input", label: "the multipart body",
|
|
194
|
+
});
|
|
191
195
|
var esc = boundary.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
192
196
|
var delimRe = new RegExp("(?:^|\\r?\\n)--" + esc + "(--)?[ \\t]*(?:\\r?\\n|$)", "g");
|
|
193
197
|
var marks = [], m;
|
|
@@ -526,8 +530,13 @@ function classifyResponse(status, headers, body, opts) {
|
|
|
526
530
|
}
|
|
527
531
|
if (status >= 300 && status < 400) return { status: "redirect", location: h["location"] || null };
|
|
528
532
|
if (status >= 400) {
|
|
529
|
-
|
|
530
|
-
|
|
533
|
+
// Decode only a bounded prefix for the message -- a huge error body must not
|
|
534
|
+
// be materialized as a full string just to show its first 512 characters.
|
|
535
|
+
// toString(enc, start, end) bounds the decode WITHOUT constructing a subarray
|
|
536
|
+
// view, so a detached body reads as "" here rather than throwing a raw
|
|
537
|
+
// TypeError that would mask the est/http-error verdict.
|
|
538
|
+
var text = Buffer.isBuffer(body) ? body.toString("utf8", 0, 512) : String(body || "").slice(0, 512);
|
|
539
|
+
throw E("est/http-error", "EST server returned HTTP " + status + (text ? ": " + text : ""));
|
|
531
540
|
}
|
|
532
541
|
return { status: "unexpected", httpStatus: status };
|
|
533
542
|
}
|
package/lib/framework-error.js
CHANGED
|
@@ -143,12 +143,13 @@ var CborError = defineClass("CborError", { withCause: true });
|
|
|
143
143
|
|
|
144
144
|
// OidError -- a malformed object-identifier: fewer than two arcs, a first
|
|
145
145
|
// arc outside 0..2, a second arc >= 40 under arcs 0/1, or a non-minimal
|
|
146
|
-
// base-128 sub-identifier.
|
|
147
|
-
|
|
146
|
+
// base-128 sub-identifier. withCause threads a raw byte-view failure (a detached
|
|
147
|
+
// backing ArrayBuffer at fromDER) as the cause rather than discarding it.
|
|
148
|
+
var OidError = defineClass("OidError", { withCause: true });
|
|
148
149
|
|
|
149
150
|
// PemError -- a malformed PEM envelope: missing / mismatched BEGIN/END
|
|
150
151
|
// markers, a bad label, or non-base64 body.
|
|
151
|
-
var PemError = defineClass("PemError");
|
|
152
|
+
var PemError = defineClass("PemError", { withCause: true });
|
|
152
153
|
|
|
153
154
|
// CertificateError -- a byte sequence that is not a well-formed X.509
|
|
154
155
|
// certificate (wrong outer structure, unparseable field, unsupported
|
package/lib/guard-all.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
//
|
|
5
|
+
// @internal -- no operator-facing namespace. This is lib composition only; the
|
|
6
|
+
// documented surface is the primitives whose boundaries compose these guards.
|
|
7
|
+
//
|
|
8
|
+
// guard-all -- the guard-family orchestrator (schema-all's role for the guard
|
|
9
|
+
// family). It assembles the per-shape fail-closed guards into one namespaced
|
|
10
|
+
// surface every input boundary composes, so a boundary depends on the family
|
|
11
|
+
// rather than re-implementing a guard inline:
|
|
12
|
+
//
|
|
13
|
+
// guard.bytes.view / .source -- untrusted byte-source -> Buffer re-view
|
|
14
|
+
// (detached-buffer fail-open defence)
|
|
15
|
+
// guard.text.decode -- byte-source -> string, cap BEFORE copy
|
|
16
|
+
// (parser-DoS string-allocation defence)
|
|
17
|
+
// guard.limits.cap / .depthCap -- config-time resource-cap validation
|
|
18
|
+
// (recursion / allocation DoS defence)
|
|
19
|
+
// guard.crypto.constantTimeEqual -- length-checked constant-time compare
|
|
20
|
+
// (timing side-channel defence)
|
|
21
|
+
//
|
|
22
|
+
// Each shape is enforced by a codebase-patterns detector: the characteristic
|
|
23
|
+
// token of a guard (the Buffer.from(x.buffer, byteOffset) re-view, the
|
|
24
|
+
// timingSafeEqual call, the MAX_DECODE_DEPTH_CEILING check) must appear ONLY in
|
|
25
|
+
// its guard module, so a new boundary cannot re-inline the shape and forget the
|
|
26
|
+
// defence.
|
|
27
|
+
|
|
28
|
+
var bytes = require("./guard-bytes");
|
|
29
|
+
var text = require("./guard-text");
|
|
30
|
+
var limits = require("./guard-limits");
|
|
31
|
+
var crypto = require("./guard-crypto");
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
bytes: bytes,
|
|
35
|
+
text: text,
|
|
36
|
+
limits: limits,
|
|
37
|
+
crypto: crypto,
|
|
38
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
// primitives whose input boundaries compose these guards (pki.asn1.decode,
|
|
7
|
+
// pki.cbor.decode, pki.ct.parseSctList, pki.webcrypto.*).
|
|
8
|
+
//
|
|
9
|
+
// guard-bytes -- fail-closed coercion of an untrusted byte-source input to a
|
|
10
|
+
// Buffer view. One of the enforced choke points of the guard family: a
|
|
11
|
+
// codebase-patterns detector requires every byte-input boundary to route
|
|
12
|
+
// through here, so the defence below cannot be forgotten at a new boundary.
|
|
13
|
+
//
|
|
14
|
+
// Defends the detached-buffer fail-OPEN: a transferred / structuredClone'd
|
|
15
|
+
// Buffer or view has a detached backing ArrayBuffer and reads as ZERO-LENGTH.
|
|
16
|
+
// An identity fast-path (`Buffer.isBuffer(x) return x`) that skips the re-view
|
|
17
|
+
// hands the caller an empty buffer, so a downstream digest / signature / parse
|
|
18
|
+
// silently processes EMPTY input instead of failing (CWE-20 improper input
|
|
19
|
+
// validation feeding a CWE-347-style verification-of-nothing). Always re-viewing
|
|
20
|
+
// through Buffer.from(x.buffer, x.byteOffset, x.byteLength) turns the detached
|
|
21
|
+
// read into a typed reject at the boundary. Size / length-field allocation
|
|
22
|
+
// bounds (CWE-770 / CWE-400, the parser-DoS class) are NOT enforced here -- they
|
|
23
|
+
// are per-format (a multi-MB CRL is legitimate, a Merkle proof is tiny), so they
|
|
24
|
+
// live in guard-params and each decoder's own cap.
|
|
25
|
+
|
|
26
|
+
// view(input, ErrorClass, code, label) -> Buffer view | throws ErrorClass(code, msg, cause)
|
|
27
|
+
// Accepts a Buffer / Uint8Array -- the DER / CBOR / CT / Merkle input contract.
|
|
28
|
+
// ErrorClass MUST be a withCause PkiError subclass (the raw detach failure is
|
|
29
|
+
// threaded as the cause).
|
|
30
|
+
function view(input, ErrorClass, code, label) {
|
|
31
|
+
if (Buffer.isBuffer(input) || input instanceof Uint8Array) {
|
|
32
|
+
try {
|
|
33
|
+
return Buffer.from(input.buffer, input.byteOffset, input.byteLength);
|
|
34
|
+
} catch (e) {
|
|
35
|
+
throw new ErrorClass(code, label + ": input is not a usable byte view (detached backing buffer?)", e);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
throw new ErrorClass(code, label + ": expected a Buffer / Uint8Array");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// source(input, ErrorClass, code, label) -> Buffer | throws ErrorClass
|
|
42
|
+
// Accepts the full W3C BufferSource (Buffer / TypedArray view / raw ArrayBuffer)
|
|
43
|
+
// -- the WebCrypto input contract. A raw ArrayBuffer is copied via Buffer.from,
|
|
44
|
+
// which also throws on a detached backing store.
|
|
45
|
+
function source(input, ErrorClass, code, label) {
|
|
46
|
+
var isAb = input instanceof ArrayBuffer;
|
|
47
|
+
if (isAb || ArrayBuffer.isView(input)) {
|
|
48
|
+
try {
|
|
49
|
+
return isAb ? Buffer.from(input) : Buffer.from(input.buffer, input.byteOffset, input.byteLength);
|
|
50
|
+
} catch (e) {
|
|
51
|
+
throw new ErrorClass(code, label + ": input is not a usable byte source (detached backing buffer?)", e);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
throw new ErrorClass(code, label + ": expected a BufferSource (ArrayBuffer / TypedArray / Buffer)");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { view: view, source: source };
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
// verify paths whose comparison composes this guard (pki.merkle,
|
|
7
|
+
// pki.webcrypto.subtle.verify).
|
|
8
|
+
//
|
|
9
|
+
// guard-crypto -- fail-closed cryptographic-invariant guards. Enforced choke
|
|
10
|
+
// point: a codebase-patterns detector requires every constant-time byte
|
|
11
|
+
// comparison to route through here, so no verify path re-implements it with a
|
|
12
|
+
// timing leak.
|
|
13
|
+
//
|
|
14
|
+
// Defends the timing side-channel class (CWE-208 observable timing discrepancy;
|
|
15
|
+
// the Lucky13 / MAC-forgery family). A secret-dependent byte comparison with an
|
|
16
|
+
// early exit (=== or a short-circuited &&) leaks the position of the first
|
|
17
|
+
// differing byte, letting an attacker recover a MAC / tag / tree root
|
|
18
|
+
// byte-by-byte. constantTimeEqual runs the whole comparison: a public length
|
|
19
|
+
// gate (lengths are not secret; crypto.timingSafeEqual RangeErrors on unequal
|
|
20
|
+
// lengths, so the gate must precede it) then a single timingSafeEqual over the
|
|
21
|
+
// equal-length buffers -- both operands are always read, no data-dependent
|
|
22
|
+
// branch. A length mismatch is an honest false, never a throw.
|
|
23
|
+
|
|
24
|
+
var nodeCrypto = require("node:crypto");
|
|
25
|
+
|
|
26
|
+
// constantTimeEqual(a, b) -> boolean. Length-checked, then constant-time over
|
|
27
|
+
// equal lengths. a and b are Buffers.
|
|
28
|
+
function constantTimeEqual(a, b) {
|
|
29
|
+
return a.length === b.length && nodeCrypto.timingSafeEqual(a, b);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { constantTimeEqual: constantTimeEqual };
|
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
// decoders whose option validation composes these guards (pki.asn1.decode,
|
|
7
|
+
// pki.cbor.decode).
|
|
8
|
+
//
|
|
9
|
+
// guard-limits -- fail-closed validation of operator-supplied decode limits at
|
|
10
|
+
// config time (the tier that throws on a bad argument so the operator catches
|
|
11
|
+
// the typo at boot). Enforced choke point: a codebase-patterns detector requires
|
|
12
|
+
// the stack-safe recursion ceiling to live only here, so a new decoder cannot be
|
|
13
|
+
// configured past it.
|
|
14
|
+
//
|
|
15
|
+
// Defends the parser-DoS / resource-exhaustion class (CWE-834 excessive
|
|
16
|
+
// recursion, CWE-770 allocation without limits; MITRE T1499.001; D3FEND
|
|
17
|
+
// Recursion-Bounding + Input-Size-Restriction). A recursive-descent decoder
|
|
18
|
+
// bounded only by a caller-tunable maxDepth overflows the native call stack (a
|
|
19
|
+
// raw RangeError, not a typed verdict) once the cap is raised past the frame
|
|
20
|
+
// limit; depthCap refuses a maxDepth above the stack-safe ceiling. A bad limit
|
|
21
|
+
// is an authoring bug, so this tier throws a TypeError (not a PkiError). NIST
|
|
22
|
+
// 800-53 SI-10 / SC-5 do not require these bounds (framework lag) -- the bound
|
|
23
|
+
// at the parser is the actual control.
|
|
24
|
+
|
|
25
|
+
var constants = require("./constants");
|
|
26
|
+
|
|
27
|
+
// cap(value, key, dflt) -> a validated non-negative integer, or dflt when value
|
|
28
|
+
// is undefined. A non-integer / non-finite / negative value is a config-time
|
|
29
|
+
// TypeError (Number.isInteger already rejects non-numbers, NaN, and Infinity).
|
|
30
|
+
function cap(value, key, dflt) {
|
|
31
|
+
if (value === undefined) return dflt;
|
|
32
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
33
|
+
throw new TypeError("decode: " + key + " must be a non-negative integer");
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// depthCap(value, key, dflt) -> a validated recursion-depth cap: cap() plus the
|
|
39
|
+
// stack-safe ceiling, so a raised maxDepth cannot drive recursive descent into a
|
|
40
|
+
// raw RangeError past the native frame limit.
|
|
41
|
+
function depthCap(value, key, dflt) {
|
|
42
|
+
var n = cap(value, key, dflt);
|
|
43
|
+
if (n > constants.LIMITS.MAX_DECODE_DEPTH_CEILING) {
|
|
44
|
+
throw new TypeError("decode: " + key + " " + n + " exceeds the stack-safe ceiling " + constants.LIMITS.MAX_DECODE_DEPTH_CEILING);
|
|
45
|
+
}
|
|
46
|
+
return n;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { cap: cap, depthCap: depthCap };
|
|
@@ -0,0 +1,63 @@
|
|
|
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
|
+
// primitives whose text boundaries compose this guard (pki.jose.parseJson, the
|
|
7
|
+
// PEM decoders, the EST transfer decoders).
|
|
8
|
+
//
|
|
9
|
+
// guard-text -- fail-closed decode of an untrusted byte-source input to a
|
|
10
|
+
// string, capping the RAW byte length BEFORE the string is materialized.
|
|
11
|
+
// Enforced choke point for the byte->string boundary.
|
|
12
|
+
//
|
|
13
|
+
// Defends the parser-DoS string-allocation class (CWE-770 allocation without
|
|
14
|
+
// limits, CWE-400 uncontrolled resource consumption; MITRE T1499.001). The
|
|
15
|
+
// ORDERING is the invariant: cap the byte length, THEN decode. Decoding a Buffer
|
|
16
|
+
// to a string first allocates a full-size string for input the cap is about to
|
|
17
|
+
// reject; worse, a buffer above Node's max string length escapes the decode as
|
|
18
|
+
// an untyped ERR_STRING_TOO_LONG instead of the caller's typed <tooLarge>. This
|
|
19
|
+
// guard defines the cap-before-copy ordering and the fatal-UTF-8 decode once so
|
|
20
|
+
// no boundary re-derives it (and drifts to cap-after, as some had).
|
|
21
|
+
|
|
22
|
+
var bytes = require("./guard-bytes");
|
|
23
|
+
|
|
24
|
+
var LATIN1 = "latin1";
|
|
25
|
+
|
|
26
|
+
// decode(input, maxBytes, ErrorClass, spec) -> string.
|
|
27
|
+
// input : a Buffer (decoded per spec.charset) or a string (taken as-is).
|
|
28
|
+
// maxBytes : the raw byte-length ceiling, checked BEFORE any string copy.
|
|
29
|
+
// ErrorClass: a PkiError subclass (withCause where spec.fatal is set).
|
|
30
|
+
// spec : { charset: "latin1"|"utf-8" (default latin1),
|
|
31
|
+
// fatal: boolean -- strict UTF-8 (a lone continuation /
|
|
32
|
+
// truncated sequence throws, never substitutes U+FFFD),
|
|
33
|
+
// tooLarge: code for the over-cap reject,
|
|
34
|
+
// badDecode: code for a fatal-charset (bad-UTF-8) reject
|
|
35
|
+
// (required only when fatal is set),
|
|
36
|
+
// badInput: code for a non-Buffer/non-string reject,
|
|
37
|
+
// label: human phrase for the message }
|
|
38
|
+
function decode(input, maxBytes, ErrorClass, spec) {
|
|
39
|
+
var charset = spec.charset || LATIN1;
|
|
40
|
+
if (Buffer.isBuffer(input)) {
|
|
41
|
+
// Re-view through the byte guard FIRST so a detached backing ArrayBuffer (a
|
|
42
|
+
// transferred / structuredClone'd Buffer, which reads as zero-length) fails
|
|
43
|
+
// closed here -- the same detached-buffer defence the byte boundaries get --
|
|
44
|
+
// instead of being decoded as an empty string. Then cap, then decode.
|
|
45
|
+
input = bytes.view(input, ErrorClass, spec.badInput, spec.label);
|
|
46
|
+
if (input.length > maxBytes) throw new ErrorClass(spec.tooLarge, spec.label + " exceeds the size cap");
|
|
47
|
+
if (spec.fatal) {
|
|
48
|
+
try { return new TextDecoder(charset, { fatal: true }).decode(input); }
|
|
49
|
+
catch (e) { throw new ErrorClass(spec.badDecode, spec.label + " is not valid " + charset, e); }
|
|
50
|
+
}
|
|
51
|
+
return input.toString(charset);
|
|
52
|
+
}
|
|
53
|
+
if (typeof input === "string") {
|
|
54
|
+
// A latin1 string's char length equals its byte length; a UTF-8 string's
|
|
55
|
+
// does not, so bound the encoded byte length.
|
|
56
|
+
var byteLen = charset === LATIN1 ? input.length : Buffer.byteLength(input, "utf8");
|
|
57
|
+
if (byteLen > maxBytes) throw new ErrorClass(spec.tooLarge, spec.label + " exceeds the size cap");
|
|
58
|
+
return input;
|
|
59
|
+
}
|
|
60
|
+
throw new ErrorClass(spec.badInput, spec.label + " expects a string or Buffer");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = { decode: decode };
|
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); }
|
|
@@ -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
|
|
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/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
|
}
|
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:9b3c1a14-9eb9-492b-a652-b6fa68d6b4b4",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-07-
|
|
8
|
+
"timestamp": "2026-07-11T07:38:46.451Z",
|
|
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.30",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.1.
|
|
25
|
+
"version": "0.1.30",
|
|
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.30",
|
|
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.30",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|