@blamejs/pki 0.6.13 → 0.6.15
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 +22 -1
- package/README.md +1 -1
- package/index.js +1 -1
- package/lib/acme.js +59 -815
- package/lib/asn1-der.js +7 -245
- package/lib/attrcert-sign.js +12 -260
- package/lib/attrcert-verify.js +1 -7
- package/lib/byte-reader.js +0 -8
- package/lib/byte-writer.js +0 -11
- package/lib/cbor-det.js +0 -54
- package/lib/cmc-build.js +15 -357
- package/lib/cmc-verify.js +6 -239
- package/lib/cmp-build.js +41 -317
- package/lib/cmp-session.js +73 -760
- package/lib/cmp-verify.js +32 -488
- package/lib/cms-compress.js +3 -32
- package/lib/cms-decrypt.js +29 -294
- package/lib/cms-encrypt.js +14 -234
- package/lib/cms-sign.js +29 -324
- package/lib/cms-verify.js +9 -500
- package/lib/composite-kem.js +2 -102
- package/lib/composite-sig.js +1 -77
- package/lib/constants.js +0 -249
- package/lib/crl-sign.js +9 -237
- package/lib/crl-verify.js +1 -10
- package/lib/crmf-sign.js +19 -198
- package/lib/crmf-verify.js +1 -12
- package/lib/csr-sign.js +3 -34
- package/lib/csr-verify.js +1 -12
- package/lib/ct.js +28 -253
- package/lib/edwards-point.js +10 -44
- package/lib/est.js +28 -764
- package/lib/framework-error.js +4 -182
- package/lib/guard-all.js +1 -58
- package/lib/guard-async.js +1 -36
- package/lib/guard-bytes.js +6 -650
- package/lib/guard-compress.js +5 -102
- package/lib/guard-crypto.js +1 -40
- package/lib/guard-der.js +2 -93
- package/lib/guard-encoding.js +2 -60
- package/lib/guard-header.js +1 -36
- package/lib/guard-identifier.js +7 -531
- package/lib/guard-intrinsic.js +1 -150
- package/lib/guard-json.js +10 -73
- package/lib/guard-limits.js +1 -77
- package/lib/guard-list.js +1 -49
- package/lib/guard-name.js +3 -209
- package/lib/guard-parsed.js +7 -335
- package/lib/guard-range.js +1 -71
- package/lib/guard-secret.js +1 -102
- package/lib/guard-text.js +1 -64
- package/lib/guard-time.js +1 -97
- package/lib/hpke.js +3 -105
- package/lib/http-digest.js +14 -142
- package/lib/http-retry-after.js +1 -47
- package/lib/http-transport.js +21 -131
- package/lib/inspect.js +30 -284
- package/lib/ip-utils.js +6 -47
- package/lib/jose.js +7 -142
- package/lib/key.js +2 -67
- package/lib/lint.js +9 -194
- package/lib/merkle.js +1 -27
- package/lib/mime.js +9 -82
- package/lib/ocsp-request-verify.js +1 -14
- package/lib/ocsp-verify.js +1 -82
- package/lib/ocsp.js +11 -169
- package/lib/oid.js +5 -337
- package/lib/path-validate.js +103 -1492
- package/lib/pbes2.js +2 -89
- package/lib/pkcs12-build.js +31 -379
- package/lib/pki-build.js +64 -290
- package/lib/rc2.js +2 -18
- package/lib/rfc3339.js +4 -27
- package/lib/scep.js +59 -233
- package/lib/schema-all.js +0 -122
- package/lib/schema-attrcert.js +4 -146
- package/lib/schema-c509.js +153 -802
- package/lib/schema-cmc.js +5 -220
- package/lib/schema-cmp.js +2 -223
- package/lib/schema-cms.js +13 -427
- package/lib/schema-crl.js +5 -68
- package/lib/schema-crmf.js +6 -167
- package/lib/schema-csr.js +1 -54
- package/lib/schema-csrattrs.js +2 -87
- package/lib/schema-engine.js +0 -152
- package/lib/schema-ocsp.js +0 -117
- package/lib/schema-pkcs12.js +2 -93
- package/lib/schema-pkcs8.js +3 -35
- package/lib/schema-pkix.js +24 -655
- package/lib/schema-smime.js +0 -54
- package/lib/schema-tsp.js +3 -99
- package/lib/schema-x509.js +4 -63
- package/lib/shbs.js +11 -95
- package/lib/sign-scheme.js +2 -97
- package/lib/sigstore.js +9 -169
- package/lib/sleep.js +2 -8
- package/lib/smime.js +15 -408
- package/lib/tls-cert-compress.js +0 -62
- package/lib/trust.js +19 -185
- package/lib/tsp-sign.js +16 -270
- package/lib/validator-all.js +1 -30
- package/lib/validator-attcert.js +2 -46
- package/lib/validator-cose.js +2 -153
- package/lib/validator-keydesc.js +1 -37
- package/lib/validator-sig.js +1 -46
- package/lib/validator-tls.js +1 -88
- package/lib/validator-tpm.js +19 -122
- package/lib/verify-seam.js +1 -24
- package/lib/webauthn-mds.js +3 -400
- package/lib/webauthn.js +12 -833
- package/lib/webcrypto.js +5 -485
- package/lib/x509-sign.js +4 -108
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/guard-secret.js
CHANGED
|
@@ -1,127 +1,30 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright (c) blamejs contributors
|
|
3
3
|
"use strict";
|
|
4
|
-
//
|
|
5
|
-
// @internal -- no operator-facing namespace. The documented surface is the KEM
|
|
6
|
-
// key-establishment paths that compose this guard (pki.cms.encrypt / decrypt).
|
|
7
|
-
//
|
|
8
|
-
// guard-secret -- wipe a secret buffer the TOOLKIT ALLOCATED at the moment it
|
|
9
|
-
// stops being needed. NIST SP 800-227 RS5 / sec. 4.2 requires that a KEM shared
|
|
10
|
-
// secret and every intermediate value be destroyed as soon as they are no longer
|
|
11
|
-
// needed; RFC 9629 sec. 7 says the same of the KEK a KEMRecipientInfo derives.
|
|
12
|
-
//
|
|
13
|
-
// The defended class is secret lifetime, not secret disclosure: a shared secret
|
|
14
|
-
// or KEK left readable in the heap widens the window in which a later memory
|
|
15
|
-
// disclosure -- a core dump, a swapped page, a same-process read primitive --
|
|
16
|
-
// yields key material for traffic that was already decrypted.
|
|
17
|
-
//
|
|
18
|
-
// SCOPE, stated honestly because the docstring is the only place a reader learns
|
|
19
|
-
// it: this is BEST EFFORT. The runtime copies buffers into places no JS can
|
|
20
|
-
// reach (node's decapsulate return is copied on the way out; importKey("raw")
|
|
21
|
-
// copies into a KeyObject), and V8 may relocate a backing store, leaving the
|
|
22
|
-
// original bytes behind. Wiping the copies the toolkit holds shortens the
|
|
23
|
-
// window. It does not support a claim that a secret never persists in memory,
|
|
24
|
-
// and no operator-facing text may imply that it does.
|
|
25
|
-
//
|
|
26
|
-
// Ownership is the contract. Only a buffer the toolkit allocated may be wiped:
|
|
27
|
-
// never a caller's opts.key / opts.cert / opts.kek / opts.password, and never the
|
|
28
|
-
// input DER. Silently destroying a caller's own memory is a worse defect than
|
|
29
|
-
// leaving a secret readable, and it is the failure mode a zeroization patch
|
|
30
|
-
// reaches for first, so the call sites pass only their own intermediates.
|
|
4
|
+
// @internal
|
|
31
5
|
|
|
32
6
|
var bytes = require("./guard-bytes");
|
|
33
7
|
var intrinsic = require("./guard-intrinsic");
|
|
34
8
|
|
|
35
|
-
// The fill this guard performs, taken from the typed-array prototype at module load and uncurried
|
|
36
|
-
// so the wipe site reads no property at all. Buffer overrides the fill with a writable
|
|
37
|
-
// `Buffer.prototype.fill`, so reading the method off the value would let a caller decide what
|
|
38
|
-
// wiping means; reading `call` off the captured function would let them decide it again, since
|
|
39
|
-
// that function is still the one sitting on the prototype (see guard-intrinsic).
|
|
40
9
|
var _fill = intrinsic.uncurry(Uint8Array.prototype.fill);
|
|
41
|
-
// The join that produces the buffer cipherFinish hands back. Captured for the same reason as
|
|
42
|
-
// the fill: a replaced `Buffer.concat` decides what the caller receives, and it would receive it
|
|
43
|
-
// while the two intermediates this module is responsible for clearing were cleared as promised.
|
|
44
10
|
var _concat = intrinsic.bufferConcat;
|
|
45
11
|
|
|
46
|
-
// zeroize(value, ErrorClass, code, label) -> the same object, cleared.
|
|
47
|
-
// value : a Buffer / TypedArray the TOOLKIT allocated, or null / undefined
|
|
48
|
-
// (absent is a no-op so a `finally` needs no branch around it).
|
|
49
|
-
// ErrorClass : the caller's typed error CONSTRUCTOR, declared with
|
|
50
|
-
// `{ withCause: true }`. The guard family carries two currencies --
|
|
51
|
-
// most guards take a (code, message) factory and call it without
|
|
52
|
-
// `new`, while guard-bytes / guard-header take the class and
|
|
53
|
-
// construct it. This module's one throw is the delegated re-view
|
|
54
|
-
// below, so it must pass what guard-bytes expects: the class, and
|
|
55
|
-
// one that accepts a cause, because guard-bytes threads the raw
|
|
56
|
-
// detach fault through as one. A plain class fails to construct at
|
|
57
|
-
// the single moment the caller needs a real error.
|
|
58
|
-
// code : the frozen domain/reason code a detached buffer rejects under.
|
|
59
|
-
// label : field phrase for the message.
|
|
60
|
-
//
|
|
61
|
-
// A detached ArrayBuffer cannot be written, and reaching one here means a caller
|
|
62
|
-
// handed over memory that was transferred away. That is a real fault and not
|
|
63
|
-
// something to swallow, so it routes through the shared re-view guard and throws
|
|
64
|
-
// typed.
|
|
65
|
-
//
|
|
66
|
-
// The `.fill(0)` shape lives only in this module. A wipe re-inlined anywhere in
|
|
67
|
-
// lib/, including a module not yet written, is flagged, so the safe implementation
|
|
68
|
-
// is also the tripwire that stops the next consumer from rolling its own partial
|
|
69
|
-
// one.
|
|
70
12
|
// @enforced-by guard-shape-reinlined
|
|
71
13
|
// @guard-shape \.fill\s*\(\s*0\s*[,)]
|
|
72
14
|
function zeroize(value, ErrorClass, code, label) {
|
|
73
15
|
if (value === null || value === undefined) return value;
|
|
74
|
-
// Re-view through the shared bytes guard: it is the single place that decides
|
|
75
|
-
// what counts as a writable BufferSource and rejects a detached one typed.
|
|
76
16
|
var view = bytes.view(value, ErrorClass, code, label);
|
|
77
|
-
// Through the fill captured at module load, never `view.fill(0)`. Buffer overrides the typed-array
|
|
78
|
-
// fill with its own, and that property is writable, so a caller who replaces it with a no-op turns
|
|
79
|
-
// every wipe in the toolkit into a silent nothing -- and nothing observable changes, because the
|
|
80
|
-
// wipe still runs, still walks the right buffer, and still returns. A secret that survives a call
|
|
81
|
-
// whose whole purpose was to clear it is the failure this guard exists to prevent, so the one
|
|
82
|
-
// operation it performs is the one operation a caller must not be able to reach.
|
|
83
17
|
_fill(view, 0);
|
|
84
18
|
return value;
|
|
85
19
|
}
|
|
86
20
|
|
|
87
|
-
// zeroizeAll(list, ErrorClass, code, label) wipes every present member, tolerating
|
|
88
|
-
// holes so a `finally` can name intermediates that may not have been reached.
|
|
89
|
-
//
|
|
90
21
|
// @enforced-by behavioral -- this is a loop over zeroize, which carries the family's only
|
|
91
|
-
// rename-proof shape (the `.fill(0)` above). It introduces no shape of its own, so a lexical
|
|
92
|
-
// detector here would anchor on a renameable symbol and go silently green (drift rule sec. 3).
|
|
93
|
-
// The behavioral guards are guard-secret.test.js (holes tolerated, every member cleared) and the
|
|
94
|
-
// CMS vectors that assert the shared secret and KEK are wiped on both the success and failure paths.
|
|
95
22
|
function zeroizeAll(list, ErrorClass, code, label) {
|
|
96
23
|
if (!list) return list;
|
|
97
24
|
for (var i = 0; i < list.length; i++) zeroize(list[i], ErrorClass, code, label);
|
|
98
25
|
return list;
|
|
99
26
|
}
|
|
100
27
|
|
|
101
|
-
// cipherFinish(transform, input, ErrorClass, code, label) -> the output, as one fresh buffer
|
|
102
|
-
// transform : a node:crypto Cipher or Decipher the CALLER built and configured (key, iv, AAD,
|
|
103
|
-
// auth tag, padding mode). This guard performs the two calls and nothing else.
|
|
104
|
-
// input : the bytes to feed it.
|
|
105
|
-
//
|
|
106
|
-
// The single place this toolkit runs a configured cipher to completion, because
|
|
107
|
-
// `Buffer.concat([c.update(x), c.final()])` abandons a copy on BOTH of its exits and the leak is
|
|
108
|
-
// invisible at the call site.
|
|
109
|
-
//
|
|
110
|
-
// On a DECIPHER the abandoned copy is plaintext. `update` returns the recovered plaintext while
|
|
111
|
-
// `final` is what decides whether the message was authentic at all -- a GCM tag mismatch, a CBC
|
|
112
|
-
// padding failure -- so on a forged message the whole plaintext exists before anything has judged
|
|
113
|
-
// it, and the `concat` never runs: that buffer becomes unreferenced garbage holding readable
|
|
114
|
-
// plaintext. RFC 5083 sec. 1 requires a receiver whose integrity check fails to destroy the
|
|
115
|
-
// plaintext, and withholding it from the caller is not destroying it. On the success path `concat`
|
|
116
|
-
// COPIES, so the update buffer is a second full copy the caller never receives and nothing else
|
|
117
|
-
// will ever clear.
|
|
118
|
-
//
|
|
119
|
-
// It covers the ENCRYPT direction too, where the intermediates are ciphertext and clearing them
|
|
120
|
-
// buys little. That is deliberate: any rule for which of the two directions must clear is a
|
|
121
|
-
// judgment made at each call site, and the sites where it is wrong are the ones nobody re-reads.
|
|
122
|
-
// One shape, cleared everywhere, needs no such judgment -- and the encrypt intermediates of a
|
|
123
|
-
// key-wrap are not obviously worthless to an attacker either.
|
|
124
|
-
//
|
|
125
28
|
// @enforced-by guard-shape-reinlined
|
|
126
29
|
// @guard-shape \.update\s*\([\s\S]{0,300}?\)\s*,\s*[A-Za-z_$][\w$]*\s*\.\s*final\s*\(\s*\)
|
|
127
30
|
function cipherFinish(transform, input, ErrorClass, code, label) {
|
|
@@ -129,8 +32,6 @@ function cipherFinish(transform, input, ErrorClass, code, label) {
|
|
|
129
32
|
try {
|
|
130
33
|
head = transform.update(input);
|
|
131
34
|
tail = transform.final();
|
|
132
|
-
// Always a fresh copy, never `head` itself: the returned buffer belongs to the caller and the
|
|
133
|
-
// two intermediates have to be clearable independently of it.
|
|
134
35
|
return _concat([head, tail]);
|
|
135
36
|
} finally {
|
|
136
37
|
zeroize(head, ErrorClass, code, label);
|
|
@@ -138,6 +39,4 @@ function cipherFinish(transform, input, ErrorClass, code, label) {
|
|
|
138
39
|
}
|
|
139
40
|
}
|
|
140
41
|
|
|
141
|
-
// Frozen for the reason the family header gives: a boundary reads its guard off this object at the
|
|
142
|
-
// call, so a writable export would let a caller replace the check rather than pass it.
|
|
143
42
|
module.exports = intrinsic.freeze({ zeroize: zeroize, zeroizeAll: zeroizeAll, cipherFinish: cipherFinish });
|
package/lib/guard-text.js
CHANGED
|
@@ -1,86 +1,27 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright (c) blamejs contributors
|
|
3
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).
|
|
4
|
+
// @internal
|
|
21
5
|
|
|
22
6
|
var bytes = require("./guard-bytes");
|
|
23
7
|
var intrinsic = require("./guard-intrinsic");
|
|
24
8
|
|
|
25
|
-
// Everything this guard does to a caller's input, taken at module load. Each name below is an
|
|
26
|
-
// ordinary writable property of a global, so reading it at call time would let a caller decide what
|
|
27
|
-
// the guard does at the moment it does it. Between them these four cover both of the guard's
|
|
28
|
-
// contracts, and both of its input arms:
|
|
29
|
-
//
|
|
30
|
-
// _toString / _decode produce the guard's entire output on the Buffer arm. Replaced with a
|
|
31
|
-
// function returning a constant, they hand a PEM header, a JOSE segment or a
|
|
32
|
-
// DN attribute value a string the caller chose in place of the bytes that
|
|
33
|
-
// were checked -- while the cap, the detached-view refusal and the strict
|
|
34
|
-
// UTF-8 rule all still run and all still pass.
|
|
35
|
-
// _byteLength measures the string arm against the cap. One returning 0 admits a string
|
|
36
|
-
// of any size, which is the allocation-DoS this guard exists to bound.
|
|
37
|
-
// _isInteger decides whether the cap itself is usable; guard.bytes.isByteSource decides
|
|
38
|
-
// whether an input takes the byte arm or the string arm.
|
|
39
|
-
// The two method captures are uncurried, so their call sites read no property of them; see
|
|
40
|
-
// guard-intrinsic for why capturing a method and then reading `.call` on it reopens the hole.
|
|
41
9
|
var _toString = intrinsic.uncurry(Buffer.prototype.toString);
|
|
42
10
|
var _decode = intrinsic.uncurry(TextDecoder.prototype.decode);
|
|
43
|
-
// Already plain functions rather than methods, so these are invoked directly.
|
|
44
11
|
var _byteLength = Buffer.byteLength;
|
|
45
12
|
var _isInteger = Number.isInteger;
|
|
46
13
|
var _TextDecoder = TextDecoder;
|
|
47
14
|
|
|
48
15
|
var LATIN1 = "latin1";
|
|
49
16
|
|
|
50
|
-
// decode(input, maxBytes, ErrorClass, spec) -> string.
|
|
51
|
-
// input : a Buffer (decoded per spec.charset) or a string (taken as-is).
|
|
52
|
-
// maxBytes : the raw byte-length ceiling, checked before any string copy.
|
|
53
|
-
// ErrorClass: a PkiError subclass (withCause where spec.fatal is set).
|
|
54
|
-
// spec : { charset: "latin1"|"utf-8" (default latin1),
|
|
55
|
-
// fatal: boolean, strict UTF-8 (a lone continuation /
|
|
56
|
-
// truncated sequence throws, never substitutes U+FFFD),
|
|
57
|
-
// tooLarge: code for the over-cap reject,
|
|
58
|
-
// badDecode: code for a fatal-charset (bad-UTF-8) reject
|
|
59
|
-
// (required only when fatal is set),
|
|
60
|
-
// badInput: code for a non-byte-source/non-string reject,
|
|
61
|
-
// label: human phrase for the message }
|
|
62
17
|
// @enforced-by behavioral -- cap-before-copy has no rename-proof code shape; the
|
|
63
|
-
// over-cap RED vectors + the detached re-view through guard.bytes.source (itself
|
|
64
|
-
// enforced) are the guard.
|
|
65
18
|
function decode(input, maxBytes, ErrorClass, spec) {
|
|
66
|
-
// maxBytes is an authoring input: an undefined / NaN / fractional cap makes
|
|
67
|
-
// the `length > maxBytes` comparison always false, silently disabling the size
|
|
68
|
-
// cap on the guard whose whole contract is cap-before-copy. Config-time
|
|
69
|
-
// TypeError (every composing boundary passes a C.LIMITS constant).
|
|
70
19
|
if (!_isInteger(maxBytes) || maxBytes < 0) {
|
|
71
20
|
throw new TypeError("guard.text.decode: maxBytes must be a non-negative integer");
|
|
72
21
|
}
|
|
73
22
|
var charset = spec.charset || LATIN1;
|
|
74
23
|
if (bytes.isByteSource(input)) {
|
|
75
|
-
// Re-view through the byte guard first so any byte source (a Buffer, a Uint8Array or other typed array, a
|
|
76
|
-
// DataView, or a raw ArrayBuffer -- a caller-injected transport may return any of these) is normalized to a
|
|
77
|
-
// usable view, and a detached backing ArrayBuffer (a transferred / structuredClone'd view, which reads as
|
|
78
|
-
// zero-length) fails closed here instead of being decoded as an empty string. Then cap, then decode.
|
|
79
24
|
input = bytes.source(input, ErrorClass, spec.badInput, spec.label);
|
|
80
|
-
// Measured through the byte guard's captured intrinsic getter, never `input.length`. On a typed
|
|
81
|
-
// array `length` is an accessor on the prototype and it is configurable, so a replacement
|
|
82
|
-
// returning 0 lets a buffer of any size past the cap and straight into the decode -- the
|
|
83
|
-
// allocation this guard exists to bound, defeated by the one read that bounds it.
|
|
84
25
|
if (bytes.lengthOf(input) > maxBytes) throw new ErrorClass(spec.tooLarge, spec.label + " exceeds the size cap");
|
|
85
26
|
if (spec.fatal) {
|
|
86
27
|
try { return _decode(new _TextDecoder(charset, { fatal: true }), input); }
|
|
@@ -89,8 +30,6 @@ function decode(input, maxBytes, ErrorClass, spec) {
|
|
|
89
30
|
return _toString(input, charset);
|
|
90
31
|
}
|
|
91
32
|
if (typeof input === "string") {
|
|
92
|
-
// A latin1 string's char length equals its byte length; a UTF-8 string's
|
|
93
|
-
// does not, so bound the encoded byte length.
|
|
94
33
|
var byteLen = charset === LATIN1 ? input.length : _byteLength(input, "utf8");
|
|
95
34
|
if (byteLen > maxBytes) throw new ErrorClass(spec.tooLarge, spec.label + " exceeds the size cap");
|
|
96
35
|
return input;
|
|
@@ -98,6 +37,4 @@ function decode(input, maxBytes, ErrorClass, spec) {
|
|
|
98
37
|
throw new ErrorClass(spec.badInput, spec.label + " expects a string or a byte source (Buffer / TypedArray / DataView / ArrayBuffer)");
|
|
99
38
|
}
|
|
100
39
|
|
|
101
|
-
// Frozen for the reason the family header gives: a boundary reads its guard off this object at the
|
|
102
|
-
// call, so a writable export would let a caller replace the check rather than pass it.
|
|
103
40
|
module.exports = intrinsic.freeze({ decode: decode });
|
package/lib/guard-time.js
CHANGED
|
@@ -1,138 +1,42 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright (c) blamejs contributors
|
|
3
3
|
"use strict";
|
|
4
|
-
//
|
|
5
|
-
// @internal -- no operator-facing namespace. The documented surface is the
|
|
6
|
-
// verifiers whose validity / currency / temporal gates compose this guard
|
|
7
|
-
// (pki.path.validate, pki.ocsp.verify, pki.tsp.*, pki.ct.*, pki.cms.sign).
|
|
8
|
-
//
|
|
9
|
-
// guard-time -- fail-closed Date-instant validation before a temporal comparison.
|
|
10
|
-
//
|
|
11
|
-
// Defends the NaN-Date fail-open class (CWE-20 improper input validation feeding
|
|
12
|
-
// a silent security-gate bypass). An Invalid Date is still `instanceof Date`, and
|
|
13
|
-
// every relational comparison against its NaN getTime() (NaN < x, NaN >= x) is
|
|
14
|
-
// false, so a validity / not-before / not-after / currency window built on an
|
|
15
|
-
// unvalidated Date silently accepts when it should reject. The class recurred at
|
|
16
|
-
// three independent boundaries (TSP genTime, OCSP producedAt/thisUpdate, CT
|
|
17
|
-
// temporal-interval) before it was centralized here; the sibling
|
|
18
|
-
// nan-date-comparison-unguarded codebase-patterns detector flags any lib function
|
|
19
|
-
// that re-inlines a `.getTime()` comparison without first rejecting a NaN, so a
|
|
20
|
-
// new boundary is routed to this guard instead of re-growing the bug.
|
|
21
|
-
//
|
|
22
|
-
// Tier split: `value instanceof Date` with a NaN time is malformed caller input
|
|
23
|
-
// (a config-time or entry-point Date, such as an operator-supplied distrustAfter
|
|
24
|
-
// or a caller `opts.time`), so assertValid throws the caller's typed error instead
|
|
25
|
-
// of returning a silent default. within() is the fail-closed window primitive: it
|
|
26
|
-
// THROWS on a malformed operand but RETURNS a boolean for in/out-of-window, so the
|
|
27
|
-
// OCSP / CRL currency callers that treat out-of-window as a `continue` skip keep
|
|
28
|
-
// their control flow while a NaN operand can never slip through as a false.
|
|
4
|
+
// @internal
|
|
29
5
|
|
|
30
|
-
// The slot predicates and the NaN test, snapshotted at load. `util.types` is an ordinary object,
|
|
31
|
-
// so a later `util.types.isDate = () => true` would otherwise decide what this guard calls a Date;
|
|
32
|
-
// the global `isNaN` is writable, and one returning false makes an invalid Date pass the check
|
|
33
|
-
// below, which is the fail-open direction.
|
|
34
6
|
var util = { types: require("./guard-intrinsic").types };
|
|
35
7
|
var _freeze = require("./guard-intrinsic").freeze;
|
|
36
8
|
var _isNaN = Number.isNaN;
|
|
37
9
|
|
|
38
|
-
// assertValid(value, E, code, label) -> the same Date, once proven valid.
|
|
39
|
-
// value : a Date (or an alleged one) from a caller boundary.
|
|
40
|
-
// E : the (code, message[, cause]) typed-error FACTORY in scope at the call
|
|
41
|
-
// site (ns.E / the module-local _err), never a defineClass class, which
|
|
42
|
-
// would crash `class cannot be invoked without new` on the error path.
|
|
43
|
-
// code : the frozen domain/reason code this boundary rejects malformed time under.
|
|
44
|
-
// label : field phrase for the message.
|
|
45
|
-
// The instant a Date holds, asked of the language rather than of the Date.
|
|
46
|
-
//
|
|
47
|
-
// `getTime` is an ordinary method on `Date.prototype`, so `class T extends Date { getTime() {} }`
|
|
48
|
-
// and an own `getTime` on any instance both answer it. Reading it off the value let a caller throw
|
|
49
|
-
// a raw exception out of a guard whose contract is a typed error, and let one return a fabricated
|
|
50
|
-
// instant that passed the check while the real one did not. It also made the read repeatable: a
|
|
51
|
-
// method asked twice may answer twice, so a window check could validate one instant and compare
|
|
52
|
-
// another.
|
|
53
|
-
//
|
|
54
|
-
// The intrinsic is called against the value, which reaches the internal slot whatever the value's
|
|
55
|
-
// own class says, and refuses a receiver with no slot behind it.
|
|
56
|
-
//
|
|
57
|
-
// The capture is of whatever `Date.prototype.getTime` holds when this module first loads, so it
|
|
58
|
-
// is the language's own function only if nothing replaced it earlier in the process. Code that
|
|
59
|
-
// runs before this module is inside the trust boundary already and can reach further than this
|
|
60
|
-
// one method, so there is no defense here to add; what the capture buys is immunity to every
|
|
61
|
-
// replacement that happens afterwards, which is the case a caller's argument can reach.
|
|
62
|
-
//
|
|
63
|
-
// The capture is uncurried through guard-intrinsic, so `instantOf` reads no property of it either.
|
|
64
|
-
// The captured function is still the one on `Date.prototype`, and an own `call` assigned to it
|
|
65
|
-
// would shadow `Function.prototype.call` and decide every instant this toolkit evaluates against.
|
|
66
10
|
var _dateGetTime = require("./guard-intrinsic").uncurry(Date.prototype.getTime);
|
|
67
11
|
|
|
68
|
-
// instantOf(value) -> the epoch milliseconds a Date holds, read through the intrinsic.
|
|
69
|
-
//
|
|
70
|
-
// The shape is the method call itself, wherever it appears. There is no boundary at which reading
|
|
71
|
-
// `getTime` off a value is the safe spelling: a caller's Date reaches the read through an override,
|
|
72
|
-
// and this toolkit's own Date reaches it through whatever `Date.prototype.getTime` has been made
|
|
73
|
-
// to be, which a dependency loaded before this module gets to decide. So the rule is that lib/
|
|
74
|
-
// calls no `getTime` at all, and the one capture above is where it lives.
|
|
75
|
-
//
|
|
76
12
|
// @enforced-by guard-shape-reinlined
|
|
77
13
|
// @guard-shape \.getTime\s*\(
|
|
78
14
|
function instantOf(value) { return _dateGetTime(value); }
|
|
79
15
|
|
|
80
16
|
// @enforced-by nan-date-comparison-unguarded
|
|
81
17
|
function assertValid(value, E, code, label) {
|
|
82
|
-
// The slot, never the prototype. `instanceof Date` asks what a value inherits from, and
|
|
83
|
-
// `Object.create(Date.prototype)` inherits everything while holding no instant, so it passes
|
|
84
|
-
// that test and `getTime()` then throws a raw TypeError out of this guard. `util.types.isDate`
|
|
85
|
-
// reads the internal slot, which a caller cannot fake and which answers the same across realms.
|
|
86
18
|
if (!util.types.isDate(value) || _isNaN(instantOf(value))) {
|
|
87
19
|
throw E(code, (label || "value") + " must be a valid Date");
|
|
88
20
|
}
|
|
89
21
|
return value;
|
|
90
22
|
}
|
|
91
23
|
|
|
92
|
-
// within(instant, lower, upper, E, code, label, opts) -> boolean (instant in window).
|
|
93
|
-
// Rejects a malformed instant / lower / upper by THROWING (assertValid), then
|
|
94
|
-
// answers containment as a boolean. Half-open [lower, upper) by default (a CT
|
|
95
|
-
// temporal-interval / an OCSP-CRL currency window); pass opts.upperInclusive for
|
|
96
|
-
// the closed [notBefore, notAfter] certificate-validity shape.
|
|
97
24
|
// @enforced-by nan-date-comparison-unguarded
|
|
98
25
|
function within(instant, lower, upper, E, code, label, opts) {
|
|
99
|
-
// assertValid rejects a non-Date / Invalid Date operand up front, so t/lo/hi below are
|
|
100
|
-
// guaranteed non-NaN -- the containment comparisons can never be a silent NaN-false. The
|
|
101
|
-
// getTime() results are bound to locals (not compared inline), so this home carries no
|
|
102
|
-
// unguarded `.getTime()`-in-a-comparison shape for the nan-date-comparison-unguarded detector.
|
|
103
26
|
assertValid(instant, E, code, label);
|
|
104
27
|
assertValid(lower, E, code, (label || "window") + " lower bound");
|
|
105
28
|
assertValid(upper, E, code, (label || "window") + " upper bound");
|
|
106
|
-
// Read through the intrinsic, so the instants compared here are the ones assertValid just
|
|
107
|
-
// proved. A caller's `getTime` override answers each call afresh, which made the value checked
|
|
108
|
-
// and the value compared two different reads of the same argument.
|
|
109
29
|
var t = instantOf(instant);
|
|
110
30
|
var lo = instantOf(lower);
|
|
111
31
|
var hi = instantOf(upper);
|
|
112
32
|
return t >= lo && (opts && opts.upperInclusive ? t <= hi : t < hi);
|
|
113
33
|
}
|
|
114
34
|
|
|
115
|
-
// isDate(value) -> boolean: does this value actually hold an instant?
|
|
116
|
-
//
|
|
117
|
-
// The predicate half of assertValid, for the boundaries that choose a default rather than refuse
|
|
118
|
-
// (`opts.genTime instanceof Date ? opts.genTime : new Date()`). `instanceof` asks what a value
|
|
119
|
-
// inherits from, so `Object.create(Date.prototype)` passes it while holding no instant, and the
|
|
120
|
-
// `.getTime()` that follows throws a raw TypeError out of a verb whose contract is a typed error.
|
|
121
|
-
// A caller who supplied one and got the default instead would be worse served still: the value
|
|
122
|
-
// they passed would have been silently replaced. This reads the slot, which cannot be faked and
|
|
123
|
-
// answers the same for a Date built in another realm.
|
|
124
|
-
//
|
|
125
|
-
// An Invalid Date has the slot and a NaN instant, so this says yes to one. A boundary that
|
|
126
|
-
// compares instants wants assertValid, which rejects both shapes.
|
|
127
35
|
// @enforced-by behavioral -- a prototype test in place of a slot test has no rename-proof code
|
|
128
|
-
// shape; the RED vectors (a slot-less Date lookalike at cms.verify / cms.sign / tsp.sign) are
|
|
129
|
-
// the guard.
|
|
130
36
|
function isDate(value) {
|
|
131
37
|
return util.types.isDate(value);
|
|
132
38
|
}
|
|
133
39
|
|
|
134
|
-
// Frozen for the reason the family header gives: a boundary reads its guard off this object at the
|
|
135
|
-
// call, so a writable export would let a caller replace the check rather than pass it.
|
|
136
40
|
module.exports = _freeze({
|
|
137
41
|
assertValid: assertValid,
|
|
138
42
|
instantOf: instantOf,
|