@blamejs/pki 0.8.31 → 0.8.49

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.
Files changed (87) hide show
  1. package/CHANGELOG.md +401 -0
  2. package/README.md +66 -15
  3. package/bin/pki.js +716 -30
  4. package/examples/issue-a-certificate.js +90 -0
  5. package/examples/post-quantum.js +66 -0
  6. package/examples/read-a-certificate.js +56 -0
  7. package/examples/revoke-and-check.js +86 -0
  8. package/examples/sign-and-verify-cms.js +63 -0
  9. package/index.js +18 -5
  10. package/lib/acme.js +223 -74
  11. package/lib/alt-sig.js +441 -0
  12. package/lib/asn1-der.js +48 -25
  13. package/lib/attrcert-sign.js +27 -19
  14. package/lib/cbor-det.js +24 -14
  15. package/lib/cmc-build.js +9 -2
  16. package/lib/cmc-verify.js +14 -3
  17. package/lib/cmp-build.js +17 -5
  18. package/lib/cmp-session.js +24 -11
  19. package/lib/cmp-verify.js +28 -19
  20. package/lib/cms-compress.js +3 -1
  21. package/lib/cms-decrypt.js +102 -7
  22. package/lib/cms-encrypt.js +59 -7
  23. package/lib/cms-sign.js +31 -9
  24. package/lib/cms-verify.js +22 -18
  25. package/lib/composite-kem.js +30 -18
  26. package/lib/composite-sig.js +29 -14
  27. package/lib/constants.js +26 -0
  28. package/lib/crl-sign.js +111 -19
  29. package/lib/crmf-sign.js +167 -15
  30. package/lib/csr-sign.js +117 -6
  31. package/lib/ct.js +1008 -58
  32. package/lib/est.js +29 -16
  33. package/lib/framework-error.js +12 -0
  34. package/lib/guard-async.js +1 -1
  35. package/lib/guard-bytes.js +103 -20
  36. package/lib/guard-crypto.js +205 -3
  37. package/lib/guard-encoding.js +6 -1
  38. package/lib/guard-identifier.js +55 -8
  39. package/lib/guard-intrinsic.js +43 -0
  40. package/lib/guard-json.js +122 -9
  41. package/lib/guard-limits.js +12 -1
  42. package/lib/guard-list.js +63 -8
  43. package/lib/guard-name.js +167 -20
  44. package/lib/guard-range.js +43 -5
  45. package/lib/guard-text.js +41 -0
  46. package/lib/guard-verdict.js +39 -8
  47. package/lib/hpke.js +688 -42
  48. package/lib/http-transport.js +156 -4
  49. package/lib/identity-match.js +4 -4
  50. package/lib/inspect.js +33 -6
  51. package/lib/jose.js +93 -25
  52. package/lib/key.js +18 -13
  53. package/lib/lint.js +165 -1
  54. package/lib/merkle.js +233 -27
  55. package/lib/ocsp.js +27 -14
  56. package/lib/oid.js +14 -4
  57. package/lib/path-validate.js +143 -61
  58. package/lib/pkcs12-build.js +7 -2
  59. package/lib/pki-build.js +31 -7
  60. package/lib/possession.js +575 -0
  61. package/lib/related-cert.js +708 -0
  62. package/lib/revocation-fetch.js +6 -6
  63. package/lib/scep.js +15 -5
  64. package/lib/schema-c509.js +5 -2
  65. package/lib/schema-cmc.js +1 -1
  66. package/lib/schema-cmp.js +8 -8
  67. package/lib/schema-cms.js +2 -15
  68. package/lib/schema-csr.js +69 -0
  69. package/lib/schema-engine.js +10 -3
  70. package/lib/schema-pkix.js +228 -2
  71. package/lib/schema-tsp.js +7 -6
  72. package/lib/sign-scheme.js +22 -9
  73. package/lib/sigstore.js +684 -168
  74. package/lib/sleep.js +8 -1
  75. package/lib/smime.js +22 -1
  76. package/lib/tlog.js +1235 -0
  77. package/lib/tls-cert-compress.js +25 -8
  78. package/lib/tsp-sign.js +120 -35
  79. package/lib/tuf.js +1395 -0
  80. package/lib/validator-tpm.js +4 -1
  81. package/lib/verify-seam.js +7 -1
  82. package/lib/webauthn-mds.js +41 -4
  83. package/lib/webauthn.js +43 -25
  84. package/lib/webcrypto.js +48 -46
  85. package/lib/x509-sign.js +225 -19
  86. package/package.json +2 -1
  87. package/sbom.cdx.json +6 -6
package/lib/tuf.js ADDED
@@ -0,0 +1,1395 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ /**
5
+ * @module pki.tuf
6
+ * @nav Transparency
7
+ * @title TUF
8
+ * @fullname The Update Framework: metadata verification and root rotation
9
+ * @order 230
10
+ * @slug tuf
11
+ *
12
+ * @intro
13
+ * The Update Framework's metadata layer: the signed documents a repository
14
+ * publishes to say which keys may sign what, and the rules a client applies
15
+ * before believing any of it. This is the trust root a sigstore verifier
16
+ * would otherwise have pinned by hand.
17
+ *
18
+ * TUF signs the canonical JSON form of a document's `signed` object, so
19
+ * `canonicalJson` is the wire format rather than a convenience: it sorts
20
+ * object keys by code point, emits no whitespace, and escapes only backslash
21
+ * and quote, leaving a literal control character as itself. `JSON.stringify`
22
+ * would write `\n` where this writes a newline byte, so the two produce
23
+ * different signatures over the same document. A float is refused rather than
24
+ * rounded, because the reference encoder cannot write one.
25
+ *
26
+ * `keyId` derives a key's identifier as the hex SHA-256 of its own canonical
27
+ * form, and `verifySignatures` recomputes it for every key a role names, so a
28
+ * document cannot list one key under another's identifier. A threshold counts
29
+ * one verified signature per distinct identifier, which is what the
30
+ * specification requires of a client meeting the same key twice.
31
+ *
32
+ * `updateRoot` walks the root chain: each new root must be signed by a
33
+ * threshold of the keys the trusted root names AND a threshold of its own, and
34
+ * its version must be exactly one more than the trusted one. That pair of
35
+ * rules is what makes a key rotation safe and a skipped intermediate
36
+ * impossible. Verifying only, and transport-free: nothing here fetches.
37
+ *
38
+ * @card
39
+ * Canonical JSON signing preimage, key-identifier binding, threshold
40
+ * verification, and the root-rotation chain walk. Fail-closed, no transport.
41
+ */
42
+
43
+ var constants = require("./constants");
44
+ var frameworkError = require("./framework-error");
45
+ var guard = require("./guard-all");
46
+ var intrinsic = require("./guard-intrinsic");
47
+ var rfc3339 = require("./rfc3339");
48
+
49
+ var TufError = frameworkError.TufError;
50
+ function _err(code, message, cause) { return new TufError(code, message, cause); }
51
+
52
+ var _isArray = intrinsic.isArray;
53
+ var _isSafeInteger = intrinsic.isSafeInteger;
54
+ var _keys = intrinsic.keys;
55
+ var _create = intrinsic.create;
56
+ var _hasOwn = intrinsic.hasOwn;
57
+ var _bufferFrom = intrinsic.bufferFrom;
58
+ var _bufferConcat = intrinsic.bufferConcat;
59
+ var _bufToString = intrinsic.bufToString;
60
+ var _push = intrinsic.push;
61
+ var _strSlice = intrinsic.uncurry(String.prototype.slice);
62
+ var _codePointAt = intrinsic.uncurry(String.prototype.codePointAt);
63
+ var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
64
+ var _numToString = intrinsic.uncurry(Number.prototype.toString);
65
+ var _dateParse = intrinsic.dateParse;
66
+ var _byteLength = intrinsic.byteLength;
67
+ var _sort = intrinsic.sort;
68
+ var _join = intrinsic.join;
69
+
70
+ var CANONICAL_MAX_DEPTH = constants.LIMITS.JSON_MAX_DEPTH;
71
+ var KEY_ID_HEX_LEN = 64;
72
+ var TUF_ROLES = ["root", "timestamp", "snapshot", "targets"];
73
+
74
+ /** @internal Canonical JSON escapes ONLY these two, each with a single backslash. Everything else,
75
+ * including a byte below 0x20, is emitted as itself. A generic JSON writer escapes far more, and
76
+ * would produce a different preimage for the same document. */
77
+ function _canonicalString(s, out, budget) {
78
+ /** @internal Every string reaching the encoding passes here, keys and values alike, so the
79
+ * well-formed check is made once. Canonical JSON is the preimage a TUF signature covers, and an
80
+ * unpaired surrogate would be converted to U+FFFD, so two documents differing only in that code unit
81
+ * would have the same signing bytes.
82
+ * @guard-via guard\.text\.assertWellFormedUtf16\( */
83
+ guard.text.assertWellFormedUtf16(s, TufError, "tuf/bad-input", "a canonical JSON string");
84
+ _emit(out, "\"", budget);
85
+ var start = 0;
86
+ for (var i = 0; i < s.length; i++) {
87
+ var c = _codePointAt(s, i);
88
+ if (c === 0x5c || c === 0x22) {
89
+ if (i > start) _emit(out, _strSlice(s, start, i), budget);
90
+ _emit(out, c === 0x5c ? "\\\\" : "\\\"", budget);
91
+ start = i + 1;
92
+ }
93
+ }
94
+ if (start < s.length) _emit(out, _strSlice(s, start), budget);
95
+ _emit(out, "\"", budget);
96
+ }
97
+
98
+ /** @internal Keys compared by CODE POINT. A default sort compares UTF-16 code units, which orders a
99
+ * surrogate pair below U+FFFF where the code point orders it above, so the two disagree on any
100
+ * document carrying a key outside the basic plane. */
101
+ function _byCodePoint(a, b) {
102
+ var n = a.length < b.length ? a.length : b.length;
103
+ for (var i = 0; i < n; i++) {
104
+ var ca = _codePointAt(a, i), cb = _codePointAt(b, i);
105
+ if (ca !== cb) return ca < cb ? -1 : 1;
106
+ }
107
+ return a.length === b.length ? 0 : (a.length < b.length ? -1 : 1);
108
+ }
109
+
110
+ /** @internal Whether a value is a plain record: an object whose prototype is `Object.prototype` or null.
111
+ * Canonical JSON encodes an object by its own enumerable keys, which is only the whole value when the
112
+ * value has no state anywhere else, and the line is drawn by PROTOTYPE rather than by naming the classes
113
+ * to refuse. A denylist of the JS built-ins left every HOST object admitted: a Blob, a stream, a
114
+ * CryptoKey, an X509Certificate, a WebAssembly module and a collection iterator each have internal state
115
+ * and no enumerable keys, so each encoded as `{}` after the named built-ins were already refused.
116
+ * Drawn this way it also refuses a class nobody has written yet.
117
+ *
118
+ * A revoked Proxy throws on the prototype read itself, so the read is guarded and the refusal is this
119
+ * module's own rather than whatever the Proxy raised. */
120
+ function _isPlainRecordByPrototype(value) {
121
+ var proto;
122
+ try { proto = intrinsic.getPrototypeOf(value); }
123
+ catch (e) { void e; return false; }
124
+ return proto === null || proto === _OBJECT_PROTOTYPE;
125
+ }
126
+ /** @internal The sentinel comes from `guard.intrinsic`, captured at load, rather than from a live
127
+ * `Object.prototype` read: comparing against the live global would let a replacement decide what counts
128
+ * as a plain record, and the whole point of this check is that nothing but a plain record is signed. */
129
+ var _OBJECT_PROTOTYPE = intrinsic.ObjectProto;
130
+
131
+ /** @internal Whether a value can be inspected at all. A revoked Proxy throws on every operation, so the
132
+ * first thing to touch it raises its TypeError rather than this module's typed refusal. */
133
+ function _isProbeable(value) {
134
+ try { intrinsic.getPrototypeOf(value); return true; }
135
+ catch (e) { void e; return false; }
136
+ }
137
+
138
+ /** @internal Every fragment is charged to a budget shared across the whole walk, and the budget is what
139
+ * bounds the work. The depth cap bounds the RECURSION and says nothing about the output, because a value
140
+ * that shares a subobject with itself is a graph rather than a tree and each reference is expanded
141
+ * independently: `v = [v, v]` repeated n times is n levels deep and 2 to the n leaves wide. MEASURED on
142
+ * the unbudgeted walk, 24 levels produced 67,108,861 bytes in 1.8 seconds and 25 ended the process with a
143
+ * fatal out-of-memory on a 2 GiB heap, from an input of 25 arrays (CWE-770). Charging each fragment
144
+ * refuses before the next doubling is accumulated, so the cost follows the budget rather than the shape
145
+ * of the input. The bound is the same `C.LIMITS.JSON_MAX_BYTES` a TUF document itself is read under: a
146
+ * canonical form larger than any document this module will parse cannot be the preimage of one. */
147
+ function _emit(out, text, budget) {
148
+ /** @internal The charge is in UTF-8 BYTES, which is the unit the budget is in and the unit the returned
149
+ * buffer is measured in. `text.length` counts UTF-16 code units, so a string outside the Basic Latin
150
+ * range undercharged by its encoded width: MEASURED, 1,048,574 CJK code points produced 3,145,724 bytes
151
+ * against a 1,048,576-byte budget, because each costs three bytes and was charged as one. */
152
+ budget.spend(_byteLength(text, "utf8"), "the canonical form");
153
+ _push(out, text);
154
+ }
155
+
156
+ function _canonical(value, out, depth, budget) {
157
+ if (depth > CANONICAL_MAX_DEPTH) {
158
+ throw _err("tuf/too-deep", "the document nests deeper than " + CANONICAL_MAX_DEPTH + " levels");
159
+ }
160
+ if (value === null) { _emit(out, "null", budget); return; }
161
+ if (value === true) { _emit(out, "true", budget); return; }
162
+ if (value === false) { _emit(out, "false", budget); return; }
163
+ if (typeof value === "string") { _canonicalString(value, out, budget); return; }
164
+ if (typeof value === "number") {
165
+ /** @internal A float has no canonical form here: the reference encoder refuses one outright, so
166
+ * a document a conforming signer produced contains none. A value past the exactly-representable
167
+ * range is refused for the same reason a narrowed one would be wrong: the digits written would
168
+ * not be the digits signed. */
169
+ if (!_isSafeInteger(value)) {
170
+ throw _err("tuf/bad-input", "canonical JSON encodes only exact integers; " +
171
+ guard.text.showValue(value) + " is not one");
172
+ }
173
+ _emit(out, _numToString(value, 10), budget);
174
+ return;
175
+ }
176
+ /** @internal A revoked Proxy throws on EVERY operation, `Array.isArray` among them, so the array test
177
+ * below would raise the Proxy's own TypeError before any check of this module's ran. Probing it once
178
+ * here, inside a guard, makes the refusal this module's own with its own code. */
179
+ if (typeof value === "object" && !_isProbeable(value)) {
180
+ throw _err("tuf/bad-input", "canonical JSON cannot read this value: it is a revoked Proxy, which " +
181
+ "throws on every operation, so there is nothing to encode");
182
+ }
183
+ if (_isArray(value)) {
184
+ _emit(out, "[", budget);
185
+ for (var i = 0; i < value.length; i++) {
186
+ if (i > 0) _emit(out, ",", budget);
187
+ _canonical(value[i], out, depth + 1, budget);
188
+ }
189
+ _emit(out, "]", budget);
190
+ return;
191
+ }
192
+ if (typeof value === "object") {
193
+ /** @internal Canonical JSON admits an object, an array, a string, an integer, a boolean and null.
194
+ * A built-in exotic is none of them, and encoding it as "every own enumerable key" silently made a
195
+ * Date and a Map into `{}` and a Buffer into an index map of its bytes. This output is the preimage
196
+ * a TUF signature covers, so a field encoding to `{}` is a field the signature does not cover while
197
+ * it is still in the caller's object: the signed form and the document the caller believes they
198
+ * signed would differ with nothing reporting it. The encoder already refuses a float, a function,
199
+ * undefined and a BigInt, so admitting these was the same rule applied to part of its range. */
200
+ if (!_isPlainRecordByPrototype(value)) {
201
+ throw _err("tuf/bad-input", "canonical JSON encodes a plain object, an array, a string, an " +
202
+ "integer, a boolean or null; " + guard.text.showValue(value) + " carries a prototype other than " +
203
+ "Object.prototype, so it is a class instance whose state is not in its own keys, and encoding it " +
204
+ "by those keys would sign a different document than the one it stands for");
205
+ }
206
+ var names = _sort(_keys(value), _byCodePoint);
207
+ _emit(out, "{", budget);
208
+ for (var k = 0; k < names.length; k++) {
209
+ if (k > 0) _emit(out, ",", budget);
210
+ _canonicalString(names[k], out, budget);
211
+ _emit(out, ":", budget);
212
+ _canonical(value[names[k]], out, depth + 1, budget);
213
+ }
214
+ _emit(out, "}", budget);
215
+ return;
216
+ }
217
+ throw _err("tuf/bad-input", "canonical JSON has no encoding for " + guard.text.showValue(typeof value));
218
+ }
219
+
220
+ /**
221
+ * @primitive pki.tuf.canonicalJson
222
+ * @signature pki.tuf.canonicalJson(value) -> Buffer
223
+ * @since 0.8.41
224
+ * @status stable
225
+ * @spec TUF
226
+ * @related pki.tuf.keyId, pki.tuf.verifySignatures
227
+ *
228
+ * The canonical JSON encoding of a value, as UTF-8 bytes. This is the preimage TUF signatures cover,
229
+ * so it is a wire format and not a formatting choice: object keys are sorted by code point, no
230
+ * whitespace is emitted, and inside a string only backslash and quote are escaped. A literal control
231
+ * character is emitted as itself, which is where `JSON.stringify` differs: it would write `\n` and the
232
+ * signature would be over other bytes.
233
+ *
234
+ * Only strings, exact integers, booleans, null, arrays and plain objects have an encoding. A float,
235
+ * a number outside the exactly-representable integers, `undefined` and a function are refused with
236
+ * `tuf/bad-input` rather than approximated, and a document nesting past `C.LIMITS.JSON_MAX_DEPTH` is
237
+ * `tuf/too-deep`.
238
+ *
239
+ * @example
240
+ * pki.tuf.canonicalJson({ b: 2, a: 1 }).toString("utf8"); // '{"a":1,"b":2}'
241
+ */
242
+ function canonicalJson(value) {
243
+ var out = [];
244
+ var budget = guard.limits.budget({ maxBytes: constants.LIMITS.JSON_MAX_BYTES });
245
+ try {
246
+ _canonical(value, out, 0, budget);
247
+ } catch (e) {
248
+ if (guard.limits.isBudgetExceeded(e)) {
249
+ throw _err("tuf/too-large", "the canonical form of this document exceeds " +
250
+ constants.LIMITS.JSON_MAX_BYTES + " bytes; a value that shares a subobject with itself expands " +
251
+ "once per reference, so a document of a few dozen fields can encode to gigabytes", e);
252
+ }
253
+ throw e;
254
+ }
255
+ return _bufferFrom(_join(out, ""), "utf8");
256
+ }
257
+
258
+ /**
259
+ * @primitive pki.tuf.keyId
260
+ * @signature pki.tuf.keyId(key) -> string
261
+ * @since 0.8.41
262
+ * @status stable
263
+ * @spec TUF
264
+ * @related pki.tuf.canonicalJson, pki.tuf.verifySignatures
265
+ *
266
+ * A key's identifier: the lowercase hex SHA-256 of the key's own canonical JSON form. The
267
+ * specification says a client must calculate each identifier to check it is correct for its key,
268
+ * which is what stops a document listing one key under another's identifier, so this is a
269
+ * verification input rather than a label.
270
+ *
271
+ * A key object carries `keytype`, `scheme` and `keyval`; a value missing any of them is `tuf/bad-key`.
272
+ *
273
+ * @example
274
+ * pki.tuf.keyId({ keytype: "ed25519", scheme: "ed25519",
275
+ * keyval: { public: "00".repeat(32) } }).length; // -> 64
276
+ */
277
+ function keyId(key) {
278
+ if (!key || typeof key !== "object" || _isArray(key)) {
279
+ throw _err("tuf/bad-key", "a TUF key is an object carrying keytype, scheme and keyval");
280
+ }
281
+ /** @internal The key is COPIED before it is validated, and the copy is what gets hashed. Validating the
282
+ * caller's object and then handing that same object to `canonicalJson` is two reads of every field, and
283
+ * an accessor answers each separately: a key could validate as `keytype: "ed25519"` and be hashed as
284
+ * `keytype: 42`, so the identifier returned would name a shape that never passed validation. The
285
+ * identifier is the identity a role's `keyids` list matches against, which is what makes that a
286
+ * question of which key is authorized rather than of tidiness.
287
+ *
288
+ * `snapshotDeep` refuses an accessor-backed member outright rather than reading it once, which is the
289
+ * stronger answer and the one `verifySignatures` already takes for the same records. */
290
+ var copied = guard.bytes.snapshotDeep(key, TufError, "tuf/bad-key", "a TUF key");
291
+ if (typeof copied.keytype !== "string" || typeof copied.scheme !== "string" ||
292
+ !copied.keyval || typeof copied.keyval !== "object") {
293
+ throw _err("tuf/bad-key", "a TUF key carries keytype, scheme and keyval; one of them is missing " +
294
+ "or is not of its stated type");
295
+ }
296
+ /** @internal Through the captured digest, which is what binds a key to the identifier a role names.
297
+ * Reached off the live hash prototype, a replacement decided that binding: a different Ed25519 key
298
+ * filed under an authorized key's identifier was accepted, and metadata it signed reported
299
+ * `verified: true` where the check otherwise throws `tuf/bad-key`. */
300
+ return _bufToString(guard.crypto.digest("sha256", canonicalJson(copied)), "hex");
301
+ }
302
+
303
+ /** @internal The major version this build implements, as the TEXT a conforming document carries. The
304
+ * comparison is made on that text rather than on a converted number, so there is no width a major
305
+ * version may not have: converted, a long one needed a digit cap, and the cap reported `1000000.0.0`
306
+ * as malformed when it is a well-formed version this build simply does not implement. A numeric
307
+ * identifier carries no leading zeros, so one spelling per value and a text comparison is exact. */
308
+ var SUPPORTED_SPEC_MAJOR = "1";
309
+
310
+ /** @internal Does an RFC 3339 date-time state UTC? `Z` and `+00:00` do: RFC 3339 sec. 4.3 says those two
311
+ * "imply that UTC is the preferred reference point for the specified time", which is the assertion the
312
+ * TUF specification sec. 4.2.3 makes when it says time is always in UTC. `-00:00` is reserved by that
313
+ * same clause for "the time in UTC is known, but the offset to local time is unknown", which it calls
314
+ * semantically different, so it names the right instant while declining to state UTC as the reference
315
+ * and is refused here. Every other offset states a different reference point. The string has already
316
+ * passed the RFC 3339 scanner, so this reads only the offset at its end. */
317
+ function _isUtcOffset(v) {
318
+ var n = v.length;
319
+ if (n === 0) return false;
320
+ var last = _charCodeAt(v, n - 1);
321
+ if (last === 0x5a || last === 0x7a) return true;
322
+ if (n < 6) return false;
323
+ if (_charCodeAt(v, n - 6) !== 0x2b) return false;
324
+ return _charCodeAt(v, n - 5) === 0x30 && _charCodeAt(v, n - 4) === 0x30 &&
325
+ _charCodeAt(v, n - 3) === 0x3a &&
326
+ _charCodeAt(v, n - 2) === 0x30 && last === 0x30;
327
+ }
328
+
329
+ /** @internal A semver NUMERIC IDENTIFIER starting at `i`: the index after it, or -1. "A numeric
330
+ * identifier MUST NOT include leading zeroes", so "0" is one and "01" is not. */
331
+ function _numericId(v, i, n) {
332
+ if (i >= n) return -1;
333
+ var first = _charCodeAt(v, i);
334
+ if (first < 0x30 || first > 0x39) return -1;
335
+ var j = i + 1;
336
+ while (j < n) { var c = _charCodeAt(v, j); if (c < 0x30 || c > 0x39) break; j++; }
337
+ if (first === 0x30 && j - i > 1) return -1;
338
+ return j;
339
+ }
340
+
341
+ /** @internal The dot-separated identifiers of a pre-release or build part: the index after the last
342
+ * one, or -1. Each is alphanumerics and hyphens and may not be empty. The parts differ in one rule:
343
+ * a pre-release identifier that is all digits "MUST NOT include leading zeroes", while a build
344
+ * identifier carries no such restriction, so `1.2.3+0010` is a version and `1.2.3-0010` is not. */
345
+ function _dotSeparatedIds(v, i, n, isPreRelease) {
346
+ for (;;) {
347
+ var start = i, allDigits = true;
348
+ while (i < n) {
349
+ var c = _charCodeAt(v, i);
350
+ var digit = c >= 0x30 && c <= 0x39;
351
+ if (!digit && !((c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a) || c === 0x2d)) break;
352
+ if (!digit) allDigits = false;
353
+ i++;
354
+ }
355
+ if (i === start) return -1;
356
+ if (isPreRelease && allDigits && _charCodeAt(v, start) === 0x30 && i - start > 1) return -1;
357
+ if (i < n && _charCodeAt(v, i) === 0x2e) { i++; continue; }
358
+ return i;
359
+ }
360
+ }
361
+
362
+ /** @internal The MAJOR component of a semantic version, or null where the string is not one. The WHOLE
363
+ * string is held to the grammar, because the specification sec. 4.3 states the field's format: "A
364
+ * string that contains the version number of the TUF specification. Its format follows the Semantic
365
+ * Versioning 2.0.0 (semver) specification". Reading the leading digits alone accepted a field that is
366
+ * not a version at all: "1.", "1.0", "1.foo", "1.0.0oops", "1.0.0.0" and a trailing space all passed
367
+ * as supported 1.x metadata. Scanned by character rather than split or matched, `lib/` taking no
368
+ * regular expressions.
369
+ *
370
+ * Shape: three numeric identifiers separated by dots, then an optional "-" pre-release and an
371
+ * optional "+" build, and nothing after either. */
372
+ function _specMajor(v) {
373
+ var n = v.length;
374
+ var end = _numericId(v, 0, n);
375
+ if (end < 0) return null;
376
+ var major = _strSlice(v, 0, end);
377
+ var i = end;
378
+ for (var part = 0; part < 2; part++) {
379
+ if (i >= n || _charCodeAt(v, i) !== 0x2e) return null;
380
+ i = _numericId(v, i + 1, n);
381
+ if (i < 0) return null;
382
+ }
383
+ if (i === n) return major;
384
+ if (_charCodeAt(v, i) === 0x2d) {
385
+ i = _dotSeparatedIds(v, i + 1, n, true);
386
+ if (i < 0) return null;
387
+ if (i === n) return major;
388
+ }
389
+ if (_charCodeAt(v, i) !== 0x2b) return null;
390
+ i = _dotSeparatedIds(v, i + 1, n, false);
391
+ return i === n ? major : null;
392
+ }
393
+
394
+ /** @internal The specification version decides whether this build's rules apply to the document at all:
395
+ * "Metadata is written according to version "spec_version" of the specification, and clients MUST verify
396
+ * that "spec_version" matches the expected version number", with adopters free to decide what counts as
397
+ * a match. This build implements 1.x, so the MAJOR version is the match and the minor and patch are not:
398
+ * a 1.x document a later minor version produced is read under rules that are a subset of its own, which
399
+ * is what semantic versioning promises, while a 2.x document is written to rules this build does not
400
+ * have. A document with no `spec_version` at all states no version to match, and TAP 6 makes the field
401
+ * mandatory, so it is refused rather than assumed to be this one.
402
+ *
403
+ * The clause binds metadata rather than root metadata, so it holds for every role, and it is enforced
404
+ * at this one door every document comes through. The verbs a client drives next answer under 1.x rules:
405
+ * a key identifier is a hash of the canonical form this version defines, and an expiry is read in the
406
+ * format this version states. A verdict from either about a document written to another major version
407
+ * is an answer about rules the document does not claim. The validated string is returned so the check
408
+ * and the reported value are one read.
409
+ *
410
+ * It runs as soon as the signed body is reachable, ahead of the field rules below it, because `_type`,
411
+ * `version` and `expires` are themselves rules of this version: reading them first decides a document
412
+ * under rules it may not be written to. What precedes it is the JSON read and the wrapper shape, since
413
+ * the document has to tokenize and the body has to exist before the field inside it can be read. A
414
+ * document carrying a fractional number is therefore `tuf/bad-json` whatever version it declares, the
415
+ * canonical form admitting no floating point at any version this build reads. */
416
+ function _assertSupportedMajor(spec, where, malformedCode) {
417
+ if (typeof spec !== "string") {
418
+ throw _err(malformedCode, where + " names no spec_version, which a client must match against the " +
419
+ "version it implements (TUF specification sec. 4.3, TAP 6)");
420
+ }
421
+ /** @internal A value that is not a semantic version is MALFORMED rather than a version this build
422
+ * does not implement, and the two are different answers: the first says the field is not a version,
423
+ * the second that the version is one this build cannot read. Only a well-formed version reaches the
424
+ * comparison. */
425
+ var major = _specMajor(spec);
426
+ if (major === null) {
427
+ throw _err(malformedCode, where + " states spec_version " + guard.text.showValue(spec) +
428
+ ", which is not a Semantic Versioning 2.0.0 value, the format the TUF specification sec. 4.3 " +
429
+ "states for the field");
430
+ }
431
+ if (major !== SUPPORTED_SPEC_MAJOR) {
432
+ throw _err("tuf/unsupported-spec-version", where + " declares spec_version " +
433
+ guard.text.showValue(spec) + ", and this build implements " + SUPPORTED_SPEC_MAJOR +
434
+ ".x: a document written to another major version states rules this build does not have");
435
+ }
436
+ return spec;
437
+ }
438
+
439
+ /** @internal The fields the two object-taking verbs read off a metadata object. None may be an
440
+ * accessor: see the refusal in `verifySignatures` for what reading one lets a caller steer. */
441
+ var _METADATA_FIELDS = ["signed", "signedBytes", "specVersion", "expires", "type", "version",
442
+ "signatures"];
443
+ /** @internal The fields a signature record carries, held to the same rule for the same reason. */
444
+ var _SIGNATURE_FIELDS = ["keyid", "sig"];
445
+
446
+ /** @internal The convenience copies a parsed object carries beside the signed body, as
447
+ * [outer field, the body member it copies]. Each is checked against the body rather than trusted,
448
+ * because the body is what the signature covers and a copy that disagrees describes a document the
449
+ * caller does not hold. Checking ONE of them was the defect: `specVersion` was cross-checked while
450
+ * `type` and `version` were not, and a caller reads `version` for a rollback comparison and `type` to
451
+ * choose which role a document belongs to, so both decide something. Absent is accepted, an object
452
+ * assembled by hand being free to carry only what it uses; present and contradicting is refused. */
453
+ var _BODY_COPIES = [["type", "_type"], ["specVersion", "spec_version"], ["version", "version"],
454
+ ["expires", "expires"]];
455
+
456
+ function _assertCopiesAgree(metadata, body, label) {
457
+ for (var ci = 0; ci < _BODY_COPIES.length; ci++) {
458
+ var outer = _BODY_COPIES[ci][0], inner = _BODY_COPIES[ci][1];
459
+ var stated = metadata[outer];
460
+ if (stated === undefined || stated === null) continue;
461
+ if (stated !== body[inner]) {
462
+ throw _err("tuf/bad-input", label + " states " + outer + " " + guard.text.showValue(stated) +
463
+ " while the signed body it carries states " + guard.text.showValue(body[inner]));
464
+ }
465
+ }
466
+ }
467
+
468
+ /** @internal The roles a root states a record for. `mirrors` is deliberately absent: the
469
+ * specification sec. 4.3 calls it OPTIONAL where it calls these four mandatory. */
470
+ var TOP_LEVEL_ROLES = ["root", "targets", "snapshot", "timestamp"];
471
+ /** @internal The same four as text for the refusal, joined through the capture at load so the list has
472
+ * one source. */
473
+ var TOP_LEVEL_ROLES_TEXT = intrinsic.join(TOP_LEVEL_ROLES, ", ");
474
+
475
+ /** @internal The parse's own use of the rule, over the raw field, read once. */
476
+ function _assertSpecVersion(signed) {
477
+ return _assertSupportedMajor(signed.spec_version, "the signed body", "tuf/bad-metadata");
478
+ }
479
+
480
+ /** @internal The rule carried to the two verbs that take a metadata OBJECT rather than bytes. Both are
481
+ * documented as taking what `parseMetadata` returns, and both are reachable with one a caller
482
+ * assembled, which is why `checkExpiry` already re-enforces the `expires` format rather than trusting
483
+ * it. The version match is the same situation: without it, each answered about a document declaring a
484
+ * version this build does not implement, `verifySignatures` reporting `verified` and `checkExpiry`
485
+ * returning true. Absence is the caller's object being incomplete rather than a document being
486
+ * malformed, so it is `tuf/bad-input` here and `tuf/bad-metadata` at the parse; an unsupported version
487
+ * is the same code on every route. */
488
+ function _assertMetadataSpecVersion(spec) {
489
+ return _assertSupportedMajor(spec, "opts.metadata", "tuf/bad-input");
490
+ }
491
+
492
+
493
+ /**
494
+ * @primitive pki.tuf.parseMetadata
495
+ * @signature pki.tuf.parseMetadata(input) -> { type, specVersion, version, expires, signed, signatures, signedBytes }
496
+ * @since 0.8.41
497
+ * @status stable
498
+ * @spec TUF
499
+ * @related pki.tuf.verifySignatures, pki.tuf.checkExpiry
500
+ *
501
+ * Read a TUF metadata document: the `signatures` wrapper and the `signed` body, with `signedBytes`
502
+ * the canonical form the signatures cover. A duplicate JSON member is refused before any field is
503
+ * read, because the canonical form would carry only one of them and the signature would then cover a
504
+ * document different from the one delivered.
505
+ *
506
+ * `_type`, a `spec_version`, a `version` that is a positive integer, and an `expires` that parses as a
507
+ * date-time are required; a document missing any is `tuf/bad-metadata`.
508
+ *
509
+ * `spec_version` must name a specification version this build implements, which is 1.x, and the major
510
+ * component is the match: a later 1.x minor is read, another major version is `tuf/unsupported-spec-
511
+ * version`. The rule is here rather than in each verb because a key identifier and an expiry are both
512
+ * read under the rules of this version.
513
+ *
514
+ * @example
515
+ * var m = pki.tuf.parseMetadata(Buffer.from(JSON.stringify({ signatures: [],
516
+ * signed: { _type: "root", spec_version: "1.0.31", version: 1, expires: "2030-01-01T00:00:00Z" } })));
517
+ * m.type; // -> "root"
518
+ */
519
+ function parseMetadata(input) {
520
+ var bytes = guard.bytes.source(input, TufError, "tuf/bad-input", "the TUF metadata");
521
+ var doc = guard.json.parse(bytes, _err, {
522
+ maxBytes: constants.LIMITS.JSON_MAX_BYTES, maxDepth: constants.LIMITS.JSON_MAX_DEPTH,
523
+ badJson: "tuf/bad-json", tooDeep: "tuf/too-deep", duplicateMember: "tuf/duplicate-member",
524
+ tooLarge: "tuf/too-large", badInput: "tuf/bad-input", label: "the TUF metadata",
525
+ /** @internal TUF's canonical JSON admits no floating point, and every number the format carries is a
526
+ * version, a threshold or a length. A fractional token is refused while it is still text: converted
527
+ * first, 1.0000000000000001 becomes 1 and the integer checks below see a conforming document. */
528
+ integersOnly: true,
529
+ });
530
+ if (!doc || typeof doc !== "object" || _isArray(doc)) {
531
+ throw _err("tuf/bad-metadata", "TUF metadata is a JSON object carrying signatures and signed");
532
+ }
533
+ if (!_isArray(doc.signatures)) {
534
+ throw _err("tuf/bad-metadata", "TUF metadata carries a signatures array");
535
+ }
536
+ var signed = doc.signed;
537
+ if (!signed || typeof signed !== "object" || _isArray(signed)) {
538
+ throw _err("tuf/bad-metadata", "TUF metadata carries a signed object");
539
+ }
540
+ var specVersion = _assertSpecVersion(signed);
541
+ if (typeof signed._type !== "string" || signed._type.length === 0) {
542
+ throw _err("tuf/bad-metadata", "the signed body names no _type");
543
+ }
544
+ if (typeof signed.version !== "number" || !_isSafeInteger(signed.version) || signed.version < 1) {
545
+ throw _err("tuf/bad-metadata", "the signed body's version is not a positive integer");
546
+ }
547
+ /** @internal The OFFSET has to denote UTC, which the shared scanner does not decide: RFC 3339 admits
548
+ * every numeric offset, so `2030-01-01T01:00:00+01:00` passes it. Section 4.2.3 says "Time is always
549
+ * in UTC", and such a value names a real instant while not being the form the specification requires,
550
+ * so a conforming client refuses the document and adopting it here would trust metadata that client
551
+ * would not. `Z` and a zero numeric offset denote the same instant and both pass. */
552
+ /** @internal The FORMAT decides, not `Date.parse`. The specification sec. 4.2.3 states it exactly:
553
+ * "Metadata date-time follows the ISO 8601 standard. The expected format of the combined date and time
554
+ * string is "YYYY-MM-DDTHH:MM:SSZ". Time is always in UTC". `Date.parse` is far looser. It ROLLS an
555
+ * impossible date forward rather than refusing it, so an expires of 2026-02-30 read as March 2 and the
556
+ * metadata was usable for two days it does not state; and it accepts a date with no time and a time
557
+ * with no zone, where the instant then depends on the reader's own timezone rather than on the
558
+ * document. The shared RFC 3339 scanner answers the format, calendar validity and leap years included.
559
+ * A zero numeric offset is accepted along with `Z`, both denoting the same instant: the clause states
560
+ * the expected form rather than forbidding an equivalent one. */
561
+ if (typeof signed.expires !== "string" || !rfc3339.isValid(signed.expires) || !_isUtcOffset(signed.expires)) {
562
+ throw _err("tuf/bad-metadata", "the signed body's expires is not an RFC 3339 date-time in the " +
563
+ "form the TUF specification sec. 4.2.3 states, \"YYYY-MM-DDTHH:MM:SSZ\"");
564
+ }
565
+ var sigs = [];
566
+ for (var i = 0; i < doc.signatures.length; i++) {
567
+ var s = doc.signatures[i];
568
+ if (!s || typeof s !== "object" || typeof s.keyid !== "string" || typeof s.sig !== "string") {
569
+ throw _err("tuf/bad-metadata", "signatures[" + i + "] carries a keyid and a sig, both strings");
570
+ }
571
+ _push(sigs, { keyid: s.keyid, sig: s.sig });
572
+ }
573
+ return guard.verdict.of({
574
+ type: signed._type,
575
+ specVersion: specVersion,
576
+ version: signed.version,
577
+ expires: signed.expires,
578
+ signed: signed,
579
+ signatures: sigs,
580
+ signedBytes: canonicalJson(signed),
581
+ });
582
+ }
583
+
584
+ function _isFiniteTime(ms) { return typeof ms === "number" && intrinsic.numberIsFinite(ms); }
585
+
586
+ /**
587
+ * @primitive pki.tuf.checkExpiry
588
+ * @signature pki.tuf.checkExpiry(metadata, now) -> true
589
+ * @since 0.8.41
590
+ * @status stable
591
+ * @spec TUF
592
+ * @related pki.tuf.parseMetadata, pki.tuf.updateRoot
593
+ *
594
+ * Assert that metadata has not expired at `now`, which is the freeze-attack check: a repository that
595
+ * stops publishing leaves a client holding metadata that stays valid forever unless its expiry is
596
+ * read. Returns `true`, or throws `tuf/expired` naming both instants. The instant is a caller value,
597
+ * never the system clock read inside the check, so a verdict is reproducible.
598
+ *
599
+ * The expiry read is `metadata.signed.expires`, the field the signature covers, and not the `expires`
600
+ * beside it: the signature check and this one have to be about one document, and an object carrying a
601
+ * lapsed expiry in the body and a future one in the copy otherwise verified and passed here at the
602
+ * same instant. Every copy the object states beside the body must agree with it, `type`, `version`,
603
+ * `expires` and `specVersion` alike, and so must a `signedBytes` it carries; any of them contradicting
604
+ * the body is `tuf/bad-input`. An object carrying no `signedBytes` is still read, a caller being free
605
+ * to ask only about an expiry.
606
+ *
607
+ * The body's `spec_version` must name a specification version this build implements, the expiry format
608
+ * being one this version states. A document `parseMetadata` returned always carries it; an object
609
+ * assembled by hand without it is `tuf/bad-input`, and one naming another major version is
610
+ * `tuf/unsupported-spec-version`.
611
+ *
612
+ * @example
613
+ * var meta = pki.tuf.parseMetadata(Buffer.from(JSON.stringify({ signatures: [],
614
+ * signed: { _type: "root", spec_version: "1.0.31", version: 1, expires: "2030-01-01T00:00:00Z" } })));
615
+ * pki.tuf.checkExpiry(meta, new Date("2027-01-01T00:00:00Z")); // -> true
616
+ */
617
+ function checkExpiry(metadata, now) {
618
+ if (!metadata || typeof metadata !== "object") {
619
+ throw _err("tuf/bad-input", "checkExpiry takes a parsed metadata object from pki.tuf.parseMetadata");
620
+ }
621
+ /** @internal ONE read of the signed BODY, and the expiry is taken from it rather than from the
622
+ * `expires` beside it. The signature covers the body, so those are two values: an object carrying a
623
+ * lapsed expiry in the body and a future one in the copy had `verifySignatures` answer about the
624
+ * signed document while this answered about the copy, and metadata correctly signed with an expiry
625
+ * in 2020 then both verified and passed this check at a 2026 instant. Expiry is the freeze-attack
626
+ * check, so the two verbs have to be about one document. A copy the caller also states must agree
627
+ * with the body, and `snapshotDeep` refuses an accessor-backed member outright, so no field can
628
+ * answer the comparison differently from the check. The fields of the object itself are held to the
629
+ * same rule, for the reason the refusal in `verifySignatures` gives, and so is the object itself
630
+ * being a plain record rather than a Proxy. */
631
+ guard.identifier.assertPlainRecord(metadata, _err, "tuf/bad-input", "opts.metadata");
632
+ guard.identifier.refuseAccessorFields(metadata, _METADATA_FIELDS, _err, "tuf/bad-input",
633
+ "opts.metadata");
634
+ var body = guard.bytes.snapshotDeep(metadata.signed, TufError, "tuf/bad-input", "metadata.signed");
635
+ if (!body || typeof body !== "object" || _isArray(body)) {
636
+ throw _err("tuf/bad-input", "checkExpiry takes a parsed metadata object from pki.tuf.parseMetadata");
637
+ }
638
+ var expires = body.expires;
639
+ var type = body._type;
640
+ _assertMetadataSpecVersion(body.spec_version);
641
+ if (typeof expires !== "string") {
642
+ throw _err("tuf/bad-input", "checkExpiry takes a parsed metadata object from pki.tuf.parseMetadata");
643
+ }
644
+ _assertCopiesAgree(metadata, body, "the metadata object");
645
+ /** @internal `signedBytes` is a copy of the body too, in bytes rather than in a field, and the two
646
+ * verbs have to agree about which objects they answer for at all: this read the body while the bytes
647
+ * beside it encoded another document, and returned true for it. This verb does not USE those bytes,
648
+ * which is why it went unnoticed, and that is the point, since an object the signature check refuses
649
+ * should not be one the expiry check answers about. Absent bytes stay acceptable, a caller being
650
+ * free to ask only about an expiry. */
651
+ var statedBytes = metadata.signedBytes;
652
+ if (statedBytes !== undefined && statedBytes !== null) {
653
+ /** @internal Bounded before the copy, as `verifySignatures` bounds the same field: these bytes must
654
+ * equal the canonical form of a body that came from a document no larger than the cap, so anything
655
+ * above it cannot match and copying it first paid an allocation the size of whatever arrived to
656
+ * reach that conclusion. */
657
+ var statedView = guard.bytes.isByteSource(statedBytes)
658
+ ? guard.bytes.source(statedBytes, _err, "tuf/bad-input", "metadata.signedBytes") : null;
659
+ if (statedView !== null) {
660
+ guard.limits.byteCap(statedView, constants.LIMITS.JSON_MAX_BYTES, _err, "tuf/too-large",
661
+ "metadata.signedBytes");
662
+ }
663
+ if (statedView === null ||
664
+ !guard.crypto.constantTimeEqual(canonicalJson(body),
665
+ guard.bytes.snapshot(statedView, _err, "tuf/bad-input", "metadata.signedBytes"))) {
666
+ throw _err("tuf/bad-input", "metadata.signedBytes is not the canonical form of metadata.signed, " +
667
+ "so the expiry asked about is not the document the object carries");
668
+ }
669
+ }
670
+ if (!guard.time.isDate(now)) throw _err("tuf/bad-input", "now must be a Date");
671
+ var atMs = guard.time.instantOf(now);
672
+ /** @internal The same format rule as the parse, the UTC offset included, because this verb is reachable
673
+ * with a metadata object a caller assembled rather than one `parseMetadata` produced: the looser
674
+ * reading is what rolled an impossible date forward into extra days of trust, and a rule enforced at
675
+ * one of two doors is a document this verb accepts and that one refuses. */
676
+ if (!rfc3339.isValid(expires) || !_isUtcOffset(expires)) {
677
+ throw _err("tuf/bad-metadata", "the metadata's expires is not an RFC 3339 date-time in the form the " +
678
+ "TUF specification sec. 4.2.3 states, \"YYYY-MM-DDTHH:MM:SSZ\"");
679
+ }
680
+ var expiresMs = _dateParse(expires);
681
+ if (!_isFiniteTime(expiresMs)) throw _err("tuf/bad-metadata", "the metadata's expires is not a date-time");
682
+ if (!(atMs < expiresMs)) {
683
+ throw _err("tuf/expired", "the " + type + " metadata expired at " + expires +
684
+ ", which is at or before the instant it was checked at");
685
+ }
686
+ return true;
687
+ }
688
+
689
+ /** @internal The SPKI an ed25519 TUF key encodes, with the raw length and the point validated. Shared
690
+ * by the verify and by adoption, so a root naming a key that is the wrong length or not a full-order
691
+ * point is refused in both places rather than in whichever one happens to read it. */
692
+ function _ed25519Spki(key) {
693
+ var raw = guard.encoding.hex(key.keyval.public, 64, _err, "tuf/bad-key", "an ed25519 public key");
694
+ if (raw.length !== 32) {
695
+ throw _err("tuf/bad-key", "an ed25519 public key is 32 bytes, got " + raw.length);
696
+ }
697
+ var spki = _bufferConcat([ED25519_SPKI_PREFIX, raw]);
698
+ edwardsPoint.validateSpki(spki, ED25519_CRV, TufError, "tuf/bad-key");
699
+ return spki;
700
+ }
701
+
702
+ /** @internal Everything that must hold for a key to verify anything, with no signature involved: the
703
+ * keytype and scheme are ones this build reads, the material imports, the declared type matches what
704
+ * imported, the curve and modulus match the scheme, and an ed25519 point is on the curve. An
705
+ * identifier is a hash of the key's canonical form and says nothing about any of that, so a role
706
+ * could name a key filed under its own identifier that verifies nothing. Only the ROOT role is
707
+ * verified during a rotation, so such a key in any other role was adopted and the client found out at
708
+ * first use. The same checks the verify makes before it looks at a signature, made at adoption.
709
+ * THE ONE IMPORT, AND THE ONE PLACE A SIGNATURE UNDER IT IS CHECKED. The identity a threshold counts
710
+ * and the key a signature is checked under come from this single import, so they cannot be two
711
+ * different keys and `keyval.public` is read once. Derived and verified separately, a key was imported
712
+ * twice per identifier, which doubled the asymmetric work a document can ask for before anything had
713
+ * authenticated it. `check` closes over the imported key and over the algorithm the declaration
714
+ * resolved, so no caller re-reads the record's own `keytype` to decide how to verify, and the
715
+ * low-order point gate on the ed25519 arm sits in the same function as the verify it protects rather
716
+ * than one call away from it. */
717
+ function _usableKey(key) {
718
+ var d = _declarationOf(key);
719
+ var imported = _checkedPublicKey(key, d);
720
+ if (d.nodeType === "ed25519") {
721
+ imported = guard.crypto.publicKey({ key: _ed25519Spki(key), format: "der", type: "spki" });
722
+ return { d: d, key: imported, check: function (sig, preimage) {
723
+ return guard.crypto.verify(null, preimage, imported, sig);
724
+ } };
725
+ }
726
+ if (d.nodeType === "ec") {
727
+ return { d: d, key: imported, check: function (sig, preimage) {
728
+ return guard.crypto.verify("sha256", preimage, { key: imported, dsaEncoding: "der" }, sig);
729
+ } };
730
+ }
731
+ /** @internal The rsa arm. The scheme the specification pairs with an rsa key is RSASSA-PSS over
732
+ * SHA-256, so that is the padding a signature is checked under; reading the padding from the document
733
+ * would let it name the one its forgery happens to satisfy. Every other keytype was refused by
734
+ * `_declarationOf`, whose table is the one list of what this build reads.
735
+ * THE SALT LENGTH IS READ FROM THE SIGNATURE, not pinned, and that is the one part of this the
736
+ * scheme name does not fix. `rsassa-pss-sha256` states the hash and the padding and says nothing
737
+ * about the salt, which is the signer's to choose, and the reference implementation signs with the
738
+ * longest salt the modulus allows. Pinning the digest length rejected those, so metadata that
739
+ * verifies everywhere else failed here and the root rotation it carried stopped. The salt is public
740
+ * and carried in the encoding, so recovering it concedes nothing: what a verifier must not take from
741
+ * the document is the hash or the padding, and both are still fixed here. Elsewhere in the toolkit
742
+ * the salt length IS pinned, because every other format declares it and the declaration is
743
+ * authenticated: `cms-verify` reads it from the RSASSA-PSS AlgorithmIdentifier, `path-validate` from
744
+ * the certificate, `jose` from the RFC 7518 row. TUF declares none, so there is nothing to read. */
745
+ return { d: d, key: imported, check: function (sig, preimage) {
746
+ return guard.crypto.verify("sha256", preimage, {
747
+ key: imported, padding: guard.crypto.padding.RSA_PKCS1_PSS,
748
+ saltLength: guard.crypto.saltLength.AUTO,
749
+ }, sig);
750
+ } };
751
+ }
752
+
753
+ /** @internal The keytype and scheme a key declares, and what the key material must then be. A key's
754
+ * identifier is the hash of the whole key object, so `keytype` and `scheme` are as authenticated as the
755
+ * material itself, and verification has to run under the algorithm they name rather than under whatever
756
+ * the material happens to support. Importing a PEM and verifying does the latter: an RSA public key
757
+ * filed under `keytype: "ecdsa"` imports fine and then satisfies an RSA PKCS#1 v1.5 signature, so a
758
+ * forger picks the algorithm by choosing the key they publish. The pairs are the ones the specification
759
+ * lists in section 4.2.2; `ecdsa-sha2-nistp256` is also accepted as a keytype, which is the spelling
760
+ * some published roots use for the same key.
761
+ *
762
+ * `nodeType` is what the imported key must report, and `curve` what an EC key's parameters must be: the
763
+ * scheme names its curve, so a P-384 key under `ecdsa-sha2-nistp256` is a key that does not match its
764
+ * own declaration. `minBits` carries the section 4.2.2 floor, "All RSA keys MUST be at least 2048 bits".
765
+ */
766
+ var KEY_DECLARATIONS = intrinsic.assign(intrinsic.create(null), {
767
+ "ed25519": { schemes: ["ed25519"], nodeType: "ed25519", curve: null, minBits: 0 },
768
+ "ecdsa": { schemes: ["ecdsa-sha2-nistp256"], nodeType: "ec", curve: "prime256v1", minBits: 0 },
769
+ "ecdsa-sha2-nistp256": { schemes: ["ecdsa-sha2-nistp256"], nodeType: "ec", curve: "prime256v1", minBits: 0 },
770
+ "rsa": { schemes: ["rsassa-pss-sha256"], nodeType: "rsa", curve: null, minBits: 2048 },
771
+ });
772
+
773
+ function _declarationOf(key) {
774
+ var d = KEY_DECLARATIONS[guard.text.keyOf(key.keytype)];
775
+ if (d === undefined) {
776
+ throw _err("tuf/unsupported-key", "this build reads ed25519, ecdsa and rsa keys; the metadata names " +
777
+ guard.text.showValue(key.keytype));
778
+ }
779
+ var scheme = guard.text.keyOf(key.scheme);
780
+ var ok = false;
781
+ for (var i = 0; i < d.schemes.length; i++) if (d.schemes[i] === scheme) ok = true;
782
+ if (!ok) {
783
+ throw _err("tuf/bad-key", "a " + key.keytype + " key pairs with the scheme " +
784
+ _join(d.schemes, " or ") + " (TUF sec. 4.2.2); this one declares " + guard.text.showValue(key.scheme));
785
+ }
786
+ return d;
787
+ }
788
+
789
+ /** @internal The resolved declaration, checked against the key material before anything verifies under
790
+ * it, and the imported key RETURNED so the caller verifies with the very object that was checked.
791
+ * Importing it again at the call site would read `keyval.public` a second time, and a caller-supplied
792
+ * accessor can answer differently on the second read, which would leave the checked key and the
793
+ * verifying key two
794
+ * different keys. An ed25519 key carries raw bytes rather than a PEM and its own arm holds the length
795
+ * and the point, so there is nothing to import for it. */
796
+ function _checkedPublicKey(key, d) {
797
+ if (d.nodeType === "ed25519") return null;
798
+ var imported = _publicFromPem(key.keyval.public, "a " + key.keytype + " public key");
799
+ if (imported.asymmetricKeyType !== d.nodeType) {
800
+ throw _err("tuf/bad-key", "the key declared as " + key.keytype + " holds a " +
801
+ guard.text.showValue(imported.asymmetricKeyType) + " public key, so the signature would be checked " +
802
+ "under an algorithm the metadata does not name");
803
+ }
804
+ var details = imported.asymmetricKeyDetails || {};
805
+ if (d.curve !== null && details.namedCurve !== d.curve) {
806
+ throw _err("tuf/bad-key", "the scheme " + key.scheme + " names the " + d.curve +
807
+ " curve; the key is on " + guard.text.showValue(details.namedCurve));
808
+ }
809
+ if (d.minBits > 0 && !(details.modulusLength >= d.minBits)) {
810
+ throw _err("tuf/bad-key", "an " + key.keytype + " key must be at least " + d.minBits +
811
+ " bits (TUF sec. 4.2.2); this one is " + guard.text.showValue(details.modulusLength));
812
+ }
813
+ return imported;
814
+ }
815
+
816
+ var ED25519_SPKI_PREFIX = _bufferFrom([0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00]);
817
+ var ED25519_CRV = 6;
818
+ var edwardsPoint = require("./edwards-point");
819
+
820
+ function _publicFromPem(pem, label) {
821
+ if (typeof pem !== "string" || pem.length === 0) {
822
+ throw _err("tuf/bad-key", label + " is stated as a non-empty PEM string");
823
+ }
824
+ try { return guard.crypto.publicKey({ key: pem, format: "pem" }); }
825
+ catch (e) { throw _err("tuf/bad-key", label + " did not parse", e); }
826
+ }
827
+
828
+ /**
829
+ * @primitive pki.tuf.verifySignatures
830
+ * @signature pki.tuf.verifySignatures(opts) -> Promise<{ verified, keyIds, threshold }>
831
+ * @since 0.8.41
832
+ * @status stable
833
+ * @spec TUF
834
+ * @defends signature-bypass (CWE-347)
835
+ * @related pki.tuf.parseMetadata, pki.tuf.updateRoot
836
+ *
837
+ * Verify a role's threshold over parsed metadata. Resolves `verified` and the distinct `keyIds` that
838
+ * counted: whether a threshold was met is a verdict about the metadata, so a shortfall resolves
839
+ * `false` rather than throwing. A KEY the build cannot read throws, the role naming a signer it cannot
840
+ * identify; a SIGNATURE it cannot read counts for nothing, that being one signer short rather than a
841
+ * malformed document.
842
+ *
843
+ * Three rules the specification states, each applied here:
844
+ *
845
+ * - A signature counts only if its `keyid` is one the role names. One from any other key contributes
846
+ * nothing, whatever it verifies over.
847
+ * - One verified signature per DISTINCT key identifier. A key listed twice, or signing twice, counts
848
+ * once, so a threshold cannot be met by repetition. Where an identifier carries more than one
849
+ * signature the FIRST is the one asked: a second cannot raise a count already capped at one, and
850
+ * asking it would let a document choose how many times the signed body is hashed.
851
+ * - Every key's identifier is recomputed from the key itself and must equal the identifier it is
852
+ * listed under. A key filed under another's identifier is `tuf/bad-key`, not a key that fails to
853
+ * verify: the document is malformed rather than unsigned.
854
+ *
855
+ * The document verified is derived from one read of `metadata.signed`, and `metadata.signedBytes` must
856
+ * equal that canonical form, so the bytes the signatures are checked over are the ones the object
857
+ * states. A `signedBytes` holding anything else is `tuf/bad-input`, which is what a parsed object whose
858
+ * Buffer was overwritten after the parse reports.
859
+ *
860
+ * That body's `spec_version` must name a specification version this build implements, a key identifier
861
+ * being a hash of the canonical form this version defines. Another major version is
862
+ * `tuf/unsupported-spec-version`. No `spec_version` at all, or a `metadata.specVersion` that contradicts
863
+ * the body's, is `tuf/bad-input`.
864
+ *
865
+ * @opts
866
+ * metadata: object, // from pki.tuf.parseMetadata
867
+ * keys: object, // the KEYID -> key map the root states
868
+ * role: object, // { keyids, threshold } for the role being checked
869
+ *
870
+ * @example
871
+ * var pair = await pki.key.generate("Ed25519");
872
+ * var raw = pki.asn1.decode(await pki.key.export(pair.publicKey)).children[1].content.subarray(1);
873
+ * var key = { keytype: "ed25519", scheme: "ed25519", keyval: { public: raw.toString("hex") } };
874
+ * var id = pki.tuf.keyId(key), keys = {}; keys[id] = key;
875
+ * var signed = { _type: "root", spec_version: "1.0.31", version: 1, expires: "2030-01-01T00:00:00Z",
876
+ * keys: keys, roles: { root: { keyids: [id], threshold: 1 },
877
+ * targets: { keyids: [id], threshold: 1 }, snapshot: { keyids: [id], threshold: 1 },
878
+ * timestamp: { keyids: [id], threshold: 1 } } };
879
+ * var sig = Buffer.from(await pki.webcrypto.subtle.sign({ name: "Ed25519" }, pair.privateKey,
880
+ * pki.tuf.canonicalJson(signed)));
881
+ * var rootBytes = Buffer.from(JSON.stringify({
882
+ * signatures: [{ keyid: id, sig: sig.toString("hex") }], signed: signed }));
883
+ * var v = await pki.tuf.verifySignatures({ metadata: pki.tuf.parseMetadata(rootBytes),
884
+ * keys: signed.keys, role: signed.roles.root });
885
+ * v.verified; // -> true
886
+ */
887
+ var _VERIFY_KEYS = intrinsic.assign(_create(null), { metadata: 1, keys: 1, role: 1 });
888
+ async function verifySignatures(opts) {
889
+ opts = opts || {};
890
+ guard.identifier.assertKnownKeys(opts, _VERIFY_KEYS, _err, "tuf/bad-input", "unknown verifySignatures option ");
891
+ /** @internal Every caller-owned input is taken ONCE, at the top, and everything below reads the copy.
892
+ * Validating the caller's object and then reading it again is two reads, and a member reached through an
893
+ * accessor can answer the two differently, so the value that was checked need not be the value that is
894
+ * used. The verification also awaits once per signature, which is the window in which a caller can
895
+ * change what it still holds. So the shape checks below run against the COPIES. */
896
+ var metadata = opts.metadata;
897
+ if (!metadata || typeof metadata !== "object") {
898
+ throw _err("tuf/bad-input", "opts.metadata is a parsed document from pki.tuf.parseMetadata");
899
+ }
900
+ /** @internal No field of the metadata object may be an accessor. Reading one is caller code running
901
+ * mid-verb, and with several fields read in sequence that was a lever rather than a nuisance: a
902
+ * getter on `signedBytes` set a supported version on the body for the duration of the copy while a
903
+ * getter on `specVersion` put the unsupported one back, and the verb reported `verified` about a
904
+ * document the object declared neither before nor after. Ordering the reads moved the lever to
905
+ * whichever field is read first, and the field read first is the subject, so there is no order that
906
+ * closes it. Refusing accessors does, and it costs a conforming caller nothing: a document
907
+ * `parseMetadata` returned carries plain values. This is the door an options bag already goes
908
+ * through, applied to the record. */
909
+ /** @internal A Proxy answers from a trap rather than from a descriptor, so a field check cannot see
910
+ * it: one supplying a supported body on the read and carrying an unsupported one otherwise had both
911
+ * verbs answer positively about a document the object never held. `assertPlainRecord` is the
912
+ * toolkit's own refusal for a caller record, and it covers an inherited Proxy and a built-in exotic
913
+ * along with it. */
914
+ guard.identifier.assertPlainRecord(metadata, _err, "tuf/bad-input", "opts.metadata");
915
+ guard.identifier.refuseAccessorFields(metadata, _METADATA_FIELDS, _err, "tuf/bad-input",
916
+ "opts.metadata");
917
+ /** @internal The BODY is copied first regardless, being the authority every check below reads. */
918
+ var body = guard.bytes.snapshotDeep(metadata.signed, TufError, "tuf/bad-input", "metadata.signed");
919
+ if (!body || typeof body !== "object" || _isArray(body)) {
920
+ throw _err("tuf/bad-input", "opts.metadata is a parsed document from pki.tuf.parseMetadata");
921
+ }
922
+ var rawSignedBytes = metadata.signedBytes;
923
+ if (!guard.bytes.isByteSource(rawSignedBytes)) {
924
+ throw _err("tuf/bad-input", "opts.metadata is a parsed document from pki.tuf.parseMetadata");
925
+ }
926
+ /** @internal Normalized to a byte view before it is copied. The test above admits what
927
+ * `guard.bytes.isByteSource` admits, which includes a DataView and an ArrayBuffer, and
928
+ * `guard.bytes.snapshot` takes neither, so canonical bytes held as the ArrayBuffer a fetch returns
929
+ * were refused for their container by the same verb that had just called them acceptable.
930
+ * `snapshotSource` views and copies in one call; the view shares the caller's store, so the snapshot
931
+ * is still the only copy, which is what the read-once reasoning below depends on.
932
+ * Bounded at the document cap before that copy. These bytes must equal the canonical form of a body
933
+ * that itself came from a document no larger than the cap, so anything above it cannot match and is
934
+ * refused rather than copied: measured, a 32 MiB value allocated 32 MiB before the comparison
935
+ * rejected it. */
936
+ var signedView = guard.bytes.source(rawSignedBytes, _err, "tuf/bad-input", "metadata.signedBytes");
937
+ guard.limits.byteCap(signedView, constants.LIMITS.JSON_MAX_BYTES, _err, "tuf/too-large",
938
+ "metadata.signedBytes");
939
+ var signedBytes = guard.bytes.snapshot(signedView, _err, "tuf/bad-input", "metadata.signedBytes");
940
+ /** @internal ONE read of the signed body, which both the version match and the bytes are taken from.
941
+ * `specVersion` beside the bytes is a SECOND value, filled at parse time, so it names the document
942
+ * that was parsed while `signedBytes` names the document that gets verified, and a parsed object is
943
+ * the caller's once it is returned: its Buffer can be overwritten. Measured, a 1.x document whose
944
+ * bytes were replaced with a validly signed 2.x one reported `verified` while the field still read
945
+ * 1.0.31. Reading the version back out of the bytes is not available, canonical JSON escaping only
946
+ * backslash and quote so that a key held as a PEM puts raw newlines inside a string and the signed
947
+ * form of any real root is not a document a strict JSON reader accepts. So the body is the source:
948
+ * the canonical form derived from it must equal the bytes the caller carries, and the version is read
949
+ * from the same copy. A stated `specVersion` that contradicts it is a caller describing a document it
950
+ * does not hold. This is the `version` swap A16 pins, answered for `spec_version` the same way.
951
+ *
952
+ * Read after both copies are taken, never before: an accessor on this field runs while the bytes and
953
+ * the body would still be the caller's, and the field is only a cross-check. */
954
+ _assertMetadataSpecVersion(body.spec_version);
955
+ if (!guard.crypto.constantTimeEqual(canonicalJson(body), signedBytes)) {
956
+ throw _err("tuf/bad-input", "metadata.signedBytes is not the canonical form of metadata.signed, so " +
957
+ "the document the signatures would be checked over is not the one the object states");
958
+ }
959
+ _assertCopiesAgree(metadata, body, "opts.metadata");
960
+ var keys = opts.keys;
961
+ if (!keys || typeof keys !== "object" || _isArray(keys)) {
962
+ throw _err("tuf/bad-input", "opts.keys is the KEYID to key map the root states");
963
+ }
964
+ /** @internal ONE read of the role, which carries the authorization: which key ids may sign, and how
965
+ * many must. It was read four times, for the presence check, the typeof, and then each field, and an
966
+ * accessor answers every read separately: a getter could present a strict role to the checks and a lax
967
+ * one to the field reads, combining one role's authorized keys with another's threshold. The verdict
968
+ * then reported a document as meeting a threshold no role had stated. */
969
+ var suppliedRole = opts.role;
970
+ if (!suppliedRole || typeof suppliedRole !== "object") {
971
+ throw _err("tuf/bad-input", "opts.role carries a keyids array and a threshold that is a positive integer");
972
+ }
973
+ var role = { keyids: suppliedRole.keyids, threshold: suppliedRole.threshold };
974
+ if (!_isArray(role.keyids) || typeof role.threshold !== "number" ||
975
+ !_isSafeInteger(role.threshold) || role.threshold < 1) {
976
+ throw _err("tuf/bad-input", "opts.role carries a keyids array and a threshold that is a positive integer");
977
+ }
978
+ role.keyids = guard.bytes.snapshotDeep(role.keyids, TufError, "tuf/bad-input", "opts.role.keyids");
979
+ /** @internal The keys MAP is a caller record whose members are read inside a loop that awaits, so it
980
+ * gets the door the other caller records here already have rather than a defensive second read. Held
981
+ * to being a plain object, which refuses a Proxy, and to carrying plain values at every identifier
982
+ * the role names, which refuses an accessor: either would otherwise answer each read separately, and
983
+ * the identifier filed under a key is checked against the copy the first read produced. An
984
+ * identifier the role repeats is resolved once now, so a getter answering correctly first and
985
+ * incorrectly afterwards had its later answer go unread, which is a check on a value nothing used.
986
+ * Closing the door removes the question instead of restoring the read. */
987
+ guard.identifier.assertPlainRecord(keys, _err, "tuf/bad-input", "opts.keys");
988
+ guard.identifier.refuseAccessorFields(keys, role.keyids, _err, "tuf/bad-input", "opts.keys");
989
+ /** @internal The role's identifiers are resolved to keys FIRST, and each key's identifier is
990
+ * recomputed, so the whole key set a role names is held to being correctly filed before any
991
+ * signature is checked. A document that mislabels a key is refused whether or not its signatures
992
+ * would have verified. */
993
+ var authorized = _create(null);
994
+ for (var i = 0; i < role.keyids.length; i++) {
995
+ var id = role.keyids[i];
996
+ if (typeof id !== "string" || id.length !== KEY_ID_HEX_LEN) {
997
+ throw _err("tuf/bad-metadata", "role.keyids[" + i + "] is not a 64-character key identifier");
998
+ }
999
+ if (!_hasOwn(keys, id)) {
1000
+ throw _err("tuf/bad-metadata", "the role names key " + id + ", which the key map does not carry");
1001
+ }
1002
+ /** @internal An identifier the role repeats resolves to the same key, and the resolution below
1003
+ * deep-copies that key and hashes its canonical form. Done per OCCURRENCE, a role could ask for
1004
+ * that work as many times as the size cap admits identifiers: measured, 7500 repetitions of one
1005
+ * identifier beside a 500 KB key took 14.9 seconds inside a one-megabyte document, synchronously,
1006
+ * before the first await and before anything had authenticated a byte of it. The answer is already
1007
+ * filed, so the repeat is a lookup. This is the same per-occurrence shape the signature walk below
1008
+ * and the role walk in `updateRoot` both carry, and the third one found. */
1009
+ if (_hasOwn(authorized, id)) continue;
1010
+ /** @internal The key is COPIED before its identifier is computed, and the COPY is what verifies. The
1011
+ * signature loop below awaits a verification per signature while this map is held, so a caller that
1012
+ * replaces a later key's `keyval.public` in that window would have the identifier checked against one
1013
+ * key and the signature verified under another: a threshold could then be met by material nobody
1014
+ * authorized, the identifier naming one key while the bytes are another's. Deriving the identifier
1015
+ * from the copy makes the two one read of one value. */
1016
+ var key = guard.bytes.snapshotDeep(keys[id], TufError, "tuf/bad-key", "the key filed under " + id);
1017
+ var computed = keyId(key);
1018
+ if (computed !== id) {
1019
+ throw _err("tuf/bad-key", "the key filed under " + id + " has identifier " + computed +
1020
+ "; a key must be listed under its own");
1021
+ }
1022
+ authorized[id] = key;
1023
+ }
1024
+ /** @internal The signature records, one read each. The loop below awaits a verification per signature and
1025
+ * re-read them on every pass, so two signatures over DIFFERENT documents could each verify against the
1026
+ * bytes present on their own turn and together meet a threshold no single document ever met. The signed
1027
+ * bytes were copied at the top for the same reason. */
1028
+ /** @internal Filed by key identifier, because an identifier names at most one signature. The
1029
+ * reference implementation the format points at builds the same map and refuses a document
1030
+ * carrying two entries for one identifier ("Multiple signatures found for keyid"), then verifies by
1031
+ * walking the ROLE's keys and looking up the one signature each names. Reading the document's list
1032
+ * instead let it decide how many verifications ran: distinct signatures for one authorized key
1033
+ * share no memo, and each costs a hash of the whole signed body, so a 500 KB body under 2400
1034
+ * signatures made with a key nobody authorized bought 2400 checks and about a second, synchronously,
1035
+ * inside the one-megabyte size cap and before anything had authenticated a byte. Walking the keys
1036
+ * the ROLE names leaves the verifications at the number of keys that role lists, so the signature
1037
+ * count is no longer a multiplier on the body: the work follows the document's own size rather than
1038
+ * the product of its body and its signature list. The role is the caller's argument, and in a root
1039
+ * rotation a candidate is also verified against the root role it states itself, which is the check
1040
+ * the specification requires of a new root, so that one call's count is the candidate's to state. */
1041
+ var sigOf = _create(null);
1042
+ var rawSigs = metadata.signatures;
1043
+ if (!_isArray(rawSigs)) throw _err("tuf/bad-input", "opts.metadata carries a signatures array");
1044
+ for (var r = 0; r < rawSigs.length; r++) {
1045
+ var rec = rawSigs[r];
1046
+ if (!rec || typeof rec !== "object") throw _err("tuf/bad-metadata", "signatures[" + r + "] is not an object");
1047
+ /** @internal Each record is a plain object with plain fields, held to it before either field is
1048
+ * read. These are copied into the list above so no field is read twice, and reading one at all is
1049
+ * caller code running inside the loop: a getter on `sig` fired while the body was still the
1050
+ * caller's and could change the document mid-verification. The copies make the verdict about one
1051
+ * document, and this makes the reads themselves inert. */
1052
+ guard.identifier.assertPlainRecord(rec, _err, "tuf/bad-metadata", "signatures[" + r + "]");
1053
+ guard.identifier.refuseAccessorFields(rec, _SIGNATURE_FIELDS, _err, "tuf/bad-metadata",
1054
+ "signatures[" + r + "]");
1055
+ var recKeyid = rec.keyid, recSig = rec.sig;
1056
+ /** @internal A keyid that is not a string names no key in the map, so such a record can never be
1057
+ * counted and never costs a verification; it is filed under nothing rather than refused. */
1058
+ if (typeof recKeyid !== "string") continue;
1059
+ /** @internal The FIRST entry an identifier carries is the one asked, and a later entry for that
1060
+ * same identifier is passed over. The two authorities differ on a repeated identifier and this
1061
+ * satisfies both. The specification admits one: "each SIGNATURE which is counted towards the
1062
+ * THRESHOLD MUST have a unique KEYID. Even if a KEYID is listed more than once ... a client MUST
1063
+ * NOT count more than one verified SIGNATURE from that KEYID", which caps the COUNT and leaves
1064
+ * the document readable, so refusing it would be stricter than the document this parser cites.
1065
+ * The reference implementation refuses it outright ("Multiple signatures found for keyid"). A
1066
+ * second entry cannot raise the count the specification caps at one, so the only thing asking it
1067
+ * could do is succeed where the first failed, which takes a document that lists a wrong signature
1068
+ * for a key ahead of a right one. Passing it over keeps the verdict the specification requires and
1069
+ * leaves the number of verifications at the number of keys the ROLE names. */
1070
+ if (_hasOwn(sigOf, recKeyid)) continue;
1071
+ sigOf[recKeyid] = recSig;
1072
+ }
1073
+ /** @internal Counted by key MATERIAL, not by identifier. A threshold is a number of KEYS, and an
1074
+ * identifier is a hash of the whole key object, so one public key can hold two identifiers and be
1075
+ * counted twice: a role naming both with a threshold of two was met by one key, the same signature
1076
+ * bytes verifying under each entry. `keyIds` still reports the identifiers that counted, which is
1077
+ * what names the signers; what the threshold is compared against is how many distinct keys they
1078
+ * are. */
1079
+ var countedMaterial = _create(null);
1080
+ var keyIds = [];
1081
+ /** @internal The keys the ROLE names, each visited once, which is what bounds the work: an identifier
1082
+ * the role repeats was filed once above, and a key the role names and the document signed for is the
1083
+ * only thing that reaches a verification. The signature list decides WHICH signature each key
1084
+ * offers, never how many are asked. */
1085
+ var ids = _keys(authorized);
1086
+ for (var s = 0; s < ids.length; s++) {
1087
+ var signerId = ids[s];
1088
+ if (!_hasOwn(sigOf, signerId)) continue;
1089
+ var use = _usableKey(authorized[signerId]);
1090
+ var materialId = _materialIdOf(use.key);
1091
+ /** @internal Counted material is skipped, since a second identifier over the same key cannot raise
1092
+ * the count. Filed on SUCCESS alone: two identifiers can name one key, and if the first one's
1093
+ * signature does not verify the second's still has to be asked, the key having signed once under
1094
+ * whichever identifier the signer used. */
1095
+ if (_hasOwn(countedMaterial, materialId)) continue;
1096
+ /** @internal A signature this build cannot read is a verdict about the metadata rather than a
1097
+ * fault: the record counts for nothing and the walk goes on, which is what it did when the decode
1098
+ * sat inside the verify and threw. */
1099
+ var sigBytes = null;
1100
+ try {
1101
+ sigBytes = guard.encoding.hex(sigOf[signerId], constants.LIMITS.JSON_MAX_BYTES, _err,
1102
+ "tuf/bad-signature", "a signature");
1103
+ } catch (e) {
1104
+ if (!(e instanceof TufError && e.code === "tuf/bad-signature")) throw e;
1105
+ }
1106
+ if (sigBytes === null) continue;
1107
+ var ok = await use.check(sigBytes, signedBytes);
1108
+ if (ok === true) {
1109
+ countedMaterial[materialId] = 1;
1110
+ _push(keyIds, signerId);
1111
+ }
1112
+ }
1113
+ return guard.verdict.of({ verified: keyIds.length >= role.threshold, keyIds: keyIds,
1114
+ threshold: role.threshold });
1115
+ }
1116
+
1117
+ /** @internal The identity of the public KEY, independent of how the metadata spells it. A threshold
1118
+ * counts keys, and a key identifier is a hash of the whole key object, so one public key can hold
1119
+ * many identifiers: a member the hash covers and the cryptography ignores (a `comment`), a PEM with
1120
+ * CRLF line endings or a space before each newline, an EC point written compressed rather than
1121
+ * uncompressed, or `ecdsa-sha2-nistp256` where another entry says `ecdsa`. Each yields a different
1122
+ * identifier for the same key, and a role naming two of them with a threshold of two was met by ONE
1123
+ * key, the same signature bytes verifying under each entry.
1124
+ *
1125
+ * So the fingerprint is taken from the IMPORTED key's JWK, which is the mathematical form: the
1126
+ * coordinates, fixed-width. MEASURED, that is what collapses the four spellings above, where the
1127
+ * exported SPKI does not: Node preserves a compressed point on export, 59 bytes against 91, while the
1128
+ * JWK coordinates of the two are identical. The scheme is deliberately not part of it, one key listed
1129
+ * under two schemes still being one key. */
1130
+ function _materialIdOf(imported) {
1131
+ /** @internal The export goes through the captured operation. Reached off the key's own prototype, a
1132
+ * replacement returning one fixed value gave two distinct authorized keys the same identifier, and a
1133
+ * two-key threshold then counted one signature. */
1134
+ return _bufToString(guard.crypto.digest("sha256",
1135
+ canonicalJson(guard.crypto.exportKey(imported, { format: "jwk" }))), "hex");
1136
+ }
1137
+
1138
+ /** @internal A root document, parsed and held to being a root that states who may sign it. */
1139
+ function _asRoot(input, label) {
1140
+ var meta = parseMetadata(input);
1141
+ if (meta.type !== "root") {
1142
+ throw _err("tuf/bad-metadata", label + " has _type " + guard.text.showValue(meta.type) + ", not root");
1143
+ }
1144
+ var signed = meta.signed;
1145
+ if (!signed.keys || typeof signed.keys !== "object" || _isArray(signed.keys)) {
1146
+ throw _err("tuf/bad-metadata", label + " carries no keys map");
1147
+ }
1148
+ if (!signed.roles || typeof signed.roles !== "object" || _isArray(signed.roles)) {
1149
+ throw _err("tuf/bad-metadata", label + " carries no roles map");
1150
+ }
1151
+ /** @internal Every top-level role, not only the one that signs the root. The specification sec. 4.3
1152
+ * states it exactly: "A role for each of "root", "snapshot", "targets", and "timestamp" MUST be
1153
+ * specified in the roles object", with "The role of "mirror" is OPTIONAL". A root carrying only its
1154
+ * own role was adopted, and what that leaves is a trust anchor that cannot authenticate anything
1155
+ * else the repository publishes: no keyids and no threshold to check a targets, snapshot or
1156
+ * timestamp document against. Each record is held to carrying both, since a role that names neither
1157
+ * is present without being usable. */
1158
+ /** @internal One verdict per KEYID for the whole walk, not per occurrence. Each occurrence
1159
+ * canonicalized and hashed the whole key twice, imported it, and derived its material identity, and
1160
+ * all of that runs on UNAUTHENTICATED metadata before any candidate signature is checked: measured,
1161
+ * a 184 KB root carrying a 50 KB key comment and repeating one keyid 500 times per role spent 3.2
1162
+ * seconds inside this walk before answering `tuf/root-unsigned`, with the document well under the
1163
+ * size cap. The work is a pure function of the key filed under the identifier, and the body is
1164
+ * already a snapshot nothing can change under the walk, so the second occurrence of an identifier
1165
+ * reuses the first one's answer. A FAILING identifier throws, so only verdicts that passed are
1166
+ * held. */
1167
+ var keyVerdict = _create(null);
1168
+ for (var r = 0; r < TOP_LEVEL_ROLES.length; r++) {
1169
+ var roleName = TOP_LEVEL_ROLES[r];
1170
+ var record = signed.roles[roleName];
1171
+ if (!record || typeof record !== "object" || _isArray(record)) {
1172
+ throw _err("tuf/bad-metadata", label + " names no " + roleName + " role, and a root states a " +
1173
+ "role for each of " + TOP_LEVEL_ROLES_TEXT + " (TUF specification sec. 4.3)");
1174
+ }
1175
+ /** @internal A record can be present and still name no signer this root carries, which is the same
1176
+ * gap wearing a well-formed shape. Two of these checks are the specification's own words and two
1177
+ * are this build refusing an unusable record with the specification silent, which the messages
1178
+ * distinguish. A KEYID is "a hexdigest of the SHA-256 hash of the canonical form of the key", so
1179
+ * one that is not a string is not a KEYID; a threshold is "A positive integer number of keys
1180
+ * (>=1)". Nothing states that the keyids must appear in this root's own `keys` map or that the
1181
+ * threshold must be reachable, and a role failing either can never be satisfied: it names keys
1182
+ * the root does not carry, or asks for more signatures than it names keys. */
1183
+ if (!_isArray(record.keyids) || record.keyids.length === 0) {
1184
+ throw _err("tuf/bad-metadata", label + "'s " + roleName + " role names no keyids, so it does " +
1185
+ "not state who may sign for it");
1186
+ }
1187
+ /** @internal The threshold is compared against the DISTINCT key ids, not the list length. A
1188
+ * verdict counts each key id once, so a role naming one key twice for a threshold of two reads as
1189
+ * reachable and is not: one signature is all that list can ever produce. */
1190
+ var distinct = _create(null);
1191
+ for (var ki = 0; ki < record.keyids.length; ki++) {
1192
+ var keyid = record.keyids[ki];
1193
+ if (typeof keyid !== "string" || keyid.length === 0) {
1194
+ throw _err("tuf/bad-metadata", label + "'s " + roleName + " role names a keyid that is not a " +
1195
+ "string, where a KEYID is the hexdigest of a hash (TUF specification sec. 4.3)");
1196
+ }
1197
+ if (_hasOwn(keyVerdict, keyid)) { distinct[keyVerdict[keyid]] = 1; continue; }
1198
+ if (!_hasOwn(signed.keys, keyid)) {
1199
+ throw _err("tuf/bad-metadata", label + "'s " + roleName + " role names keyid " +
1200
+ guard.text.showValue(keyid) + ", which this root's keys map does not carry, so the role " +
1201
+ "states a signer the root cannot identify");
1202
+ }
1203
+ /** @internal EXISTENCE IS NOT IDENTITY. A role can name an identifier the keys map carries while
1204
+ * the key filed there hashes to a different one, which is the "key listed under an identifier
1205
+ * that is not its own" `verifySignatures` refuses. Only the ROOT role is verified during a
1206
+ * rotation, so a mis-filed key in any other role was adopted and failed later, where the client
1207
+ * had already trusted the root. Recomputed here, at adoption, for every role. The code is the
1208
+ * one that condition already has, so a caller branching on it sees one answer wherever the
1209
+ * mis-filing is found. */
1210
+ if (keyId(signed.keys[keyid]) !== keyid) {
1211
+ throw _err("tuf/bad-key", label + "'s " + roleName + " role names keyid " +
1212
+ guard.text.showValue(keyid) + ", and the key filed under it derives a different identifier, " +
1213
+ "so the role names a key the root does not carry");
1214
+ }
1215
+ /** @internal Keyed on the MATERIAL, so two identifiers for one public key count once. A role
1216
+ * naming a key under two identifiers for a threshold of two states two signers and has one, and
1217
+ * the verdict counts the same way. The identity comes from the key `_usableKey` imported, so
1218
+ * holding the key to being usable and deriving what it is cost one import between them rather
1219
+ * than one each. */
1220
+ var mid = _materialIdOf(_usableKey(signed.keys[keyid]).key);
1221
+ keyVerdict[keyid] = mid;
1222
+ distinct[mid] = 1;
1223
+ }
1224
+ var distinctCount = intrinsic.keys(distinct).length;
1225
+ if (typeof record.threshold !== "number" || !_isSafeInteger(record.threshold) ||
1226
+ record.threshold < 1) {
1227
+ throw _err("tuf/bad-metadata", label + "'s " + roleName + " role states no threshold that is a " +
1228
+ "positive integer, so it does not state how many signatures it takes");
1229
+ }
1230
+ if (record.threshold > distinctCount) {
1231
+ throw _err("tuf/bad-metadata", label + "'s " + roleName + " role asks for " + record.threshold +
1232
+ " signatures and names " + distinctCount + " distinct key(s), so the role can never be met");
1233
+ }
1234
+ }
1235
+ return meta;
1236
+ }
1237
+
1238
+ /**
1239
+ * @primitive pki.tuf.updateRoot
1240
+ * @signature pki.tuf.updateRoot(opts) -> Promise<{ root, version, updated, walked }>
1241
+ * @since 0.8.41
1242
+ * @status stable
1243
+ * @spec TUF
1244
+ * @defends signature-bypass (CWE-347)
1245
+ * @related pki.tuf.verifySignatures, pki.tuf.checkExpiry
1246
+ *
1247
+ * Walk the root chain from a trusted root to the latest candidate, applying the two rules that make a
1248
+ * key rotation safe. Each new root must be signed by a threshold of the keys the PREVIOUS root names
1249
+ * and a threshold of the keys it names ITSELF: the first says the rotation was authorized by whoever
1250
+ * held the old keys, the second that the new keys can actually sign. A root meeting only one is
1251
+ * `tuf/root-unsigned`.
1252
+ *
1253
+ * Its version must be exactly one more than the trusted one. A candidate that skips a version is
1254
+ * `tuf/bad-root-version`, so no intermediate root can be passed over, and one at or below the trusted
1255
+ * version is refused by the same rule, which is the rollback check.
1256
+ *
1257
+ * The trusted root is itself verified against its own role before any candidate is read, because a chain
1258
+ * anchored in something unchecked proves nothing.
1259
+ *
1260
+ * Expiry is judged once, at the end, on the root the chain finishes on, at the `now` the caller supplies.
1261
+ * That is where the specification puts the freeze-attack check, and it is what lets a client whose pinned
1262
+ * root has lapsed catch up through the successors it is handed instead of needing its trust anchor
1263
+ * replaced by some other means. An intermediate root's own expiry is not checked, and with no candidates
1264
+ * the root the chain finishes on is the trusted root, so a lapsed root with nothing to move to is
1265
+ * `tuf/expired`.
1266
+ *
1267
+ * `candidates` are the intermediate roots in any order; `walked` reports the versions adopted, and
1268
+ * `updated` whether any candidate was.
1269
+ *
1270
+ * @opts
1271
+ * trustedRoot: BufferSource, // the root the caller already trusts
1272
+ * candidates: Array, // the later root documents, each a BufferSource
1273
+ * now: Date, // the instant expiry is judged at
1274
+ *
1275
+ * @example
1276
+ * var pair = await pki.key.generate("Ed25519");
1277
+ * var raw = pki.asn1.decode(await pki.key.export(pair.publicKey)).children[1].content.subarray(1);
1278
+ * var key = { keytype: "ed25519", scheme: "ed25519", keyval: { public: raw.toString("hex") } };
1279
+ * var id = pki.tuf.keyId(key), keys = {}; keys[id] = key;
1280
+ * // `spec_version` is mandatory on a root this verb adopts, and its major version has to be one this
1281
+ * // build implements: another major version states rules these checks do not have, and naming none
1282
+ * // states nothing to match against.
1283
+ * var signed = { _type: "root", spec_version: "1.0.31", version: 1, expires: "2030-01-01T00:00:00Z",
1284
+ * keys: keys, roles: { root: { keyids: [id], threshold: 1 },
1285
+ * targets: { keyids: [id], threshold: 1 }, snapshot: { keyids: [id], threshold: 1 },
1286
+ * timestamp: { keyids: [id], threshold: 1 } } };
1287
+ * var sig = Buffer.from(await pki.webcrypto.subtle.sign({ name: "Ed25519" }, pair.privateKey,
1288
+ * pki.tuf.canonicalJson(signed)));
1289
+ * var rootBytes = Buffer.from(JSON.stringify({
1290
+ * signatures: [{ keyid: id, sig: sig.toString("hex") }], signed: signed }));
1291
+ * var r = await pki.tuf.updateRoot({ trustedRoot: rootBytes, candidates: [],
1292
+ * now: new Date("2027-01-01T00:00:00Z") });
1293
+ * r.updated; // -> false
1294
+ */
1295
+ var _UPDATE_KEYS = intrinsic.assign(_create(null), { trustedRoot: 1, candidates: 1, now: 1 });
1296
+ async function updateRoot(opts) {
1297
+ opts = opts || {};
1298
+ guard.identifier.assertKnownKeys(opts, _UPDATE_KEYS, _err, "tuf/bad-input", "unknown updateRoot option ");
1299
+ /** @internal The instant is CAPTURED here, at the call, into a Date this function owns. A Date is
1300
+ * mutable and the caller keeps theirs, and the freeze-attack check runs at the END, after a signature
1301
+ * verification has been awaited for every root in the chain: read from the caller's object at that
1302
+ * point, a root that had expired by the instant supplied would pass if that Date were moved back while
1303
+ * the chain was walked. Expiry is judged at the instant validation began at. */
1304
+ var suppliedNow = opts.now;
1305
+ if (!guard.time.isDate(suppliedNow)) throw _err("tuf/bad-input", "opts.now must be a Date");
1306
+ var now = guard.time.toDate(guard.time.instantOf(suppliedNow));
1307
+ /** @internal One read: `opts.candidates === undefined ? [] : opts.candidates` reads it twice, and a
1308
+ * member reached through an accessor can answer the presence test and the value differently, so the
1309
+ * array whose length is checked need not be the array that is walked. */
1310
+ var suppliedCandidates = opts.candidates;
1311
+ var candidates = suppliedCandidates === undefined ? [] : suppliedCandidates;
1312
+ if (!_isArray(candidates)) throw _err("tuf/bad-input", "opts.candidates must be an array of root documents");
1313
+ var candidateCount = candidates.length;
1314
+ if (candidateCount > constants.LIMITS.TUF_MAX_ROOT_CHAIN) {
1315
+ throw _err("tuf/bad-input", "a root chain of " + candidateCount + " exceeds the " +
1316
+ constants.LIMITS.TUF_MAX_ROOT_CHAIN + "-document cap");
1317
+ }
1318
+ var trusted = _asRoot(opts.trustedRoot, "the trusted root");
1319
+ /** @internal Every candidate is read and parsed HERE, before the first await, and the walk below reads
1320
+ * only what this produced. Indexing after the await read the caller's array as it stood then, so a call
1321
+ * made with an empty list could be handed a whole chain while the trusted root was being checked, and
1322
+ * the cap above would have been applied to the empty one. The length is bound for the same reason. */
1323
+ /** @internal The trusted root is verified against its OWN role before it is used to judge
1324
+ * anything. A caller may have been handed it by any means, and a chain anchored in a document
1325
+ * nobody checked carries the whole chain's weight on nothing. */
1326
+ /** @internal Candidates are indexed by version so the walk asks for exactly N+1 at each step. A
1327
+ * list in any order therefore walks in version order, and a gap stops the walk rather than
1328
+ * letting a later root be adopted without the one before it. */
1329
+ var byVersion = _create(null);
1330
+ for (var i = 0; i < candidateCount; i++) {
1331
+ var cand = _asRoot(candidates[i], "candidates[" + i + "]");
1332
+ if (_hasOwn(byVersion, cand.version)) {
1333
+ throw _err("tuf/bad-root-version", "two candidate roots state version " + cand.version);
1334
+ }
1335
+ byVersion[cand.version] = cand;
1336
+ }
1337
+ var self = await verifySignatures({ metadata: trusted, keys: trusted.signed.keys, role: trusted.signed.roles.root });
1338
+ if (self.verified !== true) {
1339
+ throw _err("tuf/root-unsigned", "the trusted root does not meet its own root-role threshold of " +
1340
+ self.threshold + "; " + self.keyIds.length + " of its own keys signed it");
1341
+ }
1342
+ var walked = [];
1343
+ var current = trusted;
1344
+ for (;;) {
1345
+ var next = byVersion[current.version + 1];
1346
+ if (next === undefined) break;
1347
+ var byOld = await verifySignatures({ metadata: next, keys: current.signed.keys, role: current.signed.roles.root });
1348
+ if (byOld.verified !== true) {
1349
+ throw _err("tuf/root-unsigned", "root version " + next.version + " is not signed by a threshold of " +
1350
+ "the keys the trusted root version " + current.version + " names");
1351
+ }
1352
+ var byNew = await verifySignatures({ metadata: next, keys: next.signed.keys, role: next.signed.roles.root });
1353
+ if (byNew.verified !== true) {
1354
+ throw _err("tuf/root-unsigned", "root version " + next.version + " is not signed by a threshold of " +
1355
+ "the keys it names itself, so its own keys could not sign for it");
1356
+ }
1357
+ _push(walked, next.version);
1358
+ current = next;
1359
+ }
1360
+ /** @internal A candidate the walk never reached is a gap or a rollback, and is reported rather
1361
+ * than ignored: a client that silently stops at a gap would keep trusting an older root while a
1362
+ * newer one it was handed says otherwise. */
1363
+ var versions = _keys(byVersion);
1364
+ for (var v = 0; v < versions.length; v++) {
1365
+ var num = +versions[v];
1366
+ if (num > current.version) {
1367
+ throw _err("tuf/bad-root-version", "candidate root version " + num + " was not reached: the chain " +
1368
+ "stops at version " + current.version + ", so a root between them is missing");
1369
+ }
1370
+ if (num <= trusted.version) {
1371
+ throw _err("tuf/bad-root-version", "candidate root version " + num + " is not newer than the " +
1372
+ "trusted version " + trusted.version + "; a root may only move forward");
1373
+ }
1374
+ }
1375
+ /** @internal The freeze-attack check, LAST, and on the root the walk ended on. The specification puts
1376
+ * it at step 5.3.10, after the walk of steps 5.3.2 to 5.3.9, and no step checks an intermediate
1377
+ * root's expiry: a client that has been offline long enough for its pinned root to lapse is meant to
1378
+ * catch up through the successors it is handed, and checking the anchor first is what stops it,
1379
+ * leaving the only way forward a replacement of the trust anchor by some other means. With no
1380
+ * candidates the root the walk ends on is the trusted root itself, so a lapsed root with nothing to
1381
+ * move to is still refused here. */
1382
+ checkExpiry(current, now);
1383
+ return guard.verdict.of({ root: current.signed, version: current.version,
1384
+ updated: walked.length > 0, walked: walked });
1385
+ }
1386
+
1387
+ module.exports = {
1388
+ canonicalJson: canonicalJson,
1389
+ keyId: keyId,
1390
+ parseMetadata: parseMetadata,
1391
+ checkExpiry: checkExpiry,
1392
+ verifySignatures: verifySignatures,
1393
+ updateRoot: updateRoot,
1394
+ ROLES: TUF_ROLES,
1395
+ };