@blamejs/pki 0.4.14 → 0.5.0

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 (60) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/MIGRATING.md +2 -2
  3. package/README.md +142 -137
  4. package/index.js +4 -0
  5. package/lib/acme.js +73 -1
  6. package/lib/asn1-der.js +2 -0
  7. package/lib/attrcert-sign.js +4 -0
  8. package/lib/cbor-det.js +32 -16
  9. package/lib/cmc-build.js +880 -0
  10. package/lib/cmc-verify.js +657 -0
  11. package/lib/cmp-build.js +8 -7
  12. package/lib/cmp-verify.js +11 -1
  13. package/lib/cms-sign.js +170 -8
  14. package/lib/cms-verify.js +80 -14
  15. package/lib/crl-sign.js +22 -0
  16. package/lib/crmf-sign.js +5 -2
  17. package/lib/csr-sign.js +3 -0
  18. package/lib/ct.js +72 -0
  19. package/lib/est.js +828 -32
  20. package/lib/framework-error.js +13 -0
  21. package/lib/guard-bytes.js +37 -1
  22. package/lib/guard-range.js +23 -1
  23. package/lib/http-transport.js +9 -3
  24. package/lib/inspect.js +28 -5
  25. package/lib/jose.js +15 -0
  26. package/lib/lint.js +4 -0
  27. package/lib/merkle.js +5 -5
  28. package/lib/ocsp.js +139 -11
  29. package/lib/oid.js +69 -1
  30. package/lib/path-validate.js +438 -100
  31. package/lib/pkcs12-build.js +12 -0
  32. package/lib/schema-all.js +19 -1
  33. package/lib/schema-attrcert.js +27 -0
  34. package/lib/schema-c509.js +6 -0
  35. package/lib/schema-cmc.js +791 -0
  36. package/lib/schema-cmp.js +25 -0
  37. package/lib/schema-cms.js +17 -1
  38. package/lib/schema-crl.js +23 -1
  39. package/lib/schema-crmf.js +13 -0
  40. package/lib/schema-csr.js +11 -0
  41. package/lib/schema-csrattrs.js +6 -0
  42. package/lib/schema-engine.js +6 -2
  43. package/lib/schema-ocsp.js +41 -0
  44. package/lib/schema-pkcs12.js +16 -0
  45. package/lib/schema-pkcs8.js +8 -0
  46. package/lib/schema-pkix.js +6 -0
  47. package/lib/schema-smime.js +4 -4
  48. package/lib/schema-tsp.js +32 -1
  49. package/lib/schema-x509.js +14 -1
  50. package/lib/shbs.js +12 -4
  51. package/lib/sigstore.js +4 -0
  52. package/lib/smime.js +28 -7
  53. package/lib/tls-cert-compress.js +15 -3
  54. package/lib/trust.js +27 -4
  55. package/lib/tsp-sign.js +41 -6
  56. package/lib/vendor/README.md +19 -19
  57. package/lib/webauthn.js +895 -26
  58. package/lib/x509-sign.js +3 -0
  59. package/package.json +1 -1
  60. package/sbom.cdx.json +6 -6
@@ -0,0 +1,657 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ /**
5
+ * @module pki.cmc
6
+ * @nav Enrollment
7
+ * @title CMC
8
+ * @intro Interpret an RFC 5272 Full PKI Response. `verify(response, sent)` takes the response
9
+ * a CA returned and the state the client retained from its request, binds the two together
10
+ * (transaction identifier, the Sender/Recipient Nonce echo, the Data Return echo), reads the
11
+ * ordered status verdicts, and reduces them to ONE terminal outcome: `issued`, `pending`,
12
+ * `confirm-required`, `pop-required` or `rejected`. The issued certificates come from the CMS
13
+ * certificate bag, where RFC 5272 sec. 4.2 puts them. Nothing here is trusted: the bag and any
14
+ * Publish Trust Anchors control are surfaced as DATA for the caller to validate through
15
+ * `pki.path.validate`.
16
+ * @spec RFC 5272, RFC 5273, RFC 6402
17
+ * @card Interpret a CMC Full PKI Response into one terminal verdict -- transaction and nonce
18
+ * binding, the status verdicts, the certificate bag surfaced untrusted.
19
+ */
20
+
21
+ var asn1 = require("./asn1-der");
22
+ var oid = require("./oid");
23
+ var cmc = require("./schema-cmc");
24
+ var cmsVerify = require("./cms-verify"); // PD14: the carrier's signature MUST be verified
25
+ var cmsDecrypt = require("./cms-decrypt"); // ... and an AuthenticatedData carrier by its MAC
26
+ var guard = require("./guard-all");
27
+ var frameworkError = require("./framework-error");
28
+
29
+ var CmcError = frameworkError.CmcError;
30
+ function E(code, message, cause) { return new CmcError(code, message, cause); }
31
+ function O(name) { return oid.byName(name); }
32
+
33
+ var OID_TRANSACTION_ID = O("id-cmc-transactionId");
34
+ var OID_SENDER_NONCE = O("id-cmc-senderNonce");
35
+ var OID_RECIPIENT_NONCE = O("id-cmc-recipientNonce");
36
+ var OID_DATA_RETURN = O("id-cmc-dataReturn");
37
+ var OID_TRUSTED_ANCHORS = O("id-cmc-trustedAnchors");
38
+ var OID_CONFIRM_CERT_ACCEPTANCE = O("id-cmc-confirmCertAcceptance");
39
+
40
+ // CMCStatus -> the terminal outcome a caller acts on. RFC 5272 sec. 6.1.3 gives
41
+ // seven statuses; four of them mean "this exchange did not produce a certificate
42
+ // and will not without another message", so they collapse to `rejected` while the
43
+ // status list keeps the detail.
44
+ var OUTCOME_BY_STATUS = {
45
+ success: "issued",
46
+ pending: "pending",
47
+ partial: "pending",
48
+ confirmRequired: "confirm-required",
49
+ popRequired: "pop-required",
50
+ failed: "rejected",
51
+ noSupport: "rejected",
52
+ };
53
+
54
+ // Which outcome governs when a response carries SEVERAL status controls (sec.
55
+ // 6.1 requires a client to cope with that). Ranked worst-first so the verdict can
56
+ // never be improved by the ORDER the controls happen to appear in -- reporting
57
+ // the first one seen would let a server bury a failure behind a success.
58
+ var OUTCOME_SEVERITY = { rejected: 4, "pop-required": 3, "confirm-required": 2, pending: 1, issued: 0 };
59
+
60
+ // The single AttributeValue of a control that carries exactly one.
61
+ function _singleValue(control, what) {
62
+ if (control.values.length !== 1) {
63
+ throw E("cmc/bad-control", "the " + what + " control carries exactly one value, got " + control.values.length);
64
+ }
65
+ return control.values[0];
66
+ }
67
+ // The single instance of a control, or null when absent.
68
+ //
69
+ // Body-part identity is unique per element, so a responder can legally carry the
70
+ // SAME control type twice under different bodyPartIDs. Taking the first match
71
+ // would let an attacker pair one correct echo with a contradictory one and still
72
+ // satisfy the binding -- so a duplicate is refused as ambiguous rather than
73
+ // resolved. The exchange either has one answer or it has none.
74
+ function _findControl(controls, attrType, what) {
75
+ var found = null;
76
+ for (var i = 0; i < controls.length; i++) {
77
+ if (controls[i].attrType !== attrType) continue;
78
+ if (found) {
79
+ throw E("cmc/duplicate-control",
80
+ "the response carries more than one " + what + " control; which one binds the exchange is ambiguous, so it is refused");
81
+ }
82
+ found = controls[i];
83
+ }
84
+ return found;
85
+ }
86
+ function _octets(control, what) {
87
+ return asn1.read.octetString(asn1.decode(_singleValue(control, what)));
88
+ }
89
+
90
+ // The certificate / CRL bag, from whichever carrier holds it. A SignedData keeps
91
+ // them at the top level; an AuthenticatedData keeps them under `originatorInfo`
92
+ // (RFC 5652 sec. 9.1). Both carriers are accepted for a Full PKI Response, so
93
+ // reading only the SignedData shape would silently return an empty bag -- and
94
+ // "no certificates were issued" is exactly the wrong thing to report when some
95
+ // were.
96
+ function _bagOf(parsedCms, originatorKey, topKey) {
97
+ if (!parsedCms) return [];
98
+ var list = parsedCms[topKey];
99
+ if ((!list || !list.length) && parsedCms.originatorInfo) list = parsedCms.originatorInfo[originatorKey];
100
+ return (list || []).map(function (c) { return c.bytes; });
101
+ }
102
+
103
+ /**
104
+ * The transaction binding -- RFC 5272 sec. 6.6, RFC 5273 sec. 6.
105
+ *
106
+ * Both halves are CONDITIONAL on what the client sent, which is the subtlety: a
107
+ * client that sent no transactionId cannot demand one back, and a client that
108
+ * sent no senderNonce has nothing to compare a recipientNonce against. What is
109
+ * NOT conditional is the converse -- having sent one, an absent or differing echo
110
+ * is a refusal, because that is exactly the replay the nonce exists to stop.
111
+ */
112
+ function _assertBound(body, sent) {
113
+ if (sent.transactionId != null) {
114
+ var txControl = _findControl(body.controls, OID_TRANSACTION_ID, "Transaction Identifier");
115
+ if (!txControl) {
116
+ throw E("cmc/transaction-mismatch",
117
+ "the request carried a Transaction Identifier control, so the response MUST include the same one (RFC 5272 sec. 6.6)");
118
+ }
119
+ var got = asn1.read.integer(asn1.decode(_singleValue(txControl, "Transaction Identifier")));
120
+ // Compared against the value the authoring guard validated, never a fresh
121
+ // conversion of the caller's input -- a number too large to hold an integer
122
+ // exactly has already lost the digits that distinguish it from its neighbour.
123
+ if (got !== sent.transactionIdValue) {
124
+ throw E("cmc/transaction-mismatch",
125
+ "the response Transaction Identifier " + got + " does not match the request's " + sent.transactionId);
126
+ }
127
+ }
128
+
129
+ if (sent.senderNonce != null) {
130
+ var rn = _findControl(body.controls, OID_RECIPIENT_NONCE, "Recipient Nonce");
131
+ if (!rn) {
132
+ throw E("cmc/nonce-mismatch",
133
+ "the request carried a Sender Nonce, so the response MUST reflect it back as a Recipient Nonce control (RFC 5272 sec. 6.6)");
134
+ }
135
+ // Constant-time and by FULL value: guard.crypto.constantTimeEqual gates the
136
+ // length first, so a truncation or a prefix is an honest false rather than a
137
+ // match on the bytes that happen to line up.
138
+ if (!guard.crypto.constantTimeEqual(_octets(rn, "Recipient Nonce"), Buffer.from(sent.senderNonce))) {
139
+ throw E("cmc/nonce-mismatch", "the response Recipient Nonce does not match the Sender Nonce the request sent");
140
+ }
141
+ }
142
+
143
+ // RFC 5272 sec. 6.4: "If the Data Return control appears in a Full PKI Request,
144
+ // the server MUST return it as part of the PKI Response." The data is opaque to
145
+ // the server, so the check is that the SAME bytes came back.
146
+ if (sent.dataReturn != null) {
147
+ var dr = _findControl(body.controls, OID_DATA_RETURN, "Data Return");
148
+ if (!dr) {
149
+ throw E("cmc/data-return-missing",
150
+ "the request carried a Data Return control, so the server MUST return it in the PKI Response (RFC 5272 sec. 6.4)");
151
+ }
152
+ if (!_octets(dr, "Data Return").equals(Buffer.from(sent.dataReturn))) {
153
+ throw E("cmc/data-return-mismatch", "the returned Data Return control does not carry the bytes the request sent");
154
+ }
155
+ }
156
+
157
+ // A status names, in its bodyList, the body parts it is ABOUT (RFC 5272
158
+ // sec. 6.1.1). A status about a body part the request never carried is not an
159
+ // answer to this request -- and the binding controls do not catch it, because a
160
+ // server can echo the transaction and nonce correctly while reporting on
161
+ // something else entirely. Held to the same asymmetry as every other half of the
162
+ // binding: checked only when the client kept the set, and once kept, a reference
163
+ // outside it is a refusal.
164
+ // Element-by-element, and the lengths must agree: a retained `[a, b]` must not admit a
165
+ // reported `[a, b, c]`, which names a part one level deeper than anything sent.
166
+ function _pathRetained(retained, want) {
167
+ if (!Array.isArray(retained)) return false;
168
+ return retained.some(function (have) {
169
+ return Array.isArray(have) && have.length === want.length && have.every(function (seg, i) {
170
+ return String(seg) === String(want[i]);
171
+ });
172
+ });
173
+ }
174
+ if (Array.isArray(sent.bodyPartIDs)) {
175
+ var known = Object.create(null);
176
+ sent.bodyPartIDs.forEach(function (id) { known[String(id)] = true; });
177
+ // 0 is reserved as the reference to the enclosing PKIData itself (sec. 3.2.1),
178
+ // so a status about the request AS A WHOLE is in the set by definition.
179
+ known["0"] = true;
180
+ body.statuses.forEach(function (s) {
181
+ (s.bodyList || []).forEach(function (ref) {
182
+ var path = ref.bodyPartPath;
183
+ // A bodyPartPath descends INTO a nested message. Checking only its head would
184
+ // accept `[a part we sent, 999]` -- a status about something arbitrary, wearing a
185
+ // reference that passes -- so the WHOLE path is matched against the paths the
186
+ // request actually composed. Those are retained by reading each nested message
187
+ // back; a nested message that could not be read back contributes none, so a path
188
+ // into it finds no match and is refused. Either way the tail is never waved through.
189
+ if (path && path.length > 1) {
190
+ if (_pathRetained(sent.bodyPartPaths, path)) return;
191
+ throw E("cmc/body-part-unknown",
192
+ "a status control reports on a body part nested inside another message (path " + path.join("/") +
193
+ "), which this request never sent -- nothing here can confirm what that names " +
194
+ "(RFC 5272 sec. 6.1.1)");
195
+ }
196
+ var id = ref.bodyPartID != null ? ref.bodyPartID : (path && path.length ? path[0] : null);
197
+ if (id == null || known[String(id)]) return;
198
+ throw E("cmc/body-part-unknown",
199
+ "a status control reports on body part " + id + ", which this request never sent -- the response " +
200
+ "is about a different message (RFC 5272 sec. 6.1.1)");
201
+ });
202
+ });
203
+ }
204
+ }
205
+
206
+ // The worst outcome across every status control, with success assumed when there
207
+ // is none at all (RFC 5272 sec. 6.1.2: "If no status exists for a Simple or Full
208
+ // PKI Request, then the value of success is assumed").
209
+ function _reduceOutcome(statuses) {
210
+ var outcome = "issued";
211
+ for (var i = 0; i < statuses.length; i++) {
212
+ var candidate = OUTCOME_BY_STATUS[statuses[i].status];
213
+ if (OUTCOME_SEVERITY[candidate] > OUTCOME_SEVERITY[outcome]) outcome = candidate;
214
+ }
215
+ return outcome;
216
+ }
217
+
218
+ // The pendInfo / failInfo detail belonging to the status that GOVERNED, so a
219
+ // caller reading `pendToken` gets the one attached to the pending verdict rather
220
+ // than whichever status happened to carry a token.
221
+ function _governingStatus(statuses, outcome) {
222
+ for (var i = 0; i < statuses.length; i++) {
223
+ if (OUTCOME_BY_STATUS[statuses[i].status] === outcome) return statuses[i];
224
+ }
225
+ return null;
226
+ }
227
+
228
+ /**
229
+ * @primitive pki.cmc.verify
230
+ * @signature pki.cmc.verify(response, sent?) -> Promise<verdict>
231
+ * @since 0.4.16
232
+ * @status experimental
233
+ * @spec RFC 5272, RFC 5273, RFC 6402
234
+ * @defends cmc-response-replay (CWE-294)
235
+ * @related pki.schema.cmc.parse, pki.path.validate, pki.cms.verify
236
+ *
237
+ * Interpret a Full PKI Response into ONE terminal verdict. `response` is the DER or a PEM `CMS`
238
+ * block; an already-parsed message is interpreted too, but only under `allowUnverified: true`,
239
+ * because both carriers authenticate over BYTES and a parsed object the caller still owns pins
240
+ * none. `sent` is what the client retained from its request --
241
+ * `transactionId`, `senderNonce`, `dataReturn`, and `bodyPartIDs` (every identifier the request
242
+ * carried). Each of those is checked only if it was sent, and once sent an absent or differing
243
+ * echo is a refusal: that asymmetry is the replay defence. `bodyPartIDs` is the same rule applied
244
+ * to what the response is ABOUT -- a status naming a body part the request never sent is refused
245
+ * with `cmc/body-part-unknown`, which the transaction and nonce cannot catch, since a server can
246
+ * echo both correctly while reporting on something else.
247
+ *
248
+ * The verdict carries the response's own `cmsSequence` and `otherMsgs` raw, because a request whose
249
+ * only arm was the other-message form has no certificate to return and RFC 5272 sec. 4.1 puts its
250
+ * answer there instead.
251
+ *
252
+ * The verdict's `outcome` is `issued`, `pending`, `confirm-required`, `pop-required` or
253
+ * `rejected`. When a response carries several status controls the WORST governs, so a failure
254
+ * cannot be hidden behind a success earlier in the sequence.
255
+ *
256
+ * The carrier's signature MUST be verified (RFC 5272 sec. 3.2.1.3.4). A conforming SignedData
257
+ * carries its own signer certificate, so the ordinary case needs nothing from the caller: the
258
+ * signature is checked and the verdict reports `signatureVerified: true`. Where the signer cannot be
259
+ * found -- not embedded and not supplied -- the posture is fail-closed with a NAMED opt-out rather
260
+ * than a silent one: pass `certs` with the responder's certificate, or `allowUnverified: true` and
261
+ * the verdict reports `signatureVerified: false`. Doing neither is refused, because that is the case
262
+ * where a caller would otherwise assume a check happened. The opt-out covers "could not check",
263
+ * never "checked and it failed": a signature that is present and wrong is always a refusal. A
264
+ * carrier with no SignerInfo at all is not a Full PKI Response and is refused outright.
265
+ *
266
+ * `certs` SUPPLEMENTS the certificates the message carries; it does not pin the signer (a SignedData
267
+ * names its own, which is what the signature is checked against) and it establishes no trust.
268
+ * Deciding the signer is acceptable is path validation, which is the caller's.
269
+ *
270
+ * The other carrier sec. 3.2 permits, AuthenticatedData, is authenticated by its MAC instead: pass
271
+ * `recipient` with the key material and the MAC is checked through `pki.cms.decrypt`, so a caller
272
+ * who holds the key gets an authenticated verdict rather than the unauthenticated opt-out.
273
+ *
274
+ * Nothing is trusted here. `certificates` is the CMS certificate bag -- where RFC 5272 sec. 4.2
275
+ * puts the issued certificates -- surfaced raw for the caller to run through `pki.path.validate`,
276
+ * and a Publish Trust Anchors control is surfaced as `publishTrustAnchors` with `trusted: false`,
277
+ * never added to any store (RFC 5272 sec. 6.15 makes accepting one a four-part manual decision).
278
+ * The CMS signature is likewise the caller's to verify through `pki.cms.verify`.
279
+ *
280
+ * @opts
281
+ * - `transactionId` (number|bigint) -- the Transaction Identifier the request sent.
282
+ * - `senderNonce` (Buffer) -- the Sender Nonce the request sent, echoed back as Recipient Nonce.
283
+ * - `dataReturn` (Buffer) -- the Data Return payload the request sent, echoed verbatim.
284
+ * - `certs` (Buffer[]) -- extra certificates for signer lookup, for a message that does not carry
285
+ * its own signer; the certificates the message carries are searched either way.
286
+ * - `recipient` (object) -- key material for an AuthenticatedData carrier, in the shape
287
+ * `pki.cms.decrypt` takes. Its MAC is then checked and the verdict reports
288
+ * `signatureVerified: true`; the content it authenticates is bound to the content the verdict
289
+ * was read from, so a MAC over other bytes cannot stand in for it.
290
+ * - `allowUnverified` (boolean) -- interpret without verifying the carrier; sets `signatureVerified: false`.
291
+ * @example
292
+ * var b = pki.asn1.build, oid = pki.oid;
293
+ * var sid = b.sequence([b.sequence([b.set([b.sequence([b.oid(oid.byName("commonName")),
294
+ * b.utf8("CA")])])]), b.integer(1n)]);
295
+ * var attrs = b.contextConstructed(0, Buffer.concat([
296
+ * b.sequence([b.oid(oid.byName("contentType")), b.set([b.oid(oid.byName("id-cct-PKIResponse"))])]),
297
+ * b.sequence([b.oid(oid.byName("messageDigest")), b.set([b.octetString(Buffer.alloc(32, 3))])])]));
298
+ * var si = b.sequence([b.integer(1n), sid, b.sequence([b.oid(oid.byName("sha256")), b.nullValue()]),
299
+ * attrs, b.sequence([b.oid(oid.byName("rsaEncryption")), b.nullValue()]), b.octetString(Buffer.alloc(8, 1))]);
300
+ * var body = b.sequence([b.sequence([]), b.sequence([]), b.sequence([])]);
301
+ * var encap = b.sequence([b.oid(oid.byName("id-cct-PKIResponse")), b.explicit(0, b.octetString(body))]);
302
+ * var sd = b.sequence([b.integer(3n), b.set([]), encap, b.set([si])]);
303
+ * var der = b.sequence([b.oid(oid.byName("signedData")), b.explicit(0, sd)]);
304
+ * var v = await pki.cmc.verify(der, { allowUnverified: true });
305
+ * v.outcome; // "issued" -- no status control means success is assumed
306
+ * v.signatureVerified; // false -- the opt-out was named, so nothing was checked
307
+ */
308
+ function verify(response, sent) {
309
+ // Both snapshots are taken SYNCHRONOUSLY, here, before anything is deferred.
310
+ // Taking them inside the async body would be too late by one microtask: a caller
311
+ // that mutates the buffer or the options object on the line after this call has
312
+ // already changed them before that body ever runs, which is the easiest version
313
+ // of the race to hit by accident.
314
+ var frozenResponse, frozenSent;
315
+ try {
316
+ frozenSent = _snapshotSent(_assertOpts(sent));
317
+ frozenResponse = _snapshotIfBytes(response);
318
+ } catch (e) {
319
+ return Promise.reject(e); // the surface stays promise-rejecting, never throwing
320
+ }
321
+ return Promise.resolve().then(function () { return _verify(frozenResponse, frozenSent); });
322
+ }
323
+
324
+ function _assertOpts(sent) {
325
+ if (sent == null) return {};
326
+ if (typeof sent !== "object" || Array.isArray(sent) || Buffer.isBuffer(sent)) {
327
+ throw E("cmc/bad-input", "pki.cmc.verify options must be an object");
328
+ }
329
+ return sent;
330
+ }
331
+
332
+ // A private copy of the input when -- and only when -- it is memory the caller can
333
+ // still write to. Anything else is returned untouched so this cannot narrow the
334
+ // input contract of the parse it feeds; pki.schema.cmc.parse stays the one place
335
+ // that decides what an acceptable input is, and keeps raising its own typed error.
336
+ function _snapshotIfBytes(input) {
337
+ // EVERY byte form the parser accepts, not just the two most common. It takes a
338
+ // BufferSource, so an ArrayBuffer or a DataView reaches the decoder too -- and
339
+ // leaving those aliased would reopen the window for exactly the inputs that came
340
+ // in by the wider door.
341
+ if (ArrayBuffer.isView(input) || input instanceof ArrayBuffer) {
342
+ return guard.bytes.snapshotSource(input, CmcError, "cmc/bad-input", "pki.cmc.verify");
343
+ }
344
+ return input;
345
+ }
346
+
347
+ // A private copy of the exchange state the binding checks compare against. Scalars
348
+ // are copied by value and every byte buffer is copied, so nothing the caller still
349
+ // holds a reference to can change what "what we sent" means once verification has
350
+ // begun. Unknown fields are carried through untouched -- this fixes the values the
351
+ // checks below read, it does not narrow what the options object may contain.
352
+ // EVERY byte form, not just the two most common: a DataView or a bare ArrayBuffer arrives by the
353
+ // same door as a Buffer, and copying only Buffer and Uint8Array would leave those aliased across
354
+ // the very gap this closes -- reopening the window for exactly the inputs that came in by the
355
+ // wider one. A DataView is copied over its OWN window, not the whole buffer it happens to sit in.
356
+ function _copyAnyBytes(v) {
357
+ if (Buffer.isBuffer(v) || v instanceof Uint8Array) return Buffer.from(v);
358
+ if (ArrayBuffer.isView(v)) return Buffer.from(new Uint8Array(v.buffer, v.byteOffset, v.byteLength));
359
+ if (v instanceof ArrayBuffer) return Buffer.from(new Uint8Array(v));
360
+ return v;
361
+ }
362
+
363
+ function _snapshotSent(sent) {
364
+ var out = {}, k;
365
+ for (k in sent) { if (Object.prototype.hasOwnProperty.call(sent, k)) out[k] = sent[k]; }
366
+ // Normalized through the SAME authoring guard pki.cmc.build puts an authored
367
+ // integer through. A Transaction Identifier is an unbounded INTEGER on the wire,
368
+ // so one above Number.MAX_SAFE_INTEGER has already been rounded by the time it
369
+ // arrives as a `number` -- 9007199254740993 is 9007199254740992 before this code
370
+ // sees it, and a response echoing that NEIGHBOURING identifier would compare
371
+ // equal. Held here rather than converted, so a value too large to be a number is
372
+ // refused with the shape to use instead (a bigint) rather than silently bound to
373
+ // something adjacent.
374
+ // Validated, not replaced: the verdict echoes `transactionId` as the caller gave
375
+ // it, so the check is added without changing what comes back.
376
+ // Through ONE helper so nothing here can copy "the two common byte forms" and leave the rest
377
+ // aliased -- the narrowing that reopens this window for whatever came in by the wider door.
378
+ if (out.transactionId != null) {
379
+ out.transactionIdValue = guard.range.authoredInteger(out.transactionId, E, "cmc/bad-input", "sent.transactionId");
380
+ }
381
+ out.senderNonce = _copyAnyBytes(out.senderNonce);
382
+ out.dataReturn = _copyAnyBytes(out.dataReturn);
383
+ // Copied for the same reason the nonce is: the check runs after an await, and an
384
+ // array the caller still holds could have body parts appended to it in the gap,
385
+ // widening what the response is allowed to report on.
386
+ if (Array.isArray(out.bodyPartIDs)) out.bodyPartIDs = out.bodyPartIDs.slice();
387
+ // Both levels: the outer array AND each path, since appending a segment to a retained path
388
+ // in the gap would widen what the response may report on exactly as appending an id does.
389
+ if (Array.isArray(out.bodyPartPaths)) {
390
+ out.bodyPartPaths = out.bodyPartPaths.map(function (p) { return Array.isArray(p) ? p.slice() : p; });
391
+ }
392
+ if (out.recipient && typeof out.recipient === "object") {
393
+ var r = {}, rk;
394
+ for (rk in out.recipient) {
395
+ if (!Object.prototype.hasOwnProperty.call(out.recipient, rk)) continue;
396
+ r[rk] = _copyAnyBytes(out.recipient[rk]);
397
+ }
398
+ out.recipient = r;
399
+ }
400
+ if (Array.isArray(out.certs)) out.certs = out.certs.map(_copyAnyBytes);
401
+ out.allowUnverified = sent.allowUnverified === true;
402
+ return out;
403
+ }
404
+
405
+ // `response` and `sent` arrive already frozen by verify() above -- BOTH sides of
406
+ // every comparison, not just the response bytes. The checks below run across an
407
+ // await and compare what the CA returned against what the caller says it sent, so
408
+ // freezing one side while reading the other live would leave half of each
409
+ // comparison mutable.
410
+ function _verify(response, sent) {
411
+ var body = cmc.parse(response);
412
+ if (body.kind !== "pkiResponse") {
413
+ throw E("cmc/not-a-response",
414
+ "pki.cmc.verify interprets a Full PKI Response (id-cct-PKIResponse); this message is a " + body.kind);
415
+ }
416
+
417
+ return _assertAuthentic(body, sent, response).then(function (signatureVerified) {
418
+ _assertBound(body, sent);
419
+ return _shape(body, sent, signatureVerified);
420
+ });
421
+ }
422
+
423
+ /**
424
+ * PD14 -- RFC 5272 sec. 3.2.1.3.4: "As part of processing a PKI Request/Response,
425
+ * the signature(s) MUST be verified."
426
+ *
427
+ * A Full PKI Response is a SignedData (sec. 4.2), so a carrier with no SignerInfo
428
+ * at all is not one -- that is the Simple response's shape, and reading an
429
+ * `issued` verdict off it would report an enrollment result nobody signed.
430
+ *
431
+ * Verification needs the signer's certificate, which this layer cannot invent.
432
+ * So the posture is fail-closed with a NAMED opt-out rather than a silent one:
433
+ * supply `certs` and the signature is checked; supply `allowUnverified: true` and
434
+ * the verdict says `signatureVerified: false` so nothing downstream can mistake
435
+ * it for an authenticated answer. Doing neither is refused, because that is the
436
+ * case where a caller would otherwise believe a check happened.
437
+ */
438
+ function _assertAuthentic(body, sent, responseBytes) {
439
+ return Promise.resolve().then(function () {
440
+ // Authentication is over BYTES, and that is true of BOTH carriers -- a MAC and
441
+ // a signature alike. The already-parsed input form hands this layer an object
442
+ // the CALLER still owns: the verdict is read from it synchronously, the check
443
+ // runs a microtask later, and a caller that swaps the encapsulated content,
444
+ // the signers and the certificate bag in the gap would get one message's
445
+ // verdict reported beside another message's verified signature. There are no
446
+ // bytes to pin, so the honest answer is that nothing was authenticated.
447
+ var haveBytes = Buffer.isBuffer(responseBytes) || responseBytes instanceof Uint8Array ||
448
+ typeof responseBytes === "string";
449
+ // The two accepted carriers are authenticated DIFFERENTLY, so the rule has to
450
+ // be written for both: a SignedData by its SignerInfo signatures, an
451
+ // AuthenticatedData by a MAC over a key this layer never holds. Checking only
452
+ // the SignedData shape would reject every conforming AuthenticatedData
453
+ // response for having no signer -- which is not a defect in the message.
454
+ if (body.cms && body.cms.contentTypeName === "authData") {
455
+ // The MAC needs key material this layer does not invent -- but a caller who
456
+ // HAS it should not be forced through the unauthenticated opt-out, which
457
+ // would report signatureVerified:false for a response whose MAC checked out.
458
+ // pki.cms.decrypt owns AuthenticatedData; `recipient` is handed to it rather
459
+ // than the MAC being recomputed here.
460
+ if (sent.recipient != null) {
461
+ // pki.cms.decrypt deliberately refuses a pre-parsed object too, so nothing
462
+ // can hand it a parse result that skipped DER validation. When the caller
463
+ // used the already-parsed input form there are no bytes to check, and
464
+ // saying exactly that is the honest answer -- reporting "did not
465
+ // authenticate" would blame the message for what is a missing input.
466
+ if (!haveBytes) {
467
+ throw E("cmc/bad-input",
468
+ "authenticating an AuthenticatedData response needs the response as DER bytes or PEM, " +
469
+ "because its MAC is computed over them -- pass the encoded response rather than a parsed one");
470
+ }
471
+ // It re-parses, and the content check below then ties its result back to
472
+ // what was interpreted here.
473
+ return cmsDecrypt.decrypt(responseBytes, sent.recipient).then(null, function (e) {
474
+ // The CMS layer's code does not escape this surface. cms/decrypt-failed is
475
+ // deliberately oracle-free -- every secret-dependent failure collapses into
476
+ // it -- and that property is preserved by re-throwing it as this domain's
477
+ // own verdict with the cause chained, rather than adding any detail.
478
+ throw E("cmc/unverified-response",
479
+ "the AuthenticatedData response did not authenticate under the supplied recipient key", e);
480
+ }).then(function (res) {
481
+ if (!res || res.authenticated !== true) {
482
+ throw E("cmc/unverified-response", "the AuthenticatedData response did not authenticate under the supplied recipient key");
483
+ }
484
+ // The MAC covers BYTES; bind them to the ones that were interpreted.
485
+ // Verifying a MAC over content and then reporting a verdict read from
486
+ // somewhere else is the same defect as checking a signature over the
487
+ // wrong region -- authentic, and about a different message.
488
+ // Both are already Buffers -- pki.cms.decrypt returns one, and the parse
489
+ // surfaces one -- so they are compared directly rather than re-wrapped.
490
+ var authed = res.content;
491
+ var eContent = body.cms.encapContentInfo && body.cms.encapContentInfo.eContent;
492
+ if (!eContent || !Buffer.isBuffer(authed) || !guard.crypto.constantTimeEqual(authed, eContent)) {
493
+ throw E("cmc/unverified-response",
494
+ "the authenticated content is not the content this verdict was read from");
495
+ }
496
+ return true;
497
+ });
498
+ }
499
+ if (sent.allowUnverified === true) return false;
500
+ throw E("cmc/unverified-response",
501
+ "an AuthenticatedData response is authenticated by its MAC, which needs the recipient key this layer does not hold -- pass `recipient` with the key material (the shape pki.cms.decrypt takes), or `allowUnverified: true` to interpret it unauthenticated");
502
+ }
503
+ var signerInfos = (body.cms && body.cms.signerInfos) || [];
504
+ if (!signerInfos.length) {
505
+ throw E("cmc/unsigned-response",
506
+ "a Full PKI Response is a SignedData carrying at least one SignerInfo; this carrier has none, so there is no signature to verify (RFC 5272 sec. 4.2 / 3.2.1.3.4)");
507
+ }
508
+ // Same rule as the MAC arm above, applied where a signature is equally over
509
+ // bytes. The carrier's SHAPE is judged first -- an unsigned one is refused
510
+ // whatever form it arrived in -- and only the act of authenticating needs the
511
+ // bytes. A caller who has nothing but a parsed message can still interpret it
512
+ // through the named opt-out, which reports signatureVerified:false; what is
513
+ // refused is the combination that would claim a check nothing pinned.
514
+ if (!haveBytes) {
515
+ if (sent.allowUnverified === true) return false;
516
+ throw E("cmc/bad-input",
517
+ "verifying a CMC response signature needs the response as DER bytes or PEM, because the " +
518
+ "signature is over them -- pass the encoded response rather than a parsed one, or " +
519
+ "`allowUnverified: true` to interpret it unauthenticated");
520
+ }
521
+ // Verified over the SAME immutable snapshot the verdict was parsed from, not
522
+ // over the parse result: the two cannot describe different messages when both
523
+ // come from one private copy of the bytes.
524
+ //
525
+ // TRY first, and only report "cannot verify" when the signer certificate is
526
+ // genuinely nowhere to be found. A conforming SignedData carries its own signer
527
+ // certificate, which is the shape pki.cmc.build emits and the one a CA sends --
528
+ // demanding that the caller ALSO pass it would make the ordinary
529
+ // build-then-verify flow fail for a message that already contains everything
530
+ // needed. `certs` supplements the embedded bag; it was never meant to be the
531
+ // only source. What does not change is the posture: a signature that is present
532
+ // and does not verify is a refusal, and an unverifiable one still needs the
533
+ // named opt-out rather than passing quietly.
534
+ return cmsVerify.verify(responseBytes, sent.certs && sent.certs.length ? { certs: sent.certs } : {})
535
+ .then(function (res) {
536
+ if (res.valid) return true;
537
+ // EVERY failing signer, not just the first. A response may carry several,
538
+ // and the opt-out may only be honoured when none of them was actually
539
+ // checked and rejected: one signer whose certificate is missing must not
540
+ // let a DIFFERENT signer's failed signature through beside it.
541
+ var failed = (res.signers || []).filter(function (s) { return !s.ok; });
542
+ var code = failed.length ? failed[0].code : null;
543
+ var onlyMissing = failed.length > 0 && failed.every(function (s) {
544
+ return s.code === "cms/signer-cert-not-found";
545
+ });
546
+ // No signer certificate anywhere is "could not check", which the opt-out
547
+ // covers. A signature that failed against a certificate we DID find is a
548
+ // real failure, and no opt-out excuses it.
549
+ if (onlyMissing && sent.allowUnverified === true) return false;
550
+ throw E("cmc/unverified-response",
551
+ "the CMC response signature did not verify" + (code ? " (" + code + ")" : "") +
552
+ " -- pass `certs` with the responder's certificate if it is not carried in the message, or " +
553
+ "`allowUnverified: true` to accept an unauthenticated response deliberately");
554
+ });
555
+ });
556
+ }
557
+
558
+ function _shape(body, sent, signatureVerified) {
559
+ var outcome = _reduceOutcome(body.statuses);
560
+ var governing = _governingStatus(body.statuses, outcome);
561
+ var anchors = _findControl(body.controls, OID_TRUSTED_ANCHORS, "Publish Trust Anchors");
562
+ var confirm = _findControl(body.controls, OID_CONFIRM_CERT_ACCEPTANCE, "Confirm Certificate Acceptance");
563
+ var serverNonce = _findControl(body.controls, OID_SENDER_NONCE, "Sender Nonce");
564
+
565
+ return {
566
+ outcome: outcome,
567
+ statuses: body.statuses,
568
+ // The detail belonging to the governing verdict, flattened for the common read.
569
+ failInfo: governing ? governing.failInfoName : null,
570
+ failInfoValue: governing ? governing.failInfo : null,
571
+ extendedFailInfo: governing ? governing.extendedFailInfo : null,
572
+ pendToken: governing && governing.pendInfo ? governing.pendInfo.pendToken : null,
573
+ pendTime: governing && governing.pendInfo ? governing.pendInfo.pendTime : null,
574
+ statusString: governing ? governing.statusString : null,
575
+ transactionId: sent.transactionId != null ? sent.transactionId : null,
576
+ // The responder's OWN nonce, retained by the caller for the next leg of the
577
+ // same transaction (RFC 5272 sec. 6.6).
578
+ senderNonce: serverNonce ? _octets(serverNonce, "Sender Nonce") : null,
579
+ // Surfaced, never acted on -- see the primitive's note on trust.
580
+ certificates: _bagOf(body.cms, "certs", "certificates"),
581
+ crls: _bagOf(body.cms, "crls", "crls"),
582
+ // The response's OWN two sequences, raw. For a request whose only arm was the
583
+ // other-message form there is no certificate to return, and RFC 5272 sec. 4.1
584
+ // puts the answer here instead -- so leaving them out would give such an
585
+ // exchange a successful verdict with its result unreachable, and send the
586
+ // caller back to the encapsulated bytes to re-parse what was already decoded.
587
+ cmsSequence: (body.cmsSequence || []).slice(),
588
+ otherMsgs: (body.otherMsgs || []).slice(),
589
+ publishTrustAnchors: anchors ? anchors.values.slice() : null,
590
+ confirmCertId: confirm ? _singleValue(confirm, "Confirm Certificate Acceptance") : null,
591
+ // Whether the CARRIER's signature was checked. False only via the explicit
592
+ // allowUnverified opt-out -- there is no path that leaves it false silently.
593
+ signatureVerified: signatureVerified,
594
+ // Whether anything in here was TRUSTED, which is never: the certificate bag
595
+ // and any Publish Trust Anchors control are the caller's to path-validate.
596
+ trusted: false,
597
+ controls: body.controls,
598
+ unhandled: body.unhandled,
599
+ cms: body.cms,
600
+ };
601
+ }
602
+
603
+ /**
604
+ * @primitive pki.cmc.build
605
+ * @signature pki.cmc.build(spec, signer, opts?) -> Promise<Buffer|string>
606
+ * @since 0.4.16
607
+ * @status experimental
608
+ * @spec RFC 5272, RFC 6402
609
+ * @defends enrollment-request-substitution (CWE-345)
610
+ * @related pki.cmc.verify, pki.schema.cmc.parse, pki.cms.sign
611
+ *
612
+ * Build and sign an RFC 5272 Full PKI Request. `spec.requests` is the list of certification
613
+ * requests, each naming exactly one arm -- `tcr` (a PKCS#10 CSR), `crm` (a CRMF CertReqMsg, or the
614
+ * CertReqMessages `pki.crmf.build` returns when it carries exactly one) or `orm`
615
+ * (`{ type, value }`). `spec.controls` are additional controls as `{ type, value }`, and `signer`
616
+ * is the `{ cert, key }` that signs the enclosing CMS SignedData.
617
+ *
618
+ * Body part identifiers are allocated automatically, unique across the whole message and never 0
619
+ * (RFC 5272 sec. 3.2.2). A caller may pin one, and a clash is REFUSED rather than renumbered --
620
+ * silently moving an identifier would break any control that already referenced it. For a `crm`
621
+ * arm the identity is the CertReqMsg's own `certReqId`, read back out of the supplied message.
622
+ *
623
+ * `spec.identityProof: { secret, identity? }` attaches an Identity Proof V2 control whose witness is
624
+ * computed over the reqSequence bytes exactly as they are emitted (sec. 6.2.1 step 1 -- "encoded
625
+ * exactly as it appears in the Full PKI Request including the sequence type and length"). Supplying
626
+ * `identity` also emits the Identification control naming the shared secret, and -- per sec. 6.2.3 --
627
+ * derives the MAC key from `hash(secret || identity)` rather than `hash(secret)`: the two travel
628
+ * together because the control's presence is what changes the derivation. And
629
+ * `spec.popLink: { secret }` attaches a POP Link Witness V2 together with the POP Link Random
630
+ * control that PL1 requires in the same request. `spec.renewal: true` marks a renewal, which MUST
631
+ * carry neither Identification nor Identity Proof (sec. 3.2 (a)) -- asking for both is refused
632
+ * rather than silently dropped.
633
+ *
634
+ * `spec.transactionId` (number|bigint), `spec.senderNonce` and `spec.dataReturn` (bytes) attach the
635
+ * exchange-binding controls (RFC 5272 sec. 6.6 / 6.4) -- the same three `pki.cmc.verify` checks the
636
+ * response against. They are named fields rather than something to hand-encode into
637
+ * `spec.controls`, because a request that quietly omits them has no replay defence and neither end
638
+ * can tell: the verifier only enforces the halves the client says it sent. An unrecognized spec
639
+ * field is refused for the same reason -- a misspelling would otherwise build and sign a message
640
+ * that simply does not carry what was asked for.
641
+ *
642
+ * @opts
643
+ * - `pem` (boolean) -- return a PEM `CMS` block instead of DER.
644
+ * - `popLinkRandomBytes` (number) -- the length of R; default 64 (PL1 SHOULD: >= 512 bits).
645
+ * @example
646
+ * var pair = await pki.key.generate("Ed25519");
647
+ * var key = await pki.key.export(pair.privateKey);
648
+ * var spki = await pki.key.export(pair.publicKey);
649
+ * var cert = await pki.x509.sign({ subject: "client", subjectPublicKey: spki,
650
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
651
+ * var csr = await pki.csr.sign({ subject: "client", subjectPublicKey: spki }, { key: key });
652
+ * var req = await pki.cmc.build({ requests: [{ tcr: csr }] }, { cert: cert, key: key });
653
+ * pki.schema.cmc.parse(req).requests[0].arm; // "tcr"
654
+ */
655
+ var build = require("./cmc-build").build;
656
+
657
+ module.exports = { verify: verify, build: build };