@blamejs/pki 0.8.30 → 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 +411 -0
  2. package/README.md +67 -16
  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 +28 -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 +448 -2
  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 +267 -4
  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,575 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ /**
5
+ * @module pki.possession
6
+ * @nav Enrollment
7
+ * @title Possession statements
8
+ * @order 238
9
+ * @slug possession
10
+ * @fullname Statement of possession: certifying a key that cannot sign
11
+ *
12
+ * @intro
13
+ * PKCS#10 proves possession of the key being certified by signing the request
14
+ * with it, which a key-establishment key cannot do. An ML-KEM, X25519 or X448 key
15
+ * therefore cannot be certified through a plain PKCS#10 request at all. RFC 9883
16
+ * answers that by signing the request with a DIFFERENT key, one the requester
17
+ * already holds a certificate for, and naming that certificate in an attribute:
18
+ *
19
+ * PrivateKeyPossessionStatement ::= SEQUENCE {
20
+ * signer IssuerAndSerialNumber,
21
+ * cert Certificate OPTIONAL }
22
+ *
23
+ * So the request's own signature is the proof, made by a key that is not the
24
+ * subject's. `pki.csr.sign` takes the statement as
25
+ * `spec.privateKeyPossessionStatement` and signs with the key given, which is the
26
+ * one case where it accepts a signing key that is not the subject's and a subject
27
+ * key that cannot sign. Without the statement both are still refused, so the
28
+ * relaxation is scoped to the request that declares it. The same value is a CRMF
29
+ * registration control, which is why `parse` reads either.
30
+ *
31
+ * `verifyRequest` is the CA's side, and it applies the two requirements RFC 9883
32
+ * states as MUSTs: the signature on the request is validated with the public key
33
+ * from the signature certificate, and that certificate's certification path is
34
+ * validated. Path validation is not optional, so the verb needs `trustAnchors` and
35
+ * refuses without them rather than answering a question it did not fully ask.
36
+ *
37
+ * The two name comparisons the RFC states as SHOULDs end in a question a library
38
+ * cannot answer: "If they are different, the certificate policy MUST describe how
39
+ * the CA can determine that the two subject names identify the same entity." So
40
+ * `subjectMatches` and `subjectAltNamesMatch` are REPORTED, and a request whose
41
+ * names differ resolves `valid: false` with the reason naming the policy decision
42
+ * rather than being refused outright. A caller whose policy resolves it reads the
43
+ * fields and decides.
44
+ *
45
+ * `requestsSignatureCertificate` reports the one MUST NOT: the attribute is for a
46
+ * key-establishment certificate, so a request that asks for a signature key usage,
47
+ * or that asks to certify a subject key which can only ever sign, is the misuse the
48
+ * RFC forbids.
49
+ *
50
+ * @card Read and check an RFC 9883 statement of possession, the attribute that lets a
51
+ * key-establishment key be certified by a request another key signed.
52
+ */
53
+
54
+ var asn1 = require("./asn1-der");
55
+ var constants = require("./constants");
56
+ var oid = require("./oid");
57
+ var pkix = require("./schema-pkix");
58
+ var schema = require("./schema-engine");
59
+ var schemaX509 = require("./schema-x509");
60
+ var schemaCsr = require("./schema-csr");
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
+
66
+ var PossessionError = frameworkError.PossessionError;
67
+ var _create = intrinsic.create;
68
+ var _assign = intrinsic.assign;
69
+ var _forEach = intrinsic.forEach;
70
+ var _map = intrinsic.map;
71
+ var _isArray = intrinsic.isArray;
72
+ var _promiseResolve = intrinsic.uncurry(intrinsic.promiseResolve);
73
+ /** @internal The promise constructor captured at load, used as the RECEIVER for the captured
74
+ * `Promise.resolve`. Handing it the live `globalThis.Promise` instead put the capture back under a
75
+ * replaceable constructor: `Promise.resolve` builds through its receiver, so a replacement installed
76
+ * after load decided what the awaited value became. Here that value is a path-validation result, and a
77
+ * constructor substituting `{ valid: true }` for it made a correctly signed request report `valid`
78
+ * while its signature certificate chained to an untrusted CA, which is the RFC 9883 sec. 4 MUST this
79
+ * verb exists to apply. */
80
+ var _Promise = intrinsic.Promise;
81
+ var _push = intrinsic.push;
82
+ var _join = intrinsic.join;
83
+ var _every = intrinsic.every;
84
+ var _filter = intrinsic.filter;
85
+ var _keys = intrinsic.keys;
86
+ var _hasOwn = intrinsic.hasOwn;
87
+ var _strIndexOf = intrinsic.uncurry(String.prototype.indexOf);
88
+ var _bufToString = intrinsic.bufToString;
89
+
90
+ function _err(code, message, cause) { return new PossessionError(code, message, cause); }
91
+
92
+ var _seam = seam.makeSeam("possession", PossessionError, "possession/bad-input");
93
+ /** @internal Set by path-validate at load, so the path check runs through the same validator a caller
94
+ * would call and this module does not hold a second copy of it. */
95
+ var _validatePath = null;
96
+ function setEngine(engine) { _seam.setEngine(engine.verifyWithSpki); _validatePath = engine.validate; }
97
+
98
+ var OID_SOP = oid.byName("statementOfPossession");
99
+ if (!OID_SOP) throw new Error("possession: the RFC 9883 attribute OID is not in the registry");
100
+
101
+ var NS = pkix.makeNS("possession", PossessionError, oid);
102
+ /** @internal The signer is an IssuerAndSerialNumber, the same type CMS names and RFC 9763 reuses, read
103
+ * through the one shared definition so the three cannot diverge on the structure. */
104
+ var STATEMENT = schema.seq([
105
+ schema.field("signer", pkix.issuerAndSerialNumber(NS, { code: "possession/bad-statement" })),
106
+ schema.optional("certificate", schema.any(), { whenUniversal: [asn1.TAGS.SEQUENCE] }),
107
+ ], {
108
+ assert: "sequence", arity: { min: 1, max: 2 },
109
+ code: "possession/bad-statement", what: "PrivateKeyPossessionStatement",
110
+ build: function (m) {
111
+ return {
112
+ signer: m.fields.signer.value.result,
113
+ certificate: m.fields.certificate.present ? m.fields.certificate.node.bytes : null,
114
+ };
115
+ },
116
+ });
117
+
118
+ var KNOWN_VERIFY_OPTS = _assign(_create(null), {
119
+ trustAnchors: 1, time: 1, signatureCertificate: 1, intermediates: 1,
120
+ });
121
+
122
+ /** @internal Whether a certificate is the one an IssuerAndSerialNumber names. Both halves are compared,
123
+ * because a serial is unique only within an issuer. The DN comparison is the canonical one RFC 5280
124
+ * sec. 7.1 states, through the shared guard, so two spellings of one name are one name. */
125
+ function _namesCertificate(signer, certParsed) {
126
+ if (signer.serialNumber !== certParsed.serialNumber) return false;
127
+ try {
128
+ return guard.name.dnEqual(signer.issuer.rdns, certParsed.issuer.rdns,
129
+ _err, "possession/bad-input", "the signer issuer name") === true;
130
+ } catch (_e) { return false; }
131
+ }
132
+
133
+ /**
134
+ * @primitive pki.possession.parse
135
+ * @signature pki.possession.parse(value) -> { signer, certificate }
136
+ * @since 0.8.44
137
+ * @status stable
138
+ * @spec RFC 9883 sec. 3
139
+ * @defends possession-statement-confusion (CWE-347)
140
+ * @related pki.possession.verifyRequest, pki.csr.sign, pki.crmf.build
141
+ *
142
+ * Read a `PrivateKeyPossessionStatement`. `value` is the attribute value of a PKCS#10
143
+ * `statementOfPossession` attribute or the value of the CRMF registration control of the same name,
144
+ * which RFC 9883 gives the same OID, so one reader serves both.
145
+ *
146
+ * `signer` is "the issuer name and certificate serial number of the signature certificate", and
147
+ * `certificate` is that certificate when the statement carries it, or `null`. The RFC allows the
148
+ * omission: "If the issuer of the key establishment certificate will be the same as the issuer of the
149
+ * signature certificate, then this component MAY be omitted", leaving the CA to look it up.
150
+ *
151
+ * A carried certificate that `signer` does not name throws `possession/signer-mismatch`. The two would
152
+ * describe different certificates, and therefore different keys, while only one of them signed the
153
+ * request.
154
+ *
155
+ * @example
156
+ * var kem = await pki.key.generate({ name: "ML-KEM-768" });
157
+ * var sig = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
158
+ * var kemSpki = await pki.key.export(kem.publicKey, { format: "der" });
159
+ * var sigSpki = await pki.key.export(sig.publicKey, { format: "der" });
160
+ * var sigCert = await pki.x509.sign({
161
+ * subject: "CN=kem.example", subjectPublicKey: sigSpki, serialNumber: 34n,
162
+ * notBefore: new Date("2027-01-01T00:00:00Z"), notAfter: new Date("2028-01-01T00:00:00Z"),
163
+ * }, { key: sig.privateKey });
164
+ * var parsedCert = pki.schema.x509.parse(sigCert);
165
+ * var csr = await pki.csr.sign({
166
+ * subject: "CN=kem.example", subjectPublicKey: kemSpki,
167
+ * privateKeyPossessionStatement: {
168
+ * signer: { issuer: parsedCert.issuer.bytes, serialNumber: parsedCert.serialNumber },
169
+ * certificate: sigCert,
170
+ * },
171
+ * }, { key: sig.privateKey });
172
+ * var attr = pki.schema.csr.parse(csr).attributes
173
+ * .filter(function (a) { return a.type === pki.oid.byName("statementOfPossession"); })[0];
174
+ * pki.possession.parse(attr.values[0]).certificate !== null; // -> true
175
+ */
176
+ function parse(value) {
177
+ var der = guard.bytes.snapshot(value, _err, "possession/bad-statement", "the possession statement");
178
+ var node;
179
+ try { node = asn1.decode(der); }
180
+ catch (e) { throw _err("possession/bad-statement", "the possession statement must be DER", e); }
181
+ var out;
182
+ try { out = schema.walk(STATEMENT, node, NS).result; }
183
+ catch (e) {
184
+ if (e instanceof PossessionError) throw e;
185
+ throw _err("possession/bad-statement", "the possession statement must be a SEQUENCE { signer, cert? } (RFC 9883 sec. 3)", e);
186
+ }
187
+ return _bindSignerToCertificate(out);
188
+ }
189
+
190
+ /** @internal The signer-names-the-carried-certificate rule, in one place because the statement reaches a
191
+ * consumer through TWO doors: this module's `parse`, and the certification-request parser's
192
+ * recognized-attribute reader, which decodes the value in place while parsing the request. A statement
193
+ * naming one certificate while carrying another describes two different keys, and only one of them
194
+ * signed the request, so a door that skips the rule hands back a signer that does not identify the key
195
+ * the signature was made with. Both doors call this. */
196
+ function _bindSignerToCertificate(out) {
197
+ if (out.certificate !== null) {
198
+ var certParsed;
199
+ try { certParsed = schemaX509.parse(out.certificate); }
200
+ catch (e) { throw _err("possession/bad-statement", "the possession statement cert component must be an X.509 certificate", e); }
201
+ if (!_namesCertificate(out.signer, certParsed)) {
202
+ throw _err("possession/signer-mismatch",
203
+ "the certificate the statement carries is not the one its signer names (RFC 9883 sec. 3)");
204
+ }
205
+ }
206
+ return out;
207
+ }
208
+
209
+ /** @internal The statement of a parsed certification request, or null when it carries none. The
210
+ * recognized-attribute reader decodes it in place, so the value is read again only when a caller
211
+ * handed over a request some other reader parsed. The in-place record has been through the SHAPE rules
212
+ * and not the binding rule, which lives here rather than in the request parser, so it is applied to
213
+ * that record on the way out and neither door returns a statement the other would refuse. */
214
+ function statementOf(parsedCsr) {
215
+ var found = _filter(parsedCsr.attributes || [], function (a) { return a.type === OID_SOP; });
216
+ /** @internal More than one is REFUSED rather than resolved by taking the first. Selecting `[0]` skipped
217
+ * every binding check on the rest, so a request carrying a sound statement followed by one naming a
218
+ * different certificate was accepted on the strength of the first alone. Which of them a CA should honor
219
+ * is stated nowhere, the same reason the extensionRequest ambiguity is refused. */
220
+ _assertOneAttribute(found.length, "statementOfPossession");
221
+ if (!found.length) return null;
222
+ var attr = found[0];
223
+ if (attr.privateKeyPossessionStatement) return _bindSignerToCertificate(attr.privateKeyPossessionStatement);
224
+ return parse(attr.values[0]);
225
+ }
226
+
227
+ /** @internal One ambiguity refusal for every attribute this module selects a single instance of. A CSR's
228
+ * attributes are a SET and nothing orders them, so a verb that takes the first of several checks one and
229
+ * certifies against the others. */
230
+ function _assertOneAttribute(count, name) {
231
+ if (count > 1) {
232
+ throw _err("possession/bad-request", "this request carries " + count + " " + name +
233
+ " attributes, and which of them a CA should honor is not stated anywhere, so the RFC 9883 " +
234
+ "checks cannot be made against one of them alone");
235
+ }
236
+ }
237
+
238
+ /** @internal A requested extension of a certification request, decoded, or null when the request asks for
239
+ * none. The extensions a request asks for hang off its extensionRequest attribute, so they are reached
240
+ * through the attribute rather than off the request itself.
241
+ *
242
+ * An extension that is PRESENT but will not decode is a fault, not an absence. The sec. 3 name comparisons
243
+ * and the sec. 6 prohibition read these, so reporting an unreadable one as absent leaves the check with
244
+ * nothing to read and the verdict saying it does not apply, while the request itself is accepted. The
245
+ * certification-request parser holds the extension WRAPPER to its shape and not its decoded value, so
246
+ * nothing upstream refuses it either. Naming the fault is the only reading that does not quietly drop a
247
+ * check the specification asks for. */
248
+ function _requestedExtension(parsedCsr, name) {
249
+ var wanted = oid.byName(name);
250
+ var attrs = parsedCsr.attributes || [];
251
+ for (var i = 0; i < attrs.length; i++) {
252
+ var exts = attrs[i].extensions || [];
253
+ for (var j = 0; j < exts.length; j++) {
254
+ if (exts[j].oid !== wanted) continue;
255
+ /** @internal The extension IS present, its OID having matched. `decodeExtension` reports a value it
256
+ * cannot read as `decoded: null` rather than throwing, which is the same thing this function would
257
+ * otherwise return for an extension the request never asked for, so the two have to be told apart
258
+ * here or the caller cannot. */
259
+ var record;
260
+ try { record = schemaCsr.decodeExtension(exts[j]); }
261
+ catch (e) {
262
+ throw _err("possession/bad-request", "the request asks for a " + name +
263
+ " extension this build cannot read, so the RFC 9883 check it feeds cannot be " +
264
+ "made; an unreadable policy field is not an absent one", e);
265
+ }
266
+ if (record.decoded === null || record.decoded === undefined) {
267
+ throw _err("possession/bad-request", "the request asks for a " + name +
268
+ " extension whose value does not decode" + (record.code ? " (" + record.code + ")" : "") +
269
+ ", so the RFC 9883 check it feeds cannot be made; an unreadable policy field is " +
270
+ "not an absent one");
271
+ }
272
+ return record.decoded;
273
+ }
274
+ }
275
+ return null;
276
+ }
277
+
278
+ /** @internal Whether a signature certificate's subjectAltName carries the entry a request asks for, each
279
+ * form compared under its own RFC 5280 rule by `guard.name.generalNameEqual`. Keying both lists by the
280
+ * rendered value instead made the comparison verbatim for every form at once, so `EXAMPLE.com` in the
281
+ * certificate and `example.com` in the request read as two hosts although RFC 4343 makes DNS
282
+ * case-insensitive, and a directoryName re-encoded to equivalent bytes read as a second name although
283
+ * RFC 5280 sec. 7.1 compares it attribute by attribute. A form the guard cannot hold to its rule answers
284
+ * "not-comparable", which is not a match: the comparison this verb reports is one it actually made. */
285
+ function _sanCarries(certNames, asked, pairs) {
286
+ var names = certNames || [];
287
+ for (var i = 0; i < names.length; i++) {
288
+ pairs.tick();
289
+ if (guard.name.generalNameEqual(names[i], asked, _err, "possession/bad-input",
290
+ "a subjectAltName entry") === "match") return true;
291
+ }
292
+ return false;
293
+ }
294
+
295
+ /** @internal The key-usage bits that make a certificate a signature certificate, which RFC 9883 sec. 6
296
+ * states the attribute MUST NOT be used to obtain. The decoded keyUsage is a flags object, so the
297
+ * names are read off it rather than from a list of set bits. */
298
+ var SIGNATURE_USAGES = ["digitalSignature", "nonRepudiation", "keyCertSign", "cRLSign"];
299
+
300
+ /** @internal The subject keys that can ONLY sign, from the shared table in `schema-pkix`. The
301
+ * `lint/rfc9883/signature-certificate-requested` row reads the same one, so `pki.lint.csr` under that
302
+ * profile and this verb reach the same verdict about the same request: the linter grading conforming what
303
+ * this refuses is what the shared table removes. */
304
+ var _isSignatureOnlyKey = pkix.signatureOnlyKeyOid(NS);
305
+
306
+ /**
307
+ * @primitive pki.possession.verifyRequest
308
+ * @signature pki.possession.verifyRequest(request, opts) -> Promise<verdict>
309
+ * @since 0.8.44
310
+ * @status stable
311
+ * @spec RFC 9883 sec. 3, RFC 9883 sec. 4, RFC 9883 sec. 6
312
+ * @defends possession-statement-forgery (CWE-347)
313
+ * @related pki.possession.parse, pki.csr.sign, pki.path.validate
314
+ *
315
+ * The CA's side of RFC 9883. `request` is a PKCS#10 certification request carrying a
316
+ * `statementOfPossession` attribute. Applies the two requirements section 3 states as MUSTs, and
317
+ * reports the two it states as SHOULDs.
318
+ *
319
+ * The MUSTs are enforced. "The CA MUST validate the signature on the certificate request using the
320
+ * public key from the signature certificate", so the signature is checked against that key and not
321
+ * against the key being certified, which for a key-establishment key could not have signed anything.
322
+ * "The CA MUST perform certification path validation for the signature certificate as specified in
323
+ * Section 6 of [RFC5280]", so `opts.trustAnchors` is required and the verb throws without it instead of
324
+ * returning a verdict that skipped a MUST.
325
+ *
326
+ * The SHOULDs are reported. Both end in "the certificate policy MUST describe how the CA can determine
327
+ * that the two subject names identify the same entity", which is a question this library cannot answer,
328
+ * so `subjectMatches` and `subjectAltNamesMatch` carry the comparison and `valid` is `false` with a
329
+ * reason naming the policy decision when they differ. A caller whose policy resolves it reads those
330
+ * fields and decides for itself.
331
+ *
332
+ * `requestsSignatureCertificate` reports the section 6 MUST NOT: "The privateKeyPossessionStatement
333
+ * attribute MUST NOT be used to obtain a signature certificate." A request asking for `digitalSignature`,
334
+ * `nonRepudiation`, `keyCertSign` or `cRLSign` is that misuse, and so is a request whose subject key can
335
+ * only ever sign, which section 4 states as a property of the request: "the subjectPKInfo MUST contain the
336
+ * public key for the key establishment algorithm". Either way `valid` is `false`.
337
+ *
338
+ * A request carrying no statement throws `possession/absent`: it is not an unverified RFC 9883 request,
339
+ * it is a different question, and `pki.csr.verify` is the verb for it.
340
+ *
341
+ * @opts trustAnchors Required. The anchors the signature certificate's path is validated to, in the form `pki.path.validate` takes.
342
+ * @opts time The instant the path is validated at, defaulting to now when omitted or `null`. A value
343
+ * that is present and is not a usable `Date` is `possession/bad-input`.
344
+ * @opts signatureCertificate The signature certificate, for a statement that omits it; refused when it is not the one `signer` names.
345
+ * @opts intermediates The rest of the signature certificate's path, when an intermediate CA issued it rather than an anchor directly. Ordered as `pki.path.validate` takes a path, from the certificate nearest an anchor down to the issuer of the signature certificate, which this verb appends. Omitted, the signature certificate is validated on its own, which succeeds only when an anchor issued it.
346
+ *
347
+ * @example
348
+ * var kem = await pki.key.generate({ name: "ML-KEM-768" });
349
+ * var sig = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
350
+ * var kemSpki = await pki.key.export(kem.publicKey, { format: "der" });
351
+ * var sigSpki = await pki.key.export(sig.publicKey, { format: "der" });
352
+ * var ca = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
353
+ * var caSpki = await pki.key.export(ca.publicKey, { format: "der" });
354
+ * var caCert = await pki.x509.sign({
355
+ * subject: "CN=Possession CA", subjectPublicKey: caSpki, serialNumber: 1n,
356
+ * notBefore: new Date("2027-01-01T00:00:00Z"), notAfter: new Date("2028-01-01T00:00:00Z"),
357
+ * extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign"] },
358
+ * }, { key: ca.privateKey });
359
+ * var sigCert = await pki.x509.sign({
360
+ * subject: "CN=kem.example", subjectPublicKey: sigSpki, serialNumber: 34n,
361
+ * notBefore: new Date("2027-01-01T00:00:00Z"), notAfter: new Date("2028-01-01T00:00:00Z"),
362
+ * extensions: { keyUsage: ["digitalSignature"] },
363
+ * }, { cert: caCert, key: ca.privateKey });
364
+ * var p = pki.schema.x509.parse(sigCert);
365
+ * var csr = await pki.csr.sign({
366
+ * subject: "CN=kem.example", subjectPublicKey: kemSpki,
367
+ * privateKeyPossessionStatement: {
368
+ * signer: { issuer: p.issuer.bytes, serialNumber: p.serialNumber }, certificate: sigCert,
369
+ * },
370
+ * }, { key: sig.privateKey });
371
+ * var v = await pki.possession.verifyRequest(csr,
372
+ * { trustAnchors: [caCert], time: new Date("2027-06-01T00:00:00Z") });
373
+ * v.valid; // -> true
374
+ */
375
+ function verifyRequest(request, opts) {
376
+ return guard.async.deferred(function () { return _verifyRequest(request, opts); });
377
+ }
378
+
379
+ function _verifyRequest(request, opts) {
380
+ guard.identifier.assertKnownKeys(opts, KNOWN_VERIFY_OPTS, _err, "possession/bad-input",
381
+ "pki.possession.verifyRequest has an unknown option: ");
382
+ var o = guard.identifier.snapshotOptions(opts, KNOWN_VERIFY_OPTS);
383
+ if (o.trustAnchors == null || !_isArray(o.trustAnchors) || o.trustAnchors.length === 0) {
384
+ throw _err("possession/bad-input",
385
+ "opts.trustAnchors is required: RFC 9883 sec. 4 makes certification path validation of the signature certificate a MUST, and a verdict that skipped it would not answer the question asked");
386
+ }
387
+ if (_validatePath === null) {
388
+ throw _err("possession/bad-input", "the possession path validator is not initialized (require pki before use)");
389
+ }
390
+ /** @internal The documented default, constructed here. Forwarded as `undefined`, it reached the path
391
+ * validator's always-on validity check, which requires a valid Date and refuses `path/bad-input`, so
392
+ * the default never applied and every caller had to supply a clock to get any verdict at all. A value
393
+ * that is present and is not a usable Date is refused HERE, under this verb's own code, rather than
394
+ * reported against the validator a caller did not call. */
395
+ var at = o.time;
396
+ if (at === undefined || at === null) at = new intrinsic.Date();
397
+ else guard.time.assertValid(at, _err, "possession/bad-input", "opts.time (the instant the signature certificate's path is validated at)");
398
+
399
+ var der = guard.bytes.snapshot(request, _err, "possession/bad-input", "the certification request");
400
+ var parsed;
401
+ try { parsed = schemaCsr.parse(der); }
402
+ catch (e) { throw _err("possession/bad-input", "the certification request must be a PKCS#10 CertificationRequest", e); }
403
+
404
+ var statement = statementOf(parsed);
405
+ if (statement === null) {
406
+ throw _err("possession/absent",
407
+ "the certification request carries no statementOfPossession attribute, so there is no statement to check (RFC 9883 sec. 3)");
408
+ }
409
+
410
+ var sigCertDer = statement.certificate;
411
+ if (sigCertDer === null) {
412
+ if (o.signatureCertificate == null) {
413
+ throw _err("possession/no-certificate",
414
+ "the statement omits the signature certificate, so opts.signatureCertificate must supply the one its signer names (RFC 9883 sec. 3)");
415
+ }
416
+ sigCertDer = guard.bytes.snapshot(o.signatureCertificate, _err, "possession/bad-input", "opts.signatureCertificate");
417
+ var supplied;
418
+ try { supplied = schemaX509.parse(sigCertDer); }
419
+ catch (e) { throw _err("possession/bad-input", "opts.signatureCertificate must be an X.509 certificate", e); }
420
+ if (!_namesCertificate(statement.signer, supplied)) {
421
+ throw _err("possession/signer-mismatch",
422
+ "opts.signatureCertificate is not the certificate the statement's signer names (RFC 9883 sec. 3)");
423
+ }
424
+ } else if (o.signatureCertificate != null) {
425
+ throw _err("possession/bad-input",
426
+ "the statement already carries its signature certificate, so opts.signatureCertificate would name a second one");
427
+ }
428
+
429
+ var sigCert = schemaX509.parse(sigCertDer);
430
+ var reasons = [];
431
+
432
+ /** @internal A request carrying more than one extensionRequest attribute is REFUSED before any of it is
433
+ * read, the way `pki.schema.csr.decodeExtensions` refuses it. The lookups below take the first attribute
434
+ * whose extension matches, so a second attribute's `keyUsage` was never seen: a request asking for
435
+ * `keyAgreement` in the first and `digitalSignature` in the second reported the RFC 9883 sec. 6
436
+ * prohibition as not engaged. Which attribute a CA should honor is not stated anywhere, so naming the
437
+ * ambiguity is the only reading that does not silently check one and certify the other. */
438
+ var extensionRequests = 0;
439
+ _forEach(parsed.attributes || [], function (a) {
440
+ if (a.type === oid.byName("extensionRequest")) extensionRequests += 1;
441
+ });
442
+ _assertOneAttribute(extensionRequests, "extensionRequest");
443
+ /** @internal The MUST NOT, decided from the usages the request asks for AND from the subject key itself.
444
+ * The usages alone let a request omit the keyUsage extension and report the prohibition as not engaged,
445
+ * while asking to certify a key that can only ever sign. The reason names which of the two fired, since
446
+ * one message covering both would tell an operator a request named a usage when it named none. */
447
+ var requestedUsage = _requestedExtension(parsed, "keyUsage");
448
+ var askedSignatureUsage = false;
449
+ if (requestedUsage !== null) {
450
+ _forEach(SIGNATURE_USAGES, function (u) { if (requestedUsage[u] === true) askedSignatureUsage = true; });
451
+ }
452
+ var subjectKeyOid = parsed.subjectPublicKeyInfo && parsed.subjectPublicKeyInfo.algorithm
453
+ ? parsed.subjectPublicKeyInfo.algorithm.oid : null;
454
+ var subjectKeySignsOnly = _isSignatureOnlyKey(subjectKeyOid);
455
+ var requestsSignatureCertificate = askedSignatureUsage || subjectKeySignsOnly;
456
+ if (askedSignatureUsage) {
457
+ _push(reasons, "the request asks for a signature key usage, and RFC 9883 sec. 6 states the attribute MUST NOT be used to obtain a signature certificate");
458
+ }
459
+ if (subjectKeySignsOnly) {
460
+ _push(reasons, "the request asks to certify a " + (oid.name(subjectKeyOid) || subjectKeyOid) +
461
+ " subject key, which can only sign, and RFC 9883 sec. 4 requires the subjectPKInfo to carry the " +
462
+ "public key for the key establishment algorithm");
463
+ }
464
+
465
+ /** @internal The two SHOULDs, compared and reported. */
466
+ var subjectMatches = false;
467
+ try {
468
+ subjectMatches = guard.name.dnEqual(parsed.subject.rdns, sigCert.subject.rdns,
469
+ _err, "possession/bad-input", "the request subject") === true;
470
+ } catch (_e) { subjectMatches = false; }
471
+ if (!subjectMatches) {
472
+ _push(reasons, "the request subject differs from the signature certificate subject, and RFC 9883 sec. 3 leaves to the certificate policy how a CA determines the two name the same entity");
473
+ }
474
+
475
+ var requestedSan = _requestedSan(parsed);
476
+ var certSan = _certSan(sigCert);
477
+ var subjectAltNamesMatch = null;
478
+ if (requestedSan !== null) {
479
+ /** @internal ONE counter over the PAIRS rather than a cap on each list, because the work is the
480
+ * product and BOTH lists come from the request: the names it asks for, and the names in the
481
+ * certificate it embeds. Unbounded, a request repeating one name thousands of times made this
482
+ * scan every certificate name for each of them, and it runs BEFORE the signature is checked and
483
+ * before the certificate's path is validated, so an unauthenticated caller decided how long a CA
484
+ * spent inside one call. MEASURED at the cap: about 70 ms of comparison, against seconds for a
485
+ * 143 KB request without it. The budget is the one `pki.identityMatch` bounds the same nested name
486
+ * comparison with. */
487
+ var sanPairs = guard.limits.counter(
488
+ constants.LIMITS.SAN_MAX_ENTRIES * constants.LIMITS.IDENTITY_MAX_REFERENCES,
489
+ _err, "possession/too-many-names", "subject alternative name comparison");
490
+ subjectAltNamesMatch = _every(requestedSan || [], function (asked) {
491
+ return _sanCarries(certSan, asked, sanPairs);
492
+ });
493
+ if (!subjectAltNamesMatch) {
494
+ _push(reasons, "a subject alternative name the request asks for is not in the signature certificate, and RFC 9883 sec. 3 leaves to the certificate policy how a CA determines the two name the same entity");
495
+ }
496
+ }
497
+
498
+ /** @internal RFC 9883 sec. 4 makes path validation of the signature certificate a MUST, and a CA
499
+ * normally issues end-entity certificates from an intermediate. The validator is handed an ordered
500
+ * path and does not build one, so without the rest of the chain the only request that can pass is one
501
+ * whose signature certificate the anchor issued directly, and every other valid request is refused
502
+ * for a path that is in fact valid. The order is the validator's own: from the certificate nearest
503
+ * the anchor down to the issuer of the signature certificate, which goes last. */
504
+ var chain = [];
505
+ if (o.intermediates != null) {
506
+ if (!_isArray(o.intermediates)) {
507
+ throw _err("possession/bad-input",
508
+ "opts.intermediates is the rest of the signature certificate's path as an array of certificates, ordered from the one nearest a trust anchor down to the issuer of the signature certificate");
509
+ }
510
+ for (var ci = 0; ci < o.intermediates.length; ci++) {
511
+ guard.list.append(chain, guard.bytes.snapshot(o.intermediates[ci], _err, "possession/bad-input",
512
+ "opts.intermediates[" + ci + "]"));
513
+ }
514
+ }
515
+ guard.list.append(chain, sigCertDer);
516
+
517
+ var signerMaySign = _maySign(sigCert);
518
+ if (!signerMaySign) {
519
+ _push(reasons, "the signature certificate's keyUsage does not permit signing, so its key may not " +
520
+ "authorize a certification request (RFC 5280 sec. 4.2.1.3)");
521
+ }
522
+
523
+ return _promiseResolve(_Promise, _validatePath(chain, { trustAnchors: o.trustAnchors, time: at }))
524
+ .then(function (pathResult) {
525
+ var pathValidated = pathResult.valid === true;
526
+ if (!pathValidated) _push(reasons, "the signature certificate's certification path is not valid, and RFC 9883 sec. 4 makes that a MUST");
527
+ return _seam.verify(parsed.signatureAlgorithm, parsed.signatureValue,
528
+ sigCert.subjectPublicKeyInfo.bytes, parsed.certificationRequestInfoBytes)
529
+ .then(function (verified) {
530
+ if (verified !== true) _push(reasons, "the request signature does not verify under the signature certificate's public key, and RFC 9883 sec. 4 makes that a MUST");
531
+ return guard.verdict.of({}, {
532
+ valid: verified === true && pathValidated && signerMaySign && !requestsSignatureCertificate &&
533
+ subjectMatches && subjectAltNamesMatch !== false,
534
+ verified: verified === true,
535
+ pathValidated: pathValidated,
536
+ signerMaySign: signerMaySign,
537
+ signer: statement.signer,
538
+ signatureCertificate: sigCertDer,
539
+ subjectMatches: subjectMatches,
540
+ subjectAltNamesMatch: subjectAltNamesMatch,
541
+ requestsSignatureCertificate: requestsSignatureCertificate,
542
+ reason: reasons.length ? _join(reasons, "; ") : undefined,
543
+ });
544
+ });
545
+ });
546
+ }
547
+
548
+ /** @internal Whether the signature certificate's key may make this signature at all. A certificate confined
549
+ * to `keyAgreement` does not authorize a signature over a certification request, and accepting one lets a
550
+ * key never issued for signing authorize issuance. The accepted set is `pki.cms`'s: a signature over
551
+ * content is authorized by digitalSignature, contentCommitment or nonRepudiation alike. */
552
+ var _maySign = pkix.keyUsagePermits(NS, ["digitalSignature", "contentCommitment", "nonRepudiation"]);
553
+
554
+ /** @internal The subjectAltName a request asks for, or null when it asks for none. */
555
+ function _requestedSan(parsedCsr) {
556
+ var d = _requestedExtension(parsedCsr, "subjectAltName");
557
+ return d === null ? null : (d.names || []);
558
+ }
559
+
560
+ /** @internal The subjectAltName a certificate carries. */
561
+ function _certSan(parsedCert) {
562
+ var ext = _filter(parsedCert.extensions || [], function (e) { return e.oid === oid.byName("subjectAltName"); })[0];
563
+ if (ext === undefined) return [];
564
+ try { return schemaX509.decodeExtension(ext).decoded.names || []; }
565
+ catch (_e) { return []; }
566
+ }
567
+
568
+ module.exports = {
569
+ parse: parse,
570
+ verifyRequest: verifyRequest,
571
+ /** @internal The attribute OID and the statement reader, for the builders and verifiers that
572
+ * compose them; not on the curated surface. */
573
+ OID_SOP: OID_SOP, statementOf: statementOf, namesCertificate: _namesCertificate,
574
+ setEngine: setEngine,
575
+ };