@blamejs/pki 0.8.31 → 0.8.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +393 -0
- package/README.md +66 -15
- package/bin/pki.js +716 -30
- package/examples/issue-a-certificate.js +90 -0
- package/examples/post-quantum.js +66 -0
- package/examples/read-a-certificate.js +56 -0
- package/examples/revoke-and-check.js +86 -0
- package/examples/sign-and-verify-cms.js +63 -0
- package/index.js +18 -5
- package/lib/acme.js +223 -74
- package/lib/alt-sig.js +441 -0
- package/lib/asn1-der.js +48 -25
- package/lib/attrcert-sign.js +27 -19
- package/lib/cbor-det.js +24 -14
- package/lib/cmc-build.js +9 -2
- package/lib/cmc-verify.js +14 -3
- package/lib/cmp-build.js +17 -5
- package/lib/cmp-session.js +24 -11
- package/lib/cmp-verify.js +28 -19
- package/lib/cms-compress.js +3 -1
- package/lib/cms-decrypt.js +102 -7
- package/lib/cms-encrypt.js +59 -7
- package/lib/cms-sign.js +31 -9
- package/lib/cms-verify.js +22 -18
- package/lib/composite-kem.js +30 -18
- package/lib/composite-sig.js +29 -14
- package/lib/constants.js +26 -0
- package/lib/crl-sign.js +111 -19
- package/lib/crmf-sign.js +167 -15
- package/lib/csr-sign.js +117 -6
- package/lib/ct.js +939 -48
- package/lib/est.js +29 -16
- package/lib/framework-error.js +12 -0
- package/lib/guard-async.js +1 -1
- package/lib/guard-bytes.js +103 -20
- package/lib/guard-crypto.js +205 -3
- package/lib/guard-encoding.js +6 -1
- package/lib/guard-identifier.js +55 -8
- package/lib/guard-intrinsic.js +43 -0
- package/lib/guard-json.js +122 -9
- package/lib/guard-limits.js +12 -1
- package/lib/guard-list.js +63 -8
- package/lib/guard-name.js +167 -20
- package/lib/guard-range.js +43 -5
- package/lib/guard-text.js +41 -0
- package/lib/guard-verdict.js +39 -8
- package/lib/hpke.js +688 -42
- package/lib/http-transport.js +156 -4
- package/lib/identity-match.js +4 -4
- package/lib/inspect.js +33 -6
- package/lib/jose.js +93 -25
- package/lib/key.js +18 -13
- package/lib/lint.js +165 -1
- package/lib/merkle.js +233 -27
- package/lib/ocsp.js +27 -14
- package/lib/oid.js +14 -4
- package/lib/path-validate.js +143 -61
- package/lib/pkcs12-build.js +7 -2
- package/lib/pki-build.js +31 -7
- package/lib/possession.js +575 -0
- package/lib/related-cert.js +708 -0
- package/lib/revocation-fetch.js +6 -6
- package/lib/scep.js +15 -5
- package/lib/schema-c509.js +5 -2
- package/lib/schema-cmc.js +1 -1
- package/lib/schema-cmp.js +8 -8
- package/lib/schema-cms.js +2 -15
- package/lib/schema-csr.js +69 -0
- package/lib/schema-engine.js +10 -3
- package/lib/schema-pkix.js +228 -2
- package/lib/schema-tsp.js +7 -6
- package/lib/sign-scheme.js +22 -9
- package/lib/sigstore.js +684 -168
- package/lib/sleep.js +8 -1
- package/lib/smime.js +22 -1
- package/lib/tlog.js +1235 -0
- package/lib/tls-cert-compress.js +25 -8
- package/lib/tsp-sign.js +120 -35
- package/lib/tuf.js +1395 -0
- package/lib/validator-tpm.js +4 -1
- package/lib/verify-seam.js +7 -1
- package/lib/webauthn-mds.js +41 -4
- package/lib/webauthn.js +43 -25
- package/lib/webcrypto.js +48 -46
- package/lib/x509-sign.js +225 -19
- package/package.json +2 -1
- package/sbom.cdx.json +6 -6
package/lib/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 =
|
|
56
|
-
var NODE_PREFIX =
|
|
57
|
-
var EMPTY =
|
|
68
|
+
var LEAF_PREFIX = _bufferFrom([0x00]);
|
|
69
|
+
var NODE_PREFIX = _bufferFrom([0x01]);
|
|
70
|
+
var EMPTY = _bufferAlloc(0);
|
|
58
71
|
|
|
59
72
|
function _sha256(buf) {
|
|
60
|
-
return
|
|
73
|
+
return guard.crypto.digest("sha256", buf);
|
|
61
74
|
}
|
|
62
75
|
|
|
76
|
+
/** @internal A COPY, not a view. Reading each slot once is not enough when what is read shares the
|
|
77
|
+
* caller's store: a getter handing out one scratch buffer per leaf satisfies the read-once rule and still
|
|
78
|
+
* leaves every collected leaf pointing at the same bytes, so collecting the second overwrote the first
|
|
79
|
+
* and the root of [A, B] came out as the root of [B, B] with nothing reporting it. Each value is 32 bytes
|
|
80
|
+
* by the check above, so copying costs nothing worth measuring, and every byte this module folds, hashes
|
|
81
|
+
* or compares is then its own. */
|
|
63
82
|
function _toBuffer(v, field) {
|
|
64
|
-
return guard.bytes.
|
|
83
|
+
return guard.bytes.snapshot(v, MerkleError, "merkle/bad-input", field);
|
|
65
84
|
}
|
|
66
85
|
|
|
86
|
+
/** @internal The width is read on a VIEW, before the copy. A node is exactly 32 bytes, and the check
|
|
87
|
+
* enforcing that ran on the copy, so a value handed where a hash belongs was duplicated in full and
|
|
88
|
+
* only then refused: measured, `root([Buffer.alloc(64 * 1024 * 1024)])` allocated another 64 MiB to
|
|
89
|
+
* reject one leaf. Viewing allocates nothing, and the size comes off the view rather than off the
|
|
90
|
+
* caller's own `length`, which can be shadowed independently of the bytes. */
|
|
67
91
|
function _node32(v, field) {
|
|
68
|
-
var
|
|
69
|
-
|
|
70
|
-
|
|
92
|
+
var viewed = guard.bytes.source(v, MerkleError, "merkle/bad-input", field);
|
|
93
|
+
var size = guard.bytes.lengthOf(viewed);
|
|
94
|
+
if (size !== SHA256_BYTES) {
|
|
95
|
+
throw new MerkleError("merkle/bad-hash-length", field + " must be exactly 32 bytes, got " + size);
|
|
71
96
|
}
|
|
72
|
-
return
|
|
97
|
+
return guard.bytes.snapshot(viewed, MerkleError, "merkle/bad-input", field);
|
|
73
98
|
}
|
|
74
99
|
|
|
75
100
|
function _coerceCoord(v, field) {
|
|
@@ -77,19 +102,86 @@ function _coerceCoord(v, field) {
|
|
|
77
102
|
}
|
|
78
103
|
|
|
79
104
|
function _proofNodes(proof) {
|
|
80
|
-
if (!
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return
|
|
105
|
+
if (!_isArray(proof)) throw new MerkleError("merkle/bad-proof", "proof must be an array");
|
|
106
|
+
/** @internal Through the same guard as the leaf array, for the same reason: the node cap reads the
|
|
107
|
+
* length and a hand-rolled walk reads it again. The verifiers were not exposed the way the producers
|
|
108
|
+
* were, because a proof the walk collected short folds to a root that does not match the one supplied,
|
|
109
|
+
* so the comparison refuses it. That is fail-closed by consequence rather than by rule, and the rule is
|
|
110
|
+
* cheaper to keep here than to re-derive at each verifier. */
|
|
111
|
+
return guard.list.copyMap(proof, function (slot, i) { return _node32(slot, "proof[" + i + "]"); },
|
|
112
|
+
function (n) {
|
|
113
|
+
if (n > constants.LIMITS.MERKLE_MAX_PROOF_NODES) {
|
|
114
|
+
throw new MerkleError("merkle/proof-too-large",
|
|
115
|
+
"proof has " + n + " nodes, exceeds " + constants.LIMITS.MERKLE_MAX_PROOF_NODES);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
87
118
|
}
|
|
88
119
|
|
|
89
120
|
function _ctEq(a, b) {
|
|
90
121
|
return guard.crypto.constantTimeEqual(a, b);
|
|
91
122
|
}
|
|
92
123
|
|
|
124
|
+
var NO_CONSISTENCY_CLAIM =
|
|
125
|
+
"an empty older tree (oldSize 0) makes no consistency claim about a non-empty newer tree: " +
|
|
126
|
+
"RFC 6962 sec. 2.1.2 defines the proof for 0 < oldSize < newSize, and nothing here binds " +
|
|
127
|
+
"newRoot. Verify an inclusion proof against the new tree, or start from a signed tree head " +
|
|
128
|
+
"you already trust";
|
|
129
|
+
|
|
130
|
+
/** @internal Each slot is read once, into the buffer the length check and every later fold both
|
|
131
|
+
* use, so an array whose accessor answers differently on a second read cannot put one value past
|
|
132
|
+
* the check and a different one into the tree. The COUNT is read once as well, by
|
|
133
|
+
* `guard.list.copyMap`, which is the point of going through it: a hand-rolled
|
|
134
|
+
* `for (i = 0; i < v.length; i++)` re-reads the length after every slot access, so a getter on one slot
|
|
135
|
+
* that shortened the array left the cap approving four leaves and the walk collecting one, and the root
|
|
136
|
+
* committed to a tree the caller never supplied. A slot past a length that shrank reads as undefined and
|
|
137
|
+
* `_node32` refuses it, so the array is refused rather than folded short. */
|
|
138
|
+
function _leafArray(v, field) {
|
|
139
|
+
if (!_isArray(v)) {
|
|
140
|
+
throw new MerkleError("merkle/bad-input", field + " must be an array of 32-byte leaf hashes");
|
|
141
|
+
}
|
|
142
|
+
return guard.list.copyMap(v, function (slot, i) { return _node32(slot, field + "[" + i + "]"); },
|
|
143
|
+
function (n) {
|
|
144
|
+
if (n > constants.LIMITS.MERKLE_MAX_LEAVES) {
|
|
145
|
+
throw new MerkleError("merkle/too-many-leaves",
|
|
146
|
+
field + " holds " + n + " leaves, exceeds " + constants.LIMITS.MERKLE_MAX_LEAVES);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** @internal RFC 6962 sec. 2.1's "largest power of two smaller than n", for n >= 2. Doubled from
|
|
152
|
+
* 1 rather than derived through a logarithm: the leaf count is bounded well inside the exact
|
|
153
|
+
* integer range, and a float log would round at the boundary sizes this splits on. */
|
|
154
|
+
function _splitPoint(n) {
|
|
155
|
+
var k = 1;
|
|
156
|
+
while (k * 2 < n) k *= 2;
|
|
157
|
+
return k;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** @internal MTH(D[lo:hi]) for hi > lo. The empty tree is answered by the caller. */
|
|
161
|
+
function _mth(leaves, lo, hi) {
|
|
162
|
+
if (hi - lo === 1) return leaves[lo];
|
|
163
|
+
var k = _splitPoint(hi - lo);
|
|
164
|
+
return nodeHash(_mth(leaves, lo, lo + k), _mth(leaves, lo + k, hi));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** @internal PATH(m, D[lo:hi]) of RFC 6962 sec. 2.1.1, with m relative to lo. */
|
|
168
|
+
function _path(leaves, lo, hi, m) {
|
|
169
|
+
if (hi - lo === 1) return [];
|
|
170
|
+
var k = _splitPoint(hi - lo);
|
|
171
|
+
if (m < k) return _concat(_path(leaves, lo, lo + k, m), [_mth(leaves, lo + k, hi)]);
|
|
172
|
+
return _concat(_path(leaves, lo + k, hi, m - k), [_mth(leaves, lo, lo + k)]);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** @internal SUBPROOF(m, D[lo:hi], b) of RFC 6962 sec. 2.1.2, with m relative to lo. The split
|
|
176
|
+
* compares m <= k here where PATH compares m < k, which is what puts the old tree's own root in
|
|
177
|
+
* the proof exactly when the caller did not already hold it. */
|
|
178
|
+
function _subproof(leaves, lo, hi, m, b) {
|
|
179
|
+
if (m === hi - lo) return b ? [] : [_mth(leaves, lo, hi)];
|
|
180
|
+
var k = _splitPoint(hi - lo);
|
|
181
|
+
if (m <= k) return _concat(_subproof(leaves, lo, lo + k, m, b), [_mth(leaves, lo + k, hi)]);
|
|
182
|
+
return _concat(_subproof(leaves, lo + k, hi, m - k, false), [_mth(leaves, lo, lo + k)]);
|
|
183
|
+
}
|
|
184
|
+
|
|
93
185
|
/**
|
|
94
186
|
* @primitive pki.merkle.leafHash
|
|
95
187
|
* @signature pki.merkle.leafHash(entry) -> Buffer
|
|
@@ -107,7 +199,7 @@ function _ctEq(a, b) {
|
|
|
107
199
|
*/
|
|
108
200
|
function leafHash(entry) {
|
|
109
201
|
var e = _toBuffer(entry, "entry");
|
|
110
|
-
return _sha256(
|
|
202
|
+
return _sha256(_bufferConcat([LEAF_PREFIX, e]));
|
|
111
203
|
}
|
|
112
204
|
|
|
113
205
|
/**
|
|
@@ -131,7 +223,7 @@ function leafHash(entry) {
|
|
|
131
223
|
function nodeHash(left, right) {
|
|
132
224
|
var l = _node32(left, "left");
|
|
133
225
|
var r = _node32(right, "right");
|
|
134
|
-
return _sha256(
|
|
226
|
+
return _sha256(_bufferConcat([NODE_PREFIX, l, r]));
|
|
135
227
|
}
|
|
136
228
|
|
|
137
229
|
/**
|
|
@@ -180,7 +272,7 @@ function emptyRootHash() {
|
|
|
180
272
|
* var lh = pki.merkle.leafHash(Buffer.from([0]));
|
|
181
273
|
* pki.merkle.verifyInclusion({ leafIndex: 0, treeSize: 1, leafHash: lh, proof: [], rootHash: lh }); // -> true
|
|
182
274
|
*/
|
|
183
|
-
var _INCLUSION_KEYS =
|
|
275
|
+
var _INCLUSION_KEYS = _assign(_create(null), { leafIndex: 1, treeSize: 1, leafHash: 1, rootHash: 1, proof: 1 });
|
|
184
276
|
function verifyInclusion(opts) {
|
|
185
277
|
opts = opts || {};
|
|
186
278
|
guard.identifier.assertKnownKeys(opts, _INCLUSION_KEYS, function (c, m) { return new MerkleError(c, m); },
|
|
@@ -252,7 +344,7 @@ function verifyInclusion(opts) {
|
|
|
252
344
|
* var r = pki.merkle.leafHash(Buffer.from([0]));
|
|
253
345
|
* pki.merkle.verifyConsistency({ oldSize: 1, newSize: 1, oldRoot: r, newRoot: r, proof: [] }); // -> true
|
|
254
346
|
*/
|
|
255
|
-
var _CONSISTENCY_KEYS =
|
|
347
|
+
var _CONSISTENCY_KEYS = _assign(_create(null), { oldSize: 1, newSize: 1, oldRoot: 1, newRoot: 1, proof: 1 });
|
|
256
348
|
function verifyConsistency(opts) {
|
|
257
349
|
opts = opts || {};
|
|
258
350
|
guard.identifier.assertKnownKeys(opts, _CONSISTENCY_KEYS, function (c, m) { return new MerkleError(c, m); },
|
|
@@ -271,11 +363,7 @@ function verifyConsistency(opts) {
|
|
|
271
363
|
// allow:constant-time-compare-short-circuited -- oldRoot, newRoot, and er are public Merkle tree roots, not secrets, so short-circuiting the second compare leaks nothing confidential
|
|
272
364
|
return _ctEq(oldRoot, er) && _ctEq(newRoot, er);
|
|
273
365
|
}
|
|
274
|
-
throw new MerkleError("merkle/no-consistency-claim",
|
|
275
|
-
"an empty older tree (oldSize 0) makes no consistency claim about a non-empty newer tree: " +
|
|
276
|
-
"RFC 6962 sec. 2.1.2 defines the proof for 0 < oldSize < newSize, and nothing here binds " +
|
|
277
|
-
"newRoot. Verify an inclusion proof against the new tree, or start from a signed tree head " +
|
|
278
|
-
"you already trust");
|
|
366
|
+
throw new MerkleError("merkle/no-consistency-claim", NO_CONSISTENCY_CLAIM);
|
|
279
367
|
}
|
|
280
368
|
if (oldSize === newSize) {
|
|
281
369
|
if (proof.length !== 0) throw new MerkleError("merkle/sizes-equal-nonempty-proof", "equal tree sizes require an empty consistency proof");
|
|
@@ -285,7 +373,7 @@ function verifyConsistency(opts) {
|
|
|
285
373
|
|
|
286
374
|
var path = proof;
|
|
287
375
|
if ((oldSize & (oldSize - 1n)) === 0n) {
|
|
288
|
-
path = [oldRoot]
|
|
376
|
+
path = _concat([oldRoot], proof);
|
|
289
377
|
}
|
|
290
378
|
var fn = oldSize - 1n;
|
|
291
379
|
var sn = newSize - 1n;
|
|
@@ -313,10 +401,128 @@ function verifyConsistency(opts) {
|
|
|
313
401
|
return okOld && okNew;
|
|
314
402
|
}
|
|
315
403
|
|
|
404
|
+
/**
|
|
405
|
+
* @primitive pki.merkle.root
|
|
406
|
+
* @signature pki.merkle.root(leafHashes) -> Buffer
|
|
407
|
+
* @since 0.8.34
|
|
408
|
+
* @status stable
|
|
409
|
+
* @spec RFC 6962, RFC 9162
|
|
410
|
+
* @related pki.merkle.leafHash, pki.merkle.inclusionProof, pki.merkle.verifyInclusion
|
|
411
|
+
*
|
|
412
|
+
* The RFC 6962 Merkle Tree Hash `MTH(D[n])` over a whole tree, returned as a
|
|
413
|
+
* 32-byte root ready to pass to `verifyInclusion` as `rootHash`. Takes leaf
|
|
414
|
+
* HASHES, the same form `verifyInclusion` takes as `leafHash`; map entries
|
|
415
|
+
* through `pki.merkle.leafHash` first. An empty array is the empty tree and
|
|
416
|
+
* returns `emptyRootHash()`.
|
|
417
|
+
*
|
|
418
|
+
* Throws `merkle/bad-input` if `leafHashes` is not an array or a leaf is not a
|
|
419
|
+
* Buffer / Uint8Array, `merkle/bad-hash-length` if a leaf is not exactly 32
|
|
420
|
+
* bytes, and `merkle/too-many-leaves` above `C.LIMITS.MERKLE_MAX_LEAVES`.
|
|
421
|
+
*
|
|
422
|
+
* @example
|
|
423
|
+
* var leaves = [Buffer.from([0]), Buffer.from([1])].map(pki.merkle.leafHash);
|
|
424
|
+
* pki.merkle.root(leaves); // -> <Buffer 32-byte tree head>
|
|
425
|
+
*/
|
|
426
|
+
function root(leafHashes) {
|
|
427
|
+
var leaves = _leafArray(leafHashes, "leafHashes");
|
|
428
|
+
if (leaves.length === 0) return emptyRootHash();
|
|
429
|
+
return _mth(leaves, 0, leaves.length);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* @primitive pki.merkle.inclusionProof
|
|
434
|
+
* @signature pki.merkle.inclusionProof(opts) -> Buffer[]
|
|
435
|
+
* @since 0.8.34
|
|
436
|
+
* @status stable
|
|
437
|
+
* @spec RFC 6962, RFC 9162
|
|
438
|
+
* @related pki.merkle.verifyInclusion, pki.merkle.root
|
|
439
|
+
*
|
|
440
|
+
* Produce the RFC 6962 sec. 2.1.1 audit path `PATH(leafIndex, D[n])` for one
|
|
441
|
+
* leaf of a tree held in memory. The returned array is the `proof` that
|
|
442
|
+
* `verifyInclusion` folds against `root(leafHashes)`.
|
|
443
|
+
*
|
|
444
|
+
* The tree size is the length of `leafHashes` and is not a separate option, so
|
|
445
|
+
* a size and a leaf array cannot disagree. Throws `merkle/empty-tree` on an
|
|
446
|
+
* empty tree, `merkle/index-out-of-range` for a `leafIndex` at or above the leaf
|
|
447
|
+
* count, and the `leafHashes` errors `root` throws.
|
|
448
|
+
*
|
|
449
|
+
* @opts
|
|
450
|
+
* leafHashes: Buffer[], // the whole tree, each leaf a 32-byte hash
|
|
451
|
+
* leafIndex: number | bigint, // 0-based position of the leaf to prove
|
|
452
|
+
*
|
|
453
|
+
* @example
|
|
454
|
+
* var leaves = [Buffer.from([0]), Buffer.from([1])].map(pki.merkle.leafHash);
|
|
455
|
+
* pki.merkle.inclusionProof({ leafHashes: leaves, leafIndex: 0 }); // -> [<Buffer ...>]
|
|
456
|
+
*/
|
|
457
|
+
var _INCLUSION_PROOF_KEYS = _assign(_create(null), { leafHashes: 1, leafIndex: 1 });
|
|
458
|
+
function inclusionProof(opts) {
|
|
459
|
+
opts = opts || {};
|
|
460
|
+
guard.identifier.assertKnownKeys(opts, _INCLUSION_PROOF_KEYS, _merkleErr,
|
|
461
|
+
"merkle/bad-input", "unknown inclusionProof option ");
|
|
462
|
+
var leaves = _leafArray(opts.leafHashes, "leafHashes");
|
|
463
|
+
var n = leaves.length;
|
|
464
|
+
if (n === 0) throw new MerkleError("merkle/empty-tree", "an empty tree has no leaves to include");
|
|
465
|
+
var leafIndex = _coerceCoord(opts.leafIndex, "leafIndex");
|
|
466
|
+
var m = guard.range.int(leafIndex, 0n, _BigInt(n - 1), _merkleErr,
|
|
467
|
+
"merkle/index-out-of-range", "leafIndex");
|
|
468
|
+
return _path(leaves, 0, n, m);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* @primitive pki.merkle.consistencyProof
|
|
473
|
+
* @signature pki.merkle.consistencyProof(opts) -> Buffer[]
|
|
474
|
+
* @since 0.8.34
|
|
475
|
+
* @status stable
|
|
476
|
+
* @spec RFC 6962, RFC 9162
|
|
477
|
+
* @related pki.merkle.verifyConsistency, pki.merkle.root
|
|
478
|
+
*
|
|
479
|
+
* Produce the RFC 6962 sec. 2.1.2 consistency proof `PROOF(oldSize, D[n])`
|
|
480
|
+
* between an older tree of `oldSize` leaves and the tree held in `leafHashes`.
|
|
481
|
+
* The returned array is the `proof` that `verifyConsistency` checks against
|
|
482
|
+
* `root(leafHashes.slice(0, oldSize))` and `root(leafHashes)`.
|
|
483
|
+
*
|
|
484
|
+
* The newer size is the length of `leafHashes` and is not a separate option.
|
|
485
|
+
* Equal sizes produce the empty proof, which is what the verifier requires
|
|
486
|
+
* there. Throws `merkle/old-size-exceeds-new` when `oldSize` is past the leaf
|
|
487
|
+
* count and `merkle/no-consistency-claim` for an `oldSize` of 0 against a
|
|
488
|
+
* non-empty tree, matching `verifyConsistency`: RFC 6962 sec. 2.1.2 defines the
|
|
489
|
+
* proof for `0 < oldSize < newSize`, so there is no proof to produce.
|
|
490
|
+
*
|
|
491
|
+
* @opts
|
|
492
|
+
* leafHashes: Buffer[], // the newer tree, each leaf a 32-byte hash
|
|
493
|
+
* oldSize: number | bigint, // leaf count of the older tree
|
|
494
|
+
*
|
|
495
|
+
* @example
|
|
496
|
+
* var leaves = [Buffer.from([0]), Buffer.from([1])].map(pki.merkle.leafHash);
|
|
497
|
+
* pki.merkle.consistencyProof({ leafHashes: leaves, oldSize: 1 }); // -> [<Buffer ...>]
|
|
498
|
+
*/
|
|
499
|
+
var _CONSISTENCY_PROOF_KEYS = _assign(_create(null), { leafHashes: 1, oldSize: 1 });
|
|
500
|
+
function consistencyProof(opts) {
|
|
501
|
+
opts = opts || {};
|
|
502
|
+
guard.identifier.assertKnownKeys(opts, _CONSISTENCY_PROOF_KEYS, _merkleErr,
|
|
503
|
+
"merkle/bad-input", "unknown consistencyProof option ");
|
|
504
|
+
var leaves = _leafArray(opts.leafHashes, "leafHashes");
|
|
505
|
+
var n = leaves.length;
|
|
506
|
+
var oldSize = _coerceCoord(opts.oldSize, "oldSize");
|
|
507
|
+
if (oldSize > _BigInt(n)) {
|
|
508
|
+
throw new MerkleError("merkle/old-size-exceeds-new", "oldSize " + oldSize + " exceeds newSize " + n);
|
|
509
|
+
}
|
|
510
|
+
if (oldSize === 0n) {
|
|
511
|
+
if (n === 0) return [];
|
|
512
|
+
throw new MerkleError("merkle/no-consistency-claim", NO_CONSISTENCY_CLAIM);
|
|
513
|
+
}
|
|
514
|
+
var m = guard.range.int(oldSize, 1n, _BigInt(n), _merkleErr, "merkle/bad-input", "oldSize");
|
|
515
|
+
if (m === n) return [];
|
|
516
|
+
return _subproof(leaves, 0, n, m, true);
|
|
517
|
+
}
|
|
518
|
+
|
|
316
519
|
module.exports = {
|
|
317
520
|
leafHash: leafHash,
|
|
318
521
|
nodeHash: nodeHash,
|
|
319
522
|
emptyRootHash: emptyRootHash,
|
|
523
|
+
root: root,
|
|
320
524
|
verifyInclusion: verifyInclusion,
|
|
321
525
|
verifyConsistency: verifyConsistency,
|
|
526
|
+
inclusionProof: inclusionProof,
|
|
527
|
+
consistencyProof: consistencyProof,
|
|
322
528
|
};
|