@blamejs/pki 0.1.28 → 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 +23 -1
- package/lib/asn1-der.js +8 -21
- package/lib/cbor-det.js +5 -19
- package/lib/ct.js +3 -7
- package/lib/est.js +15 -6
- package/lib/framework-error.js +10 -7
- 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 -6
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,29 @@ 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
|
|
21
|
+
|
|
22
|
+
A detached-backed BufferSource now fails closed with a typed error at every byte-input boundary.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- pki.webcrypto digest / sign / verify no longer silently process a detached-backed Buffer as EMPTY input (a fail-open where a transferred backing ArrayBuffer left the view zero-length); a detached BufferSource is now rejected with a typed webcrypto/data error, as is getRandomValues.
|
|
27
|
+
- pki.asn1.decode, pki.cbor.decode, and pki.ct.parseSctList reject a detached-backed Buffer or view with a typed error (asn1/not-buffer, cbor/not-buffer, ct/bad-input) instead of a raw TypeError or a misleading truncated-input verdict. The underlying byte-view failure is threaded as the error cause.
|
|
28
|
+
|
|
29
|
+
## v0.1.28 — 2026-07-10
|
|
8
30
|
|
|
9
31
|
Merkle transparency proof verification joins the toolkit as pki.merkle.
|
|
10
32
|
|
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,9 +106,7 @@ function _className(bits) {
|
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
function _asBuffer(input, who) {
|
|
108
|
-
|
|
109
|
-
if (input instanceof Uint8Array) return Buffer.from(input);
|
|
110
|
-
throw new Asn1Error("asn1/not-buffer", who + ": expected a Buffer / Uint8Array");
|
|
109
|
+
return guard.bytes.view(input, Asn1Error, "asn1/not-buffer", who);
|
|
111
110
|
}
|
|
112
111
|
|
|
113
112
|
// ---- Decoder --------------------------------------------------------
|
|
@@ -150,27 +149,15 @@ function _asBuffer(input, who) {
|
|
|
150
149
|
* var node = pki.asn1.decode(der);
|
|
151
150
|
* node.tagNumber === pki.asn1.TAGS.SEQUENCE;
|
|
152
151
|
*/
|
|
153
|
-
// A size/depth cap opt: absent means the constants.LIMITS default; a provided
|
|
154
|
-
// value must be a non-negative finite integer. A non-finite cap (NaN, Infinity)
|
|
155
|
-
// silently DISABLES the guard -- every `>` compare against it is false -- so a
|
|
156
|
-
// deeply nested input would run to a bare RangeError instead of the typed
|
|
157
|
-
// asn1/too-deep verdict. A bad cap is a config fault: throw at entry.
|
|
158
|
-
function _capOpt(v, key, dflt) {
|
|
159
|
-
if (v === undefined) return dflt;
|
|
160
|
-
if (typeof v !== "number" || !isFinite(v) || v < 0 || Math.floor(v) !== v) {
|
|
161
|
-
throw new TypeError("decode: " + key + " must be a non-negative integer");
|
|
162
|
-
}
|
|
163
|
-
return v;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
152
|
function decode(input, opts) {
|
|
167
153
|
opts = opts || {};
|
|
168
154
|
var buf = _asBuffer(input, "decode");
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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);
|
|
174
161
|
if (buf.length > maxBytes) {
|
|
175
162
|
throw new Asn1Error("asn1/too-large", "input " + buf.length + " bytes exceeds cap " + maxBytes);
|
|
176
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,19 +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
|
-
if (input instanceof Uint8Array) return Buffer.from(input.buffer, input.byteOffset, input.byteLength);
|
|
62
|
-
throw new CborError("cbor/not-buffer", who + ": expected a Buffer / Uint8Array");
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Config-time cap validation: a bad cap is an authoring bug, so it throws a
|
|
66
|
-
// TypeError (not a CborError) exactly like asn1-der's _capOpt.
|
|
67
|
-
function _capOpt(v, key, dflt) {
|
|
68
|
-
if (v === undefined) return dflt;
|
|
69
|
-
if (typeof v !== "number" || !Number.isInteger(v) || v < 0) {
|
|
70
|
-
throw new TypeError("decode: " + key + " must be a non-negative integer");
|
|
71
|
-
}
|
|
72
|
-
return v;
|
|
61
|
+
return guard.bytes.view(input, CborError, "cbor/not-buffer", who);
|
|
73
62
|
}
|
|
74
63
|
|
|
75
64
|
// Determinism profile -> a ruleset. v1 whitelists only "deterministic"
|
|
@@ -271,12 +260,9 @@ function _decodeItem(buf, start, limit, depth, maxD, rules, state) {
|
|
|
271
260
|
function decode(input, opts) {
|
|
272
261
|
opts = opts || {};
|
|
273
262
|
var buf = _asBuffer(input, "decode");
|
|
274
|
-
var maxBytes =
|
|
275
|
-
var maxDepth =
|
|
276
|
-
|
|
277
|
-
throw new TypeError("decode: maxDepth " + maxDepth + " exceeds the stack-safe ceiling " + constants.LIMITS.MAX_DECODE_DEPTH_CEILING);
|
|
278
|
-
}
|
|
279
|
-
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);
|
|
280
266
|
var rules = _profile(opts.profile);
|
|
281
267
|
if (buf.length > maxBytes) throw new CborError("cbor/too-large", "input " + buf.length + " bytes exceeds cap " + maxBytes);
|
|
282
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,9 +112,7 @@ function _peelInner(extValue) {
|
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
function _toBuffer(v, field) {
|
|
114
|
-
|
|
115
|
-
if (v instanceof Uint8Array) return Buffer.from(v);
|
|
116
|
-
throw new CtError("ct/bad-input", field + " must be a Buffer or Uint8Array");
|
|
115
|
+
return guard.bytes.view(v, CtError, "ct/bad-input", field);
|
|
117
116
|
}
|
|
118
117
|
|
|
119
118
|
// Parse one SerializedSCT body inside a sub-reader bounded to the element (so a
|
|
@@ -199,10 +198,7 @@ function _parseSct(r, sctLen) {
|
|
|
199
198
|
* }
|
|
200
199
|
*/
|
|
201
200
|
function parseSctList(extValue) {
|
|
202
|
-
|
|
203
|
-
throw new CtError("ct/bad-input", "parseSctList expects the SCT-list extension value as a Buffer or Uint8Array");
|
|
204
|
-
}
|
|
205
|
-
var blob = _peelInner(Buffer.isBuffer(extValue) ? extValue : Buffer.from(extValue));
|
|
201
|
+
var blob = _peelInner(_toBuffer(extValue, "the SCT-list extension value"));
|
|
206
202
|
if (blob.length > C.LIMITS.SCT_MAX_BYTES) {
|
|
207
203
|
throw new CtError("ct/too-large", "SCT list " + blob.length + " bytes exceeds the cap " + C.LIMITS.SCT_MAX_BYTES);
|
|
208
204
|
}
|
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
|
@@ -127,8 +127,9 @@ var ConstantsError = defineClass("ConstantsError");
|
|
|
127
127
|
// Asn1Error -- malformed / non-canonical DER: truncated TLV, indefinite
|
|
128
128
|
// length, non-minimal length or integer, leftover trailing bytes, depth
|
|
129
129
|
// or size cap exceeded. DER is a canonical encoding, so anything the
|
|
130
|
-
// decoder rejects is permanently invalid.
|
|
131
|
-
|
|
130
|
+
// decoder rejects is permanently invalid. withCause threads a raw byte-view
|
|
131
|
+
// failure (a detached backing ArrayBuffer) as the cause rather than discarding it.
|
|
132
|
+
var Asn1Error = defineClass("Asn1Error", { withCause: true });
|
|
132
133
|
|
|
133
134
|
// CborError -- malformed / non-deterministic CBOR: a reserved or indefinite
|
|
134
135
|
// additional-info value, a stray break, a non-minimal ("preferred") argument,
|
|
@@ -136,17 +137,19 @@ var Asn1Error = defineClass("Asn1Error");
|
|
|
136
137
|
// key, a non-minimal / oversized bignum, bad UTF-8, a wrong-typed tag body,
|
|
137
138
|
// leftover trailing bytes, or a depth / size cap exceeded. RFC 8949 core-
|
|
138
139
|
// deterministic encoding (sec. 4.2.1) is canonical, so anything the decoder
|
|
139
|
-
// rejects is permanently invalid.
|
|
140
|
-
|
|
140
|
+
// rejects is permanently invalid. withCause threads a raw byte-view failure
|
|
141
|
+
// (a detached backing ArrayBuffer) as the cause rather than discarding it.
|
|
142
|
+
var CborError = defineClass("CborError", { withCause: true });
|
|
141
143
|
|
|
142
144
|
// OidError -- a malformed object-identifier: fewer than two arcs, a first
|
|
143
145
|
// arc outside 0..2, a second arc >= 40 under arcs 0/1, or a non-minimal
|
|
144
|
-
// base-128 sub-identifier.
|
|
145
|
-
|
|
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 });
|
|
146
149
|
|
|
147
150
|
// PemError -- a malformed PEM envelope: missing / mismatched BEGIN/END
|
|
148
151
|
// markers, a bad label, or non-base64 body.
|
|
149
|
-
var PemError = defineClass("PemError");
|
|
152
|
+
var PemError = defineClass("PemError", { withCause: true });
|
|
150
153
|
|
|
151
154
|
// CertificateError -- a byte sequence that is not a well-formed X.509
|
|
152
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,10 +48,7 @@ var MAX_RANDOM_BYTES = 65536;
|
|
|
47
48
|
// ---- value helpers ---------------------------------------------------
|
|
48
49
|
|
|
49
50
|
function _toBuf(data, who) {
|
|
50
|
-
|
|
51
|
-
if (data instanceof ArrayBuffer) return Buffer.from(data);
|
|
52
|
-
if (ArrayBuffer.isView(data)) return Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
|
53
|
-
throw new WebCryptoError("webcrypto/data", (who || "input") + ": expected BufferSource (ArrayBuffer / TypedArray / Buffer)");
|
|
51
|
+
return guard.bytes.source(data, WebCryptoError, "webcrypto/data", who || "input");
|
|
54
52
|
}
|
|
55
53
|
|
|
56
54
|
function _toArrayBuffer(buf) {
|
|
@@ -343,7 +341,7 @@ SubtleCrypto.prototype.verify = async function verify(algorithm, key, signature,
|
|
|
343
341
|
// in constant time. The length check leaks nothing a constant-time compare
|
|
344
342
|
// would protect; the secret-dependent byte comparison is timingSafeEqual.
|
|
345
343
|
var digest = hm.digest();
|
|
346
|
-
return
|
|
344
|
+
return guard.crypto.constantTimeEqual(digest, sig);
|
|
347
345
|
}
|
|
348
346
|
throw new WebCryptoError("webcrypto/not-supported", "verify: unsupported algorithm " + JSON.stringify(name));
|
|
349
347
|
};
|
|
@@ -718,7 +716,11 @@ Crypto.prototype.getRandomValues = function getRandomValues(typedArray) {
|
|
|
718
716
|
if (typedArray.byteLength > MAX_RANDOM_BYTES) {
|
|
719
717
|
throw new WebCryptoError("webcrypto/data", "getRandomValues: byteLength exceeds " + MAX_RANDOM_BYTES);
|
|
720
718
|
}
|
|
721
|
-
|
|
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"));
|
|
722
724
|
return typedArray;
|
|
723
725
|
};
|
|
724
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
|
]
|