@blamejs/pki 0.6.14 → 0.6.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -1
- package/lib/acme.js +59 -815
- package/lib/asn1-der.js +7 -245
- package/lib/attrcert-sign.js +12 -260
- package/lib/attrcert-verify.js +1 -7
- package/lib/byte-reader.js +0 -8
- package/lib/byte-writer.js +0 -11
- package/lib/cbor-det.js +0 -54
- package/lib/cmc-build.js +15 -357
- package/lib/cmc-verify.js +6 -239
- package/lib/cmp-build.js +41 -317
- package/lib/cmp-session.js +73 -760
- package/lib/cmp-verify.js +32 -488
- package/lib/cms-compress.js +3 -32
- package/lib/cms-decrypt.js +29 -294
- package/lib/cms-encrypt.js +14 -234
- package/lib/cms-sign.js +29 -324
- package/lib/cms-verify.js +9 -500
- package/lib/composite-kem.js +2 -102
- package/lib/composite-sig.js +1 -77
- package/lib/constants.js +0 -249
- package/lib/crl-sign.js +9 -237
- package/lib/crl-verify.js +1 -10
- package/lib/crmf-sign.js +19 -198
- package/lib/crmf-verify.js +1 -12
- package/lib/csr-sign.js +3 -34
- package/lib/csr-verify.js +1 -12
- package/lib/ct.js +28 -253
- package/lib/edwards-point.js +10 -44
- package/lib/est.js +28 -764
- package/lib/framework-error.js +4 -182
- package/lib/guard-all.js +1 -58
- package/lib/guard-async.js +1 -36
- package/lib/guard-bytes.js +6 -650
- package/lib/guard-compress.js +5 -102
- package/lib/guard-crypto.js +1 -40
- package/lib/guard-der.js +2 -93
- package/lib/guard-encoding.js +2 -60
- package/lib/guard-header.js +1 -36
- package/lib/guard-identifier.js +7 -531
- package/lib/guard-intrinsic.js +1 -150
- package/lib/guard-json.js +10 -73
- package/lib/guard-limits.js +1 -77
- package/lib/guard-list.js +1 -49
- package/lib/guard-name.js +3 -209
- package/lib/guard-parsed.js +7 -335
- package/lib/guard-range.js +1 -71
- package/lib/guard-secret.js +1 -102
- package/lib/guard-text.js +1 -64
- package/lib/guard-time.js +1 -97
- package/lib/hpke.js +3 -105
- package/lib/http-digest.js +14 -142
- package/lib/http-retry-after.js +1 -47
- package/lib/http-transport.js +21 -131
- package/lib/inspect.js +30 -284
- package/lib/ip-utils.js +6 -47
- package/lib/jose.js +7 -142
- package/lib/key.js +2 -67
- package/lib/lint.js +9 -194
- package/lib/merkle.js +1 -27
- package/lib/mime.js +9 -82
- package/lib/ocsp-request-verify.js +1 -14
- package/lib/ocsp-verify.js +1 -82
- package/lib/ocsp.js +11 -169
- package/lib/oid.js +5 -337
- package/lib/path-validate.js +103 -1492
- package/lib/pbes2.js +2 -89
- package/lib/pkcs12-build.js +31 -379
- package/lib/pki-build.js +64 -290
- package/lib/rc2.js +2 -18
- package/lib/rfc3339.js +4 -27
- package/lib/scep.js +8 -252
- package/lib/schema-all.js +0 -122
- package/lib/schema-attrcert.js +4 -146
- package/lib/schema-c509.js +153 -802
- package/lib/schema-cmc.js +5 -220
- package/lib/schema-cmp.js +2 -223
- package/lib/schema-cms.js +13 -427
- package/lib/schema-crl.js +5 -68
- package/lib/schema-crmf.js +6 -167
- package/lib/schema-csr.js +1 -54
- package/lib/schema-csrattrs.js +2 -87
- package/lib/schema-engine.js +0 -152
- package/lib/schema-ocsp.js +0 -117
- package/lib/schema-pkcs12.js +2 -93
- package/lib/schema-pkcs8.js +3 -35
- package/lib/schema-pkix.js +24 -655
- package/lib/schema-smime.js +0 -54
- package/lib/schema-tsp.js +3 -99
- package/lib/schema-x509.js +4 -63
- package/lib/shbs.js +11 -95
- package/lib/sign-scheme.js +2 -97
- package/lib/sigstore.js +9 -169
- package/lib/sleep.js +2 -8
- package/lib/smime.js +15 -408
- package/lib/tls-cert-compress.js +0 -62
- package/lib/trust.js +19 -185
- package/lib/tsp-sign.js +16 -270
- package/lib/validator-all.js +1 -30
- package/lib/validator-attcert.js +2 -46
- package/lib/validator-cose.js +2 -153
- package/lib/validator-keydesc.js +1 -37
- package/lib/validator-sig.js +1 -46
- package/lib/validator-tls.js +1 -88
- package/lib/validator-tpm.js +19 -122
- package/lib/verify-seam.js +1 -24
- package/lib/webauthn-mds.js +3 -400
- package/lib/webauthn.js +12 -833
- package/lib/webcrypto.js +5 -485
- package/lib/x509-sign.js +4 -108
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/cmc-verify.js
CHANGED
|
@@ -22,12 +22,10 @@
|
|
|
22
22
|
var asn1 = require("./asn1-der");
|
|
23
23
|
var oid = require("./oid");
|
|
24
24
|
var cmc = require("./schema-cmc");
|
|
25
|
-
var cmsVerify = require("./cms-verify");
|
|
26
|
-
var cmsDecrypt = require("./cms-decrypt");
|
|
25
|
+
var cmsVerify = require("./cms-verify");
|
|
26
|
+
var cmsDecrypt = require("./cms-decrypt");
|
|
27
27
|
var guard = require("./guard-all");
|
|
28
28
|
var intrinsic = require("./guard-intrinsic");
|
|
29
|
-
// Own-key membership through an operation taken at load. Written out, the question reads two
|
|
30
|
-
// replaceable properties to answer one thing, and here it decides which controls a response carries.
|
|
31
29
|
var _hasOwn = intrinsic.hasOwn;
|
|
32
30
|
var frameworkError = require("./framework-error");
|
|
33
31
|
|
|
@@ -42,10 +40,6 @@ var OID_DATA_RETURN = O("id-cmc-dataReturn");
|
|
|
42
40
|
var OID_TRUSTED_ANCHORS = O("id-cmc-trustedAnchors");
|
|
43
41
|
var OID_CONFIRM_CERT_ACCEPTANCE = O("id-cmc-confirmCertAcceptance");
|
|
44
42
|
|
|
45
|
-
// CMCStatus -> the terminal outcome a caller acts on. RFC 5272 sec. 6.1.3 gives
|
|
46
|
-
// seven statuses; four of them mean "this exchange did not produce a certificate
|
|
47
|
-
// and will not without another message", so they collapse to `rejected` while the
|
|
48
|
-
// status list keeps the detail.
|
|
49
43
|
var OUTCOME_BY_STATUS = {
|
|
50
44
|
success: "issued",
|
|
51
45
|
pending: "pending",
|
|
@@ -56,26 +50,14 @@ var OUTCOME_BY_STATUS = {
|
|
|
56
50
|
noSupport: "rejected",
|
|
57
51
|
};
|
|
58
52
|
|
|
59
|
-
// Which outcome governs when a response carries SEVERAL status controls (sec.
|
|
60
|
-
// 6.1 requires a client to cope with that). Ranked worst-first so the verdict can
|
|
61
|
-
// never be improved by the order the controls happen to appear in: reporting
|
|
62
|
-
// the first one seen would let a server bury a failure behind a success.
|
|
63
53
|
var OUTCOME_SEVERITY = { rejected: 4, "pop-required": 3, "confirm-required": 2, pending: 1, issued: 0 };
|
|
64
54
|
|
|
65
|
-
// The single AttributeValue of a control that carries exactly one.
|
|
66
55
|
function _singleValue(control, what) {
|
|
67
56
|
if (control.values.length !== 1) {
|
|
68
57
|
throw E("cmc/bad-control", "the " + what + " control carries exactly one value, got " + control.values.length);
|
|
69
58
|
}
|
|
70
59
|
return control.values[0];
|
|
71
60
|
}
|
|
72
|
-
// The single instance of a control, or null when absent.
|
|
73
|
-
//
|
|
74
|
-
// Body-part identity is unique per element, so a responder can legally carry the
|
|
75
|
-
// same control type twice under different bodyPartIDs. Taking the first match
|
|
76
|
-
// would let an attacker pair one correct echo with a contradictory one and still
|
|
77
|
-
// satisfy the binding, so a duplicate is refused as ambiguous instead of
|
|
78
|
-
// resolved. The exchange either has one answer or it has none.
|
|
79
61
|
function _findControl(controls, attrType, what) {
|
|
80
62
|
var found = null;
|
|
81
63
|
for (var i = 0; i < controls.length; i++) {
|
|
@@ -92,12 +74,6 @@ function _octets(control, what) {
|
|
|
92
74
|
return asn1.read.octetString(asn1.decode(_singleValue(control, what)));
|
|
93
75
|
}
|
|
94
76
|
|
|
95
|
-
// The certificate / CRL bag, from whichever carrier holds it. A SignedData keeps
|
|
96
|
-
// them at the top level; an AuthenticatedData keeps them under `originatorInfo`
|
|
97
|
-
// (RFC 5652 sec. 9.1). Both carriers are accepted for a Full PKI Response, so
|
|
98
|
-
// reading only the SignedData shape would silently return an empty bag, and
|
|
99
|
-
// "no certificates were issued" is exactly the wrong thing to report when some
|
|
100
|
-
// were.
|
|
101
77
|
function _bagOf(parsedCms, originatorKey, topKey) {
|
|
102
78
|
if (!parsedCms) return [];
|
|
103
79
|
var list = parsedCms[topKey];
|
|
@@ -105,29 +81,13 @@ function _bagOf(parsedCms, originatorKey, topKey) {
|
|
|
105
81
|
return (list || []).map(function (c) { return c.bytes; });
|
|
106
82
|
}
|
|
107
83
|
|
|
108
|
-
|
|
109
|
-
* The transaction binding -- RFC 5272 sec. 6.6, RFC 5273 sec. 6.
|
|
110
|
-
*
|
|
111
|
-
* Both halves are CONDITIONAL on what the client sent, which is the subtlety: a
|
|
112
|
-
* client that sent no transactionId cannot demand one back, and a client that
|
|
113
|
-
* sent no senderNonce has nothing to compare a recipientNonce against. What is
|
|
114
|
-
* not conditional is the converse: having sent one, an absent or differing echo
|
|
115
|
-
* is a refusal, because that is exactly the replay the nonce exists to stop.
|
|
116
|
-
*
|
|
117
|
-
* Returns which halves ran, because "conditional" and "checked" are different
|
|
118
|
-
* facts and the verdict has to be able to tell them apart.
|
|
119
|
-
*/
|
|
84
|
+
|
|
120
85
|
function _assertBound(body, sent) {
|
|
121
|
-
// A zero-length value binds nothing. An empty senderNonce echoes equal in every
|
|
122
|
-
// exchange that also used one, so counting its presence would report boundToRequest
|
|
123
|
-
// true for a response captured from any of them -- the exact replay this gate exists
|
|
124
|
-
// to refuse, passing because the comparison it ran was vacuous. The same holds for an
|
|
125
|
-
// empty transactionId or dataReturn: a binding has to carry entropy to be a binding.
|
|
126
86
|
function _carries(v) {
|
|
127
87
|
if (v == null) return false;
|
|
128
88
|
if (typeof v === "string") return v.length > 0;
|
|
129
89
|
if (Buffer.isBuffer(v) || v instanceof Uint8Array) return v.length > 0; // allow:byte-source-narrow -- sent.senderNonce / dataReturn are already normalized to a Buffer by _snapshotSent; this is a length test on a settled value, not an input door
|
|
130
|
-
return true;
|
|
90
|
+
return true;
|
|
131
91
|
}
|
|
132
92
|
var bound = {
|
|
133
93
|
transactionId: _carries(sent.transactionId),
|
|
@@ -142,9 +102,6 @@ function _assertBound(body, sent) {
|
|
|
142
102
|
"the request carried a Transaction Identifier control, so the response MUST include the same one (RFC 5272 sec. 6.6)");
|
|
143
103
|
}
|
|
144
104
|
var got = asn1.read.integer(asn1.decode(_singleValue(txControl, "Transaction Identifier")));
|
|
145
|
-
// Compared against the value the authoring guard validated, never a fresh
|
|
146
|
-
// conversion of the caller's input -- a number too large to hold an integer
|
|
147
|
-
// exactly has already lost the digits that distinguish it from its neighbor.
|
|
148
105
|
if (got !== sent.transactionIdValue) {
|
|
149
106
|
throw E("cmc/transaction-mismatch",
|
|
150
107
|
"the response Transaction Identifier " + got + " does not match the request's " + sent.transactionId);
|
|
@@ -157,17 +114,11 @@ function _assertBound(body, sent) {
|
|
|
157
114
|
throw E("cmc/nonce-mismatch",
|
|
158
115
|
"the request carried a Sender Nonce, so the response MUST reflect it back as a Recipient Nonce control (RFC 5272 sec. 6.6)");
|
|
159
116
|
}
|
|
160
|
-
// Constant-time and by full value: guard.crypto.constantTimeEqual gates the
|
|
161
|
-
// length first, so a truncation or a prefix is an honest false and not a
|
|
162
|
-
// match on the bytes that happen to line up.
|
|
163
117
|
if (!guard.crypto.constantTimeEqual(_octets(rn, "Recipient Nonce"), Buffer.from(sent.senderNonce))) {
|
|
164
118
|
throw E("cmc/nonce-mismatch", "the response Recipient Nonce does not match the Sender Nonce the request sent");
|
|
165
119
|
}
|
|
166
120
|
}
|
|
167
121
|
|
|
168
|
-
// RFC 5272 sec. 6.4: "If the Data Return control appears in a Full PKI Request,
|
|
169
|
-
// the server MUST return it as part of the PKI Response." The data is opaque to
|
|
170
|
-
// the server, so the check is that the same bytes came back.
|
|
171
122
|
if (sent.dataReturn != null) {
|
|
172
123
|
var dr = _findControl(body.controls, OID_DATA_RETURN, "Data Return");
|
|
173
124
|
if (!dr) {
|
|
@@ -179,15 +130,6 @@ function _assertBound(body, sent) {
|
|
|
179
130
|
}
|
|
180
131
|
}
|
|
181
132
|
|
|
182
|
-
// A status names, in its bodyList, the body parts it is ABOUT (RFC 5272
|
|
183
|
-
// sec. 6.1.1). A status about a body part the request never carried is not an
|
|
184
|
-
// answer to this request -- and the binding controls do not catch it, because a
|
|
185
|
-
// server can echo the transaction and nonce correctly while reporting on
|
|
186
|
-
// something else entirely. Held to the same asymmetry as every other half of the
|
|
187
|
-
// binding: checked only when the client kept the set, and once kept, a reference
|
|
188
|
-
// outside it is a refusal.
|
|
189
|
-
// Element-by-element, and the lengths must agree: a retained `[a, b]` must not admit a
|
|
190
|
-
// reported `[a, b, c]`, which names a part one level deeper than anything sent.
|
|
191
133
|
function _pathRetained(retained, want) {
|
|
192
134
|
if (!Array.isArray(retained)) return false;
|
|
193
135
|
return retained.some(function (have) {
|
|
@@ -199,18 +141,10 @@ function _assertBound(body, sent) {
|
|
|
199
141
|
if (Array.isArray(sent.bodyPartIDs)) {
|
|
200
142
|
var known = Object.create(null);
|
|
201
143
|
sent.bodyPartIDs.forEach(function (id) { known[String(id)] = true; });
|
|
202
|
-
// 0 is reserved as the reference to the enclosing PKIData itself (sec. 3.2.1),
|
|
203
|
-
// so a status about the request as a whole is in the set by definition.
|
|
204
144
|
known["0"] = true;
|
|
205
145
|
body.statuses.forEach(function (s) {
|
|
206
146
|
(s.bodyList || []).forEach(function (ref) {
|
|
207
147
|
var path = ref.bodyPartPath;
|
|
208
|
-
// A bodyPartPath descends INTO a nested message. Checking only its head would
|
|
209
|
-
// accept `[a part we sent, 999]`, a status about something arbitrary wearing a
|
|
210
|
-
// reference that passes, so the whole path is matched against the paths the
|
|
211
|
-
// request actually composed. Those are retained by reading each nested message
|
|
212
|
-
// back; a nested message that could not be read back contributes none, so a path
|
|
213
|
-
// into it finds no match and is refused. Either way the tail is never waved through.
|
|
214
148
|
if (path && path.length > 1) {
|
|
215
149
|
if (_pathRetained(sent.bodyPartPaths, path)) return;
|
|
216
150
|
throw E("cmc/body-part-unknown",
|
|
@@ -227,24 +161,6 @@ function _assertBound(body, sent) {
|
|
|
227
161
|
});
|
|
228
162
|
}
|
|
229
163
|
|
|
230
|
-
// Every check above is conditional on the caller having retained something, so a
|
|
231
|
-
// caller who retained nothing runs none of them and still gets a full verdict
|
|
232
|
-
// (`issued`, signature verified, certificates surfaced) off a Full PKI Response
|
|
233
|
-
// captured from any earlier exchange with the same CA. That is the CWE-294 this
|
|
234
|
-
// module names as its defense, and leaving it opt-in leaves the defense off for
|
|
235
|
-
// whoever did not know to ask.
|
|
236
|
-
//
|
|
237
|
-
// The posture here is the one _assertAuthentic already takes for the carrier
|
|
238
|
-
// signature, applied to the other question: check it when the caller supplies
|
|
239
|
-
// what it takes, and refuse when nothing does, unless the caller names that. The
|
|
240
|
-
// two opt-outs stay separate because they answer separate questions. A replayed
|
|
241
|
-
// response is authentic, so `allowUnverified` covering it would let "I could not
|
|
242
|
-
// check the signature" stand in for "I did not check which exchange this
|
|
243
|
-
// answers".
|
|
244
|
-
//
|
|
245
|
-
// bodyPartIDs does not count toward it. Identifiers are allocated per message
|
|
246
|
-
// from a small range (sec. 3.2.2), so the same set recurs across requests and a
|
|
247
|
-
// status naming one says nothing about which request it answers.
|
|
248
164
|
bound.boundToRequest = bound.transactionId || bound.senderNonce || bound.dataReturn;
|
|
249
165
|
if (!bound.boundToRequest && sent.allowUnbound !== true) {
|
|
250
166
|
throw E("cmc/unbound-response",
|
|
@@ -256,9 +172,6 @@ function _assertBound(body, sent) {
|
|
|
256
172
|
return bound;
|
|
257
173
|
}
|
|
258
174
|
|
|
259
|
-
// The worst outcome across every status control, with success assumed when there
|
|
260
|
-
// is none at all (RFC 5272 sec. 6.1.2: "If no status exists for a Simple or Full
|
|
261
|
-
// PKI Request, then the value of success is assumed").
|
|
262
175
|
function _reduceOutcome(statuses) {
|
|
263
176
|
var outcome = "issued";
|
|
264
177
|
for (var i = 0; i < statuses.length; i++) {
|
|
@@ -268,9 +181,6 @@ function _reduceOutcome(statuses) {
|
|
|
268
181
|
return outcome;
|
|
269
182
|
}
|
|
270
183
|
|
|
271
|
-
// The pendInfo / failInfo detail belonging to the status that GOVERNED, so a
|
|
272
|
-
// caller reading `pendToken` gets the one attached to the pending verdict rather
|
|
273
|
-
// than whichever status happened to carry a token.
|
|
274
184
|
function _governingStatus(statuses, outcome) {
|
|
275
185
|
for (var i = 0; i < statuses.length; i++) {
|
|
276
186
|
if (OUTCOME_BY_STATUS[statuses[i].status] === outcome) return statuses[i];
|
|
@@ -373,17 +283,12 @@ function _governingStatus(statuses, outcome) {
|
|
|
373
283
|
* v.boundToRequest; // false: nothing ties this response to a request either
|
|
374
284
|
*/
|
|
375
285
|
function verify(response, sent) {
|
|
376
|
-
// Both snapshots are taken SYNCHRONOUSLY, here, before anything is deferred.
|
|
377
|
-
// Taking them inside the async body would be too late by one microtask: a caller
|
|
378
|
-
// that mutates the buffer or the options object on the line after this call has
|
|
379
|
-
// already changed them before that body ever runs, which is the easiest version
|
|
380
|
-
// of the race to hit by accident.
|
|
381
286
|
var frozenResponse, frozenSent;
|
|
382
287
|
try {
|
|
383
288
|
frozenSent = _snapshotSent(_assertOpts(sent));
|
|
384
289
|
frozenResponse = _snapshotIfBytes(response);
|
|
385
290
|
} catch (e) {
|
|
386
|
-
return Promise.reject(e);
|
|
291
|
+
return Promise.reject(e);
|
|
387
292
|
}
|
|
388
293
|
return guard.async.deferred(function () { return _verify(frozenResponse, frozenSent); });
|
|
389
294
|
}
|
|
@@ -396,35 +301,13 @@ function _assertOpts(sent) {
|
|
|
396
301
|
return sent;
|
|
397
302
|
}
|
|
398
303
|
|
|
399
|
-
// A private copy of the input when -- and only when -- it is memory the caller can
|
|
400
|
-
// still write to. Anything else is returned untouched so this cannot narrow the
|
|
401
|
-
// input contract of the parse it feeds; pki.schema.cmc.parse stays the one place
|
|
402
|
-
// that decides what an acceptable input is, and keeps raising its own typed error.
|
|
403
304
|
function _snapshotIfBytes(input) {
|
|
404
|
-
// EVERY byte form the parser accepts, not just the two most common. It takes a
|
|
405
|
-
// BufferSource, so an ArrayBuffer or a DataView reaches the decoder too -- and
|
|
406
|
-
// leaving those aliased would reopen the window for exactly the inputs that came
|
|
407
|
-
// in by the wider door.
|
|
408
305
|
if (guard.bytes.isByteSource(input)) {
|
|
409
306
|
return guard.bytes.snapshotSource(input, CmcError, "cmc/bad-input", "pki.cmc.verify");
|
|
410
307
|
}
|
|
411
308
|
return input;
|
|
412
309
|
}
|
|
413
310
|
|
|
414
|
-
// A private copy of the exchange state the binding checks compare against. Scalars
|
|
415
|
-
// are copied by value and every byte buffer is copied, so nothing the caller still
|
|
416
|
-
// holds a reference to can change what "what we sent" means once verification has
|
|
417
|
-
// begun. Unknown fields are carried through untouched -- this fixes the values the
|
|
418
|
-
// checks below read, it does not narrow what the options object may contain.
|
|
419
|
-
// EVERY byte form, not just the two most common: a DataView or a bare ArrayBuffer arrives by the
|
|
420
|
-
// same door as a Buffer, and copying only Buffer and Uint8Array would leave those aliased across
|
|
421
|
-
// the very gap this closes -- reopening the window for exactly the inputs that came in by the
|
|
422
|
-
// wider one. A DataView is copied over its OWN window, not the whole buffer it happens to sit in.
|
|
423
|
-
// One call, because guard.bytes decides what a byte form is and where its bytes are. The three
|
|
424
|
-
// hand-rolled branches this replaces read `v.buffer`, `v.byteOffset` and `v.byteLength` off the
|
|
425
|
-
// value, and all three are accessors a caller's subclass can answer: one that lied copied a
|
|
426
|
-
// different store than the array held, so the bytes checked here and the bytes signed later were
|
|
427
|
-
// two different reads.
|
|
428
311
|
function _copyAnyBytes(v) {
|
|
429
312
|
if (!guard.bytes.isByteSource(v)) return v;
|
|
430
313
|
return guard.bytes.snapshotSource(v, CmcError, "cmc/bad-input", "a byte field of the request");
|
|
@@ -433,29 +316,12 @@ function _copyAnyBytes(v) {
|
|
|
433
316
|
function _snapshotSent(sent) {
|
|
434
317
|
var out = {}, k;
|
|
435
318
|
for (k in sent) { if (_hasOwn(sent, k)) out[k] = sent[k]; }
|
|
436
|
-
// Normalized through the SAME authoring guard pki.cmc.build puts an authored
|
|
437
|
-
// integer through. A Transaction Identifier is an unbounded INTEGER on the wire,
|
|
438
|
-
// so one above Number.MAX_SAFE_INTEGER has already been rounded by the time it
|
|
439
|
-
// arrives as a `number` -- 9007199254740993 is 9007199254740992 before this code
|
|
440
|
-
// sees it, and a response echoing that NEIGHBORING identifier would compare
|
|
441
|
-
// equal. Held here rather than converted, so a value too large to be a number is
|
|
442
|
-
// refused with the shape to use instead (a bigint) rather than silently bound to
|
|
443
|
-
// something adjacent.
|
|
444
|
-
// Validated, not replaced: the verdict echoes `transactionId` as the caller gave
|
|
445
|
-
// it, so the check is added without changing what comes back.
|
|
446
|
-
// Through ONE helper so nothing here can copy "the two common byte forms" and leave the rest
|
|
447
|
-
// aliased -- the narrowing that reopens this window for whatever came in by the wider door.
|
|
448
319
|
if (out.transactionId != null) {
|
|
449
320
|
out.transactionIdValue = guard.range.authoredInteger(out.transactionId, E, "cmc/bad-input", "sent.transactionId");
|
|
450
321
|
}
|
|
451
322
|
out.senderNonce = _copyAnyBytes(out.senderNonce);
|
|
452
323
|
out.dataReturn = _copyAnyBytes(out.dataReturn);
|
|
453
|
-
// Copied for the same reason the nonce is: the check runs after an await, and an
|
|
454
|
-
// array the caller still holds could have body parts appended to it in the gap,
|
|
455
|
-
// widening what the response is allowed to report on.
|
|
456
324
|
if (Array.isArray(out.bodyPartIDs)) out.bodyPartIDs = out.bodyPartIDs.slice();
|
|
457
|
-
// Both levels: the outer array AND each path, since appending a segment to a retained path
|
|
458
|
-
// in the gap would widen what the response may report on exactly as appending an id does.
|
|
459
325
|
if (Array.isArray(out.bodyPartPaths)) {
|
|
460
326
|
out.bodyPartPaths = out.bodyPartPaths.map(function (p) { return Array.isArray(p) ? p.slice() : p; });
|
|
461
327
|
}
|
|
@@ -468,18 +334,11 @@ function _snapshotSent(sent) {
|
|
|
468
334
|
out.recipient = r;
|
|
469
335
|
}
|
|
470
336
|
if (Array.isArray(out.certs)) out.certs = out.certs.map(_copyAnyBytes);
|
|
471
|
-
// Both opt-outs normalized to a strict boolean off the ORIGINAL object, so a
|
|
472
|
-
// truthy-but-not-true value cannot switch a check off by accident.
|
|
473
337
|
out.allowUnverified = sent.allowUnverified === true;
|
|
474
338
|
out.allowUnbound = sent.allowUnbound === true;
|
|
475
339
|
return out;
|
|
476
340
|
}
|
|
477
341
|
|
|
478
|
-
// `response` and `sent` arrive already frozen by verify() above -- BOTH sides of
|
|
479
|
-
// every comparison, not just the response bytes. The checks below run across an
|
|
480
|
-
// await and compare what the CA returned against what the caller says it sent, so
|
|
481
|
-
// freezing one side while reading the other live would leave half of each
|
|
482
|
-
// comparison mutable.
|
|
483
342
|
function _verify(response, sent) {
|
|
484
343
|
var body = cmc.parse(response);
|
|
485
344
|
if (body.kind !== "pkiResponse") {
|
|
@@ -492,73 +351,25 @@ function _verify(response, sent) {
|
|
|
492
351
|
});
|
|
493
352
|
}
|
|
494
353
|
|
|
495
|
-
|
|
496
|
-
* PD14 -- RFC 5272 sec. 3.2.1.3.4: "As part of processing a PKI Request/Response,
|
|
497
|
-
* the signature(s) MUST be verified."
|
|
498
|
-
*
|
|
499
|
-
* A Full PKI Response is a SignedData (sec. 4.2), so a carrier with no SignerInfo
|
|
500
|
-
* at all is not one -- that is the Simple response's shape, and reading an
|
|
501
|
-
* `issued` verdict off it would report an enrollment result nobody signed.
|
|
502
|
-
*
|
|
503
|
-
* Verification needs the signer's certificate, which this layer cannot invent.
|
|
504
|
-
* So the posture is fail-closed with a NAMED opt-out rather than a silent one:
|
|
505
|
-
* supply `certs` and the signature is checked; supply `allowUnverified: true` and
|
|
506
|
-
* the verdict says `signatureVerified: false` so nothing downstream can mistake
|
|
507
|
-
* it for an authenticated answer. Doing neither is refused, because that is the
|
|
508
|
-
* case where a caller would otherwise believe a check happened.
|
|
509
|
-
*/
|
|
354
|
+
|
|
510
355
|
function _assertAuthentic(body, sent, responseBytes) {
|
|
511
356
|
return Promise.resolve().then(function () {
|
|
512
|
-
// Authentication is over BYTES, and that is true of BOTH carriers -- a MAC and
|
|
513
|
-
// a signature alike. The already-parsed input form hands this layer an object
|
|
514
|
-
// the CALLER still owns: the verdict is read from it synchronously, the check
|
|
515
|
-
// runs a microtask later, and a caller that swaps the encapsulated content,
|
|
516
|
-
// the signers and the certificate bag in the gap would get one message's
|
|
517
|
-
// verdict reported beside another message's verified signature. There are no
|
|
518
|
-
// bytes to pin, so the honest answer is that nothing was authenticated.
|
|
519
357
|
var haveBytes = typeof responseBytes === "string" ||
|
|
520
358
|
Buffer.isBuffer(responseBytes) || responseBytes instanceof Uint8Array; // allow:byte-source-narrow -- responseBytes is _snapshotIfBytes's return: any BufferSource is already a Buffer here, so this is Buffer-or-string by construction
|
|
521
|
-
// The two accepted carriers are authenticated DIFFERENTLY, so the rule has to
|
|
522
|
-
// be written for both: a SignedData by its SignerInfo signatures, an
|
|
523
|
-
// AuthenticatedData by a MAC over a key this layer never holds. Checking only
|
|
524
|
-
// the SignedData shape would reject every conforming AuthenticatedData
|
|
525
|
-
// response for having no signer -- which is not a defect in the message.
|
|
526
359
|
if (body.cms && body.cms.contentTypeName === "authData") {
|
|
527
|
-
// The MAC needs key material this layer does not invent -- but a caller who
|
|
528
|
-
// HAS it should not be forced through the unauthenticated opt-out, which
|
|
529
|
-
// would report signatureVerified:false for a response whose MAC checked out.
|
|
530
|
-
// pki.cms.decrypt owns AuthenticatedData; `recipient` is handed to it rather
|
|
531
|
-
// than the MAC being recomputed here.
|
|
532
360
|
if (sent.recipient != null) {
|
|
533
|
-
// pki.cms.decrypt deliberately refuses a pre-parsed object too, so nothing
|
|
534
|
-
// can hand it a parse result that skipped DER validation. When the caller
|
|
535
|
-
// used the already-parsed input form there are no bytes to check, and
|
|
536
|
-
// saying exactly that is the honest answer -- reporting "did not
|
|
537
|
-
// authenticate" would blame the message for what is a missing input.
|
|
538
361
|
if (!haveBytes) {
|
|
539
362
|
throw E("cmc/bad-input",
|
|
540
363
|
"authenticating an AuthenticatedData response needs the response as DER bytes or PEM, " +
|
|
541
364
|
"because its MAC is computed over them -- pass the encoded response rather than a parsed one");
|
|
542
365
|
}
|
|
543
|
-
// It re-parses, and the content check below then ties its result back to
|
|
544
|
-
// what was interpreted here.
|
|
545
366
|
return cmsDecrypt.decrypt(responseBytes, sent.recipient).then(null, function (e) {
|
|
546
|
-
// The CMS layer's code does not escape this surface. cms/decrypt-failed is
|
|
547
|
-
// deliberately oracle-free -- every secret-dependent failure collapses into
|
|
548
|
-
// it -- and that property is preserved by re-throwing it as this domain's
|
|
549
|
-
// own verdict with the cause chained, rather than adding any detail.
|
|
550
367
|
throw E("cmc/unverified-response",
|
|
551
368
|
"the AuthenticatedData response did not authenticate under the supplied recipient key", e);
|
|
552
369
|
}).then(function (res) {
|
|
553
370
|
if (!res || res.authenticated !== true) {
|
|
554
371
|
throw E("cmc/unverified-response", "the AuthenticatedData response did not authenticate under the supplied recipient key");
|
|
555
372
|
}
|
|
556
|
-
// The MAC covers BYTES; bind them to the ones that were interpreted.
|
|
557
|
-
// Verifying a MAC over content and then reporting a verdict read from
|
|
558
|
-
// somewhere else is the same defect as checking a signature over the
|
|
559
|
-
// wrong region -- authentic, and about a different message.
|
|
560
|
-
// Both are already Buffers -- pki.cms.decrypt returns one, and the parse
|
|
561
|
-
// surfaces one -- so they are compared directly rather than re-wrapped.
|
|
562
373
|
var authed = res.content;
|
|
563
374
|
var eContent = body.cms.encapContentInfo && body.cms.encapContentInfo.eContent;
|
|
564
375
|
if (!eContent || !Buffer.isBuffer(authed) || !guard.crypto.constantTimeEqual(authed, eContent)) {
|
|
@@ -577,12 +388,6 @@ function _assertAuthentic(body, sent, responseBytes) {
|
|
|
577
388
|
throw E("cmc/unsigned-response",
|
|
578
389
|
"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)");
|
|
579
390
|
}
|
|
580
|
-
// Same rule as the MAC arm above, applied where a signature is equally over
|
|
581
|
-
// bytes. The carrier's SHAPE is judged first -- an unsigned one is refused
|
|
582
|
-
// whatever form it arrived in -- and only the act of authenticating needs the
|
|
583
|
-
// bytes. A caller who has nothing but a parsed message can still interpret it
|
|
584
|
-
// through the named opt-out, which reports signatureVerified:false; what is
|
|
585
|
-
// refused is the combination that would claim a check nothing pinned.
|
|
586
391
|
if (!haveBytes) {
|
|
587
392
|
if (sent.allowUnverified === true) return false;
|
|
588
393
|
throw E("cmc/bad-input",
|
|
@@ -590,34 +395,14 @@ function _assertAuthentic(body, sent, responseBytes) {
|
|
|
590
395
|
"signature is over them -- pass the encoded response rather than a parsed one, or " +
|
|
591
396
|
"`allowUnverified: true` to interpret it unauthenticated");
|
|
592
397
|
}
|
|
593
|
-
// Verified over the SAME immutable snapshot the verdict was parsed from, not
|
|
594
|
-
// over the parse result: the two cannot describe different messages when both
|
|
595
|
-
// come from one private copy of the bytes.
|
|
596
|
-
//
|
|
597
|
-
// TRY first, and only report "cannot verify" when the signer certificate is
|
|
598
|
-
// genuinely nowhere to be found. A conforming SignedData carries its own signer
|
|
599
|
-
// certificate, which is the shape pki.cmc.build emits and the one a CA sends --
|
|
600
|
-
// demanding that the caller ALSO pass it would make the ordinary
|
|
601
|
-
// build-then-verify flow fail for a message that already contains everything
|
|
602
|
-
// needed. `certs` supplements the embedded bag; it was never meant to be the
|
|
603
|
-
// only source. What does not change is the posture: a signature that is present
|
|
604
|
-
// and does not verify is a refusal, and an unverifiable one still needs the
|
|
605
|
-
// named opt-out rather than passing quietly.
|
|
606
398
|
return cmsVerify.verify(responseBytes, sent.certs && sent.certs.length ? { certs: sent.certs } : {})
|
|
607
399
|
.then(function (res) {
|
|
608
400
|
if (res.valid) return true;
|
|
609
|
-
// EVERY failing signer, not just the first. A response may carry several,
|
|
610
|
-
// and the opt-out may only be honored when none of them was actually
|
|
611
|
-
// checked and rejected: one signer whose certificate is missing must not
|
|
612
|
-
// let a DIFFERENT signer's failed signature through beside it.
|
|
613
401
|
var failed = (res.signers || []).filter(function (s) { return !s.ok; });
|
|
614
402
|
var code = failed.length ? failed[0].code : null;
|
|
615
403
|
var onlyMissing = failed.length > 0 && failed.every(function (s) {
|
|
616
404
|
return s.code === "cms/signer-cert-not-found";
|
|
617
405
|
});
|
|
618
|
-
// No signer certificate anywhere is "could not check", which the opt-out
|
|
619
|
-
// covers. A signature that failed against a certificate we DID find is a
|
|
620
|
-
// real failure, and no opt-out excuses it.
|
|
621
406
|
if (onlyMissing && sent.allowUnverified === true) return false;
|
|
622
407
|
throw E("cmc/unverified-response",
|
|
623
408
|
"the CMC response signature did not verify" + (code ? " (" + code + ")" : "") +
|
|
@@ -637,7 +422,6 @@ function _shape(body, sent, signatureVerified, bound) {
|
|
|
637
422
|
return {
|
|
638
423
|
outcome: outcome,
|
|
639
424
|
statuses: body.statuses,
|
|
640
|
-
// The detail belonging to the governing verdict, flattened for the common read.
|
|
641
425
|
failInfo: governing ? governing.failInfoName : null,
|
|
642
426
|
failInfoValue: governing ? governing.failInfo : null,
|
|
643
427
|
extendedFailInfo: governing ? governing.extendedFailInfo : null,
|
|
@@ -645,29 +429,14 @@ function _shape(body, sent, signatureVerified, bound) {
|
|
|
645
429
|
pendTime: governing && governing.pendInfo ? governing.pendInfo.pendTime : null,
|
|
646
430
|
statusString: governing ? governing.statusString : null,
|
|
647
431
|
transactionId: sent.transactionId != null ? sent.transactionId : null,
|
|
648
|
-
// The responder's OWN nonce, retained by the caller for the next leg of the
|
|
649
|
-
// same transaction (RFC 5272 sec. 6.6).
|
|
650
432
|
senderNonce: serverNonce ? _octets(serverNonce, "Sender Nonce") : null,
|
|
651
|
-
// Surfaced, never acted on -- see the primitive's note on trust.
|
|
652
433
|
certificates: _bagOf(body.cms, "certs", "certificates"),
|
|
653
434
|
crls: _bagOf(body.cms, "crls", "crls"),
|
|
654
|
-
// The response's OWN two sequences, raw. For a request whose only arm was the
|
|
655
|
-
// other-message form there is no certificate to return, and RFC 5272 sec. 4.1
|
|
656
|
-
// puts the answer here instead -- so leaving them out would give such an
|
|
657
|
-
// exchange a successful verdict with its result unreachable, and send the
|
|
658
|
-
// caller back to the encapsulated bytes to re-parse what was already decoded.
|
|
659
435
|
cmsSequence: (body.cmsSequence || []).slice(),
|
|
660
436
|
otherMsgs: (body.otherMsgs || []).slice(),
|
|
661
437
|
publishTrustAnchors: anchors ? anchors.values.slice() : null,
|
|
662
438
|
confirmCertId: confirm ? _singleValue(confirm, "Confirm Certificate Acceptance") : null,
|
|
663
|
-
// Whether the CARRIER's signature was checked. False only via the explicit
|
|
664
|
-
// allowUnverified opt-out -- there is no path that leaves it false silently.
|
|
665
439
|
signatureVerified: signatureVerified,
|
|
666
|
-
// Which halves of the exchange binding ran (RFC 5272 sec. 6.6 / 6.4). Each is
|
|
667
|
-
// conditional on the caller having retained the value, so a bare `outcome`
|
|
668
|
-
// cannot say whether this response answers this request or an earlier one.
|
|
669
|
-
// These can. `boundToRequest` is false only via the explicit allowUnbound
|
|
670
|
-
// opt-out; nothing leaves it false silently.
|
|
671
440
|
bound: {
|
|
672
441
|
transactionId: bound.transactionId,
|
|
673
442
|
senderNonce: bound.senderNonce,
|
|
@@ -675,8 +444,6 @@ function _shape(body, sent, signatureVerified, bound) {
|
|
|
675
444
|
bodyPartIDs: bound.bodyPartIDs,
|
|
676
445
|
},
|
|
677
446
|
boundToRequest: bound.boundToRequest,
|
|
678
|
-
// Whether anything in here was TRUSTED, which is never: the certificate bag
|
|
679
|
-
// and any Publish Trust Anchors control are the caller's to path-validate.
|
|
680
447
|
trusted: false,
|
|
681
448
|
controls: body.controls,
|
|
682
449
|
unhandled: body.unhandled,
|