@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/lint.js CHANGED
@@ -3972,6 +3972,89 @@ var RFC9881_RULES = [
3972
3972
  var RFC9909_RULES = [
3973
3973
  _pqcSigKeyUsageRule("lint/rfc9909/slhdsa-key-usage", "rfc9909", "RFC 9909 6", "SLH-DSA", _isSlhDsa),
3974
3974
  ];
3975
+ /** @internal Section 4.1 states the criticality at SHOULD NOT ("This extension SHOULD NOT be marked
3976
+ * critical", the section adding that marking it "would severely impact interoperability"), so it is
3977
+ * graded at warn here rather than held to a value by the shared criticality table, which carries only
3978
+ * the MUSTs. The same reason keeps issuerAltName out of the CRL table. Placement is a MUST in the same
3979
+ * section: "For certificate chains, this extension MUST only be included in the end-entity
3980
+ * certificate", so the two clauses are two rows at two strengths. */
3981
+ var OID_SAPKI = oid.byName("subjectAltPublicKeyInfo");
3982
+ var OID_ALT_ALG = oid.byName("altSignatureAlgorithm");
3983
+ var OID_ALT_VAL = oid.byName("altSignatureValue");
3984
+ /** @internal ITU-T X.509 (2019) clause 9.8. Two clauses at two strengths, and the difference matters.
3985
+ * Clause 9.8.3 states the pairing as a shall: "When the altSignatureAlgorithm extension is included in
3986
+ * a particular value that is an instance of a data type that supports extensions, the altSignatureValue
3987
+ * extension shall also be included." Clause 7.2.2 requires the full set in a certificate: one "shall
3988
+ * contain alternative public key information in the subjectAltPublicKeyInfo extension ..., an
3989
+ * alternative signature algorithm value in the altSignatureAlgorithm extension ... and an alternative
3990
+ * signature value in the altSignatureValue extension". Criticality is stated only in NOTEs recommending
3991
+ * non-critical, with the clauses saying the extensions "may be flagged as critical or as non-critical",
3992
+ * so that row is a warning and never an error: grading a NOTE as an error would invent a requirement
3993
+ * level the text does not state. */
3994
+ var X509_ALTSIG_RULES = [
3995
+ {
3996
+ id: "lint/x509-altsig/algorithm-without-value", severity: "error", source: "x509-altsig", citation: "ITU-T X.509 (2019) 9.8.3",
3997
+ message: "a value carrying altSignatureAlgorithm must also carry altSignatureValue",
3998
+ appliesTo: function (cert, ctx) { return !!ctx.raw("altSignatureAlgorithm"); },
3999
+ check: function (cert, ctx) {
4000
+ return ctx.raw("altSignatureValue") ? null : { context: { present: "altSignatureAlgorithm", missing: "altSignatureValue" } };
4001
+ },
4002
+ },
4003
+ {
4004
+ id: "lint/x509-altsig/value-without-algorithm", severity: "error", source: "x509-altsig", citation: "ITU-T X.509 (2019) 9.8.3",
4005
+ message: "a value carrying altSignatureValue must also carry altSignatureAlgorithm, which states how to verify it",
4006
+ appliesTo: function (cert, ctx) { return !!ctx.raw("altSignatureValue"); },
4007
+ check: function (cert, ctx) {
4008
+ return ctx.raw("altSignatureAlgorithm") ? null : { context: { present: "altSignatureValue", missing: "altSignatureAlgorithm" } };
4009
+ },
4010
+ },
4011
+ {
4012
+ id: "lint/x509-altsig/incomplete-set", severity: "error", source: "x509-altsig", citation: "ITU-T X.509 (2019) 7.2.2",
4013
+ message: "a certificate carrying the alternative cryptographic algorithm extensions must carry all three",
4014
+ appliesTo: function (cert, ctx) {
4015
+ return !!(ctx.raw("subjectAltPublicKeyInfo") || ctx.raw("altSignatureAlgorithm") || ctx.raw("altSignatureValue"));
4016
+ },
4017
+ check: function (cert, ctx) {
4018
+ var missing = ["subjectAltPublicKeyInfo", "altSignatureAlgorithm", "altSignatureValue"]
4019
+ .filter(function (n) { return !ctx.raw(n); });
4020
+ return missing.length ? { context: { missing: missing.join(", ") } } : null;
4021
+ },
4022
+ },
4023
+ {
4024
+ id: "lint/x509-altsig/critical", severity: "warn", source: "x509-altsig", citation: "ITU-T X.509 (2019) 9.8.2 / 9.8.3",
4025
+ message: "the alternative cryptographic algorithm extensions are recommended non-critical",
4026
+ check: function (cert) {
4027
+ var rows = [];
4028
+ (cert.extensions || []).forEach(function (e) {
4029
+ if (e.critical !== true) return;
4030
+ if (e.oid !== OID_SAPKI && e.oid !== OID_ALT_ALG && e.oid !== OID_ALT_VAL) return;
4031
+ rows.push({ context: { extension: e.name || e.oid, recommended: "non-critical", was: "critical" } });
4032
+ });
4033
+ return rows.length ? rows : null;
4034
+ },
4035
+ },
4036
+ ];
4037
+
4038
+ var RFC9763_RULES = [
4039
+ {
4040
+ id: "lint/rfc9763/related-certificate-critical", severity: "warn", source: "rfc9763", citation: "RFC 9763 4.1",
4041
+ message: "the relatedCertificate extension should not be marked critical",
4042
+ appliesTo: function (cert, ctx) { return !!ctx.raw("relatedCertificate"); },
4043
+ check: function (cert, ctx) {
4044
+ var e = ctx.raw("relatedCertificate");
4045
+ return e.critical === true ? { context: { extension: "relatedCertificate", recommended: "non-critical", was: "critical" } } : null;
4046
+ },
4047
+ },
4048
+ {
4049
+ id: "lint/rfc9763/related-certificate-on-ca", severity: "error", source: "rfc9763", citation: "RFC 9763 4.1",
4050
+ message: "the relatedCertificate extension must only be included in the end-entity certificate",
4051
+ appliesTo: function (cert, ctx) { return !!ctx.raw("relatedCertificate"); },
4052
+ check: function (cert, ctx) {
4053
+ var bc = ctx.decode("basicConstraints");
4054
+ return (bc && bc.value && bc.value.cA === true) ? { context: { extension: "relatedCertificate", cA: true } } : null;
4055
+ },
4056
+ },
4057
+ ];
3975
4058
 
3976
4059
  /** @internal RFC 5280 sec. 5 states the criticality of each CRL extension it profiles, and sec. 5.3
3977
4060
  * does the same for the entry extensions. certificateIssuer is critical (sec. 5.3.3) because a
@@ -6329,6 +6412,8 @@ var PROFILES = Object.assign(Object.create(null), {
6329
6412
  "rfc9881": RFC9881_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
6330
6413
  "rfc9909": RFC9909_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
6331
6414
  "rfc9935": RFC9935_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
6415
+ "rfc9763": RFC9763_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
6416
+ "x509-altsig": X509_ALTSIG_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
6332
6417
  "cabf-tls": CABF_TLS_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
6333
6418
  "cabf-smime": CABF_SMIME_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
6334
6419
  "cabf-cs": CABF_CS_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
@@ -6351,6 +6436,7 @@ var PROFILES = Object.assign(Object.create(null), {
6351
6436
  .concat(RFC5280_EXTENSION_SYNTAX_RULES),
6352
6437
  });
6353
6438
  var ALL_RULES = RFC5280_RULES.concat(RFC9881_RULES).concat(RFC9909_RULES).concat(RFC9935_RULES)
6439
+ .concat(RFC9763_RULES).concat(X509_ALTSIG_RULES)
6354
6440
  .concat(CABF_TLS_RULES).concat(CABF_SMIME_RULES).concat(CABF_CS_RULES).concat(ETSI_QC_RULES)
6355
6441
  /** @internal The root-program rows are in the registry so `rules()` enumerates them, and stay
6356
6442
  * inert under the default set because each one asks whether the caller named the profile. */
@@ -6363,11 +6449,89 @@ var CABF_TLS_CSR_RULES = CABF_TLS_RULES.filter(function (r) {
6363
6449
  /** @internal A requested extension whose value does not decode is the same fault in a request as
6364
6450
  * in a certificate, so the request profiles take the same row the certificate profiles do. */
6365
6451
  var CSR_STRUCTURAL_RULES = RFC2986_CSR_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES);
6452
+
6453
+ var OID_SOP_ATTR = oid.byName("statementOfPossession");
6454
+ var SOP_SIGNATURE_USAGES = ["digitalSignature", "nonRepudiation", "keyCertSign", "cRLSign"];
6455
+ /** @internal RFC 9883 sec. 6 states one prohibition outright: "The privateKeyPossessionStatement
6456
+ * attribute MUST NOT be used to obtain a signature certificate." Sec. 4 states the same requirement as a
6457
+ * property of the request: "the subjectPKInfo MUST contain the public key for the key establishment
6458
+ * algorithm". The attribute exists so a key-establishment key, which cannot sign, can still be certified;
6459
+ * a request carrying it while asking for a signature key usage, or naming a subject key that can only
6460
+ * sign, is asking for the certificate those sentences forbid. The two name comparisons sec. 3 states are
6461
+ * deliberately absent here: each ends in "the certificate policy MUST describe how the CA can determine
6462
+ * that the two subject names identify the same entity", which is a question no linter can answer, and
6463
+ * `pki.possession.verifyRequest` reports the comparison for a caller's policy to resolve. */
6464
+ function _csrHasPossessionStatement(csr) {
6465
+ return (csr.attributes || []).some(function (a) { return a.type === OID_SOP_ATTR; });
6466
+ }
6467
+ /** @internal The same table `pki.possession.verifyRequest` reads, so the linter and the verifier grade one
6468
+ * request alike. Read off the requested key usages alone, this row passed a request that named no usage
6469
+ * while asking to certify a key that can only ever sign, which is the sec. 4 MUST on the subjectPKInfo and
6470
+ * the sec. 6 prohibition both, and which the verifier refuses; `pki.csr.sign` under this profile then
6471
+ * emitted it. */
6472
+ var _isSignatureOnlyKey = pkix.signatureOnlyKeyOid(NS);
6473
+ function _csrSubjectKeyOid(csr) {
6474
+ return csr.subjectPublicKeyInfo && csr.subjectPublicKeyInfo.algorithm
6475
+ ? csr.subjectPublicKeyInfo.algorithm.oid : null;
6476
+ }
6477
+ var RFC9883_CSR_RULES = [
6478
+ {
6479
+ id: "lint/rfc9883/signature-certificate-requested", severity: "error", source: "rfc9883", citation: "RFC 9883 4 and 6",
6480
+ message: "a request carrying a possession statement must not ask for a signature certificate",
6481
+ appliesTo: function (csr) { return _csrHasPossessionStatement(csr); },
6482
+ check: function (csr, ctx) {
6483
+ /** @internal The subject key decides on its own when it can only sign, so this is read whether or
6484
+ * not the request names a usage. A key that can do either says nothing here and is judged by the
6485
+ * usages the request asks for. */
6486
+ var keyOid = _csrSubjectKeyOid(csr);
6487
+ if (_isSignatureOnlyKey(keyOid)) {
6488
+ return { context: { subjectKeyAlgorithm: oid.name(keyOid) || keyOid } };
6489
+ }
6490
+ var ku = ctx.decode("keyUsage");
6491
+ var value = ku && ku.value;
6492
+ if (!value) return null;
6493
+ var asked = SOP_SIGNATURE_USAGES.filter(function (u) { return value[u] === true; });
6494
+ return asked.length ? { context: { keyUsage: asked.join(", ") } } : null;
6495
+ },
6496
+ },
6497
+ {
6498
+ id: "lint/rfc9883/statement-value-undecodable", severity: "error", source: "rfc9883", citation: "RFC 9883 3",
6499
+ message: "a statementOfPossession attribute value must be a well-formed PrivateKeyPossessionStatement",
6500
+ appliesTo: function (csr) { return _csrHasPossessionStatement(csr); },
6501
+ check: function (csr) {
6502
+ var rows = [];
6503
+ (csr.attributes || []).forEach(function (a) {
6504
+ if (a.type !== OID_SOP_ATTR) return;
6505
+ if (a.privateKeyPossessionStatement === undefined) {
6506
+ rows.push({ context: { attribute: a.name || a.type } });
6507
+ }
6508
+ });
6509
+ return rows.length ? rows : null;
6510
+ },
6511
+ },
6512
+ /** @internal Sec. 3 states the attribute's syntax in the singular and nothing anywhere in RFC 9883 says
6513
+ * which of several a CA should honor, so a request carrying more than one leaves every check sec. 3 asks
6514
+ * for without a subject: the checks would be made against one statement while the CA certifies a request
6515
+ * that carries another naming a different certificate. `pki.possession.verifyRequest` refuses such a
6516
+ * request with `possession/bad-request`, and without this row the linter graded it conforming and
6517
+ * `pki.csr.sign` under this profile would emit it. */
6518
+ {
6519
+ id: "lint/rfc9883/statement-attribute-repeated", severity: "error", source: "rfc9883", citation: "RFC 9883 3",
6520
+ message: "a request must carry one statementOfPossession attribute, not several",
6521
+ appliesTo: function (csr) { return _csrHasPossessionStatement(csr); },
6522
+ check: function (csr) {
6523
+ var count = 0;
6524
+ (csr.attributes || []).forEach(function (a) { if (a.type === OID_SOP_ATTR) count += 1; });
6525
+ return count > 1 ? { context: { attributes: String(count) } } : null;
6526
+ },
6527
+ },
6528
+ ];
6366
6529
  var CSR_PROFILES = Object.assign(Object.create(null), {
6367
6530
  "rfc2986": CSR_STRUCTURAL_RULES,
6368
6531
  "cabf-tls": CSR_STRUCTURAL_RULES.concat(CABF_TLS_CSR_RULES),
6532
+ "rfc9883": RFC9883_CSR_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
6369
6533
  });
6370
- var ALL_CSR_RULES = CSR_STRUCTURAL_RULES;
6534
+ var ALL_CSR_RULES = CSR_STRUCTURAL_RULES.concat(RFC9883_CSR_RULES);
6371
6535
 
6372
6536
  function _subjectIsNonEmpty(csr) {
6373
6537
  var rdns = csr.subject && csr.subject.rdns;
package/lib/merkle.js CHANGED
@@ -42,34 +42,59 @@
42
42
  * `verifyConsistency` over sync SHA-256, fail-closed, transport-free.
43
43
  */
44
44
 
45
- var nodeCrypto = require("node:crypto");
46
45
  var constants = require("./constants");
47
46
  var frameworkError = require("./framework-error");
48
47
  var guard = require("./guard-all");
48
+ var intrinsic = require("./guard-intrinsic");
49
+
50
+ /** @internal The operations this module decides with, bound at load. A proof is assembled by
51
+ * concatenation, so a replaced `Array.prototype.concat` chooses which nodes the proof carries and in
52
+ * what order, and a replaced `Buffer.concat` chooses the bytes a node hash covers. Taking the captures
53
+ * also puts this file in the live-read gate's scope, which is how it stays converted. */
54
+ var _concat = intrinsic.concat;
55
+ var _bufferConcat = intrinsic.bufferConcat;
56
+ var _bufferFrom = intrinsic.bufferFrom;
57
+ var _bufferAlloc = intrinsic.bufferAlloc;
58
+ var _isArray = intrinsic.isArray;
59
+ var _assign = intrinsic.assign;
60
+ var _create = intrinsic.create;
61
+ var _BigInt = intrinsic.BigInt;
49
62
 
50
63
  var MerkleError = frameworkError.MerkleError;
51
64
 
52
65
  function _merkleErr(c, m) { return new MerkleError(c, m); }
53
66
 
54
67
  var SHA256_BYTES = 32;
55
- var LEAF_PREFIX = Buffer.from([0x00]);
56
- var NODE_PREFIX = Buffer.from([0x01]);
57
- var EMPTY = Buffer.alloc(0);
68
+ var LEAF_PREFIX = _bufferFrom([0x00]);
69
+ var NODE_PREFIX = _bufferFrom([0x01]);
70
+ var EMPTY = _bufferAlloc(0);
58
71
 
59
72
  function _sha256(buf) {
60
- return nodeCrypto.createHash("sha256").update(buf).digest();
73
+ return guard.crypto.digest("sha256", buf);
61
74
  }
62
75
 
76
+ /** @internal A COPY, not a view. Reading each slot once is not enough when what is read shares the
77
+ * caller's store: a getter handing out one scratch buffer per leaf satisfies the read-once rule and still
78
+ * leaves every collected leaf pointing at the same bytes, so collecting the second overwrote the first
79
+ * and the root of [A, B] came out as the root of [B, B] with nothing reporting it. Each value is 32 bytes
80
+ * by the check above, so copying costs nothing worth measuring, and every byte this module folds, hashes
81
+ * or compares is then its own. */
63
82
  function _toBuffer(v, field) {
64
- return guard.bytes.view(v, MerkleError, "merkle/bad-input", field);
83
+ return guard.bytes.snapshot(v, MerkleError, "merkle/bad-input", field);
65
84
  }
66
85
 
86
+ /** @internal The width is read on a VIEW, before the copy. A node is exactly 32 bytes, and the check
87
+ * enforcing that ran on the copy, so a value handed where a hash belongs was duplicated in full and
88
+ * only then refused: measured, `root([Buffer.alloc(64 * 1024 * 1024)])` allocated another 64 MiB to
89
+ * reject one leaf. Viewing allocates nothing, and the size comes off the view rather than off the
90
+ * caller's own `length`, which can be shadowed independently of the bytes. */
67
91
  function _node32(v, field) {
68
- var buf = _toBuffer(v, field);
69
- if (buf.length !== SHA256_BYTES) {
70
- throw new MerkleError("merkle/bad-hash-length", field + " must be exactly 32 bytes, got " + buf.length);
92
+ var viewed = guard.bytes.source(v, MerkleError, "merkle/bad-input", field);
93
+ var size = guard.bytes.lengthOf(viewed);
94
+ if (size !== SHA256_BYTES) {
95
+ throw new MerkleError("merkle/bad-hash-length", field + " must be exactly 32 bytes, got " + size);
71
96
  }
72
- return buf;
97
+ return guard.bytes.snapshot(viewed, MerkleError, "merkle/bad-input", field);
73
98
  }
74
99
 
75
100
  function _coerceCoord(v, field) {
@@ -77,19 +102,86 @@ function _coerceCoord(v, field) {
77
102
  }
78
103
 
79
104
  function _proofNodes(proof) {
80
- if (!Array.isArray(proof)) throw new MerkleError("merkle/bad-proof", "proof must be an array");
81
- if (proof.length > constants.LIMITS.MERKLE_MAX_PROOF_NODES) {
82
- throw new MerkleError("merkle/proof-too-large", "proof has " + proof.length + " nodes, exceeds " + constants.LIMITS.MERKLE_MAX_PROOF_NODES);
83
- }
84
- var out = [];
85
- for (var i = 0; i < proof.length; i++) out.push(_node32(proof[i], "proof[" + i + "]"));
86
- return out;
105
+ if (!_isArray(proof)) throw new MerkleError("merkle/bad-proof", "proof must be an array");
106
+ /** @internal Through the same guard as the leaf array, for the same reason: the node cap reads the
107
+ * length and a hand-rolled walk reads it again. The verifiers were not exposed the way the producers
108
+ * were, because a proof the walk collected short folds to a root that does not match the one supplied,
109
+ * so the comparison refuses it. That is fail-closed by consequence rather than by rule, and the rule is
110
+ * cheaper to keep here than to re-derive at each verifier. */
111
+ return guard.list.copyMap(proof, function (slot, i) { return _node32(slot, "proof[" + i + "]"); },
112
+ function (n) {
113
+ if (n > constants.LIMITS.MERKLE_MAX_PROOF_NODES) {
114
+ throw new MerkleError("merkle/proof-too-large",
115
+ "proof has " + n + " nodes, exceeds " + constants.LIMITS.MERKLE_MAX_PROOF_NODES);
116
+ }
117
+ });
87
118
  }
88
119
 
89
120
  function _ctEq(a, b) {
90
121
  return guard.crypto.constantTimeEqual(a, b);
91
122
  }
92
123
 
124
+ var NO_CONSISTENCY_CLAIM =
125
+ "an empty older tree (oldSize 0) makes no consistency claim about a non-empty newer tree: " +
126
+ "RFC 6962 sec. 2.1.2 defines the proof for 0 < oldSize < newSize, and nothing here binds " +
127
+ "newRoot. Verify an inclusion proof against the new tree, or start from a signed tree head " +
128
+ "you already trust";
129
+
130
+ /** @internal Each slot is read once, into the buffer the length check and every later fold both
131
+ * use, so an array whose accessor answers differently on a second read cannot put one value past
132
+ * the check and a different one into the tree. The COUNT is read once as well, by
133
+ * `guard.list.copyMap`, which is the point of going through it: a hand-rolled
134
+ * `for (i = 0; i < v.length; i++)` re-reads the length after every slot access, so a getter on one slot
135
+ * that shortened the array left the cap approving four leaves and the walk collecting one, and the root
136
+ * committed to a tree the caller never supplied. A slot past a length that shrank reads as undefined and
137
+ * `_node32` refuses it, so the array is refused rather than folded short. */
138
+ function _leafArray(v, field) {
139
+ if (!_isArray(v)) {
140
+ throw new MerkleError("merkle/bad-input", field + " must be an array of 32-byte leaf hashes");
141
+ }
142
+ return guard.list.copyMap(v, function (slot, i) { return _node32(slot, field + "[" + i + "]"); },
143
+ function (n) {
144
+ if (n > constants.LIMITS.MERKLE_MAX_LEAVES) {
145
+ throw new MerkleError("merkle/too-many-leaves",
146
+ field + " holds " + n + " leaves, exceeds " + constants.LIMITS.MERKLE_MAX_LEAVES);
147
+ }
148
+ });
149
+ }
150
+
151
+ /** @internal RFC 6962 sec. 2.1's "largest power of two smaller than n", for n >= 2. Doubled from
152
+ * 1 rather than derived through a logarithm: the leaf count is bounded well inside the exact
153
+ * integer range, and a float log would round at the boundary sizes this splits on. */
154
+ function _splitPoint(n) {
155
+ var k = 1;
156
+ while (k * 2 < n) k *= 2;
157
+ return k;
158
+ }
159
+
160
+ /** @internal MTH(D[lo:hi]) for hi > lo. The empty tree is answered by the caller. */
161
+ function _mth(leaves, lo, hi) {
162
+ if (hi - lo === 1) return leaves[lo];
163
+ var k = _splitPoint(hi - lo);
164
+ return nodeHash(_mth(leaves, lo, lo + k), _mth(leaves, lo + k, hi));
165
+ }
166
+
167
+ /** @internal PATH(m, D[lo:hi]) of RFC 6962 sec. 2.1.1, with m relative to lo. */
168
+ function _path(leaves, lo, hi, m) {
169
+ if (hi - lo === 1) return [];
170
+ var k = _splitPoint(hi - lo);
171
+ if (m < k) return _concat(_path(leaves, lo, lo + k, m), [_mth(leaves, lo + k, hi)]);
172
+ return _concat(_path(leaves, lo + k, hi, m - k), [_mth(leaves, lo, lo + k)]);
173
+ }
174
+
175
+ /** @internal SUBPROOF(m, D[lo:hi], b) of RFC 6962 sec. 2.1.2, with m relative to lo. The split
176
+ * compares m <= k here where PATH compares m < k, which is what puts the old tree's own root in
177
+ * the proof exactly when the caller did not already hold it. */
178
+ function _subproof(leaves, lo, hi, m, b) {
179
+ if (m === hi - lo) return b ? [] : [_mth(leaves, lo, hi)];
180
+ var k = _splitPoint(hi - lo);
181
+ if (m <= k) return _concat(_subproof(leaves, lo, lo + k, m, b), [_mth(leaves, lo + k, hi)]);
182
+ return _concat(_subproof(leaves, lo + k, hi, m - k, false), [_mth(leaves, lo, lo + k)]);
183
+ }
184
+
93
185
  /**
94
186
  * @primitive pki.merkle.leafHash
95
187
  * @signature pki.merkle.leafHash(entry) -> Buffer
@@ -107,7 +199,7 @@ function _ctEq(a, b) {
107
199
  */
108
200
  function leafHash(entry) {
109
201
  var e = _toBuffer(entry, "entry");
110
- return _sha256(Buffer.concat([LEAF_PREFIX, e]));
202
+ return _sha256(_bufferConcat([LEAF_PREFIX, e]));
111
203
  }
112
204
 
113
205
  /**
@@ -131,7 +223,7 @@ function leafHash(entry) {
131
223
  function nodeHash(left, right) {
132
224
  var l = _node32(left, "left");
133
225
  var r = _node32(right, "right");
134
- return _sha256(Buffer.concat([NODE_PREFIX, l, r]));
226
+ return _sha256(_bufferConcat([NODE_PREFIX, l, r]));
135
227
  }
136
228
 
137
229
  /**
@@ -180,7 +272,7 @@ function emptyRootHash() {
180
272
  * var lh = pki.merkle.leafHash(Buffer.from([0]));
181
273
  * pki.merkle.verifyInclusion({ leafIndex: 0, treeSize: 1, leafHash: lh, proof: [], rootHash: lh }); // -> true
182
274
  */
183
- var _INCLUSION_KEYS = Object.assign(Object.create(null), { leafIndex: 1, treeSize: 1, leafHash: 1, rootHash: 1, proof: 1 });
275
+ var _INCLUSION_KEYS = _assign(_create(null), { leafIndex: 1, treeSize: 1, leafHash: 1, rootHash: 1, proof: 1 });
184
276
  function verifyInclusion(opts) {
185
277
  opts = opts || {};
186
278
  guard.identifier.assertKnownKeys(opts, _INCLUSION_KEYS, function (c, m) { return new MerkleError(c, m); },
@@ -252,7 +344,7 @@ function verifyInclusion(opts) {
252
344
  * var r = pki.merkle.leafHash(Buffer.from([0]));
253
345
  * pki.merkle.verifyConsistency({ oldSize: 1, newSize: 1, oldRoot: r, newRoot: r, proof: [] }); // -> true
254
346
  */
255
- var _CONSISTENCY_KEYS = Object.assign(Object.create(null), { oldSize: 1, newSize: 1, oldRoot: 1, newRoot: 1, proof: 1 });
347
+ var _CONSISTENCY_KEYS = _assign(_create(null), { oldSize: 1, newSize: 1, oldRoot: 1, newRoot: 1, proof: 1 });
256
348
  function verifyConsistency(opts) {
257
349
  opts = opts || {};
258
350
  guard.identifier.assertKnownKeys(opts, _CONSISTENCY_KEYS, function (c, m) { return new MerkleError(c, m); },
@@ -271,11 +363,7 @@ function verifyConsistency(opts) {
271
363
  // allow:constant-time-compare-short-circuited -- oldRoot, newRoot, and er are public Merkle tree roots, not secrets, so short-circuiting the second compare leaks nothing confidential
272
364
  return _ctEq(oldRoot, er) && _ctEq(newRoot, er);
273
365
  }
274
- throw new MerkleError("merkle/no-consistency-claim",
275
- "an empty older tree (oldSize 0) makes no consistency claim about a non-empty newer tree: " +
276
- "RFC 6962 sec. 2.1.2 defines the proof for 0 < oldSize < newSize, and nothing here binds " +
277
- "newRoot. Verify an inclusion proof against the new tree, or start from a signed tree head " +
278
- "you already trust");
366
+ throw new MerkleError("merkle/no-consistency-claim", NO_CONSISTENCY_CLAIM);
279
367
  }
280
368
  if (oldSize === newSize) {
281
369
  if (proof.length !== 0) throw new MerkleError("merkle/sizes-equal-nonempty-proof", "equal tree sizes require an empty consistency proof");
@@ -285,7 +373,7 @@ function verifyConsistency(opts) {
285
373
 
286
374
  var path = proof;
287
375
  if ((oldSize & (oldSize - 1n)) === 0n) {
288
- path = [oldRoot].concat(proof);
376
+ path = _concat([oldRoot], proof);
289
377
  }
290
378
  var fn = oldSize - 1n;
291
379
  var sn = newSize - 1n;
@@ -313,10 +401,128 @@ function verifyConsistency(opts) {
313
401
  return okOld && okNew;
314
402
  }
315
403
 
404
+ /**
405
+ * @primitive pki.merkle.root
406
+ * @signature pki.merkle.root(leafHashes) -> Buffer
407
+ * @since 0.8.34
408
+ * @status stable
409
+ * @spec RFC 6962, RFC 9162
410
+ * @related pki.merkle.leafHash, pki.merkle.inclusionProof, pki.merkle.verifyInclusion
411
+ *
412
+ * The RFC 6962 Merkle Tree Hash `MTH(D[n])` over a whole tree, returned as a
413
+ * 32-byte root ready to pass to `verifyInclusion` as `rootHash`. Takes leaf
414
+ * HASHES, the same form `verifyInclusion` takes as `leafHash`; map entries
415
+ * through `pki.merkle.leafHash` first. An empty array is the empty tree and
416
+ * returns `emptyRootHash()`.
417
+ *
418
+ * Throws `merkle/bad-input` if `leafHashes` is not an array or a leaf is not a
419
+ * Buffer / Uint8Array, `merkle/bad-hash-length` if a leaf is not exactly 32
420
+ * bytes, and `merkle/too-many-leaves` above `C.LIMITS.MERKLE_MAX_LEAVES`.
421
+ *
422
+ * @example
423
+ * var leaves = [Buffer.from([0]), Buffer.from([1])].map(pki.merkle.leafHash);
424
+ * pki.merkle.root(leaves); // -> <Buffer 32-byte tree head>
425
+ */
426
+ function root(leafHashes) {
427
+ var leaves = _leafArray(leafHashes, "leafHashes");
428
+ if (leaves.length === 0) return emptyRootHash();
429
+ return _mth(leaves, 0, leaves.length);
430
+ }
431
+
432
+ /**
433
+ * @primitive pki.merkle.inclusionProof
434
+ * @signature pki.merkle.inclusionProof(opts) -> Buffer[]
435
+ * @since 0.8.34
436
+ * @status stable
437
+ * @spec RFC 6962, RFC 9162
438
+ * @related pki.merkle.verifyInclusion, pki.merkle.root
439
+ *
440
+ * Produce the RFC 6962 sec. 2.1.1 audit path `PATH(leafIndex, D[n])` for one
441
+ * leaf of a tree held in memory. The returned array is the `proof` that
442
+ * `verifyInclusion` folds against `root(leafHashes)`.
443
+ *
444
+ * The tree size is the length of `leafHashes` and is not a separate option, so
445
+ * a size and a leaf array cannot disagree. Throws `merkle/empty-tree` on an
446
+ * empty tree, `merkle/index-out-of-range` for a `leafIndex` at or above the leaf
447
+ * count, and the `leafHashes` errors `root` throws.
448
+ *
449
+ * @opts
450
+ * leafHashes: Buffer[], // the whole tree, each leaf a 32-byte hash
451
+ * leafIndex: number | bigint, // 0-based position of the leaf to prove
452
+ *
453
+ * @example
454
+ * var leaves = [Buffer.from([0]), Buffer.from([1])].map(pki.merkle.leafHash);
455
+ * pki.merkle.inclusionProof({ leafHashes: leaves, leafIndex: 0 }); // -> [<Buffer ...>]
456
+ */
457
+ var _INCLUSION_PROOF_KEYS = _assign(_create(null), { leafHashes: 1, leafIndex: 1 });
458
+ function inclusionProof(opts) {
459
+ opts = opts || {};
460
+ guard.identifier.assertKnownKeys(opts, _INCLUSION_PROOF_KEYS, _merkleErr,
461
+ "merkle/bad-input", "unknown inclusionProof option ");
462
+ var leaves = _leafArray(opts.leafHashes, "leafHashes");
463
+ var n = leaves.length;
464
+ if (n === 0) throw new MerkleError("merkle/empty-tree", "an empty tree has no leaves to include");
465
+ var leafIndex = _coerceCoord(opts.leafIndex, "leafIndex");
466
+ var m = guard.range.int(leafIndex, 0n, _BigInt(n - 1), _merkleErr,
467
+ "merkle/index-out-of-range", "leafIndex");
468
+ return _path(leaves, 0, n, m);
469
+ }
470
+
471
+ /**
472
+ * @primitive pki.merkle.consistencyProof
473
+ * @signature pki.merkle.consistencyProof(opts) -> Buffer[]
474
+ * @since 0.8.34
475
+ * @status stable
476
+ * @spec RFC 6962, RFC 9162
477
+ * @related pki.merkle.verifyConsistency, pki.merkle.root
478
+ *
479
+ * Produce the RFC 6962 sec. 2.1.2 consistency proof `PROOF(oldSize, D[n])`
480
+ * between an older tree of `oldSize` leaves and the tree held in `leafHashes`.
481
+ * The returned array is the `proof` that `verifyConsistency` checks against
482
+ * `root(leafHashes.slice(0, oldSize))` and `root(leafHashes)`.
483
+ *
484
+ * The newer size is the length of `leafHashes` and is not a separate option.
485
+ * Equal sizes produce the empty proof, which is what the verifier requires
486
+ * there. Throws `merkle/old-size-exceeds-new` when `oldSize` is past the leaf
487
+ * count and `merkle/no-consistency-claim` for an `oldSize` of 0 against a
488
+ * non-empty tree, matching `verifyConsistency`: RFC 6962 sec. 2.1.2 defines the
489
+ * proof for `0 < oldSize < newSize`, so there is no proof to produce.
490
+ *
491
+ * @opts
492
+ * leafHashes: Buffer[], // the newer tree, each leaf a 32-byte hash
493
+ * oldSize: number | bigint, // leaf count of the older tree
494
+ *
495
+ * @example
496
+ * var leaves = [Buffer.from([0]), Buffer.from([1])].map(pki.merkle.leafHash);
497
+ * pki.merkle.consistencyProof({ leafHashes: leaves, oldSize: 1 }); // -> [<Buffer ...>]
498
+ */
499
+ var _CONSISTENCY_PROOF_KEYS = _assign(_create(null), { leafHashes: 1, oldSize: 1 });
500
+ function consistencyProof(opts) {
501
+ opts = opts || {};
502
+ guard.identifier.assertKnownKeys(opts, _CONSISTENCY_PROOF_KEYS, _merkleErr,
503
+ "merkle/bad-input", "unknown consistencyProof option ");
504
+ var leaves = _leafArray(opts.leafHashes, "leafHashes");
505
+ var n = leaves.length;
506
+ var oldSize = _coerceCoord(opts.oldSize, "oldSize");
507
+ if (oldSize > _BigInt(n)) {
508
+ throw new MerkleError("merkle/old-size-exceeds-new", "oldSize " + oldSize + " exceeds newSize " + n);
509
+ }
510
+ if (oldSize === 0n) {
511
+ if (n === 0) return [];
512
+ throw new MerkleError("merkle/no-consistency-claim", NO_CONSISTENCY_CLAIM);
513
+ }
514
+ var m = guard.range.int(oldSize, 1n, _BigInt(n), _merkleErr, "merkle/bad-input", "oldSize");
515
+ if (m === n) return [];
516
+ return _subproof(leaves, 0, n, m, true);
517
+ }
518
+
316
519
  module.exports = {
317
520
  leafHash: leafHash,
318
521
  nodeHash: nodeHash,
319
522
  emptyRootHash: emptyRootHash,
523
+ root: root,
320
524
  verifyInclusion: verifyInclusion,
321
525
  verifyConsistency: verifyConsistency,
526
+ inclusionProof: inclusionProof,
527
+ consistencyProof: consistencyProof,
322
528
  };