@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.
Files changed (87) hide show
  1. package/CHANGELOG.md +393 -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 +939 -48
  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
@@ -0,0 +1,708 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ /**
5
+ * @module pki.relatedCert
6
+ * @nav Signing
7
+ * @title Related certificates
8
+ * @fullname Related certificates: binding a second certificate of the same subject
9
+ * @order 236
10
+ * @slug related-cert
11
+ *
12
+ * @intro
13
+ * One subject, two certificates. A migration to post-quantum signatures leaves a
14
+ * subject holding a classical certificate and a PQC one at the same time, and a
15
+ * protocol that can use both needs each certificate to say the other exists.
16
+ * RFC 9763 does that in two structures: a CSR attribute where the requester
17
+ * proves it holds the certificate it is naming, and a certificate extension where
18
+ * the issuer records which certificate the new one relates to.
19
+ *
20
+ * `requestSignedData` builds the bytes the requester signs. Section 3.1 fixes
21
+ * them as the DER `IssuerAndSerialNumber` followed by the DER `BinaryTime`, and
22
+ * nothing else. `locationInfo` travels in the attribute and is outside those
23
+ * bytes, so a verified proof says the requester holds the certificate and says
24
+ * nothing about where that certificate can be fetched. `pki.csr.sign` takes the
25
+ * whole attribute as `relatedCertRequest` and encodes the first two fields from
26
+ * this same function, so the bytes signed and the bytes emitted cannot drift
27
+ * apart.
28
+ *
29
+ * `verifyRequest` checks that proof against the certificate `certID` names,
30
+ * through the engine the certificate path uses, so the advertised algorithm is
31
+ * bound to the key the same way. Handing it any other certificate throws: a proof
32
+ * checked against a certificate the request did not name answers a different
33
+ * question than the one asked. The structure names no algorithm, so the identifier
34
+ * comes from the same resolver the signing verbs use, which is what reaches every
35
+ * algorithm they sign with including an RSASSA-PSS key whose parameters belong to
36
+ * its identifier.
37
+ *
38
+ * `certificateHash` and `matchesCertificate` are the extension side.
39
+ * `pki.x509.sign` takes `extensions.relatedCertificate`, and the form handed the
40
+ * related certificate computes the digest here, so no caller can name one
41
+ * algorithm while carrying the output of another. The algorithm defaults the way
42
+ * section 4.1 directs, to the hash the related certificate's own signature OID
43
+ * indicates; a certificate signed with Ed25519 or ML-DSA indicates none, and that
44
+ * throws instead of falling back to a hash the document does not name. `sha1` is
45
+ * refused: a chosen-prefix collision on a certificate is a demonstrated attack.
46
+ *
47
+ * The extension is emitted non-critical, which section 4.1 asks for at SHOULD NOT,
48
+ * and a critical one still parses: `pki.lint.certificate` with the `rfc9763`
49
+ * profile grades it, alongside the section's MUST that the extension appear only
50
+ * in the end-entity certificate of a chain.
51
+ *
52
+ * @card Build the bytes a requester signs, verify the proof it made, and compute or
53
+ * check the digest that names a related certificate (RFC 9763).
54
+ */
55
+
56
+ var asn1 = require("./asn1-der");
57
+ var oid = require("./oid");
58
+ var pkix = require("./schema-pkix");
59
+ var schema = require("./schema-engine");
60
+ var schemaX509 = require("./schema-x509");
61
+ var frameworkError = require("./framework-error");
62
+ var guard = require("./guard-all");
63
+ var intrinsic = require("./guard-intrinsic");
64
+ var seam = require("./verify-seam");
65
+ var signScheme = require("./sign-scheme");
66
+ var C = require("./constants");
67
+
68
+ var RelatedCertError = frameworkError.RelatedCertError;
69
+ var b = asn1.build;
70
+ var _create = intrinsic.create;
71
+ var _assign = intrinsic.assign;
72
+ var _forEach = intrinsic.forEach;
73
+ var _isBuffer = intrinsic.isBuffer;
74
+ var _bufferFrom = intrinsic.bufferFrom;
75
+ var _bufferConcat = intrinsic.bufferConcat;
76
+ var _BigInt = intrinsic.BigInt;
77
+ var _floor = intrinsic.floor;
78
+ var _String = intrinsic.String;
79
+ var _keys = intrinsic.keys;
80
+ var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
81
+ var _charAt = intrinsic.uncurry(String.prototype.charAt);
82
+ var _fromCharCode = String.fromCharCode;
83
+
84
+ function _err(code, message, cause) { return new RelatedCertError(code, message, cause); }
85
+
86
+ var _seam = seam.makeSeam("related-cert", RelatedCertError, "relatedcert/bad-input");
87
+
88
+ var NS = pkix.makeNS("relatedcert", RelatedCertError, oid);
89
+ var ALGORITHM_IDENTIFIER = pkix.algorithmIdentifier(NS);
90
+ /** @internal The shared `IssuerAndSerialNumber`, used to read back the identifier this module just
91
+ * encoded so the identity comparison has the decoded `Name` the RFC 5280 sec. 7.1 rules need. It reads
92
+ * our OWN bytes, not a caller's, which is what makes a second decode safe here: the caller's record was
93
+ * read once on the way in and these bytes are the result. */
94
+ var ISSUER_AND_SERIAL = pkix.issuerAndSerialNumber(NS, { code: "relatedcert/bad-input", what: "certID" });
95
+ function _certIdParts(der) {
96
+ return schema.walk(ISSUER_AND_SERIAL, asn1.decode(der), NS).result;
97
+ }
98
+
99
+ var DIGEST_LENGTHS = pkix.digestLengths();
100
+ var SIG_OID_DIGEST = pkix.signatureDigestNames(oid);
101
+
102
+ /** @internal The digests this module will bind a certificate identity to. sha1 is registered and
103
+ * decodable, and is absent here: a chosen-prefix collision on a certificate is a demonstrated
104
+ * attack, so a value naming it is refused before anything is computed. A caller reading a hashValue a
105
+ * producer wrote under sha1 still gets the algorithm name off the parsed extension. */
106
+ var ALLOWED_DIGESTS = _assign(_create(null), {
107
+ sha256: 1, sha384: 1, sha512: 1, "sha3-256": 1, "sha3-512": 1,
108
+ });
109
+
110
+ /** @internal The two digests the substitution below resolves against. They only have to differ. */
111
+ var PROBE_DIGESTS = ["sha256", "sha512"];
112
+ /** @internal Whether naming a digest would change the algorithm identifier this key signs with, asked
113
+ * by running the substitution instead of listing which key kinds admit a choice. An EdDSA or ML-DSA
114
+ * key resolves to one identifier whatever digest is asked for, so naming one is a request that would
115
+ * be dropped; an RSA or ECDSA key resolves to a different identifier per digest. A key kind added to
116
+ * the signing side is covered here without a second table naming it. */
117
+ /** @internal The digest an id-RSASSA-PSS SPKI pins, or null when the key pins none or is not that
118
+ * algorithm. `signScheme.pssSpkiPinnedHash` assumes its argument IS an id-RSASSA-PSS key and complains
119
+ * about the parameters of anything else, so the algorithm is checked before it is asked. */
120
+ function _pinnedDigestName(certLike) {
121
+ var spki = certLike && certLike.subjectPublicKeyInfo;
122
+ var keyOid = spki && spki.algorithm ? spki.algorithm.oid : null;
123
+ if (keyOid === null || keyOid !== oid.byName("rsassaPss")) return null;
124
+ var pinned = signScheme.pssSpkiPinnedHash(certLike, _schemeErr);
125
+ if (pinned === null || pinned === undefined) return null;
126
+ /** @internal Returned in THIS module's vocabulary. The scheme layer answers in the WebCrypto spelling
127
+ * ("SHA-256") while a digestAlgorithm here is the lowercase registry name ("sha256"), so comparing the
128
+ * two directly reports a disagreement between two spellings of one hash. The match is made by
129
+ * normalizing both rather than by a second table that could drift from the first. */
130
+ var want = _normalizeDigestSpelling(pinned);
131
+ var names = _keys(ALLOWED_DIGESTS);
132
+ for (var i = 0; i < names.length; i++) {
133
+ if (_normalizeDigestSpelling(names[i]) === want) return names[i];
134
+ }
135
+ return null;
136
+ }
137
+ function _normalizeDigestSpelling(name) {
138
+ var out = "";
139
+ var s = _String(name);
140
+ for (var i = 0; i < s.length; i++) {
141
+ var c = _charCodeAt(s, i);
142
+ if (c === 0x2d) continue;
143
+ out += c >= 0x41 && c <= 0x5a ? _fromCharCode(c + 32) : _charAt(s, i);
144
+ }
145
+ return out;
146
+ }
147
+
148
+ function _digestChangesTheAlgorithm(certLike) {
149
+ /** @internal A key that RESTRICTS its hash is asked before it is probed. An id-RSASSA-PSS SPKI may pin
150
+ * one, and that key refuses every other digest outright, so the second probe threw before the digest the
151
+ * caller asked for was ever resolved and a valid proof under the key's own hash was reported as an
152
+ * unsupported algorithm. A pinned key admits no choice, which is the answer the probe computes for
153
+ * everything else, so it is returned directly rather than reached through a request the key forbids. */
154
+ if (_pinnedDigestName(certLike) !== null) return false;
155
+ var first = _resolveScheme(certLike, PROBE_DIGESTS[0]).sigAlgId;
156
+ var second = _resolveScheme(certLike, PROBE_DIGESTS[1]).sigAlgId;
157
+ return !guard.crypto.constantTimeEqual(first, second);
158
+ }
159
+
160
+ /** @internal `combinedRsaSig` selects the combined `sha256WithRSAEncryption` form an ASN.1 signature
161
+ * field carries, which is what `pki.csr.sign` and `pki.x509.sign` emit; without it an RSA key resolves
162
+ * to the bare `rsaEncryption` a CMS signerInfo uses beside a separate digest field. */
163
+ function _resolveScheme(certLike, digest) {
164
+ return signScheme.resolveSignScheme(certLike, { combinedRsaSig: true, digestAlgorithm: digest }, true, _schemeErr);
165
+ }
166
+
167
+ /** @internal `serialNumberHex` is here because a certID read back by a parser carries it beside the
168
+ * serial, and this encoder takes both a caller's description and a parsed record. It is redundant
169
+ * with `serialNumber`, which is the authority the encoder reads, so it is checked against that rather
170
+ * than accepted and ignored: a value disagreeing with the serial beside it describes two different
171
+ * certificates, and only one of them would be signed. */
172
+ var KNOWN_CERT_ID_KEYS = _assign(_create(null), { issuer: 1, serialNumber: 1, serialNumberHex: 1 });
173
+ var KNOWN_PREIMAGE_KEYS = _assign(_create(null), { certID: 1, requestTime: 1 });
174
+ var KNOWN_VERIFY_OPTS = _assign(_create(null), { digestAlgorithm: 1, signatureAlgorithm: 1, maxAge: 1, at: 1 });
175
+
176
+ /** @internal The freshness check RFC 9763 sec. 3.2 requires of the certification authority: "MUST check
177
+ * that the BinaryTime indicated in the requestTime field is sufficiently fresh." The WINDOW is not the
178
+ * toolkit's to choose, the same clause saying "sufficient freshness is defined by local policy and is out
179
+ * of the scope of this document", so `maxAge` carries it and there is no default: the proof covers the
180
+ * certID and the requestTime alone (sec. 3.1), so a captured attribute replayed into a fresh request for
181
+ * an attacker's key verifies forever without it, and a verifier that cannot perform a MUST check must not
182
+ * answer true. `at` is the instant judged against, for a caller reading a request recorded earlier. */
183
+ function _assertFresh(requestTime, o) {
184
+ if (o.maxAge === undefined) {
185
+ throw _err("relatedcert/no-freshness-policy",
186
+ "pki.relatedCert.verifyRequest needs opts.maxAge, the seconds a requestTime may be old: RFC 9763 " +
187
+ "sec. 3.2 requires the certification authority to check that it is sufficiently fresh and leaves " +
188
+ "the window to local policy, and the proof covers the certID and the requestTime alone, so a " +
189
+ "captured attribute replayed into a new request verifies forever without one");
190
+ }
191
+ var maxAge = guard.range.authoredInteger(o.maxAge, _err, "relatedcert/bad-input", "opts.maxAge");
192
+ if (maxAge < 0n) throw _err("relatedcert/bad-input", "opts.maxAge must not be negative");
193
+ var at = o.at === undefined ? new intrinsic.Date() : o.at;
194
+ if (!guard.time.isDate(at)) throw _err("relatedcert/bad-input", "opts.at must be a Date");
195
+ guard.time.assertValid(at, _err, "relatedcert/bad-input", "opts.at");
196
+ var atMs = guard.time.instantOf(at);
197
+ var atSecs = _BigInt(_floor(atMs / C.TIME.seconds(1)));
198
+ /** @internal Both ends are refused. A requestTime older than the window is the replay the clause is
199
+ * about; one in the FUTURE is a request whose own clock disagrees with the verifier's, and admitting it
200
+ * would let a producer set a time that stays fresh for as long as they chose. */
201
+ if (requestTime + maxAge < atSecs) {
202
+ throw _err("relatedcert/stale-request",
203
+ "the requestTime is older than the " + maxAge + " seconds opts.maxAge allows (RFC 9763 sec. 3.2)");
204
+ }
205
+ if (requestTime > atSecs) {
206
+ throw _err("relatedcert/stale-request",
207
+ "the requestTime is in the future, so it names no instant this verifier can judge as fresh");
208
+ }
209
+ }
210
+
211
+ var _NAME_SCHEMA = pkix.name(NS);
212
+
213
+ /** @internal A Name the caller supplied as DER, held to being one before it reaches a preimage the
214
+ * caller will sign. Bytes that are not a Name would be signed as whatever they are, and the proof
215
+ * would then attest to a structure no verifier can read back as an issuer. */
216
+ function _nameDer(value, what) {
217
+ var der = guard.bytes.snapshot(value, _err, "relatedcert/bad-input", what);
218
+ if (der.length === 0) throw _err("relatedcert/bad-input", what + " must not be empty");
219
+ var node;
220
+ try { node = asn1.decode(der); }
221
+ catch (e) { throw _err("relatedcert/bad-input", what + " must be a DER-encoded Name", e); }
222
+ if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children) {
223
+ throw _err("relatedcert/bad-input", what + " must be a Name, which is a SEQUENCE of RelativeDistinguishedName");
224
+ }
225
+ /** @internal An EMPTY Name is a readable Name and names no issuer, so it is refused here where the
226
+ * schema below is content with it: `30 00` is a SEQUENCE OF nothing. A certID identifies a
227
+ * certificate, and this toolkit's own signer refuses an empty issuer, so accepting it would sign an
228
+ * identifier for a certificate `pki.x509.sign` would not emit. An empty SUBJECT is a different
229
+ * question and stays legal, a certificate being allowed to carry its identity in a
230
+ * subjectAltName. */
231
+ if (node.children.length === 0) {
232
+ throw _err("relatedcert/bad-input", what + " is an empty Name, which identifies no issuer");
233
+ }
234
+ /** @internal The outer tag is not the type. A SEQUENCE holding anything at all passed this check, so
235
+ * bytes no verifier can read back as an issuer were signed as whatever they are: `30 02 05 00` is a
236
+ * SEQUENCE carrying a NULL, and `pki.csr.sign` under `profile: "none"` emitted a request this
237
+ * toolkit's own parser then refuses with `csr/bad-rdn`. The node is read with the SHARED X.509 Name
238
+ * schema, which is the reader on the other side, so what is signed here is what a verifier can
239
+ * read back. */
240
+ try { schema.walk(_NAME_SCHEMA, node, NS); }
241
+ catch (e) {
242
+ if (e instanceof RelatedCertError) throw e;
243
+ throw _err("relatedcert/bad-input", what + " is not a readable Name: a SEQUENCE of " +
244
+ "RelativeDistinguishedName, each a SET of AttributeTypeAndValue", e);
245
+ }
246
+ return der;
247
+ }
248
+
249
+ /** @internal BinaryTime ::= INTEGER (0..MAX), "the number of seconds, excluding leap seconds, after
250
+ * midnight UTC, January 1, 1970" (RFC 6019 sec. 2). A Date is read to whole seconds, which is the
251
+ * unit the type counts. A fractional number is refused: a truncation the caller did not ask for
252
+ * produces a signature over a time it did not name. */
253
+ function _binaryTime(value, what) {
254
+ if (intrinsic.types.isDate(value)) {
255
+ guard.time.assertValid(value, _err, "relatedcert/bad-input", what);
256
+ var secs = _floor(guard.time.instantOf(value) / C.TIME.seconds(1));
257
+ if (secs < 0) throw _err("relatedcert/bad-input", what + " must not precede the epoch (BinaryTime is INTEGER (0..MAX))");
258
+ return _BigInt(secs);
259
+ }
260
+ /** @internal A fractional number reaches the shared authored-integer guard and is refused there, so
261
+ * a truncation the caller did not ask for cannot become a signature over a time it did not name. */
262
+ var n = guard.range.authoredInteger(value, _err, "relatedcert/bad-input", what);
263
+ if (n < 0n) throw _err("relatedcert/bad-input", what + " must not be negative (BinaryTime is INTEGER (0..MAX))");
264
+ return n;
265
+ }
266
+
267
+ function _serialNumber(value, what) {
268
+ var n = guard.range.authoredInteger(value, _err, "relatedcert/bad-input", what);
269
+ if (n < 1n) throw _err("relatedcert/bad-input", what + " must be a positive integer (RFC 5280 sec. 4.1.2.2)");
270
+ return n;
271
+ }
272
+
273
+ /** @internal The IssuerAndSerialNumber DER of a certID the caller described, or of one a parser
274
+ * returned. A parsed record carries its issuer as a Name record whose `bytes` are the raw DER, so
275
+ * both shapes reach the same two reads and the encoder has one input form. */
276
+ function _certIdDer(certID) {
277
+ if (certID === null || typeof certID !== "object") {
278
+ throw _err("relatedcert/bad-input", "certID must be an object { issuer, serialNumber }");
279
+ }
280
+ var snap = guard.identifier.snapshotOptions(certID, KNOWN_CERT_ID_KEYS);
281
+ guard.identifier.assertKnownKeys(certID, KNOWN_CERT_ID_KEYS, _err, "relatedcert/bad-input",
282
+ "certID has an unknown field: ");
283
+ var issuer = snap.issuer;
284
+ if (issuer !== null && typeof issuer === "object" && !_isBuffer(issuer)) {
285
+ /** @internal A Name record from a parser. `bytes` is read ONCE: a presence test followed by a second
286
+ * read is two reads of one member, and an accessor could satisfy the test and then supply other
287
+ * bytes to encode. */
288
+ var issuerBytes = issuer.bytes;
289
+ if (issuerBytes !== undefined) issuer = issuerBytes;
290
+ }
291
+ var serial = _serialNumber(snap.serialNumber, "certID.serialNumber");
292
+ if (snap.serialNumberHex !== undefined && snap.serialNumberHex !== null) {
293
+ if (typeof snap.serialNumberHex !== "string") throw _err("relatedcert/bad-input", "certID.serialNumberHex must be a hex string");
294
+ var fromHex;
295
+ try { fromHex = _BigInt("0x" + snap.serialNumberHex); }
296
+ catch (e) { throw _err("relatedcert/bad-input", "certID.serialNumberHex must be a hex string", e); }
297
+ if (fromHex !== serial) {
298
+ throw _err("relatedcert/bad-input", "certID.serialNumberHex does not name the same serial as certID.serialNumber");
299
+ }
300
+ }
301
+ return b.sequence([b.raw(_nameDer(issuer, "certID.issuer")), b.integer(serial)]);
302
+ }
303
+
304
+ /**
305
+ * @primitive pki.relatedCert.requestSignedData
306
+ * @signature pki.relatedCert.requestSignedData(spec) -> Buffer
307
+ * @since 0.8.42
308
+ * @status stable
309
+ * @spec RFC 9763 sec. 3.1, RFC 6019 sec. 2
310
+ * @defends related-certificate-proof-replay (CWE-347)
311
+ * @related pki.relatedCert.verifyRequest, pki.csr.sign
312
+ *
313
+ * The bytes a `relatedCertRequest` proof is made over. RFC 9763 sec. 3.1 states them exactly: "the
314
+ * signature field contains a digital signature over the concatenation of DER-encoded
315
+ * IssuerAndSerialNumber and BinaryTime", signed with the key of the certificate `certID` names. `spec`
316
+ * takes `certID` (`{ issuer, serialNumber }`, where `issuer` is a `Name` as DER or a parsed `Name`
317
+ * record and `serialNumber` is a positive integer) and `requestTime` (a `Date`, or whole seconds since
318
+ * the epoch as a number or a bigint).
319
+ *
320
+ * `locationInfo` is outside these bytes, which is the scope the clause draws: a verified proof says
321
+ * the requester holds the certificate, and says nothing about where that certificate can be fetched.
322
+ * A caller that acts on `locationInfo` is acting on an unauthenticated field.
323
+ *
324
+ * @example
325
+ * var issuer = pki.x509.parseDn("CN=Example CA, O=Example");
326
+ * var signMe = pki.relatedCert.requestSignedData({
327
+ * certID: { issuer: issuer.bytes, serialNumber: 42n },
328
+ * requestTime: new Date("2027-01-15T00:00:00Z"),
329
+ * });
330
+ * signMe.length > 0; // -> true; sign these bytes with the key of certificate 42
331
+ */
332
+ function requestSignedData(spec) {
333
+ if (spec === null || typeof spec !== "object") {
334
+ throw _err("relatedcert/bad-input", "pki.relatedCert.requestSignedData takes a { certID, requestTime } object");
335
+ }
336
+ var snap = guard.identifier.snapshotOptions(spec, KNOWN_PREIMAGE_KEYS);
337
+ guard.identifier.assertKnownKeys(spec, KNOWN_PREIMAGE_KEYS, _err, "relatedcert/bad-input",
338
+ "pki.relatedCert.requestSignedData has an unknown option: ");
339
+ return _preimageFrom(_certIdDer(snap.certID), snap.requestTime);
340
+ }
341
+
342
+ /** @internal The preimage built from an ALREADY-ENCODED certID. The verifier encodes the identifier once
343
+ * and passes the same bytes here and to the binding comparison, so a caller whose `certID` or whose
344
+ * `issuer` / `serialNumber` inside it answers per read cannot have one identifier hashed and another
345
+ * compared. That difference is what would let a proof made for one certificate verify against a second
346
+ * one sharing its public key. */
347
+ function _preimageFrom(certIdDer, requestTime) {
348
+ return _bufferConcat([certIdDer, b.integer(_binaryTime(requestTime, "requestTime"))]);
349
+ }
350
+
351
+ /** @internal The digest name a caller supplied, held to the set this module will bind an identity to.
352
+ * A name outside it is refused whether the reason is that the algorithm is unregistered, that this
353
+ * build cannot compute it, or that it is sha1. */
354
+ function _digestName(value, what) {
355
+ if (typeof value !== "string") throw _err("relatedcert/bad-input", what + " must be a digest algorithm name");
356
+ if (!ALLOWED_DIGESTS[value]) {
357
+ throw _err("relatedcert/bad-input", what + " must name one of sha256 / sha384 / sha512 / sha3-256 / sha3-512; got " + guard.text.showValue(value));
358
+ }
359
+ return value;
360
+ }
361
+
362
+ /** @internal A certificate the caller handed in, parsed once. The full parse is what holds the bytes
363
+ * to being a certificate at all, so a hash is never taken over something that merely starts like one. */
364
+ function _parseCert(certDer, what) {
365
+ var der = guard.bytes.snapshot(certDer, _err, "relatedcert/bad-input", what);
366
+ try { return { der: der, parsed: schemaX509.parse(der) }; }
367
+ catch (e) { throw _err("relatedcert/bad-input", what + " must be a DER-encoded X.509 certificate", e); }
368
+ }
369
+
370
+ /**
371
+ * @primitive pki.relatedCert.certificateHash
372
+ * @signature pki.relatedCert.certificateHash(certificate, digestAlgorithm?) -> { hashAlgorithm, hashValue }
373
+ * @since 0.8.42
374
+ * @status stable
375
+ * @spec RFC 9763 sec. 4.1
376
+ * @defends related-certificate-substitution (CWE-347)
377
+ * @related pki.relatedCert.matchesCertificate, pki.x509.sign
378
+ *
379
+ * The `RelatedCertificate` value naming a certificate: the digest of the whole certificate, with the
380
+ * algorithm that produced it. RFC 9763 sec. 4.1 hashes "the entire related certificate", so the input
381
+ * is the certificate's full DER rather than its `tbsCertificate`.
382
+ *
383
+ * With no `digestAlgorithm`, the algorithm is derived the way sec. 4.1 directs: "If there is a hash
384
+ * algorithm explicitly indicated by the related certificate's signature OID (e.g.,
385
+ * ecdsa-with-SHA512), that hash algorithm SHOULD also be used for this extension." A certificate
386
+ * whose signature OID indicates no hash, such as one signed with Ed25519 or ML-DSA, leaves that
387
+ * derivation without an answer and throws `relatedcert/no-digest`; name the algorithm to resolve it.
388
+ *
389
+ * `sha256`, `sha384`, `sha512`, `sha3-256` and `sha3-512` are accepted. `sha1` is refused, including
390
+ * when it is what the certificate's own signature OID indicates.
391
+ *
392
+ * @example
393
+ * var kp = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
394
+ * var spki = await pki.key.export(kp.publicKey, { format: "der" });
395
+ * var related = await pki.x509.sign({ subject: "CN=Held", subjectPublicKey: spki, serialNumber: 42n,
396
+ * notBefore: new Date("2027-01-01T00:00:00Z"), notAfter: new Date("2028-01-01T00:00:00Z") }, { key: kp.privateKey });
397
+ * pki.relatedCert.certificateHash(related).hashAlgorithm; // -> "sha256", from this certificate's signature OID
398
+ * pki.relatedCert.certificateHash(related, "sha512").hashValue.length; // -> 64
399
+ */
400
+ function certificateHash(certificate, digestAlgorithm) {
401
+ var c = _parseCert(certificate, "the related certificate");
402
+ var name;
403
+ if (digestAlgorithm === undefined || digestAlgorithm === null) {
404
+ var indicated = SIG_OID_DIGEST[c.parsed.signatureAlgorithm.oid];
405
+ if (indicated === undefined) {
406
+ throw _err("relatedcert/no-digest",
407
+ "the related certificate's signature algorithm (" + (c.parsed.signatureAlgorithm.name || c.parsed.signatureAlgorithm.oid) +
408
+ ") indicates no hash algorithm, so name one (RFC 9763 sec. 4.1)");
409
+ }
410
+ if (!ALLOWED_DIGESTS[indicated]) {
411
+ throw _err("relatedcert/bad-input",
412
+ "the related certificate's signature algorithm indicates " + indicated +
413
+ ", which is not used to bind a certificate identity here, so name a digest algorithm");
414
+ }
415
+ name = indicated;
416
+ } else {
417
+ name = _digestName(digestAlgorithm, "digestAlgorithm");
418
+ }
419
+ return { hashAlgorithm: name, hashValue: guard.crypto.digest(name, c.der) };
420
+ }
421
+
422
+ /** @internal A digest value with the algorithm that produced it, held to the set this module acts on
423
+ * and to that algorithm's output length. Three callers read a value this way: the extension builder
424
+ * before emitting one, the comparison below before trusting one, and the parser through the length
425
+ * table they share, so a length rule cannot hold on one side of the round trip and not the other. */
426
+ function digestValue(hashAlgorithm, hashValue) {
427
+ var name = _digestName(hashAlgorithm, "the hashAlgorithm");
428
+ var value = guard.bytes.snapshot(hashValue, _err, "relatedcert/bad-input", "the hashValue");
429
+ var want = DIGEST_LENGTHS[name];
430
+ if (want !== undefined && value.length !== want) {
431
+ throw _err("relatedcert/bad-input", "the hashValue is " + value.length + " octets, and " + name + " produces " + want);
432
+ }
433
+ if (value.length === 0) throw _err("relatedcert/bad-input", "the hashValue must not be empty");
434
+ return { hashAlgorithm: name, hashValue: value };
435
+ }
436
+
437
+ /**
438
+ * @primitive pki.relatedCert.matchesCertificate
439
+ * @signature pki.relatedCert.matchesCertificate(relatedCertificate, certificate) -> boolean
440
+ * @since 0.8.42
441
+ * @status stable
442
+ * @spec RFC 9763 sec. 4.1
443
+ * @defends related-certificate-substitution (CWE-347)
444
+ * @related pki.relatedCert.certificateHash, pki.schema.x509.parse
445
+ *
446
+ * Whether a parsed `relatedCertificate` extension value names the given certificate. Takes the value
447
+ * a `pki.schema.x509.parse` extension carries, `{ hashAlgorithm, hashValue }`, and the candidate
448
+ * certificate's DER, recomputes the digest under the algorithm the value names, and compares in
449
+ * constant time.
450
+ *
451
+ * A value this build cannot act on throws. A `false` from an algorithm nobody computed says the
452
+ * certificate does not match, when what happened is that no comparison was made. Only a digest that
453
+ * was computed and disagreed returns `false`.
454
+ *
455
+ * @example
456
+ * var kp = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
457
+ * var spki = await pki.key.export(kp.publicKey, { format: "der" });
458
+ * var related = await pki.x509.sign({ subject: "CN=Held", subjectPublicKey: spki, serialNumber: 42n,
459
+ * notBefore: new Date("2027-01-01T00:00:00Z"), notAfter: new Date("2028-01-01T00:00:00Z") }, { key: kp.privateKey });
460
+ * var leaf = await pki.x509.sign({ subject: "CN=New Key", subjectPublicKey: spki, serialNumber: 43n,
461
+ * notBefore: new Date("2027-01-01T00:00:00Z"), notAfter: new Date("2028-01-01T00:00:00Z"),
462
+ * extensions: { relatedCertificate: { relatedCertificate: related } } }, { key: kp.privateKey });
463
+ * var ext = pki.schema.x509.parse(leaf).extensions
464
+ * .filter(function (e) { return e.oid === pki.oid.byName("relatedCertificate"); })[0];
465
+ * pki.relatedCert.matchesCertificate(pki.schema.x509.decodeExtension(ext).decoded, related); // -> true
466
+ */
467
+ function matchesCertificate(relatedCertificate, certificate) {
468
+ if (relatedCertificate === null || typeof relatedCertificate !== "object") {
469
+ throw _err("relatedcert/bad-input", "the relatedCertificate value must be an object { hashAlgorithm, hashValue }");
470
+ }
471
+ var v = digestValue(relatedCertificate.hashAlgorithm, relatedCertificate.hashValue);
472
+ var c = _parseCert(certificate, "the candidate certificate");
473
+ /** @internal Through the captured digest. Capturing `createHash` alone leaves `update` and `digest`
474
+ * on the live hash prototype, so a replacement decided what the comparison was OVER: measured with
475
+ * two certificates differing only in serial number, an `update` that hashed the first turned the
476
+ * second's verdict from false to true, which is the binding this extension exists to make. */
477
+ return guard.crypto.constantTimeEqual(guard.crypto.digest(v.hashAlgorithm, c.der), v.hashValue);
478
+ }
479
+
480
+ /** @internal The AlgorithmIdentifier the proof's key signs with, resolved through the same scheme
481
+ * resolver every signing verb here uses, so the identifier a proof is verified under is the one this
482
+ * toolkit would have emitted for that key. Assembling it by hand from a key OID and a digest name is
483
+ * what produced an identifier with the parameters field ABSENT, which RFC 4055 sec. 5 requires to be
484
+ * an explicit NULL for the RSA algorithms; every RSA proof then failed to verify.
485
+ *
486
+ * The KEY decides the algorithm, not the certificate's signatureAlgorithm: that field names what the
487
+ * ISSUER signed the certificate with, whose key may be of another kind entirely, while the proof is
488
+ * made by the SUBJECT's key. The DIGEST still comes from that field, which is the relation sec. 4.1
489
+ * names. */
490
+ function _proofAlgorithm(parsed, digestAlgorithm) {
491
+ var keyName = parsed.subjectPublicKeyInfo.algorithm.name || parsed.subjectPublicKeyInfo.algorithm.oid;
492
+ var digest = null;
493
+ if (digestAlgorithm === undefined || digestAlgorithm === null) {
494
+ var indicated = SIG_OID_DIGEST[parsed.signatureAlgorithm.oid];
495
+ if (indicated !== undefined) {
496
+ digest = _digestName(indicated, "the digest the related certificate's signature algorithm indicates");
497
+ }
498
+ } else {
499
+ digest = _digestName(digestAlgorithm, "digestAlgorithm");
500
+ }
501
+
502
+ var certLike = { subjectPublicKeyInfo: parsed.subjectPublicKeyInfo };
503
+ var takesADigest;
504
+ try { takesADigest = _digestChangesTheAlgorithm(certLike); }
505
+ catch (e) {
506
+ throw _err("relatedcert/unsupported-algorithm",
507
+ "a proof made by a " + keyName + " key is not resolved here, so name signatureAlgorithm as a DER AlgorithmIdentifier",
508
+ e);
509
+ }
510
+ if (!takesADigest) {
511
+ /** @internal A key that admits no choice comes in two kinds, and they answer a named digest
512
+ * differently. A key whose algorithm carries its own digest (EdDSA, ML-DSA) can be handed no digest at
513
+ * all, so naming one is a request that would be dropped. A key whose SPKI PINS a digest has exactly
514
+ * one, and naming THAT one agrees with the key: refusing it would reject the only digest the key can
515
+ * be used with, while naming a different one is the real disagreement and is named as such. */
516
+ var pinnedDigest = _pinnedDigestName(certLike);
517
+ if (digestAlgorithm !== undefined && digestAlgorithm !== null) {
518
+ if (pinnedDigest === null) {
519
+ throw _err("relatedcert/bad-input",
520
+ "a " + keyName + " key admits no digest choice, so digestAlgorithm must be omitted");
521
+ }
522
+ if (digest !== pinnedDigest) {
523
+ throw _err("relatedcert/bad-input", "the " + keyName + " key is restricted to " + pinnedDigest +
524
+ ", so digestAlgorithm must be that or omitted, not " + digest);
525
+ }
526
+ }
527
+ return _readAlgorithmIdentifier(
528
+ _resolveScheme(certLike, pinnedDigest === null ? PROBE_DIGESTS[0] : pinnedDigest).sigAlgId, keyName);
529
+ }
530
+ if (digest === null) {
531
+ throw _err("relatedcert/no-digest",
532
+ "the related certificate's signature algorithm indicates no hash algorithm, so name digestAlgorithm (RFC 9763 sec. 4.1)");
533
+ }
534
+ var scheme;
535
+ try { scheme = _resolveScheme(certLike, digest); }
536
+ catch (e) {
537
+ throw _err("relatedcert/unsupported-algorithm",
538
+ "no signature algorithm pairs a " + keyName + " key with " + digest, e);
539
+ }
540
+ return _readAlgorithmIdentifier(scheme.sigAlgId, keyName);
541
+ }
542
+
543
+ function _schemeErr(kind, message, cause) { return new RelatedCertError("relatedcert/" + kind, message, cause); }
544
+
545
+ /** @internal The resolver hands back the AlgorithmIdentifier as DER, and the seam reads a decoded
546
+ * record, so it is read through the shared sub-schema: the parameters field then reaches the engine as
547
+ * the resolver wrote it. */
548
+ function _readAlgorithmIdentifier(der, keyName) {
549
+ try { return schema.walk(ALGORITHM_IDENTIFIER, asn1.decode(der), NS).result; }
550
+ catch (e) {
551
+ throw _err("relatedcert/unsupported-algorithm",
552
+ "the signature algorithm resolved for a " + keyName + " key is not a readable AlgorithmIdentifier", e);
553
+ }
554
+ }
555
+
556
+ /** @internal An AlgorithmIdentifier the caller named as DER, read through the shared sub-schema so it
557
+ * is held to the same shape a parsed one is. */
558
+ function _namedAlgorithm(value) {
559
+ var der = guard.bytes.snapshot(value, _err, "relatedcert/bad-input", "signatureAlgorithm");
560
+ try { return schema.walk(ALGORITHM_IDENTIFIER, asn1.decode(der), NS).result; }
561
+ catch (e) { throw _err("relatedcert/bad-input", "signatureAlgorithm must be a DER-encoded AlgorithmIdentifier", e); }
562
+ }
563
+
564
+ /**
565
+ * @primitive pki.relatedCert.verifyRequest
566
+ * @signature pki.relatedCert.verifyRequest(requesterCertificate, certificate, opts?) -> Promise<boolean>
567
+ * @since 0.8.42
568
+ * @status stable
569
+ * @spec RFC 9763 sec. 3.1
570
+ * @defends related-certificate-proof-forgery (CWE-347)
571
+ * @related pki.relatedCert.requestSignedData, pki.schema.csr.parse, pki.path.validate
572
+ *
573
+ * Verify the proof of possession in a `relatedCertRequest` CSR attribute. `requesterCertificate` is
574
+ * the value `pki.schema.csr.parse` carries on that attribute, and `certificate` is the DER of the
575
+ * certificate its `certID` names. Returns `true` when the signature over the bytes
576
+ * `pki.relatedCert.requestSignedData` describes verifies under that certificate's subject key, and
577
+ * `false` when a signature was checked and did not verify.
578
+ *
579
+ * The certificate handed in must be the one `certID` names: an issuer and serial that do not match
580
+ * throw `relatedcert/cert-mismatch`, since verifying a proof against a certificate the request did
581
+ * not name answers a different question than the one asked.
582
+ *
583
+ * The structure carries no algorithm identifier, so one is derived: the key decides the algorithm,
584
+ * resolved through the same resolver `pki.x509.sign` and `pki.csr.sign` use, and the digest comes from
585
+ * the hash the certificate's signature OID indicates, which is the relation sec. 4.1 names for the
586
+ * sibling extension. Every algorithm those verbs sign with is therefore reached, including an
587
+ * RSASSA-PSS key whose parameters are part of its algorithm identifier. `opts.digestAlgorithm` names
588
+ * the digest instead, and a key whose algorithm fixes its own digest, such as an EdDSA or ML-DSA key,
589
+ * refuses that option instead of ignoring it. `opts.signatureAlgorithm` takes a DER
590
+ * `AlgorithmIdentifier` outright. A key that cannot sign, such as an ML-KEM or X25519 key, throws
591
+ * `relatedcert/unsupported-algorithm`.
592
+ *
593
+ * What the proof covers is `certID` and `requestTime` only. `locationInfo` is outside it, so a
594
+ * `true` here does not authenticate where the certificate may be fetched.
595
+ *
596
+ * The signature is read the way every other ASN.1 signature field is, so an ECDSA proof is the DER
597
+ * `SEQUENCE { r, s }` of RFC 5480 sec. 2, not the fixed-width `r || s` a WebCrypto `sign` returns.
598
+ * Handing over the fixed-width form resolves `false` rather than throwing, because a signature that
599
+ * does not decode is a signature that does not verify; the example re-encodes it.
600
+ *
601
+ * `opts.maxAge` is REQUIRED, in seconds. RFC 9763 sec. 3.2 makes the freshness check the certification
602
+ * authority's: it "MUST check that the BinaryTime indicated in the requestTime field is sufficiently
603
+ * fresh", and the same clause leaves the window to local policy, so there is no window this verb could
604
+ * choose for you. Without one a captured attribute copied into a new request for another key verifies
605
+ * forever, the proof covering the certID and the requestTime alone, so a missing policy is
606
+ * `relatedcert/no-freshness-policy` rather than a default. A `requestTime` older than the window, or in the
607
+ * future, is `relatedcert/stale-request`.
608
+ *
609
+ * @opts maxAge REQUIRED. The seconds a `requestTime` may be old, which RFC 9763 sec. 3.2 leaves to local policy. A whole non-negative integer.
610
+ * @opts at The instant to judge freshness against, as a `Date`. The current time when omitted; pass one to read a request recorded earlier.
611
+ * @opts digestAlgorithm The digest for the proof: `"sha256"` / `"sha384"` / `"sha512"` / `"sha3-256"` / `"sha3-512"`. Derived from the certificate's signature OID when omitted, and refused for a key whose algorithm fixes its own digest.
612
+ * @opts signatureAlgorithm A DER `AlgorithmIdentifier` naming the proof's signature algorithm outright, in place of the derivation.
613
+ *
614
+ * @example
615
+ * var kp = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
616
+ * var spki = await pki.key.export(kp.publicKey, { format: "der" });
617
+ * var held = await pki.x509.sign({ subject: "CN=Held", subjectPublicKey: spki, serialNumber: 42n,
618
+ * notBefore: new Date("2027-01-01T00:00:00Z"), notAfter: new Date("2028-01-01T00:00:00Z") }, { key: kp.privateKey });
619
+ * var parsed = pki.schema.x509.parse(held);
620
+ * var certID = { issuer: parsed.issuer.bytes, serialNumber: parsed.serialNumber };
621
+ * var signMe = pki.relatedCert.requestSignedData({ certID: certID, requestTime: 1800000000 });
622
+ * // An ECDSA proof goes into the BIT STRING as DER SEQUENCE { r, s }. WebCrypto returns the
623
+ * // fixed-width r || s instead, so it is re-encoded here.
624
+ * var raw = Buffer.from(await pki.webcrypto.subtle.sign({ name: "ECDSA", hash: "SHA-256" }, kp.privateKey, signMe));
625
+ * var half = raw.length / 2;
626
+ * var proof = pki.asn1.build.sequence([
627
+ * pki.asn1.build.integer(BigInt("0x" + raw.subarray(0, half).toString("hex"))),
628
+ * pki.asn1.build.integer(BigInt("0x" + raw.subarray(half).toString("hex"))),
629
+ * ]);
630
+ * var csr = await pki.csr.sign({ subject: "CN=New Key", subjectPublicKey: spki,
631
+ * relatedCertRequest: { certID: certID, requestTime: 1800000000,
632
+ * locationInfo: ["https://certs.example/held.cer"], signature: proof } }, { key: kp.privateKey });
633
+ * var attr = pki.schema.csr.parse(csr).attributes
634
+ * .filter(function (a) { return a.type === pki.oid.byName("relatedCertRequest"); })[0];
635
+ * // The window is the certification authority's policy, so it is named: five minutes here, judged
636
+ * // against the instant the request was made, which is what a CA reading a recorded request does.
637
+ * await pki.relatedCert.verifyRequest(attr.relatedCertRequest, held,
638
+ * { maxAge: 300, at: new Date(1800000060 * 1000) }); // -> true
639
+ */
640
+ function verifyRequest(requesterCertificate, certificate, opts) {
641
+ /** @internal Every refusal reaches the caller as a rejection, so a verb that returns a promise never
642
+ * also throws past it: a caller handling one path and not the other would miss the refusals. */
643
+ return guard.async.deferred(function () { return _verifyRequest(requesterCertificate, certificate, opts); });
644
+ }
645
+
646
+ function _verifyRequest(requesterCertificate, certificate, opts) {
647
+ if (requesterCertificate === null || typeof requesterCertificate !== "object") {
648
+ throw _err("relatedcert/bad-input", "pki.relatedCert.verifyRequest takes the parsed relatedCertRequest attribute value");
649
+ }
650
+ var o = guard.identifier.snapshotOptions(opts, KNOWN_VERIFY_OPTS);
651
+ guard.identifier.assertKnownKeys(opts, KNOWN_VERIFY_OPTS, _err, "relatedcert/bad-input",
652
+ "pki.relatedCert.verifyRequest has an unknown option: ");
653
+
654
+ var c = _parseCert(certificate, "the related certificate");
655
+ /** @internal The preimage is rebuilt from the request's own fields, so what is verified is what the
656
+ * request says, not what the certificate says: a mismatch between the two is reported below, and
657
+ * hashing the certificate's values would cover it over. */
658
+ var named = _certIdDer(requesterCertificate.certID);
659
+ /** @internal Read ONCE into the value both the freshness check and the preimage use, so a request whose
660
+ * `requestTime` is an accessor cannot present a fresh instant to the check and a different one to the
661
+ * bytes the proof is verified over. */
662
+ var requestSecs = _binaryTime(requesterCertificate.requestTime, "requestTime");
663
+ _assertFresh(requestSecs, o);
664
+ var preimage = _bufferConcat([named, b.integer(requestSecs)]);
665
+ /** @internal The IDENTITY check is by name, not by bytes. RFC 9763 sec. 3.2 says the CA "extracts the
666
+ * IssuerAndSerialNumber from the indicated certificate and compares this value against the
667
+ * IssuerAndSerialNumber provided in the certID field", and `issuer` is a `Name`, so RFC 5280 sec. 7.1
668
+ * governs: two encodings of one name are one name. Comparing the DER refused a conforming request, a
669
+ * certificate holding its issuer CN as a PrintableString against a request naming the same CN as a
670
+ * UTF8String. The PREIMAGE above is still the request's own bytes, which is what sec. 3.1 says the
671
+ * signature covers, so only the comparison folds encodings and the signed bytes are untouched.
672
+ * The comparison is no longer constant-time and does not need to be: an issuer name and a serial
673
+ * number are both carried in the certificate and in the request, so there is no secret for an early
674
+ * exit to leak. The constant-time compare remains where it answers about a digest. */
675
+ var namedParts = _certIdParts(named);
676
+ if (namedParts.serialNumber !== c.parsed.serialNumber ||
677
+ !guard.name.dnEqual(namedParts.issuer.rdns, c.parsed.issuer.rdns, _err,
678
+ "relatedcert/cert-mismatch", "the certID issuer")) {
679
+ throw _err("relatedcert/cert-mismatch",
680
+ "the certificate given is not the one certID names (RFC 9763 sec. 3.2)");
681
+ }
682
+
683
+ var sigAlg = o.signatureAlgorithm !== undefined && o.signatureAlgorithm !== null
684
+ ? _namedAlgorithm(o.signatureAlgorithm)
685
+ : _proofAlgorithm(c.parsed, o.digestAlgorithm);
686
+
687
+ var sig = requesterCertificate.signature;
688
+ /** @internal `bytes` and `unusedBits` are read ONCE each, for the reason given at `certID.issuer`. */
689
+ var sigBytes = sig !== null && typeof sig === "object" && !_isBuffer(sig) ? sig.bytes : undefined;
690
+ if (sigBytes !== undefined) {
691
+ sig = { unusedBits: sig.unusedBits, bytes: guard.bytes.snapshot(sigBytes, _err, "relatedcert/bad-input", "the proof signature") };
692
+ } else {
693
+ sig = { unusedBits: 0, bytes: guard.bytes.snapshot(sig, _err, "relatedcert/bad-input", "the proof signature") };
694
+ }
695
+ if (sig.bytes.length === 0) throw _err("relatedcert/bad-input", "the proof signature must not be empty");
696
+
697
+ return _seam.verify(sigAlg, sig, c.parsed.subjectPublicKeyInfo.bytes, preimage);
698
+ }
699
+
700
+ module.exports = {
701
+ requestSignedData: requestSignedData,
702
+ verifyRequest: verifyRequest,
703
+ certificateHash: certificateHash,
704
+ matchesCertificate: matchesCertificate,
705
+ /** @internal Read a digest value the way the comparison reads one; not on the curated surface. */
706
+ digestValue: digestValue,
707
+ setEngine: _seam.setEngine,
708
+ };