@blamejs/pki 0.8.30 → 0.8.48
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 +411 -0
- package/README.md +67 -16
- package/bin/pki.js +716 -30
- package/examples/issue-a-certificate.js +90 -0
- package/examples/post-quantum.js +66 -0
- package/examples/read-a-certificate.js +56 -0
- package/examples/revoke-and-check.js +86 -0
- package/examples/sign-and-verify-cms.js +63 -0
- package/index.js +18 -5
- package/lib/acme.js +223 -74
- package/lib/alt-sig.js +441 -0
- package/lib/asn1-der.js +48 -25
- package/lib/attrcert-sign.js +27 -19
- package/lib/cbor-det.js +24 -14
- package/lib/cmc-build.js +9 -2
- package/lib/cmc-verify.js +14 -3
- package/lib/cmp-build.js +17 -5
- package/lib/cmp-session.js +24 -11
- package/lib/cmp-verify.js +28 -19
- package/lib/cms-compress.js +3 -1
- package/lib/cms-decrypt.js +102 -7
- package/lib/cms-encrypt.js +59 -7
- package/lib/cms-sign.js +31 -9
- package/lib/cms-verify.js +22 -18
- package/lib/composite-kem.js +30 -18
- package/lib/composite-sig.js +29 -14
- package/lib/constants.js +28 -0
- package/lib/crl-sign.js +111 -19
- package/lib/crmf-sign.js +167 -15
- package/lib/csr-sign.js +117 -6
- package/lib/ct.js +939 -48
- package/lib/est.js +29 -16
- package/lib/framework-error.js +12 -0
- package/lib/guard-async.js +1 -1
- package/lib/guard-bytes.js +103 -20
- package/lib/guard-crypto.js +205 -3
- package/lib/guard-encoding.js +6 -1
- package/lib/guard-identifier.js +55 -8
- package/lib/guard-intrinsic.js +43 -0
- package/lib/guard-json.js +122 -9
- package/lib/guard-limits.js +12 -1
- package/lib/guard-list.js +63 -8
- package/lib/guard-name.js +167 -20
- package/lib/guard-range.js +43 -5
- package/lib/guard-text.js +41 -0
- package/lib/guard-verdict.js +39 -8
- package/lib/hpke.js +688 -42
- package/lib/http-transport.js +156 -4
- package/lib/identity-match.js +4 -4
- package/lib/inspect.js +448 -2
- package/lib/jose.js +93 -25
- package/lib/key.js +18 -13
- package/lib/lint.js +165 -1
- package/lib/merkle.js +233 -27
- package/lib/ocsp.js +27 -14
- package/lib/oid.js +14 -4
- package/lib/path-validate.js +143 -61
- package/lib/pkcs12-build.js +7 -2
- package/lib/pki-build.js +31 -7
- package/lib/possession.js +575 -0
- package/lib/related-cert.js +708 -0
- package/lib/revocation-fetch.js +6 -6
- package/lib/scep.js +15 -5
- package/lib/schema-c509.js +5 -2
- package/lib/schema-cmc.js +1 -1
- package/lib/schema-cmp.js +8 -8
- package/lib/schema-cms.js +2 -15
- package/lib/schema-csr.js +69 -0
- package/lib/schema-engine.js +10 -3
- package/lib/schema-pkix.js +267 -4
- package/lib/schema-tsp.js +7 -6
- package/lib/sign-scheme.js +22 -9
- package/lib/sigstore.js +684 -168
- package/lib/sleep.js +8 -1
- package/lib/smime.js +22 -1
- package/lib/tlog.js +1235 -0
- package/lib/tls-cert-compress.js +25 -8
- package/lib/tsp-sign.js +120 -35
- package/lib/tuf.js +1395 -0
- package/lib/validator-tpm.js +4 -1
- package/lib/verify-seam.js +7 -1
- package/lib/webauthn-mds.js +41 -4
- package/lib/webauthn.js +43 -25
- package/lib/webcrypto.js +48 -46
- package/lib/x509-sign.js +225 -19
- package/package.json +2 -1
- package/sbom.cdx.json +6 -6
package/lib/merkle.js
CHANGED
|
@@ -42,34 +42,59 @@
|
|
|
42
42
|
* `verifyConsistency` over sync SHA-256, fail-closed, transport-free.
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
|
-
var nodeCrypto = require("node:crypto");
|
|
46
45
|
var constants = require("./constants");
|
|
47
46
|
var frameworkError = require("./framework-error");
|
|
48
47
|
var guard = require("./guard-all");
|
|
48
|
+
var intrinsic = require("./guard-intrinsic");
|
|
49
|
+
|
|
50
|
+
/** @internal The operations this module decides with, bound at load. A proof is assembled by
|
|
51
|
+
* concatenation, so a replaced `Array.prototype.concat` chooses which nodes the proof carries and in
|
|
52
|
+
* what order, and a replaced `Buffer.concat` chooses the bytes a node hash covers. Taking the captures
|
|
53
|
+
* also puts this file in the live-read gate's scope, which is how it stays converted. */
|
|
54
|
+
var _concat = intrinsic.concat;
|
|
55
|
+
var _bufferConcat = intrinsic.bufferConcat;
|
|
56
|
+
var _bufferFrom = intrinsic.bufferFrom;
|
|
57
|
+
var _bufferAlloc = intrinsic.bufferAlloc;
|
|
58
|
+
var _isArray = intrinsic.isArray;
|
|
59
|
+
var _assign = intrinsic.assign;
|
|
60
|
+
var _create = intrinsic.create;
|
|
61
|
+
var _BigInt = intrinsic.BigInt;
|
|
49
62
|
|
|
50
63
|
var MerkleError = frameworkError.MerkleError;
|
|
51
64
|
|
|
52
65
|
function _merkleErr(c, m) { return new MerkleError(c, m); }
|
|
53
66
|
|
|
54
67
|
var SHA256_BYTES = 32;
|
|
55
|
-
var LEAF_PREFIX =
|
|
56
|
-
var NODE_PREFIX =
|
|
57
|
-
var EMPTY =
|
|
68
|
+
var LEAF_PREFIX = _bufferFrom([0x00]);
|
|
69
|
+
var NODE_PREFIX = _bufferFrom([0x01]);
|
|
70
|
+
var EMPTY = _bufferAlloc(0);
|
|
58
71
|
|
|
59
72
|
function _sha256(buf) {
|
|
60
|
-
return
|
|
73
|
+
return guard.crypto.digest("sha256", buf);
|
|
61
74
|
}
|
|
62
75
|
|
|
76
|
+
/** @internal A COPY, not a view. Reading each slot once is not enough when what is read shares the
|
|
77
|
+
* caller's store: a getter handing out one scratch buffer per leaf satisfies the read-once rule and still
|
|
78
|
+
* leaves every collected leaf pointing at the same bytes, so collecting the second overwrote the first
|
|
79
|
+
* and the root of [A, B] came out as the root of [B, B] with nothing reporting it. Each value is 32 bytes
|
|
80
|
+
* by the check above, so copying costs nothing worth measuring, and every byte this module folds, hashes
|
|
81
|
+
* or compares is then its own. */
|
|
63
82
|
function _toBuffer(v, field) {
|
|
64
|
-
return guard.bytes.
|
|
83
|
+
return guard.bytes.snapshot(v, MerkleError, "merkle/bad-input", field);
|
|
65
84
|
}
|
|
66
85
|
|
|
86
|
+
/** @internal The width is read on a VIEW, before the copy. A node is exactly 32 bytes, and the check
|
|
87
|
+
* enforcing that ran on the copy, so a value handed where a hash belongs was duplicated in full and
|
|
88
|
+
* only then refused: measured, `root([Buffer.alloc(64 * 1024 * 1024)])` allocated another 64 MiB to
|
|
89
|
+
* reject one leaf. Viewing allocates nothing, and the size comes off the view rather than off the
|
|
90
|
+
* caller's own `length`, which can be shadowed independently of the bytes. */
|
|
67
91
|
function _node32(v, field) {
|
|
68
|
-
var
|
|
69
|
-
|
|
70
|
-
|
|
92
|
+
var viewed = guard.bytes.source(v, MerkleError, "merkle/bad-input", field);
|
|
93
|
+
var size = guard.bytes.lengthOf(viewed);
|
|
94
|
+
if (size !== SHA256_BYTES) {
|
|
95
|
+
throw new MerkleError("merkle/bad-hash-length", field + " must be exactly 32 bytes, got " + size);
|
|
71
96
|
}
|
|
72
|
-
return
|
|
97
|
+
return guard.bytes.snapshot(viewed, MerkleError, "merkle/bad-input", field);
|
|
73
98
|
}
|
|
74
99
|
|
|
75
100
|
function _coerceCoord(v, field) {
|
|
@@ -77,19 +102,86 @@ function _coerceCoord(v, field) {
|
|
|
77
102
|
}
|
|
78
103
|
|
|
79
104
|
function _proofNodes(proof) {
|
|
80
|
-
if (!
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return
|
|
105
|
+
if (!_isArray(proof)) throw new MerkleError("merkle/bad-proof", "proof must be an array");
|
|
106
|
+
/** @internal Through the same guard as the leaf array, for the same reason: the node cap reads the
|
|
107
|
+
* length and a hand-rolled walk reads it again. The verifiers were not exposed the way the producers
|
|
108
|
+
* were, because a proof the walk collected short folds to a root that does not match the one supplied,
|
|
109
|
+
* so the comparison refuses it. That is fail-closed by consequence rather than by rule, and the rule is
|
|
110
|
+
* cheaper to keep here than to re-derive at each verifier. */
|
|
111
|
+
return guard.list.copyMap(proof, function (slot, i) { return _node32(slot, "proof[" + i + "]"); },
|
|
112
|
+
function (n) {
|
|
113
|
+
if (n > constants.LIMITS.MERKLE_MAX_PROOF_NODES) {
|
|
114
|
+
throw new MerkleError("merkle/proof-too-large",
|
|
115
|
+
"proof has " + n + " nodes, exceeds " + constants.LIMITS.MERKLE_MAX_PROOF_NODES);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
87
118
|
}
|
|
88
119
|
|
|
89
120
|
function _ctEq(a, b) {
|
|
90
121
|
return guard.crypto.constantTimeEqual(a, b);
|
|
91
122
|
}
|
|
92
123
|
|
|
124
|
+
var NO_CONSISTENCY_CLAIM =
|
|
125
|
+
"an empty older tree (oldSize 0) makes no consistency claim about a non-empty newer tree: " +
|
|
126
|
+
"RFC 6962 sec. 2.1.2 defines the proof for 0 < oldSize < newSize, and nothing here binds " +
|
|
127
|
+
"newRoot. Verify an inclusion proof against the new tree, or start from a signed tree head " +
|
|
128
|
+
"you already trust";
|
|
129
|
+
|
|
130
|
+
/** @internal Each slot is read once, into the buffer the length check and every later fold both
|
|
131
|
+
* use, so an array whose accessor answers differently on a second read cannot put one value past
|
|
132
|
+
* the check and a different one into the tree. The COUNT is read once as well, by
|
|
133
|
+
* `guard.list.copyMap`, which is the point of going through it: a hand-rolled
|
|
134
|
+
* `for (i = 0; i < v.length; i++)` re-reads the length after every slot access, so a getter on one slot
|
|
135
|
+
* that shortened the array left the cap approving four leaves and the walk collecting one, and the root
|
|
136
|
+
* committed to a tree the caller never supplied. A slot past a length that shrank reads as undefined and
|
|
137
|
+
* `_node32` refuses it, so the array is refused rather than folded short. */
|
|
138
|
+
function _leafArray(v, field) {
|
|
139
|
+
if (!_isArray(v)) {
|
|
140
|
+
throw new MerkleError("merkle/bad-input", field + " must be an array of 32-byte leaf hashes");
|
|
141
|
+
}
|
|
142
|
+
return guard.list.copyMap(v, function (slot, i) { return _node32(slot, field + "[" + i + "]"); },
|
|
143
|
+
function (n) {
|
|
144
|
+
if (n > constants.LIMITS.MERKLE_MAX_LEAVES) {
|
|
145
|
+
throw new MerkleError("merkle/too-many-leaves",
|
|
146
|
+
field + " holds " + n + " leaves, exceeds " + constants.LIMITS.MERKLE_MAX_LEAVES);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** @internal RFC 6962 sec. 2.1's "largest power of two smaller than n", for n >= 2. Doubled from
|
|
152
|
+
* 1 rather than derived through a logarithm: the leaf count is bounded well inside the exact
|
|
153
|
+
* integer range, and a float log would round at the boundary sizes this splits on. */
|
|
154
|
+
function _splitPoint(n) {
|
|
155
|
+
var k = 1;
|
|
156
|
+
while (k * 2 < n) k *= 2;
|
|
157
|
+
return k;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** @internal MTH(D[lo:hi]) for hi > lo. The empty tree is answered by the caller. */
|
|
161
|
+
function _mth(leaves, lo, hi) {
|
|
162
|
+
if (hi - lo === 1) return leaves[lo];
|
|
163
|
+
var k = _splitPoint(hi - lo);
|
|
164
|
+
return nodeHash(_mth(leaves, lo, lo + k), _mth(leaves, lo + k, hi));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** @internal PATH(m, D[lo:hi]) of RFC 6962 sec. 2.1.1, with m relative to lo. */
|
|
168
|
+
function _path(leaves, lo, hi, m) {
|
|
169
|
+
if (hi - lo === 1) return [];
|
|
170
|
+
var k = _splitPoint(hi - lo);
|
|
171
|
+
if (m < k) return _concat(_path(leaves, lo, lo + k, m), [_mth(leaves, lo + k, hi)]);
|
|
172
|
+
return _concat(_path(leaves, lo + k, hi, m - k), [_mth(leaves, lo, lo + k)]);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** @internal SUBPROOF(m, D[lo:hi], b) of RFC 6962 sec. 2.1.2, with m relative to lo. The split
|
|
176
|
+
* compares m <= k here where PATH compares m < k, which is what puts the old tree's own root in
|
|
177
|
+
* the proof exactly when the caller did not already hold it. */
|
|
178
|
+
function _subproof(leaves, lo, hi, m, b) {
|
|
179
|
+
if (m === hi - lo) return b ? [] : [_mth(leaves, lo, hi)];
|
|
180
|
+
var k = _splitPoint(hi - lo);
|
|
181
|
+
if (m <= k) return _concat(_subproof(leaves, lo, lo + k, m, b), [_mth(leaves, lo + k, hi)]);
|
|
182
|
+
return _concat(_subproof(leaves, lo + k, hi, m - k, false), [_mth(leaves, lo, lo + k)]);
|
|
183
|
+
}
|
|
184
|
+
|
|
93
185
|
/**
|
|
94
186
|
* @primitive pki.merkle.leafHash
|
|
95
187
|
* @signature pki.merkle.leafHash(entry) -> Buffer
|
|
@@ -107,7 +199,7 @@ function _ctEq(a, b) {
|
|
|
107
199
|
*/
|
|
108
200
|
function leafHash(entry) {
|
|
109
201
|
var e = _toBuffer(entry, "entry");
|
|
110
|
-
return _sha256(
|
|
202
|
+
return _sha256(_bufferConcat([LEAF_PREFIX, e]));
|
|
111
203
|
}
|
|
112
204
|
|
|
113
205
|
/**
|
|
@@ -131,7 +223,7 @@ function leafHash(entry) {
|
|
|
131
223
|
function nodeHash(left, right) {
|
|
132
224
|
var l = _node32(left, "left");
|
|
133
225
|
var r = _node32(right, "right");
|
|
134
|
-
return _sha256(
|
|
226
|
+
return _sha256(_bufferConcat([NODE_PREFIX, l, r]));
|
|
135
227
|
}
|
|
136
228
|
|
|
137
229
|
/**
|
|
@@ -180,7 +272,7 @@ function emptyRootHash() {
|
|
|
180
272
|
* var lh = pki.merkle.leafHash(Buffer.from([0]));
|
|
181
273
|
* pki.merkle.verifyInclusion({ leafIndex: 0, treeSize: 1, leafHash: lh, proof: [], rootHash: lh }); // -> true
|
|
182
274
|
*/
|
|
183
|
-
var _INCLUSION_KEYS =
|
|
275
|
+
var _INCLUSION_KEYS = _assign(_create(null), { leafIndex: 1, treeSize: 1, leafHash: 1, rootHash: 1, proof: 1 });
|
|
184
276
|
function verifyInclusion(opts) {
|
|
185
277
|
opts = opts || {};
|
|
186
278
|
guard.identifier.assertKnownKeys(opts, _INCLUSION_KEYS, function (c, m) { return new MerkleError(c, m); },
|
|
@@ -252,7 +344,7 @@ function verifyInclusion(opts) {
|
|
|
252
344
|
* var r = pki.merkle.leafHash(Buffer.from([0]));
|
|
253
345
|
* pki.merkle.verifyConsistency({ oldSize: 1, newSize: 1, oldRoot: r, newRoot: r, proof: [] }); // -> true
|
|
254
346
|
*/
|
|
255
|
-
var _CONSISTENCY_KEYS =
|
|
347
|
+
var _CONSISTENCY_KEYS = _assign(_create(null), { oldSize: 1, newSize: 1, oldRoot: 1, newRoot: 1, proof: 1 });
|
|
256
348
|
function verifyConsistency(opts) {
|
|
257
349
|
opts = opts || {};
|
|
258
350
|
guard.identifier.assertKnownKeys(opts, _CONSISTENCY_KEYS, function (c, m) { return new MerkleError(c, m); },
|
|
@@ -271,11 +363,7 @@ function verifyConsistency(opts) {
|
|
|
271
363
|
// allow:constant-time-compare-short-circuited -- oldRoot, newRoot, and er are public Merkle tree roots, not secrets, so short-circuiting the second compare leaks nothing confidential
|
|
272
364
|
return _ctEq(oldRoot, er) && _ctEq(newRoot, er);
|
|
273
365
|
}
|
|
274
|
-
throw new MerkleError("merkle/no-consistency-claim",
|
|
275
|
-
"an empty older tree (oldSize 0) makes no consistency claim about a non-empty newer tree: " +
|
|
276
|
-
"RFC 6962 sec. 2.1.2 defines the proof for 0 < oldSize < newSize, and nothing here binds " +
|
|
277
|
-
"newRoot. Verify an inclusion proof against the new tree, or start from a signed tree head " +
|
|
278
|
-
"you already trust");
|
|
366
|
+
throw new MerkleError("merkle/no-consistency-claim", NO_CONSISTENCY_CLAIM);
|
|
279
367
|
}
|
|
280
368
|
if (oldSize === newSize) {
|
|
281
369
|
if (proof.length !== 0) throw new MerkleError("merkle/sizes-equal-nonempty-proof", "equal tree sizes require an empty consistency proof");
|
|
@@ -285,7 +373,7 @@ function verifyConsistency(opts) {
|
|
|
285
373
|
|
|
286
374
|
var path = proof;
|
|
287
375
|
if ((oldSize & (oldSize - 1n)) === 0n) {
|
|
288
|
-
path = [oldRoot]
|
|
376
|
+
path = _concat([oldRoot], proof);
|
|
289
377
|
}
|
|
290
378
|
var fn = oldSize - 1n;
|
|
291
379
|
var sn = newSize - 1n;
|
|
@@ -313,10 +401,128 @@ function verifyConsistency(opts) {
|
|
|
313
401
|
return okOld && okNew;
|
|
314
402
|
}
|
|
315
403
|
|
|
404
|
+
/**
|
|
405
|
+
* @primitive pki.merkle.root
|
|
406
|
+
* @signature pki.merkle.root(leafHashes) -> Buffer
|
|
407
|
+
* @since 0.8.34
|
|
408
|
+
* @status stable
|
|
409
|
+
* @spec RFC 6962, RFC 9162
|
|
410
|
+
* @related pki.merkle.leafHash, pki.merkle.inclusionProof, pki.merkle.verifyInclusion
|
|
411
|
+
*
|
|
412
|
+
* The RFC 6962 Merkle Tree Hash `MTH(D[n])` over a whole tree, returned as a
|
|
413
|
+
* 32-byte root ready to pass to `verifyInclusion` as `rootHash`. Takes leaf
|
|
414
|
+
* HASHES, the same form `verifyInclusion` takes as `leafHash`; map entries
|
|
415
|
+
* through `pki.merkle.leafHash` first. An empty array is the empty tree and
|
|
416
|
+
* returns `emptyRootHash()`.
|
|
417
|
+
*
|
|
418
|
+
* Throws `merkle/bad-input` if `leafHashes` is not an array or a leaf is not a
|
|
419
|
+
* Buffer / Uint8Array, `merkle/bad-hash-length` if a leaf is not exactly 32
|
|
420
|
+
* bytes, and `merkle/too-many-leaves` above `C.LIMITS.MERKLE_MAX_LEAVES`.
|
|
421
|
+
*
|
|
422
|
+
* @example
|
|
423
|
+
* var leaves = [Buffer.from([0]), Buffer.from([1])].map(pki.merkle.leafHash);
|
|
424
|
+
* pki.merkle.root(leaves); // -> <Buffer 32-byte tree head>
|
|
425
|
+
*/
|
|
426
|
+
function root(leafHashes) {
|
|
427
|
+
var leaves = _leafArray(leafHashes, "leafHashes");
|
|
428
|
+
if (leaves.length === 0) return emptyRootHash();
|
|
429
|
+
return _mth(leaves, 0, leaves.length);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* @primitive pki.merkle.inclusionProof
|
|
434
|
+
* @signature pki.merkle.inclusionProof(opts) -> Buffer[]
|
|
435
|
+
* @since 0.8.34
|
|
436
|
+
* @status stable
|
|
437
|
+
* @spec RFC 6962, RFC 9162
|
|
438
|
+
* @related pki.merkle.verifyInclusion, pki.merkle.root
|
|
439
|
+
*
|
|
440
|
+
* Produce the RFC 6962 sec. 2.1.1 audit path `PATH(leafIndex, D[n])` for one
|
|
441
|
+
* leaf of a tree held in memory. The returned array is the `proof` that
|
|
442
|
+
* `verifyInclusion` folds against `root(leafHashes)`.
|
|
443
|
+
*
|
|
444
|
+
* The tree size is the length of `leafHashes` and is not a separate option, so
|
|
445
|
+
* a size and a leaf array cannot disagree. Throws `merkle/empty-tree` on an
|
|
446
|
+
* empty tree, `merkle/index-out-of-range` for a `leafIndex` at or above the leaf
|
|
447
|
+
* count, and the `leafHashes` errors `root` throws.
|
|
448
|
+
*
|
|
449
|
+
* @opts
|
|
450
|
+
* leafHashes: Buffer[], // the whole tree, each leaf a 32-byte hash
|
|
451
|
+
* leafIndex: number | bigint, // 0-based position of the leaf to prove
|
|
452
|
+
*
|
|
453
|
+
* @example
|
|
454
|
+
* var leaves = [Buffer.from([0]), Buffer.from([1])].map(pki.merkle.leafHash);
|
|
455
|
+
* pki.merkle.inclusionProof({ leafHashes: leaves, leafIndex: 0 }); // -> [<Buffer ...>]
|
|
456
|
+
*/
|
|
457
|
+
var _INCLUSION_PROOF_KEYS = _assign(_create(null), { leafHashes: 1, leafIndex: 1 });
|
|
458
|
+
function inclusionProof(opts) {
|
|
459
|
+
opts = opts || {};
|
|
460
|
+
guard.identifier.assertKnownKeys(opts, _INCLUSION_PROOF_KEYS, _merkleErr,
|
|
461
|
+
"merkle/bad-input", "unknown inclusionProof option ");
|
|
462
|
+
var leaves = _leafArray(opts.leafHashes, "leafHashes");
|
|
463
|
+
var n = leaves.length;
|
|
464
|
+
if (n === 0) throw new MerkleError("merkle/empty-tree", "an empty tree has no leaves to include");
|
|
465
|
+
var leafIndex = _coerceCoord(opts.leafIndex, "leafIndex");
|
|
466
|
+
var m = guard.range.int(leafIndex, 0n, _BigInt(n - 1), _merkleErr,
|
|
467
|
+
"merkle/index-out-of-range", "leafIndex");
|
|
468
|
+
return _path(leaves, 0, n, m);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* @primitive pki.merkle.consistencyProof
|
|
473
|
+
* @signature pki.merkle.consistencyProof(opts) -> Buffer[]
|
|
474
|
+
* @since 0.8.34
|
|
475
|
+
* @status stable
|
|
476
|
+
* @spec RFC 6962, RFC 9162
|
|
477
|
+
* @related pki.merkle.verifyConsistency, pki.merkle.root
|
|
478
|
+
*
|
|
479
|
+
* Produce the RFC 6962 sec. 2.1.2 consistency proof `PROOF(oldSize, D[n])`
|
|
480
|
+
* between an older tree of `oldSize` leaves and the tree held in `leafHashes`.
|
|
481
|
+
* The returned array is the `proof` that `verifyConsistency` checks against
|
|
482
|
+
* `root(leafHashes.slice(0, oldSize))` and `root(leafHashes)`.
|
|
483
|
+
*
|
|
484
|
+
* The newer size is the length of `leafHashes` and is not a separate option.
|
|
485
|
+
* Equal sizes produce the empty proof, which is what the verifier requires
|
|
486
|
+
* there. Throws `merkle/old-size-exceeds-new` when `oldSize` is past the leaf
|
|
487
|
+
* count and `merkle/no-consistency-claim` for an `oldSize` of 0 against a
|
|
488
|
+
* non-empty tree, matching `verifyConsistency`: RFC 6962 sec. 2.1.2 defines the
|
|
489
|
+
* proof for `0 < oldSize < newSize`, so there is no proof to produce.
|
|
490
|
+
*
|
|
491
|
+
* @opts
|
|
492
|
+
* leafHashes: Buffer[], // the newer tree, each leaf a 32-byte hash
|
|
493
|
+
* oldSize: number | bigint, // leaf count of the older tree
|
|
494
|
+
*
|
|
495
|
+
* @example
|
|
496
|
+
* var leaves = [Buffer.from([0]), Buffer.from([1])].map(pki.merkle.leafHash);
|
|
497
|
+
* pki.merkle.consistencyProof({ leafHashes: leaves, oldSize: 1 }); // -> [<Buffer ...>]
|
|
498
|
+
*/
|
|
499
|
+
var _CONSISTENCY_PROOF_KEYS = _assign(_create(null), { leafHashes: 1, oldSize: 1 });
|
|
500
|
+
function consistencyProof(opts) {
|
|
501
|
+
opts = opts || {};
|
|
502
|
+
guard.identifier.assertKnownKeys(opts, _CONSISTENCY_PROOF_KEYS, _merkleErr,
|
|
503
|
+
"merkle/bad-input", "unknown consistencyProof option ");
|
|
504
|
+
var leaves = _leafArray(opts.leafHashes, "leafHashes");
|
|
505
|
+
var n = leaves.length;
|
|
506
|
+
var oldSize = _coerceCoord(opts.oldSize, "oldSize");
|
|
507
|
+
if (oldSize > _BigInt(n)) {
|
|
508
|
+
throw new MerkleError("merkle/old-size-exceeds-new", "oldSize " + oldSize + " exceeds newSize " + n);
|
|
509
|
+
}
|
|
510
|
+
if (oldSize === 0n) {
|
|
511
|
+
if (n === 0) return [];
|
|
512
|
+
throw new MerkleError("merkle/no-consistency-claim", NO_CONSISTENCY_CLAIM);
|
|
513
|
+
}
|
|
514
|
+
var m = guard.range.int(oldSize, 1n, _BigInt(n), _merkleErr, "merkle/bad-input", "oldSize");
|
|
515
|
+
if (m === n) return [];
|
|
516
|
+
return _subproof(leaves, 0, n, m, true);
|
|
517
|
+
}
|
|
518
|
+
|
|
316
519
|
module.exports = {
|
|
317
520
|
leafHash: leafHash,
|
|
318
521
|
nodeHash: nodeHash,
|
|
319
522
|
emptyRootHash: emptyRootHash,
|
|
523
|
+
root: root,
|
|
320
524
|
verifyInclusion: verifyInclusion,
|
|
321
525
|
verifyConsistency: verifyConsistency,
|
|
526
|
+
inclusionProof: inclusionProof,
|
|
527
|
+
consistencyProof: consistencyProof,
|
|
322
528
|
};
|
package/lib/ocsp.js
CHANGED
|
@@ -26,6 +26,19 @@
|
|
|
26
26
|
var nodeCrypto = require("crypto");
|
|
27
27
|
var asn1 = require("./asn1-der");
|
|
28
28
|
var pkiBuild = require("./pki-build");
|
|
29
|
+
var _ocspIntrinsic = require("./guard-intrinsic");
|
|
30
|
+
/** @internal The promise a signature self-check is awaited through, built from the constructor captured
|
|
31
|
+
* at load. `Promise.resolve` builds from its RECEIVER, so reading the global binding at the call hands
|
|
32
|
+
* the construction to whatever that binding holds, and one settling without awaiting its argument runs
|
|
33
|
+
* the continuation before the check has finished. */
|
|
34
|
+
var _Promise = _ocspIntrinsic.Promise;
|
|
35
|
+
var _promiseResolve = _ocspIntrinsic.uncurry(_ocspIntrinsic.promiseResolve);
|
|
36
|
+
/** @internal A refusal returned as a REJECTED promise is a verdict too: read from the global binding, a
|
|
37
|
+
* replacement whose `reject` resolves instead turns each `ocsp/bad-input` below into a pass. */
|
|
38
|
+
var _promiseReject = _ocspIntrinsic.uncurry(_ocspIntrinsic.promiseReject);
|
|
39
|
+
/** @internal The CertID hashes bind a response to the certificate it answers for, and they are awaited
|
|
40
|
+
* as a pair, so a replaced `all` decides what that pair contains. */
|
|
41
|
+
var _promiseAll = _ocspIntrinsic.uncurry(_ocspIntrinsic.promiseAll);
|
|
29
42
|
var oid = require("./oid");
|
|
30
43
|
var x509 = require("./schema-x509");
|
|
31
44
|
var ocspSchema = require("./schema-ocsp");
|
|
@@ -100,7 +113,7 @@ var REQUESTER_NONCE_FLOOR = 32, RESPONDER_NONCE_FLOOR = 1;
|
|
|
100
113
|
function _buildCertID(cert, issuer, hashName) {
|
|
101
114
|
var wc = HASH_WC[guard.text.keyOf(hashName)];
|
|
102
115
|
if (!wc) throw _err("ocsp/bad-input", "unsupported CertID hashAlgorithm " + guard.text.showValue(hashName) + " (sha1 / sha256 / sha384 / sha512)");
|
|
103
|
-
return
|
|
116
|
+
return _promiseAll(_Promise, [
|
|
104
117
|
_digest(wc, issuer.subject.bytes),
|
|
105
118
|
_digest(wc, _keyValue(issuer.subjectPublicKeyInfo.bytes)),
|
|
106
119
|
]).then(function (hashes) {
|
|
@@ -189,7 +202,7 @@ function _buildRequest(query, opts) {
|
|
|
189
202
|
requestorNameDer = signerCert.subject.bytes;
|
|
190
203
|
}
|
|
191
204
|
|
|
192
|
-
return
|
|
205
|
+
return _promiseAll(_Promise, queries.map(function (q) {
|
|
193
206
|
if (!q || typeof q !== "object") throw _err("ocsp/bad-input", "each query must be { cert, issuer }");
|
|
194
207
|
guard.identifier.assertKnownKeys(q, _QUERY_KEYS, _err, "ocsp/bad-input", "unknown query field (a query is { cert, issuer, singleRequestExtensions? }): ");
|
|
195
208
|
if (q.cert == null || q.issuer == null) throw _err("ocsp/bad-input", "each query must be { cert, issuer }");
|
|
@@ -227,7 +240,7 @@ function _buildRequest(query, opts) {
|
|
|
227
240
|
return signScheme.signOverTbs(scheme, opts.signer.key, tbsRequest, _signE).then(function (sig) {
|
|
228
241
|
/** @internal The request is held to the same proof a certificate is: the signature verifies
|
|
229
242
|
* against the public key the certificate it carries declares. */
|
|
230
|
-
return
|
|
243
|
+
return _promiseResolve(_Promise, _b.assertSignatureVerifies(tbsRequest, sig, signerCert.subjectPublicKeyInfo.bytes, scheme)).then(function () {
|
|
231
244
|
var optionalSignature = b.sequence([scheme.sigAlgId, b.bitString(sig, 0), b.explicit(0, b.sequence([b.raw(signerCertDer)]))]);
|
|
232
245
|
return _emitReq(b.sequence([tbsRequest, b.explicit(0, optionalSignature)]), opts);
|
|
233
246
|
});
|
|
@@ -347,7 +360,7 @@ function _signResponse(responseData, responder, respCert, respCertDer, responder
|
|
|
347
360
|
if (!responses.length) throw _err("ocsp/bad-input", "a response MUST include at least one SingleResponse (RFC 6960 sec. 4.2.1)");
|
|
348
361
|
|
|
349
362
|
return _responderID(responseData.responderID, respCert).then(function (ridNode) {
|
|
350
|
-
return
|
|
363
|
+
return _promiseAll(_Promise, responses.map(function (r) { return _buildSingleResponse(r, opts); })).then(function (built) {
|
|
351
364
|
var srNodes = built.map(function (x) { return x.node; });
|
|
352
365
|
var anyNonIssued = built.some(function (x) { return x.nonIssued; });
|
|
353
366
|
/** @internal Sec. 2.2 and 4.4.8: a "revoked" answer for a non-issued certificate MUST carry the
|
|
@@ -366,7 +379,7 @@ function _signResponse(responseData, responder, respCert, respCertDer, responder
|
|
|
366
379
|
return signScheme.signOverTbs(scheme, responderKey, responseDataDer, _signE).then(function (sig) {
|
|
367
380
|
/** @internal The response is held to the same proof a certificate is: the signature verifies
|
|
368
381
|
* against the public key the responder certificate declares. */
|
|
369
|
-
return
|
|
382
|
+
return _promiseResolve(_Promise, _b.assertSignatureVerifies(responseDataDer, sig, respCert.subjectPublicKeyInfo.bytes, scheme)).then(function () {
|
|
370
383
|
var basicChildren = [responseDataDer, scheme.sigAlgId, b.bitString(sig, 0)];
|
|
371
384
|
if (opts.embedCert !== false) basicChildren.push(b.explicit(0, b.sequence([b.raw(respCertDer)])));
|
|
372
385
|
var responseBytes = b.sequence([b.oid(OID_OCSP_BASIC), b.octetString(b.sequence(basicChildren))]);
|
|
@@ -406,19 +419,19 @@ function _snapshotSignerKey(key, owned) {
|
|
|
406
419
|
}
|
|
407
420
|
|
|
408
421
|
function _responderID(rid, respCert) {
|
|
409
|
-
if (rid == null || rid === "byName") return
|
|
422
|
+
if (rid == null || rid === "byName") return _promiseResolve(_Promise, b.explicit(1, b.raw(respCert.subject.bytes)));
|
|
410
423
|
if (rid === "byKey") return _digest("SHA-1", _keyValue(respCert.subjectPublicKeyInfo.bytes)).then(function (kh) { return b.explicit(2, b.octetString(kh)); });
|
|
411
424
|
throw _err("ocsp/bad-input", "responderID must be \"byName\" or \"byKey\"");
|
|
412
425
|
}
|
|
413
426
|
function _buildSingleResponse(r, opts) {
|
|
414
427
|
r = r || {};
|
|
415
428
|
var certIdP;
|
|
416
|
-
if (typeof r !== "object") return
|
|
429
|
+
if (typeof r !== "object") return _promiseReject(_Promise, _err("ocsp/bad-input", "each response entry must be an object"));
|
|
417
430
|
try { guard.identifier.assertKnownKeys(r, _SINGLE_RESPONSE_KEYS, _err, "ocsp/bad-input", "unknown response entry field (accepted: cert, issuer, certID, hashAlgorithm, status, thisUpdate, nextUpdate, singleExtensions): "); }
|
|
418
|
-
catch (e) { return
|
|
419
|
-
if (r.certID != null) certIdP =
|
|
431
|
+
catch (e) { return _promiseReject(_Promise, e); }
|
|
432
|
+
if (r.certID != null) certIdP = _promiseResolve(_Promise, b.raw(guard.bytes.view(r.certID, OcspError, "ocsp/bad-input", "a response entry's certID")));
|
|
420
433
|
else if (r.cert != null && r.issuer != null) certIdP = _buildCertID(_certOf(r.cert, "a response certificate"), _certOf(r.issuer, "a response issuer"), r.hashAlgorithm || "sha1");
|
|
421
|
-
else return
|
|
434
|
+
else return _promiseReject(_Promise, _err("ocsp/bad-input", "each response entry needs { certID } or { cert, issuer }"));
|
|
422
435
|
/** @internal Every per-entry refusal stays INSIDE this chain. These entries are built through a
|
|
423
436
|
* map into Promise.all, so a synchronous throw here would abort the map before Promise.all
|
|
424
437
|
* attached handlers, and an earlier entry's rejection would be orphaned: the caller catches
|
|
@@ -653,15 +666,15 @@ function verify(response, opts) {
|
|
|
653
666
|
opts = guard.identifier.optionsObject(opts, _err, "ocsp/bad-input", "pki.ocsp.verify options");
|
|
654
667
|
guard.identifier.assertKnownKeys(opts, _VERIFY_OPTS, _err, "ocsp/bad-input",
|
|
655
668
|
"pki.ocsp.verify has an unknown option (the request nonce to bind against is `requestNonce`): ");
|
|
656
|
-
} catch (e) { return
|
|
657
|
-
if (opts.cert == null || opts.issuer == null) return
|
|
669
|
+
} catch (e) { return _promiseReject(_Promise, e); }
|
|
670
|
+
if (opts.cert == null || opts.issuer == null) return _promiseReject(_Promise, _err("ocsp/bad-input", "verify requires opts.cert and opts.issuer"));
|
|
658
671
|
var parsed, cert, issuerCert, time;
|
|
659
672
|
try {
|
|
660
673
|
parsed = _responseFromBytes(response);
|
|
661
674
|
cert = _certOf(opts.cert, "the target certificate");
|
|
662
675
|
issuerCert = _certOf(opts.issuer, "the issuer certificate");
|
|
663
676
|
time = opts.time == null ? new Date() : _asDate(opts.time);
|
|
664
|
-
} catch (e) { return
|
|
677
|
+
} catch (e) { return _promiseReject(_Promise, e); }
|
|
665
678
|
/** @internal The nonce rule is applied by the acceptance gate rather than restated here, so this
|
|
666
679
|
* door and every other consumer of that gate answer alike. The bytes are snapshotted here so a
|
|
667
680
|
* fault in them carries this door's own code; a value that is not bytes is passed through for the
|
|
@@ -670,7 +683,7 @@ function verify(response, opts) {
|
|
|
670
683
|
var reqNonce = opts.requestNonce;
|
|
671
684
|
if (guard.bytes.isByteSource(reqNonce)) {
|
|
672
685
|
try { reqNonce = guard.bytes.snapshotSource(reqNonce, OcspError, "ocsp/bad-input", "opts.requestNonce"); }
|
|
673
|
-
catch (e) { return
|
|
686
|
+
catch (e) { return _promiseReject(_Promise, e); }
|
|
674
687
|
}
|
|
675
688
|
return pathValidate.verifyOcspResponse(parsed, cert, issuerCert, time,
|
|
676
689
|
{ historicalMode: opts.historicalMode === true, requestNonce: reqNonce }).then(function (verdict) {
|
package/lib/oid.js
CHANGED
|
@@ -65,11 +65,13 @@ var FAMILIES = Object.assign(Object.create(null), {
|
|
|
65
65
|
policyConstraints: 36, extKeyUsage: 37, freshestCRL: 46,
|
|
66
66
|
inhibitAnyPolicy: 54,
|
|
67
67
|
anyPolicy: [32, 0], anyExtendedKeyUsage: [37, 0],
|
|
68
|
-
targetInformation: 55, noRevAvail: 56
|
|
68
|
+
targetInformation: 55, noRevAvail: 56,
|
|
69
|
+
subjectAltPublicKeyInfo: 72, altSignatureAlgorithm: 73, altSignatureValue: 74 } },
|
|
69
70
|
|
|
70
71
|
pkixAccess: { base: [1, 3, 6, 1, 5, 5, 7, 1], of: {
|
|
71
72
|
authorityInfoAccess: 1, acAuditIdentity: 4, aaControls: 6, acProxying: 10,
|
|
72
73
|
acmeIdentifier: 31,
|
|
74
|
+
relatedCertificate: 36,
|
|
73
75
|
subjectInfoAccess: 11,
|
|
74
76
|
tlsFeature: 24,
|
|
75
77
|
qcStatements: 3,
|
|
@@ -187,6 +189,12 @@ var FAMILIES = Object.assign(Object.create(null), {
|
|
|
187
189
|
|
|
188
190
|
wisun: { base: [1, 3, 6, 1, 4, 1, 45605], of: { fanDevice: 1 } },
|
|
189
191
|
|
|
192
|
+
/** @internal RFC 9883 assigns its attribute on a private-enterprise arc rather than a PKIX one, and
|
|
193
|
+
* uses the SAME value as a PKCS#10 attribute and as a CRMF registration control, so the two names
|
|
194
|
+
* resolve to one dotted OID. `byName` is one-to-one in the other direction, so the attribute name is
|
|
195
|
+
* the one the registry returns for the dotted form. */
|
|
196
|
+
rfc9883: { base: [1, 3, 6, 1, 4, 1, 22112, 2], of: { statementOfPossession: 1 } },
|
|
197
|
+
|
|
190
198
|
ct: { base: [1, 3, 6, 1, 4, 1, 11129, 2, 4], of: {
|
|
191
199
|
signedCertificateTimestampList: 2, precertificatePoison: 3,
|
|
192
200
|
precertificateSigningCert: 4, ocspSignedCertificateTimestampList: 5 } },
|
|
@@ -314,7 +322,9 @@ var FAMILIES = Object.assign(Object.create(null), {
|
|
|
314
322
|
msgSigDigest: 5, contentIdentifier: 7, equivalentLabels: 9, contentReference: 10,
|
|
315
323
|
signingCertificate: 12, timeStampToken: 14, decryptKeyID: 37, signingCertificateV2: 47,
|
|
316
324
|
cmcUnsignedData: 34,
|
|
317
|
-
|
|
325
|
+
binarySigningTime: 46,
|
|
326
|
+
asymmDecryptKeyID: 54, relatedCertRequest: 60,
|
|
327
|
+
certificationRequestInfoTemplate: 61, extensionReqTemplate: 62 } },
|
|
318
328
|
|
|
319
329
|
ansiX962: { base: [1, 2, 840, 10045], of: {
|
|
320
330
|
ecPublicKey: [2, 1], prime192v1: [3, 1, 1], prime256v1: [3, 1, 7],
|
|
@@ -443,8 +453,8 @@ function _assertEncodableArcs(arcs, who) {
|
|
|
443
453
|
if (arcs.length < 2) {
|
|
444
454
|
throw new OidError("oid/bad-input", who + ": an OID must have at least 2 arcs");
|
|
445
455
|
}
|
|
446
|
-
var root =
|
|
447
|
-
var second =
|
|
456
|
+
var root = guard.range.authoredInteger(arcs[0], _oidError, "oid/bad-arc", who + ": the root arc");
|
|
457
|
+
var second = guard.range.authoredInteger(arcs[1], _oidError, "oid/bad-arc", who + ": the second arc");
|
|
448
458
|
if (root > 2n) throw new OidError("oid/bad-arc", who + ": the root arc must be 0, 1, or 2 (X.660)");
|
|
449
459
|
if (root < 2n && second > 39n) throw new OidError("oid/bad-arc", who + ": the second arc must be 0..39 under roots 0 and 1 (X.660)");
|
|
450
460
|
}
|