@blamejs/pki 0.5.4 → 0.5.6
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 +42 -0
- package/MIGRATING.md +72 -0
- package/lib/acme.js +47 -19
- package/lib/attrcert-sign.js +11 -7
- package/lib/cmp-session.js +23 -10
- package/lib/cmp-verify.js +12 -4
- package/lib/cms-decrypt.js +71 -30
- package/lib/cms-sign.js +11 -0
- package/lib/cms-verify.js +22 -8
- package/lib/crl-sign.js +19 -7
- package/lib/est.js +77 -1
- package/lib/guard-all.js +8 -0
- package/lib/guard-async.js +37 -0
- package/lib/guard-encoding.js +35 -6
- package/lib/guard-identifier.js +27 -1
- package/lib/guard-json.js +44 -8
- package/lib/guard-name.js +31 -8
- package/lib/guard-parsed.js +482 -0
- package/lib/hpke.js +36 -3
- package/lib/jose.js +8 -0
- package/lib/lint.js +20 -4
- package/lib/merkle.js +9 -0
- package/lib/ocsp.js +44 -8
- package/lib/path-validate.js +88 -44
- package/lib/pbes2.js +11 -1
- package/lib/pkcs12-build.js +105 -30
- package/lib/pki-build.js +12 -1
- package/lib/schema-cms.js +17 -2
- package/lib/schema-crl.js +7 -1
- package/lib/schema-ocsp.js +6 -1
- package/lib/schema-pkcs12.js +7 -2
- package/lib/schema-pkix.js +14 -0
- package/lib/schema-x509.js +14 -1
- package/lib/sign-scheme.js +22 -5
- package/lib/sigstore.js +10 -0
- package/lib/smime.js +47 -0
- package/lib/trust.js +121 -10
- package/lib/tsp-sign.js +112 -19
- package/lib/validator-cose.js +86 -1
- package/lib/validator-tpm.js +8 -3
- package/lib/webauthn-mds.js +10 -18
- package/lib/webauthn.js +40 -18
- package/lib/x509-sign.js +6 -2
- package/package.json +5 -1
- package/sbom.cdx.json +6 -6
|
@@ -0,0 +1,482 @@
|
|
|
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 verbs
|
|
6
|
+
// whose "a DER Buffer, a PEM string, or a parsed X" argument composes this guard
|
|
7
|
+
// (pki.path.validate / build, pki.crl.verify / isRevoked, pki.x509.sign,
|
|
8
|
+
// pki.attrcert.sign, pki.ocsp.*, pki.cmp.session).
|
|
9
|
+
//
|
|
10
|
+
// guard-parsed -- fail-closed acceptance of a CLAIMED-parsed structure.
|
|
11
|
+
//
|
|
12
|
+
// Every verb that takes bytes also takes the parser's output, so a caller holding
|
|
13
|
+
// a parsed certificate need not re-encode and re-parse it. The convenience carries
|
|
14
|
+
// a trust boundary: the object arrives from the caller, not from the parser, and
|
|
15
|
+
// the code downstream reads its fields as though the parser had produced them.
|
|
16
|
+
//
|
|
17
|
+
// Defends the type-confusion / unverified-provenance class (CWE-843, CWE-345).
|
|
18
|
+
// Two distinct failures follow from accepting a claimed-parsed object on a partial
|
|
19
|
+
// duck-type test, and the toolkit has had both:
|
|
20
|
+
//
|
|
21
|
+
// - A MISSING field reads as an absent feature rather than as a malformed input.
|
|
22
|
+
// `crlExtensions` omitted altogether made a scope-restricted CRL answer as an
|
|
23
|
+
// unrestricted one, because every scope guard reads the list as `(list || [])`.
|
|
24
|
+
// The guard cannot fire on a list that is not there.
|
|
25
|
+
// - A partial object reaches code that dereferences a field it lacks, and the
|
|
26
|
+
// verb throws a raw TypeError instead of its own typed error -- a fault an
|
|
27
|
+
// operator cannot catch by code and a fuzz harness reports as a finding.
|
|
28
|
+
//
|
|
29
|
+
// The rule is completeness, checked at the DOOR: an object claiming to be parser
|
|
30
|
+
// output must carry EVERY field the consuming code dereferences, each with the type
|
|
31
|
+
// that code assumes. It is not a signature check and does not pretend to be -- an
|
|
32
|
+
// object can still describe a certificate that never existed, which is why the
|
|
33
|
+
// fields a decision depends on are re-derived from `tbsBytes` where that matters.
|
|
34
|
+
// What it buys is that "parsed" means one thing at every door, so a shape refused
|
|
35
|
+
// by `pki.path.build` is not accepted by `pki.path.validate`.
|
|
36
|
+
//
|
|
37
|
+
// Each entry point keeps its own typed error: `E` is the caller's (code, message)
|
|
38
|
+
// factory and `code` its own domain reason, so routing a door through this guard
|
|
39
|
+
// changes what is checked, never what the operator reads.
|
|
40
|
+
|
|
41
|
+
var bytes = require("./guard-bytes");
|
|
42
|
+
|
|
43
|
+
// Every form the toolkit calls "bytes": a Buffer, any typed-array view, a DataView, or a bare
|
|
44
|
+
// ArrayBuffer. Testing `Buffer.isBuffer(x) || x instanceof Uint8Array` is narrower than what the
|
|
45
|
+
// verbs document, and the narrowness is invisible in the good case -- an ArrayBuffer would fall
|
|
46
|
+
// through to the object branch and be refused as a rebuilt structure, which reads to the caller as
|
|
47
|
+
// "your certificate is malformed" when what happened is that their container was not recognised.
|
|
48
|
+
// guard.bytes is what finally reads them, and it accepts all four, so the door has to as well.
|
|
49
|
+
function _isBytes(x) {
|
|
50
|
+
return Buffer.isBuffer(x) || ArrayBuffer.isView(x) || x instanceof ArrayBuffer;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// The shapes below assert the PARSER'S COMPLETE OUTPUT -- every field
|
|
54
|
+
// pki.schema.x509.parse and pki.schema.crl.parse assign, with the type each is
|
|
55
|
+
// assigned -- and not the subset some consumer happens to dereference today.
|
|
56
|
+
//
|
|
57
|
+
// The distinction is the whole point. A shape derived from one consumer's reads is
|
|
58
|
+
// correct for that consumer and silently incomplete at every other door it gets
|
|
59
|
+
// reused at, and the gap is invisible: the missing field is not named
|
|
60
|
+
// anywhere, so nothing fails until a caller supplies an object without it. This
|
|
61
|
+
// guard shipped derived from the path validator's reads, and three fields the other
|
|
62
|
+
// doors depend on were absent -- `serialNumber`, which pki.ocsp.buildRequest encodes
|
|
63
|
+
// into the request; `issuer.bytes`, which the OCSP responder-identity comparison
|
|
64
|
+
// reads; and an extension entry's `critical`, whose ABSENCE reads as non-critical to
|
|
65
|
+
// `if (!ext.critical) continue`, so an unknown critical extension passes unhandled.
|
|
66
|
+
//
|
|
67
|
+
// Anchoring on the parser removes the enumeration step that produced those gaps: the
|
|
68
|
+
// only sanctioned way to obtain one of these objects is to run the parser, the parser
|
|
69
|
+
// assigns every field unconditionally, and so "complete" needs no consumer census and
|
|
70
|
+
// does not drift when a new consumer reads a new field. Adding a field to a parser's
|
|
71
|
+
// output belongs here in the same change -- the api-snapshot pins that surface, so
|
|
72
|
+
// the two move together.
|
|
73
|
+
|
|
74
|
+
// A directory name. `dn` is the RFC 4514 string, `bytes` the raw encoded Name that a
|
|
75
|
+
// byte-level issuer/subject comparison hashes, and `rdns` the structure the RFC 5280
|
|
76
|
+
// sec. 7.1 comparison walks.
|
|
77
|
+
//
|
|
78
|
+
// The walk goes all the way to the attributes. `rdns` being an array is not the
|
|
79
|
+
// property anything downstream depends on: guard-name's dnEqual compares each
|
|
80
|
+
// attribute's `type` and canonicalizes its `value`, and the WebAuthn subject-CN
|
|
81
|
+
// lookup reads `name` and `value` -- so an array of anything would pass a
|
|
82
|
+
// shallow test and reach code that dereferences three fields it does not have.
|
|
83
|
+
// Name ::= RDNSequence ::= SEQUENCE OF RelativeDistinguishedName, and an RDN is a
|
|
84
|
+
// SET SIZE (1..MAX), so the parser produces an array of NON-EMPTY arrays; the
|
|
85
|
+
// attribute value is always a string, either the decoded directory string or the
|
|
86
|
+
// RFC 4514 "#hex" form the parser falls back to for a type it does not decode.
|
|
87
|
+
function _isAttributeTypeAndValue(a) {
|
|
88
|
+
return !!a && typeof a.type === "string" && _isOptName(a.name) && typeof a.value === "string";
|
|
89
|
+
}
|
|
90
|
+
function _isRdnSequence(rdns) {
|
|
91
|
+
if (!Array.isArray(rdns)) return false;
|
|
92
|
+
for (var i = 0; i < rdns.length; i++) {
|
|
93
|
+
if (!Array.isArray(rdns[i]) || rdns[i].length === 0) return false;
|
|
94
|
+
if (!rdns[i].every(_isAttributeTypeAndValue)) return false;
|
|
95
|
+
}
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
function _isName(n) {
|
|
99
|
+
return !!n && _isRdnSequence(n.rdns) && Buffer.isBuffer(n.bytes) && typeof n.dn === "string";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// `name` is the registry lookup of the OID and is null for an OID the registry does
|
|
103
|
+
// not carry, so the assertion is on the KEY's presence with the parser's own type.
|
|
104
|
+
function _isOptName(v) { return typeof v === "string" || v === null; }
|
|
105
|
+
|
|
106
|
+
function _isAlgorithmIdentifier(a) {
|
|
107
|
+
return !!a && typeof a.oid === "string" && _isOptName(a.name) &&
|
|
108
|
+
(Buffer.isBuffer(a.parameters) || a.parameters === null);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function _isBitString(b) {
|
|
112
|
+
return !!b && Buffer.isBuffer(b.bytes) && typeof b.unusedBits === "number";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// A parsed CERTIFICATE extension entry: dispatched on `.oid`, gated on `.critical`,
|
|
116
|
+
// and its `.value` the RAW extension bytes, which the certificate parser leaves
|
|
117
|
+
// undecoded. An entry carrying a `.name` but no `.oid` is the shape that slips past
|
|
118
|
+
// an OID-keyed lookup while reading as present to a human; one carrying no
|
|
119
|
+
// `.critical` is the shape that turns a fail-closed unknown-critical check into a
|
|
120
|
+
// skipped iteration.
|
|
121
|
+
function _isExtensionEntry(e) {
|
|
122
|
+
return !!e && typeof e.oid === "string" && _isOptName(e.name) &&
|
|
123
|
+
typeof e.critical === "boolean" && Buffer.isBuffer(e.value);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// A parsed CRL extension entry. The CRL parser DECODES the values it knows -- a
|
|
127
|
+
// cRLNumber surfaces as a BigInt, a reasonCode as a Number -- so the value type
|
|
128
|
+
// varies by extension and only its PRESENCE can be asserted here. The dispatch key
|
|
129
|
+
// and the criticality flag are the same as a certificate's, and both decide.
|
|
130
|
+
function _isCrlExtensionEntry(e) {
|
|
131
|
+
return !!e && typeof e.oid === "string" && _isOptName(e.name) &&
|
|
132
|
+
typeof e.critical === "boolean" && e.value !== undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// isCert(o) -- the complete parsed-certificate shape.
|
|
136
|
+
//
|
|
137
|
+
// Exported as a PREDICATE beside the throwing door because a caller deriving a
|
|
138
|
+
// DEDUPE KEY (cmp's extraCerts pool) must answer "not usable" rather than throw --
|
|
139
|
+
// a non-deduped entry is a redundant slot, while a key derived from a partial object
|
|
140
|
+
// could collapse two different certificates onto one.
|
|
141
|
+
//
|
|
142
|
+
// @enforced-by guard-shape-reinlined -- shares the `accept` shape below: any door
|
|
143
|
+
// testing tbsBytes and returning the object is the re-inline this replaces.
|
|
144
|
+
function isCert(o) {
|
|
145
|
+
return !!o && typeof o === "object" &&
|
|
146
|
+
Buffer.isBuffer(o.tbsBytes) &&
|
|
147
|
+
typeof o.version === "number" &&
|
|
148
|
+
typeof o.serialNumber === "bigint" &&
|
|
149
|
+
typeof o.serialNumberHex === "string" &&
|
|
150
|
+
_isAlgorithmIdentifier(o.signatureAlgorithm) &&
|
|
151
|
+
_isAlgorithmIdentifier(o.tbsSignatureAlgorithm) &&
|
|
152
|
+
_isBitString(o.signatureValue) &&
|
|
153
|
+
!!o.validity && o.validity.notBefore instanceof Date && o.validity.notAfter instanceof Date &&
|
|
154
|
+
_isName(o.issuer) &&
|
|
155
|
+
_isName(o.subject) &&
|
|
156
|
+
!!o.subjectPublicKeyInfo && Buffer.isBuffer(o.subjectPublicKeyInfo.bytes) &&
|
|
157
|
+
_isAlgorithmIdentifier(o.subjectPublicKeyInfo.algorithm) &&
|
|
158
|
+
_isBitString(o.subjectPublicKeyInfo.publicKey) &&
|
|
159
|
+
Array.isArray(o.extensions) && o.extensions.every(_isExtensionEntry);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// isCrl(o) -- the complete parsed-CertificateList shape. `crlExtensions` and each
|
|
163
|
+
// entry's `crlEntryExtensions` are REQUIRED arrays rather than optional ones: the
|
|
164
|
+
// scope guards read them as `(list || [])`, so an omitted list is indistinguishable
|
|
165
|
+
// from an empty one and the difference decides whether a serial may be answered.
|
|
166
|
+
// `nextUpdate` is the one genuinely optional field -- the parser assigns null when
|
|
167
|
+
// the CRL omits it, and a validator distinguishes the two.
|
|
168
|
+
//
|
|
169
|
+
// @enforced-by guard-shape-reinlined -- shares the `accept` shape below.
|
|
170
|
+
function isCrl(o) {
|
|
171
|
+
return !!o && typeof o === "object" &&
|
|
172
|
+
Buffer.isBuffer(o.tbsBytes) &&
|
|
173
|
+
typeof o.version === "number" &&
|
|
174
|
+
_isAlgorithmIdentifier(o.signatureAlgorithm) &&
|
|
175
|
+
_isBitString(o.signatureValue) &&
|
|
176
|
+
_isName(o.issuer) &&
|
|
177
|
+
o.thisUpdate instanceof Date &&
|
|
178
|
+
(o.nextUpdate === null || o.nextUpdate instanceof Date) &&
|
|
179
|
+
Array.isArray(o.crlExtensions) && o.crlExtensions.every(_isCrlExtensionEntry) &&
|
|
180
|
+
Array.isArray(o.revokedCertificates) && o.revokedCertificates.every(function (e) {
|
|
181
|
+
return !!e && typeof e.serialNumber === "bigint" &&
|
|
182
|
+
typeof e.serialNumberHex === "string" &&
|
|
183
|
+
e.revocationDate instanceof Date &&
|
|
184
|
+
Array.isArray(e.crlEntryExtensions) && e.crlEntryExtensions.every(_isCrlExtensionEntry);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Neither predicate may THROW. A caller-supplied object can define an accessor that
|
|
189
|
+
// throws on read, and a raw fault escaping a completeness CHECK defeats the point of
|
|
190
|
+
// checking -- the composing verb's contract is that every failure is its own typed
|
|
191
|
+
// error, and a dedupe caller using the predicate directly would get a raw fault where
|
|
192
|
+
// it expects a boolean. A property that cannot be read is not one the parser produced,
|
|
193
|
+
// so a throw is simply "not the shape". Wrapped HERE rather than at the door, because
|
|
194
|
+
// both are exported and the rule has to hold for both.
|
|
195
|
+
function _safe(shape) {
|
|
196
|
+
return function (o) {
|
|
197
|
+
try { return shape(o) === true; }
|
|
198
|
+
catch (_e) {
|
|
199
|
+
return false; // a field that cannot be read is not a field the parser produced
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
var certShape = _safe(isCert);
|
|
204
|
+
var crlShape = _safe(isCrl);
|
|
205
|
+
|
|
206
|
+
var _SHAPES = { certificate: certShape, crl: crlShape };
|
|
207
|
+
|
|
208
|
+
// accept(input, kind, parse, E, code, label) -> the parsed structure | throws
|
|
209
|
+
//
|
|
210
|
+
// The DOOR every "bytes or a parsed X" argument goes through. Bytes and PEM are
|
|
211
|
+
// handed to `parse`; an object CLAIMING to be parser output (it carries the
|
|
212
|
+
// `tbsBytes` the parser always produces) must satisfy the complete shape for
|
|
213
|
+
// `kind`; anything else is a typed input fault naming what the argument accepts.
|
|
214
|
+
//
|
|
215
|
+
// The claim test is `tbsBytes !== undefined`, not a truthiness test: an object
|
|
216
|
+
// carrying `tbsBytes: null` is claiming to be parsed and failing, and must be told
|
|
217
|
+
// so rather than being sent to a byte parser that reports something unrelated.
|
|
218
|
+
//
|
|
219
|
+
// Reading the claim is itself a read of a caller-supplied object, so it goes through
|
|
220
|
+
// the same try/catch the shape walk does. A getter or Proxy trap that throws would
|
|
221
|
+
// otherwise escape from OUTSIDE the guarded walk and reach the operator as a raw
|
|
222
|
+
// fault at every door composing this -- the failure the walk is wrapped to prevent,
|
|
223
|
+
// one property earlier. An unreadable claim is treated as a claim MADE and failed,
|
|
224
|
+
// matching fromTrustedSource below: an object that cannot answer what it is has not
|
|
225
|
+
// come from the parser, and saying so is more useful than reporting its type.
|
|
226
|
+
//
|
|
227
|
+
// @enforced-by guard-shape-reinlined
|
|
228
|
+
// The shape is the partial duck-type acceptance this guard replaces: testing
|
|
229
|
+
// tbsBytes (with or without one companion field) and returning the object.
|
|
230
|
+
// @guard-shape (?:if\s*\(|return\s*\(?)[^;\n]*\.tbsBytes[^;\n]*\)\s*(?:\{\s*)?return\s+\w+\s*;
|
|
231
|
+
// @guard-via guard\.parsed\.(?:accept|isCert|isCrl)\(
|
|
232
|
+
function accept(input, kind, parse, E, code, label) {
|
|
233
|
+
var who = label || "the argument";
|
|
234
|
+
var shape = _SHAPES[kind];
|
|
235
|
+
if (!shape) throw new TypeError("guard.parsed.accept: unknown kind " + kind);
|
|
236
|
+
if (_isBytes(input) || typeof input === "string") return parse(input);
|
|
237
|
+
var claimsParsed = false;
|
|
238
|
+
if (input && typeof input === "object") {
|
|
239
|
+
try { claimsParsed = input.tbsBytes !== undefined; }
|
|
240
|
+
catch (_e) {
|
|
241
|
+
claimsParsed = true; // an object that cannot answer what it is did not come from the parser
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (claimsParsed) {
|
|
245
|
+
if (!shape(input)) {
|
|
246
|
+
throw E(code, who + " claims to be a parsed " + kind + " (it carries tbsBytes) but is not a complete one -- pass the bytes, or the unmodified output of the matching pki.schema parser, since a partial object would be read as though the parser had produced it");
|
|
247
|
+
}
|
|
248
|
+
return input;
|
|
249
|
+
}
|
|
250
|
+
throw E(code, who + " must be a " + kind + " DER Buffer, a PEM string, or a parsed " + kind);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ---- provenance: the parser's own mark -------------------------------------
|
|
254
|
+
//
|
|
255
|
+
// Some verbs cannot be satisfied by completeness alone. A signature check has three
|
|
256
|
+
// parts -- the signature, the algorithm that verifies it, and the byte range it
|
|
257
|
+
// covers -- and on a parsed object all three are separate properties, each of them
|
|
258
|
+
// individually well-formed. Pair a real CA's signature over a certificate it issued
|
|
259
|
+
// with that certificate's own signed bytes and algorithm, relabel the three, and
|
|
260
|
+
// every part of the check passes for a structure that never existed. A MAC check has
|
|
261
|
+
// the same shape: the range MACed and the content returned as verified are two
|
|
262
|
+
// properties, so one object can say "verify this" and "return that".
|
|
263
|
+
//
|
|
264
|
+
// What those verbs need is not a shape but a PROVENANCE: these fields were derived
|
|
265
|
+
// together, from one byte string, by the parser. So the parser records the bytes it
|
|
266
|
+
// derived the object from, and the door derives the structure it verifies from those
|
|
267
|
+
// bytes again.
|
|
268
|
+
//
|
|
269
|
+
// Recording the bytes rather than a boolean is what makes this hold. A flag saying
|
|
270
|
+
// "this came from the parser" is defeated by keeping a legitimately parsed object and
|
|
271
|
+
// then assigning over its fields -- the flag is still there and now describes
|
|
272
|
+
// something else. Re-deriving from the recorded bytes discards every later edit,
|
|
273
|
+
// because the verdict is computed from the byte string and not from the object the
|
|
274
|
+
// caller is holding. The object a caller passes is therefore a way of NAMING bytes,
|
|
275
|
+
// which is all it was ever safe for it to be.
|
|
276
|
+
//
|
|
277
|
+
// The record is kept OFF the object, in a WeakMap the parser owns, keyed by the
|
|
278
|
+
// returned object itself. Nothing about the record is reachable through the object,
|
|
279
|
+
// which is what makes it unforgeable:
|
|
280
|
+
//
|
|
281
|
+
// - A property, however hidden, is READ through the object, and every read of an
|
|
282
|
+
// object is interceptable. A Proxy's getOwnPropertyDescriptor and get traps are
|
|
283
|
+
// handed whatever key they are asked about -- the symbol need not be known to be
|
|
284
|
+
// answered for -- so a Proxy could claim any mark and name any bytes. A WeakMap
|
|
285
|
+
// lookup is on IDENTITY: a Proxy is a different object from the parser's result
|
|
286
|
+
// and simply is not a key.
|
|
287
|
+
// - `Object.create(parsed)` is likewise a different object, so it inherits no
|
|
288
|
+
// record, while `Object.assign({}, parsed)` and `{...parsed}` are different
|
|
289
|
+
// objects too. All three -- the ways a mixed structure is actually assembled --
|
|
290
|
+
// fall out of the same rule rather than needing three checks.
|
|
291
|
+
//
|
|
292
|
+
// The recorded bytes are COPIED. Recording the caller's Buffer by reference would let
|
|
293
|
+
// it be overwritten between the parse and the verify, which is the same defeat by
|
|
294
|
+
// another route: the door would faithfully re-derive from bytes that had changed
|
|
295
|
+
// underneath it. A PEM string needs no copy, strings being immutable.
|
|
296
|
+
//
|
|
297
|
+
// This is not a signature check and does not pretend to be. It says only "verify the
|
|
298
|
+
// bytes this object was parsed from", which is precisely the claim the three-part
|
|
299
|
+
// check was resting on without ever asking for it.
|
|
300
|
+
var PROVENANCE = new WeakMap();
|
|
301
|
+
|
|
302
|
+
// recordingParser(kind, parse) -> a parser that records what it parsed.
|
|
303
|
+
//
|
|
304
|
+
// Recording is not a separate step a caller could perform, and the record is not a
|
|
305
|
+
// value a caller can read back. Both follow from the same requirement: the record has
|
|
306
|
+
// to mean "the parser derived this object from these exact bytes", and every way of
|
|
307
|
+
// exposing it weakens that to something else.
|
|
308
|
+
//
|
|
309
|
+
// - An exported `mark(obj, kind, bytes)` would let any code assert provenance for an
|
|
310
|
+
// object it did not parse, or overwrite the provenance of one it did not produce.
|
|
311
|
+
// The only way to obtain a record is therefore to actually run the parse.
|
|
312
|
+
// - An exported `sourceOf(obj)` would hand back the recorded Buffer, which the caller
|
|
313
|
+
// could then overwrite -- defeating the copy by writing through the value the copy
|
|
314
|
+
// was made to protect.
|
|
315
|
+
//
|
|
316
|
+
// The SNAPSHOT is taken before the parse and the parser is given the snapshot, so the
|
|
317
|
+
// bytes the parser reads and the bytes the record names are the same object by
|
|
318
|
+
// construction. Taking it afterwards from the caller's argument would leave a window:
|
|
319
|
+
// a typed-array subclass with stateful `byteOffset` / `byteLength` getters, or any
|
|
320
|
+
// SharedArrayBuffer, can present one set of bytes to the parser and another to a later
|
|
321
|
+
// copy. guard.bytes.snapshot reads through the BufferSource contract and copies.
|
|
322
|
+
//
|
|
323
|
+
// @enforced-by guard-shape-reinlined -- shares the fromTrustedSource shape below; a
|
|
324
|
+
// door re-testing the claim fields itself is the re-inline both replace.
|
|
325
|
+
function recordingParser(kind, parse, ErrorClass, code, label) {
|
|
326
|
+
return function (input) {
|
|
327
|
+
var isText = typeof input === "string"; // strings are immutable; nothing to snapshot
|
|
328
|
+
var snap = isText ? input : bytes.snapshotSource(input, ErrorClass, code, label);
|
|
329
|
+
var out = parse(snap);
|
|
330
|
+
// The RECORD gets its own copy, not the buffer the parser just read.
|
|
331
|
+
//
|
|
332
|
+
// A parser surfaces raw byte ranges -- tbsBytes, a Name's encoded form, an extension's value --
|
|
333
|
+
// as VIEWS onto the buffer it parsed, which is what makes them exact and cheap. So the object
|
|
334
|
+
// handed back reaches into that buffer, and a caller holding it can write through those views:
|
|
335
|
+
// `parsed.tbsBytes.fill(0)` would then overwrite the very bytes the record names, and the
|
|
336
|
+
// re-derivation would faithfully reproduce whatever was written. A record the object can reach
|
|
337
|
+
// is not a record of anything. This copy is never handed out and nothing aliases it.
|
|
338
|
+
if (out && typeof out === "object") {
|
|
339
|
+
PROVENANCE.set(out, { kind: kind, source: isText ? snap : Buffer.from(snap) });
|
|
340
|
+
}
|
|
341
|
+
return out;
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// recordingWalker(kind, walkNode, decodeBytes) -> function (node) -> the walked structure.
|
|
346
|
+
//
|
|
347
|
+
// The recordingParser sibling for a producer handed an already-DECODED node rather than bytes.
|
|
348
|
+
// One structure needs it: the RFC 7292 authSafe's SignedData. A PFX is decoded BER-tolerantly
|
|
349
|
+
// because real stores carry the indefinite-length encoding, so its inner SignedData cannot be
|
|
350
|
+
// re-derived by the strict `parse` entry the byte doors use -- that entry would refuse a store
|
|
351
|
+
// this toolkit accepts today. The record therefore names the bytes AND how to walk them again.
|
|
352
|
+
//
|
|
353
|
+
// The same three properties hold as for recordingParser, and for the same reasons. The record can
|
|
354
|
+
// only be obtained by actually running the walk, so no code can assert provenance for a structure
|
|
355
|
+
// it did not produce. The recorded bytes are a private COPY of the node's, because the walked
|
|
356
|
+
// result surfaces views onto the buffer the PFX was decoded from and a caller holding one could
|
|
357
|
+
// otherwise write through it into the very bytes the record names. And `derive` re-runs THIS
|
|
358
|
+
// walker over that copy, so a re-derivation reproduces the structure exactly -- the decode is the
|
|
359
|
+
// producer's own, never a stricter one substituted at the door.
|
|
360
|
+
//
|
|
361
|
+
// @enforced-by guard-shape-reinlined -- shares recordingParser's shape below: the PROVENANCE.set
|
|
362
|
+
// that binds a structure to the bytes it was derived from appears only in this module, so no
|
|
363
|
+
// producer elsewhere can mint a record for a structure it did not itself walk.
|
|
364
|
+
function recordingWalker(kind, walkNode, decodeBytes) {
|
|
365
|
+
return function (node) {
|
|
366
|
+
var out = walkNode(node);
|
|
367
|
+
if (out && typeof out === "object" && node && _isBytes(node.bytes)) {
|
|
368
|
+
PROVENANCE.set(out, {
|
|
369
|
+
kind: kind,
|
|
370
|
+
source: Buffer.from(node.bytes),
|
|
371
|
+
derive: function (src) { return walkNode(decodeBytes(src)); },
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
return out;
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// The provenance record for `obj`, or undefined. @internal to this module: touches no
|
|
379
|
+
// property of `obj` at all -- the lookup is on the object's identity.
|
|
380
|
+
function _recordOf(obj, kind) {
|
|
381
|
+
if (!obj || typeof obj !== "object") return undefined;
|
|
382
|
+
var rec = PROVENANCE.get(obj);
|
|
383
|
+
return (rec && rec.kind === kind) ? rec : undefined;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// fromTrustedSource(input, kind, claimFields, parse, E, code, why) -> the parsed
|
|
387
|
+
// structure | throws.
|
|
388
|
+
//
|
|
389
|
+
// The door for a verb that decides INTEGRITY. Bytes are parsed. An object carrying
|
|
390
|
+
// the parser's record is RE-PARSED from the bytes that record names -- so a caller
|
|
391
|
+
// may keep passing the parsed form, and anything done to that object since it was
|
|
392
|
+
// parsed is discarded rather than trusted. An object that CLAIMS to be parser output
|
|
393
|
+
// -- it carries a field only a parsed structure of this kind has -- but carries no
|
|
394
|
+
// record was not produced by the parser, and is refused with the caller's own reason.
|
|
395
|
+
//
|
|
396
|
+
// `claimFields` exist so that refusal names what happened, rather than handing the
|
|
397
|
+
// object to a byte parser that would report something about its type instead.
|
|
398
|
+
//
|
|
399
|
+
// @enforced-by guard-shape-reinlined
|
|
400
|
+
// @guard-shape (?:responseStatus|integrityMode|macedBytes|tbsResponseDataBytes)\s*!==\s*undefined
|
|
401
|
+
// @guard-via guard\.parsed\.(?:fromTrustedSource|recordingParser)\(
|
|
402
|
+
function fromTrustedSource(input, kind, claimFields, parse, E, code, why) {
|
|
403
|
+
if (input && typeof input === "object" && !_isBytes(input)) {
|
|
404
|
+
// A structure the walker recorded carries its own way back; the byte doors re-derive with the
|
|
405
|
+
// parser they were given.
|
|
406
|
+
var rec = _recordOf(input, kind);
|
|
407
|
+
if (rec !== undefined) return (rec.derive || parse)(rec.source);
|
|
408
|
+
for (var i = 0; i < claimFields.length; i++) {
|
|
409
|
+
// A claim field can be an accessor that throws; a guard answers, it does not relay.
|
|
410
|
+
var claims;
|
|
411
|
+
try { claims = input[claimFields[i]] !== undefined; }
|
|
412
|
+
catch (_e) {
|
|
413
|
+
claims = true; // a claim that cannot be read is certainly not the parser's own result
|
|
414
|
+
}
|
|
415
|
+
if (claims) throw E(code, why);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return parse(input);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ---- the certificate / CRL door --------------------------------------------
|
|
422
|
+
//
|
|
423
|
+
// acceptDerived(input, kind, parse, E, code, label) -> the parsed structure | throws.
|
|
424
|
+
//
|
|
425
|
+
// THE door for a certificate or a CRL, at every boundary that takes one. Bytes and PEM are parsed;
|
|
426
|
+
// the parser's own result is re-derived from the bytes it recorded; an object that claims to be one
|
|
427
|
+
// without that record is refused.
|
|
428
|
+
//
|
|
429
|
+
// It is one rule for every door rather than a rule for the doors that "reach a verdict", because
|
|
430
|
+
// that judgment is the thing that keeps being got wrong. A certificate is a trust decision wherever
|
|
431
|
+
// it appears: an issuer certificate handed to a signer names who the new certificate says issued it;
|
|
432
|
+
// a root handed to an attestation check IS the anchor; a certificate handed to a request builder
|
|
433
|
+
// names what the request asks about. Sorting them into verdict and non-verdict doors invites exactly
|
|
434
|
+
// the substitution this prevents at whichever door was sorted wrong -- so no door is.
|
|
435
|
+
//
|
|
436
|
+
// The claim fields are the ones a parsed structure has and a byte input does not, so an object that
|
|
437
|
+
// was rebuilt is told what happened rather than handed to a byte parser that would report something
|
|
438
|
+
// about its type instead.
|
|
439
|
+
var _CLAIMS = {
|
|
440
|
+
certificate: ["tbsBytes", "subjectPublicKeyInfo", "serialNumberHex"],
|
|
441
|
+
crl: ["tbsBytes", "revokedCertificates", "crlExtensions"],
|
|
442
|
+
cms: ["signerInfos", "encapContentInfo"],
|
|
443
|
+
};
|
|
444
|
+
var _WHY = {
|
|
445
|
+
certificate: "the signed byte range, the signature and the fields that range encodes are separate properties of a parsed object, so a REBUILT certificate (Object.assign, spread, a JSON round-trip) could have them describe different certificates -- keep a real CA certificate's signed bytes and signature and replace only its public key and every field is still well-formed",
|
|
446
|
+
crl: "the signed byte range, the revocation list and the scope extensions are separate properties of a parsed object, so a REBUILT CRL could have them describe different CRLs -- empty the revocation list and a correctly signed CRL reports a revoked certificate as good",
|
|
447
|
+
cms: "the signed attribute bytes, the signature, the encapsulated content and the certificates that verify it are separate properties of a parsed object, so a REBUILT SignedData could have them describe different messages -- keep a genuine signer's signature and signed attributes and put other content beside them, and every part of the check passes for content that signer never signed",
|
|
448
|
+
};
|
|
449
|
+
// @enforced-by guard-shape-reinlined -- shares the fromTrustedSource shape it composes: a door that
|
|
450
|
+
// tests the claim fields itself, rather than routing here, is the re-inline both replace.
|
|
451
|
+
function acceptDerived(input, kind, parse, E, code, label) {
|
|
452
|
+
var claims = _CLAIMS[kind];
|
|
453
|
+
if (!claims) throw new TypeError("guard.parsed.acceptDerived: unknown kind " + kind);
|
|
454
|
+
var who = label || "the argument";
|
|
455
|
+
// An object that is neither bytes nor a claim is named as the wrong TYPE here rather than handed
|
|
456
|
+
// to the byte parser. The parser would refuse it too, but with its own domain's code -- a caller
|
|
457
|
+
// who passed the wrong thing to pki.path.validate should read a path/* fault, not an x509/* one
|
|
458
|
+
// from a layer they did not call.
|
|
459
|
+
if (input !== null && input !== undefined && typeof input === "object" && !_isBytes(input)) {
|
|
460
|
+
var claimsSomething = false;
|
|
461
|
+
for (var i = 0; i < claims.length && !claimsSomething; i++) {
|
|
462
|
+
try { claimsSomething = input[claims[i]] !== undefined; }
|
|
463
|
+
catch (_e) {
|
|
464
|
+
claimsSomething = true; // a claim that cannot be read is certainly not the parser's result
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (!claimsSomething) {
|
|
468
|
+
throw E(code, who + " must be a " + kind + " DER Buffer, a PEM string, or a parsed " + kind);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
var ns = { certificate: "x509", crl: "crl", cms: "cms" }[kind];
|
|
472
|
+
return fromTrustedSource(input, kind, claims, parse, E, code,
|
|
473
|
+
who + " must be its DER bytes, a PEM string, or an unmodified pki.schema." + ns +
|
|
474
|
+
".parse result: " + _WHY[kind]);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
module.exports = {
|
|
478
|
+
accept: accept, acceptDerived: acceptDerived,
|
|
479
|
+
fromTrustedSource: fromTrustedSource, recordingParser: recordingParser,
|
|
480
|
+
recordingWalker: recordingWalker,
|
|
481
|
+
isCert: certShape, isCrl: crlShape,
|
|
482
|
+
};
|
package/lib/hpke.js
CHANGED
|
@@ -388,7 +388,40 @@ function _recipPrivate(suite, sk) {
|
|
|
388
388
|
// Resolve and validate the HPKE mode: RFC 9180 sec. 5.1 defines exactly base /
|
|
389
389
|
// psk / auth / auth-psk. An unknown mode must fail closed, never key-schedule
|
|
390
390
|
// with an out-of-registry mode byte.
|
|
391
|
-
|
|
391
|
+
// The sender and the recipient read the same option object from OPPOSITE ends, so each direction
|
|
392
|
+
// gets its own table rather than sharing their union. A union recognises every name at both ends and
|
|
393
|
+
// so accepts the one that cannot do anything there: `senderPublicKey` handed to setupS, or
|
|
394
|
+
// `senderKey` handed to setupR, is silently ignored -- which is the exact silence these tables exist
|
|
395
|
+
// to remove, in a wider form. An option that means nothing where it was passed is a misunderstanding
|
|
396
|
+
// worth reporting, and usually a misdirected auth-mode setup.
|
|
397
|
+
//
|
|
398
|
+
// Every one of these narrows or authenticates: a misspelled `psk` leaves a psk-mode setup with no
|
|
399
|
+
// pre-shared key and a misspelled `senderKey` leaves an auth-mode setup unauthenticated, each
|
|
400
|
+
// failing with a message about the field the caller believes they supplied. `eph` is the
|
|
401
|
+
// deterministic-KAT seam and is sender-side only; supplied by mistake it fixes the ephemeral key,
|
|
402
|
+
// which is the one option here whose silent acceptance is catastrophic.
|
|
403
|
+
var _COMMON_KEYS = { mode: 1, info: 1, psk: 1, pskId: 1 };
|
|
404
|
+
function _withCommon(extra) {
|
|
405
|
+
var out = {};
|
|
406
|
+
Object.keys(_COMMON_KEYS).forEach(function (k) { out[k] = 1; });
|
|
407
|
+
Object.keys(extra).forEach(function (k) { out[k] = 1; });
|
|
408
|
+
return out;
|
|
409
|
+
}
|
|
410
|
+
var _SETUP_S_KEYS = _withCommon({
|
|
411
|
+
senderKey: 1, // auth modes: the sender's OWN KEM private key
|
|
412
|
+
eph: 1, // test-vector determinism: fixes the ephemeral key this side generates
|
|
413
|
+
});
|
|
414
|
+
var _SETUP_R_KEYS = _withCommon({
|
|
415
|
+
senderPublicKey: 1, // auth modes: the sender's KEM public key, to authenticate them
|
|
416
|
+
});
|
|
417
|
+
function _mode(opts, keys, who) {
|
|
418
|
+
guard.identifier.assertKnownKeys(opts, keys, _err, "hpke/bad-input", function (k) {
|
|
419
|
+
var other = (keys === _SETUP_S_KEYS ? _SETUP_R_KEYS : _SETUP_S_KEYS);
|
|
420
|
+
return "unknown HPKE " + who + " option " + JSON.stringify(k) +
|
|
421
|
+
(Object.prototype.hasOwnProperty.call(other, k)
|
|
422
|
+
? " -- that option belongs to the other end of the exchange, where it would authenticate or seed; here it would do nothing"
|
|
423
|
+
: "") + " -- accepted: " + Object.keys(keys).sort().join(", ");
|
|
424
|
+
});
|
|
392
425
|
var mode = (opts.mode == null) ? MODE_BASE : opts.mode;
|
|
393
426
|
if (mode !== MODE_BASE && mode !== MODE_PSK && mode !== MODE_AUTH && mode !== MODE_AUTH_PSK) {
|
|
394
427
|
throw _err("hpke/unknown-mode", "unsupported HPKE mode " + JSON.stringify(mode) + " (RFC 9180 sec. 5.1 defines base / psk / auth / auth-psk)");
|
|
@@ -435,7 +468,7 @@ var suites = {
|
|
|
435
468
|
function _setupS(ids, pkR, opts) {
|
|
436
469
|
opts = opts || {};
|
|
437
470
|
var suite = _suite(ids);
|
|
438
|
-
var mode = _mode(opts);
|
|
471
|
+
var mode = _mode(opts, _SETUP_S_KEYS, "sender setup");
|
|
439
472
|
var r = _recipPublic(suite, pkR);
|
|
440
473
|
var kem = suite.kem, k;
|
|
441
474
|
if (mode === MODE_AUTH || mode === MODE_AUTH_PSK) {
|
|
@@ -476,7 +509,7 @@ function _setupS(ids, pkR, opts) {
|
|
|
476
509
|
function _setupR(ids, enc, skR, opts) {
|
|
477
510
|
opts = opts || {};
|
|
478
511
|
var suite = _suite(ids);
|
|
479
|
-
var mode = _mode(opts);
|
|
512
|
+
var mode = _mode(opts, _SETUP_R_KEYS, "recipient setup");
|
|
480
513
|
var r = _recipPrivate(suite, skR);
|
|
481
514
|
var kem = suite.kem, ss;
|
|
482
515
|
if (mode === MODE_AUTH || mode === MODE_AUTH_PSK) {
|
package/lib/jose.js
CHANGED
|
@@ -365,8 +365,14 @@ function assertPublicJwk(jwk) {
|
|
|
365
365
|
* var v = await pki.jose.verify(jws, { profile: "acme-outer", key: accountJwk });
|
|
366
366
|
* v.header.alg; // -> "ES256"
|
|
367
367
|
*/
|
|
368
|
+
// `key` NAMES the key this message must be signed under and `profile` selects which header rules
|
|
369
|
+
// apply -- both narrow what verifies, so a misspelling of either silently widens it back to the
|
|
370
|
+
// default. That is the shape this check exists for: the caller asked for something stricter and got
|
|
371
|
+
// the looser behaviour, with nothing said.
|
|
372
|
+
var _VERIFY_KEYS = { key: 1, profile: 1 };
|
|
368
373
|
async function verify(jws, opts) {
|
|
369
374
|
opts = opts || {};
|
|
375
|
+
guard.identifier.assertKnownKeys(opts, _VERIFY_KEYS, E, "jose/bad-input", "unknown pki.jose.verify option ");
|
|
370
376
|
if (!jws || typeof jws !== "object" || Array.isArray(jws)) throw E("jose/bad-jws", "a flattened JWS object is required");
|
|
371
377
|
if (Object.prototype.hasOwnProperty.call(jws, "signatures")) throw E("jose/bad-jws", "the multi-signature signatures member is forbidden");
|
|
372
378
|
if (Object.prototype.hasOwnProperty.call(jws, "header")) throw E("jose/bad-jws", "the unprotected header member is forbidden");
|
|
@@ -459,8 +465,10 @@ async function verify(jws, opts) {
|
|
|
459
465
|
* nonce: "oFvnlFP1wIhRlYS2jTaXbA", url: "https://ca.example/acme/new-acct" };
|
|
460
466
|
* var jws = await pki.jose.sign({ protected: hdr, payload: Buffer.from("{}"), key: priv });
|
|
461
467
|
*/
|
|
468
|
+
var _SIGN_KEYS = { protected: 1, payload: 1, key: 1, jwk: 1, profile: 1 };
|
|
462
469
|
async function sign(opts) {
|
|
463
470
|
opts = opts || {};
|
|
471
|
+
guard.identifier.assertKnownKeys(opts, _SIGN_KEYS, E, "jose/bad-input", "unknown pki.jose.sign option ");
|
|
464
472
|
var header = opts.protected;
|
|
465
473
|
var payload = opts.payload;
|
|
466
474
|
if (!Buffer.isBuffer(payload)) throw E("jose/bad-input", "payload must be a Buffer (empty Buffer for POST-as-GET)");
|
package/lib/lint.js
CHANGED
|
@@ -85,15 +85,31 @@ function _finding(rule, detail) {
|
|
|
85
85
|
|
|
86
86
|
// ---- ingestion: bytes/PEM/parsed -> parsed, or a fatal lint/unparseable finding ----
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
// A certificate lint reports on is the one its bytes describe. Every rule reads a sibling field off
|
|
89
|
+
// this object and none re-derives it, so an assembled object produces a report about fields that
|
|
90
|
+
// were never in any certificate -- and a lint report reading "clean" is the answer an operator acts
|
|
91
|
+
// on. That makes it a decision like any other, so it takes the same derivation: the parser's record,
|
|
92
|
+
// not the caller's object.
|
|
93
|
+
//
|
|
94
|
+
// Returns the DERIVED certificate or null, rather than a boolean, because the derived value is what
|
|
95
|
+
// the rules must run against -- answering "yes, that is a certificate" and then linting the object
|
|
96
|
+
// would be the check computed and thrown away. Never throws: _ingest's contract is to return a
|
|
97
|
+
// finding for bad DATA and reserve throws for a wrong-TYPE argument.
|
|
98
|
+
function _derivedCert(o) {
|
|
99
|
+
if (!o || typeof o !== "object" || Buffer.isBuffer(o) || ArrayBuffer.isView(o) || o instanceof ArrayBuffer) return null;
|
|
100
|
+
try {
|
|
101
|
+
var p = guard.parsed.acceptDerived(o, "certificate", x509.parse, _cfg, "lint/bad-input", "the certificate");
|
|
102
|
+
return guard.parsed.isCert(p) ? p : null;
|
|
103
|
+
} catch (_e) {
|
|
104
|
+
return null; // not the parser's own output: linted as bytes below, or reported as unparseable
|
|
105
|
+
}
|
|
91
106
|
}
|
|
92
107
|
|
|
93
108
|
// Returns { cert } on success, or { fatal: <Finding> } when hostile bytes do not parse
|
|
94
109
|
// (the never-throw data path). Throws LintError ONLY on a wrong-TYPE input (config misuse).
|
|
95
110
|
function _ingest(input) {
|
|
96
|
-
|
|
111
|
+
var derived = _derivedCert(input);
|
|
112
|
+
if (derived) return { cert: derived };
|
|
97
113
|
var der;
|
|
98
114
|
if (Buffer.isBuffer(input)) der = input;
|
|
99
115
|
else if (typeof input === "string") {
|
package/lib/merkle.js
CHANGED
|
@@ -188,8 +188,14 @@ function emptyRootHash() {
|
|
|
188
188
|
* var lh = pki.merkle.leafHash(Buffer.from([0]));
|
|
189
189
|
* pki.merkle.verifyInclusion({ leafIndex: 0, treeSize: 1, leafHash: lh, proof: [], rootHash: lh }); // -> true
|
|
190
190
|
*/
|
|
191
|
+
// Every field of this verb is REQUIRED, so a misspelling is the one input that reads as an omission
|
|
192
|
+
// rather than as a value -- and an omitted required field is caught, while a misspelled one leaves
|
|
193
|
+
// the caller believing they supplied a proof about a leaf they never named.
|
|
194
|
+
var _INCLUSION_KEYS = { leafIndex: 1, treeSize: 1, leafHash: 1, rootHash: 1, proof: 1 };
|
|
191
195
|
function verifyInclusion(opts) {
|
|
192
196
|
opts = opts || {};
|
|
197
|
+
guard.identifier.assertKnownKeys(opts, _INCLUSION_KEYS, function (c, m) { return new MerkleError(c, m); },
|
|
198
|
+
"merkle/bad-input", "unknown verifyInclusion option ");
|
|
193
199
|
var leafIndex = _coerceCoord(opts.leafIndex, "leafIndex");
|
|
194
200
|
var treeSize = _coerceCoord(opts.treeSize, "treeSize");
|
|
195
201
|
if (treeSize === 0n) throw new MerkleError("merkle/empty-tree", "an empty tree has no leaves to include");
|
|
@@ -254,8 +260,11 @@ function verifyInclusion(opts) {
|
|
|
254
260
|
* var r = pki.merkle.leafHash(Buffer.from([0]));
|
|
255
261
|
* pki.merkle.verifyConsistency({ oldSize: 1, newSize: 1, oldRoot: r, newRoot: r, proof: [] }); // -> true
|
|
256
262
|
*/
|
|
263
|
+
var _CONSISTENCY_KEYS = { oldSize: 1, newSize: 1, oldRoot: 1, newRoot: 1, proof: 1 };
|
|
257
264
|
function verifyConsistency(opts) {
|
|
258
265
|
opts = opts || {};
|
|
266
|
+
guard.identifier.assertKnownKeys(opts, _CONSISTENCY_KEYS, function (c, m) { return new MerkleError(c, m); },
|
|
267
|
+
"merkle/bad-input", "unknown verifyConsistency option ");
|
|
259
268
|
var oldSize = _coerceCoord(opts.oldSize, "oldSize");
|
|
260
269
|
var newSize = _coerceCoord(opts.newSize, "newSize");
|
|
261
270
|
var oldRoot = _node32(opts.oldRoot, "oldRoot");
|