@blamejs/pki 0.6.47 → 0.6.49

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/lib/cmc-build.js CHANGED
@@ -15,9 +15,18 @@ var crmf = require("./schema-crmf");
15
15
  var cmcFmt = require("./schema-cmc");
16
16
  var oid = require("./oid");
17
17
  var cmsSign = require("./cms-sign");
18
+ var cmsEncrypt = require("./cms-encrypt");
19
+ var cmsDecrypt = require("./cms-decrypt");
20
+ var key = require("./key");
21
+ var _createPublicKey = nodeCrypto.createPublicKey;
18
22
  var guard = require("./guard-all");
19
23
  var intrinsic = require("./guard-intrinsic");
20
24
  var _hasOwn = intrinsic.hasOwn;
25
+ var _isArray = intrinsic.isArray;
26
+ var _isBuffer = intrinsic.isBuffer;
27
+ var _bufferFrom = intrinsic.bufferFrom;
28
+ var _bufferConcat = intrinsic.bufferConcat;
29
+ var _subarray = intrinsic.subarray;
21
30
  var frameworkError = require("./framework-error");
22
31
 
23
32
  var CmcError = frameworkError.CmcError;
@@ -27,6 +36,7 @@ function O(name) { return oid.byName(name); }
27
36
 
28
37
  var OID_IDENTITY_PROOF_V2 = O("id-cmc-identityProofV2");
29
38
  var OID_POP_LINK_RANDOM = O("id-cmc-popLinkRandom");
39
+ var OID_DECRYPTED_POP = O("id-cmc-decryptedPOP");
30
40
  var OID_IDENTIFICATION = O("id-cmc-identification");
31
41
  var OID_SHA256 = O("sha256");
32
42
  var OID_HMAC_SHA256 = O("hmacWithSHA256");
@@ -42,7 +52,7 @@ var POP_LINK_RANDOM_BYTES = 64;
42
52
 
43
53
  var KNOWN_SPEC_KEYS = intrinsic.assign(intrinsic.create(null), {
44
54
  requests: 1, controls: 1, cmsSequence: 1, otherMsgSequence: 1,
45
- identityProof: 1, popLink: 1, renewal: 1,
55
+ identityProof: 1, popLink: 1, renewal: 1, popChallenge: 1,
46
56
  transactionId: 1, senderNonce: 1, dataReturn: 1,
47
57
  });
48
58
 
@@ -100,6 +110,61 @@ var KNOWN_REQUEST_KEYS = intrinsic.assign(intrinsic.create(null), { tcr: 1, crm:
100
110
  var KNOWN_CONTROL_KEYS = intrinsic.assign(intrinsic.create(null), { type: 1, value: 1 });
101
111
  var KNOWN_IDENTITY_PROOF_KEYS = intrinsic.assign(intrinsic.create(null), { identity: 1, secret: 1 });
102
112
  var KNOWN_POP_LINK_KEYS = intrinsic.assign(intrinsic.create(null), { secret: 1 });
113
+ var KNOWN_MAC_KEYS = intrinsic.assign(intrinsic.create(null), { identifier: 1, secret: 1 });
114
+ var KNOWN_MAC_CARRIER_KEYS = intrinsic.assign(intrinsic.create(null), { mac: 1 });
115
+ var UTF8_STRING_TAG = 12;
116
+
117
+ /** @internal Whether two caller-supplied secrets are the same secret, comparing the bytes each spells
118
+ * out so a string and the same bytes are not read as two different credentials. Compared in constant
119
+ * time: the two values come from one caller here, but the primitive is the one this toolkit compares
120
+ * secret-derived bytes with everywhere. */
121
+ function _sameSecret(a, byIdentity) {
122
+ var ab = null, bb = null;
123
+ try {
124
+ ab = _secretCopy(a, "the shared secret");
125
+ bb = _secretCopy(byIdentity, "the identityProof secret");
126
+ return ab !== null && bb !== null && ab.length === bb.length && guard.crypto.constantTimeEqual(ab, bb);
127
+ } finally {
128
+ /** @internal Both copies, on every exit: one input being unusable is the path that leaves the other
129
+ * one allocated, and a comparison that refuses is still a comparison that copied. */
130
+ var made = [];
131
+ if (ab) guard.list.append(made, ab);
132
+ if (bb) guard.list.append(made, bb);
133
+ if (made.length) guard.secret.zeroizeAll(made, CmcError, "cmc/bad-input", "the compared shared-secret copies");
134
+ }
135
+ }
136
+ function _secretCopy(v, what) {
137
+ if (typeof v === "string") return _bufferFrom(v, "utf8");
138
+ if (guard.bytes.isByteSource(v)) return guard.bytes.snapshotSource(v, CmcError, "cmc/bad-input", what);
139
+ return null;
140
+ }
141
+
142
+ /** @internal The key a shared-secret carrier derives its MAC key from: the identifier encoded as UTF-8
143
+ * followed by the shared secret (RFC 5272 sec. 3.2(c)). Both halves of the toolkit read this one
144
+ * function, so a request it builds and a message it authenticates derive the same bytes. The caller
145
+ * owns the returned buffer and wipes it. */
146
+ function macPassword(mac) {
147
+ if (!mac || typeof mac !== "object" || _isArray(mac) || _isBuffer(mac)) {
148
+ throw E("cmc/bad-input", "a shared-secret carrier is { identifier, secret } (RFC 5272 sec. 3.2)");
149
+ }
150
+ guard.identifier.assertKnownKeys(mac, KNOWN_MAC_KEYS, E, "cmc/bad-input", "unknown shared-secret field ");
151
+ if (typeof mac.identifier !== "string" || mac.identifier.length === 0) {
152
+ throw E("cmc/bad-input", "a shared-secret carrier needs the identifier the authority knows the client by, as a non-empty string (RFC 5272 sec. 3.2)");
153
+ }
154
+ if (typeof mac.secret !== "string" && !guard.bytes.isByteSource(mac.secret)) {
155
+ throw E("cmc/bad-input", "a shared-secret carrier needs the secret as a string or bytes (RFC 5272 sec. 3.2)");
156
+ }
157
+ var secretBytes = typeof mac.secret === "string" ? _bufferFrom(mac.secret, "utf8")
158
+ : guard.bytes.snapshotSource(mac.secret, CmcError, "cmc/bad-input", "the shared secret");
159
+ if (secretBytes.length === 0) {
160
+ guard.secret.zeroize(secretBytes, CmcError, "cmc/bad-input", "the shared secret");
161
+ throw E("cmc/bad-input", "a shared-secret carrier needs a non-empty secret (RFC 5272 sec. 3.2)");
162
+ }
163
+ var idBytes = _bufferFrom(mac.identifier, "utf8");
164
+ var joined = _bufferConcat([idBytes, secretBytes]);
165
+ guard.secret.zeroizeAll([idBytes, secretBytes], CmcError, "cmc/bad-input", "the shared-secret encoding");
166
+ return joined;
167
+ }
103
168
 
104
169
  function fixedRequestId(req, index) {
105
170
  if (!req || typeof req !== "object") throw E("cmc/bad-input", "each request must be an object");
@@ -125,13 +190,59 @@ function fixedRequestId(req, index) {
125
190
  return req.bodyPartID == null ? null : req.bodyPartID;
126
191
  }
127
192
 
128
- async function _assertTcrPops(requests) {
193
+ async function _assertTcrPops(requests, popProvenRequest) {
129
194
  for (var i = 0; i < requests.length; i++) {
130
- if (requests[i] && requests[i].tcr != null) await _assertOneTcrPop(requests[i].tcr, i);
195
+ if (requests[i] && requests[i].tcr != null) await _assertOneTcrPop(requests[i].tcr, i, popProvenRequest);
131
196
  }
132
197
  }
133
- async function _assertOneTcrPop(tcr, i) {
134
- var parsed = csr.parse(_der(tcr, "a tcr certificationRequest"));
198
+ /** @internal The parameters an algorithm identifier carries, which for these four are absent or NULL. */
199
+ function _assertNoAlgorithmParameters(algId, what) {
200
+ var params = algId.parameters;
201
+ if (params == null) return;
202
+ if (params.length === 2 && params[0] === 0x05 && params[1] === 0x00) return;
203
+ throw E("cmc/bad-pop-challenge",
204
+ "the challenge's " + what + " carries a parameter value, and the algorithms RFC 5274 sec. 4.2 " +
205
+ "names take none: they are encoded with the parameters absent or NULL");
206
+ }
207
+
208
+ /** @internal Whether these bytes are one DER OCTET STRING and nothing else. */
209
+ function _isNoSignatureValue(bytes) {
210
+ if (!bytes || !bytes.length) return false;
211
+ var node;
212
+ try { node = asn1.decode(bytes); }
213
+ catch (_e) { return false; } // allow:swallow-unverified bytes that do not decode are not a NoSignatureValue
214
+ return node.tagClass === "universal" && node.tagNumber === asn1.TAGS.OCTET_STRING && !node.constructed;
215
+ }
216
+
217
+ /** @internal A key that can only decrypt cannot sign the request that asks for its certificate, so RFC
218
+ * 5272 App. C.1 gives it a signature algorithm that carries no proof and sends the proof through the
219
+ * sec. 6.7 challenge instead. A request in that form is accepted here only when this message answers a
220
+ * challenge for that same request; without one it carries no proof of possession at all. */
221
+ async function _assertOneTcrPop(tcr, i, popProvenRequest) {
222
+ var der = _der(tcr, "a tcr certificationRequest");
223
+ var parsed = csr.parse(der);
224
+ if (parsed.signatureAlgorithm.oid === O("id-alg-noSignature")) {
225
+ var params = parsed.signatureAlgorithm.parameters;
226
+ if (!params || params.length !== 2 || params[0] !== 0x05 || params[1] !== 0x00) {
227
+ throw E("cmc/bad-popo", "a tcr request [" + i + "] uses id-alg-noSignature, whose parameters must be " +
228
+ "present and encoded as NULL (RFC 5272 App. C.1)");
229
+ }
230
+ /** @internal "NoSignatureValue ::= OCTET STRING" (App. C.1), carried in the signature BIT STRING.
231
+ * The section says it holds the hash of the request but names no algorithm, so the encoding is what
232
+ * can be checked here; a value that is not one is a request an authority would reject. */
233
+ var sig = parsed.signatureValue;
234
+ if (!sig || sig.unusedBits !== 0 || !_isNoSignatureValue(sig.bytes)) {
235
+ throw E("cmc/bad-popo", "a tcr request [" + i + "] uses id-alg-noSignature, whose signature is a " +
236
+ "NoSignatureValue OCTET STRING (RFC 5272 App. C.1)");
237
+ }
238
+ if (!popProvenRequest || popProvenRequest.length !== der.length ||
239
+ !guard.crypto.constantTimeEqual(popProvenRequest, der)) {
240
+ throw E("cmc/bad-popo", "a tcr request [" + i + "] is signed with id-alg-noSignature, which proves " +
241
+ "nothing about the key; such a request carries its proof of possession in an answered " +
242
+ "challenge, so pass the one the authority sent as popChallenge (RFC 5272 App. C.1, sec. 6.7)");
243
+ }
244
+ return;
245
+ }
135
246
  if (!(await csrVerify.verifyCsrSignature(parsed))) {
136
247
  throw E("cmc/bad-popo", "a tcr request [" + i + "] failed its proof-of-possession: the PKCS#10 signature does not verify under the request's own subject public key (RFC 5272 sec. 6.3), so a CA would reject it -- refusing it here rather than signing it");
137
248
  }
@@ -225,10 +336,58 @@ var BINDING_TYPE_NAME = intrinsic.assign(intrinsic.create(null), {
225
336
  dataReturn: "an OCTET STRING (RFC 5272 sec. 6.4)",
226
337
  });
227
338
 
339
+ /** @internal Controls the builder computes for itself from a spec field. Their value is a proof, so a
340
+ * second one arriving hand-encoded cannot be reconciled with the computed one the way an Identification
341
+ * name can: the two would both be emitted, and a message carrying two proofs of the same thing is one an
342
+ * authority has no rule for. Naming the spec field that produces it says which of the two to drop. */
343
+ var GENERATED_BY_OID = intrinsic.create(null);
344
+ GENERATED_BY_OID[O("id-cmc-identityProofV2")] = { field: "identityProof" };
345
+ GENERATED_BY_OID[O("id-cmc-popLinkWitnessV2")] = { field: "popLink" };
346
+ GENERATED_BY_OID[O("id-cmc-popLinkRandom")] = { field: "popLink" };
347
+ GENERATED_BY_OID[O("id-cmc-decryptedPOP")] = { field: "popChallenge", perTarget: true };
348
+
349
+ /** @internal The bodyPartID a Decrypted POP names, which is the request its proof covers. Several may
350
+ * ride in one message, one per request, so it is the target that has to be unique rather than the
351
+ * control. A value that will not read as one is treated as naming nothing, and a second such control is
352
+ * refused because there is no target to tell the two apart by. */
353
+ function _decryptedPopTarget(controlValue) {
354
+ try {
355
+ var node = asn1.decode(controlValue);
356
+ return intrinsic.bigIntToString(asn1.read.integer(node.children[0]), 10);
357
+ } catch (_e) { return null; } // allow:swallow-unverified an unreadable control names no request
358
+ }
359
+
360
+ function _assertNotAlsoComputed(controlType, controlValue, seen, spec) {
361
+ var generated = GENERATED_BY_OID[controlType];
362
+ if (!generated) return;
363
+ /** @internal Tested the way the producer tests it. A field this reads as present but the emitting
364
+ * code skips would refuse a message that carries one control, not two. */
365
+ if (spec[generated.field]) {
366
+ throw E("cmc/bad-input",
367
+ "spec." + generated.field + " computes this control, and spec.controls carries one as well; a " +
368
+ "request carries one or the other, since a message with two leaves the authority no proof to " +
369
+ "check (RFC 5272 sec. 6.2.1 / 6.3.1 / 6.7)");
370
+ }
371
+ var target = generated.perTarget ? _decryptedPopTarget(controlValue) : null;
372
+ var slot = generated.perTarget ? controlType + ":" + (target === null ? "?" : target) : controlType;
373
+ if (seen[guard.text.keyOf(slot)]) {
374
+ throw E("cmc/bad-input",
375
+ "spec.controls carries two " + (oid.name(controlType) || controlType) + " controls" +
376
+ (generated.perTarget
377
+ ? (target === null
378
+ ? " that name no readable certification request, so nothing tells the two proofs apart"
379
+ : " naming certification request " + target + ", so the authority has two proofs of one request")
380
+ : "") +
381
+ "; a message with two leaves the authority no proof to check (RFC 5272 sec. 6.2.1 / 6.3.1 / 6.7)");
382
+ }
383
+ seen[guard.text.keyOf(slot)] = true;
384
+ }
385
+
228
386
  function _assertNoDuplicateBinding(callerControls, spec) {
229
387
  var seen = intrinsic.create(null);
230
388
  callerControls.forEach(function (c) {
231
- var field = BINDING_BY_OID[_oidOf(c.type)];
389
+ var controlOid = _oidOf(c.type);
390
+ var field = BINDING_BY_OID[controlOid];
232
391
  if (!field) return;
233
392
  try { BINDING_READER[field](asn1.decode(_der(c.value, "a control value"))); }
234
393
  catch (e) {
@@ -314,6 +473,175 @@ function identityProofV2(secret, reqSequenceBytes, identity) {
314
473
  }
315
474
  }
316
475
 
476
+ /** @internal RFC 5274 sec. 4.2 names what a conforming client implements for this exchange: SHA-1 MUST
477
+ * and SHA-256 SHOULD for the witness, HMAC-SHA1 MUST and HMAC-SHA256 SHOULD for the proof. Both MUSTs
478
+ * are legacy algorithms and are accepted because the profile requires them of a client answering a
479
+ * challenge, which is a server's choice to make rather than this side's. Anything else is refused: a
480
+ * proof computed under an algorithm the server did not ask for is not an answer. */
481
+ var POP_WITNESS_HASH = intrinsic.create(null);
482
+ POP_WITNESS_HASH[O("sha1")] = "sha1";
483
+ POP_WITNESS_HASH[O("sha256")] = "sha256";
484
+ var POP_MAC_HASH = intrinsic.create(null);
485
+ POP_MAC_HASH[O("hmacWithSHA1")] = "sha1";
486
+ POP_MAC_HASH[O("hmacWithSHA256")] = "sha256";
487
+ var POP_MAC_KEY_MAX_BYTES = 64;
488
+ var KNOWN_POP_CHALLENGE_KEYS = intrinsic.assign(intrinsic.create(null),
489
+ { challenge: 1, recipient: 1 });
490
+
491
+ /** @internal The key this challenge is addressed to is the one being certified, which by definition has
492
+ * no certificate yet, so the recipient cannot be selected by matching one. Sec. 6.7 says the identifier
493
+ * "SHOULD be the SKI" of the request, and that an issuerAndSerialNumber form carries a NULL issuer with
494
+ * the request's bodyPartID as the serial: neither names a certificate this caller holds. So each
495
+ * recipient is tried with the key, and the WITNESS decides which attempt recovered the right value,
496
+ * which is the check the section already requires and the only one that can answer the question. A
497
+ * caller who does hold the certificate can still pass it and select the ordinary way. */
498
+ var POP_MAX_RECIPIENTS = 8;
499
+
500
+ /** @internal The CMS recipient kinds that identify a key. A password or a key-encryption key opens the
501
+ * envelope without saying whose it is, so a decryption through one proves possession of no key. */
502
+ var ASYMMETRIC_RECIPIENT = intrinsic.assign(intrinsic.create(null), { ktri: 1, kari: 1, ori: 1 });
503
+
504
+ /** @internal What identifies a request within its arm, which is everything a TaggedRequest carries
505
+ * except the body part identifier the new message is entitled to renumber. Defined once, so the two
506
+ * sides of the comparison below cannot read different things: the other-message arm is identified by
507
+ * its type together with its value, since a value alone could belong to another type. */
508
+ function _requestIdentity(parsed) {
509
+ if (!parsed) return null;
510
+ if (parsed.arm === "tcr") return { arm: "tcr", bytes: parsed.certificationRequestBytes };
511
+ if (parsed.arm === "crm") return { arm: "crm", bytes: parsed.certReqMsgBytes };
512
+ if (parsed.arm === "orm" && parsed.requestMessageType) {
513
+ return { arm: "orm",
514
+ bytes: _bufferConcat([b.oid(parsed.requestMessageType), parsed.requestMessageValueBytes]) };
515
+ }
516
+ return null;
517
+ }
518
+
519
+ /** @internal Whether the key that opened the challenge is the key the request asks to have certified.
520
+ * Only a tcr is answered this way: a crm request carries its own proof-of-possession field. */
521
+ function _provesRequestKey(openingKey, outgoing) {
522
+ if (!openingKey || !outgoing || !outgoing.certificationRequestBytes) return false;
523
+ var requested;
524
+ /** @internal Compared as keys rather than as encodings: an elliptic-curve point written in compressed
525
+ * form and the same point written uncompressed are different bytes and the same key, and it is the key
526
+ * the section is about. */
527
+ try {
528
+ requested = _createPublicKey({
529
+ key: csr.parse(outgoing.certificationRequestBytes).subjectPublicKeyInfo.bytes,
530
+ format: "der", type: "spki",
531
+ });
532
+ } catch (_e) { return false; } // allow:swallow-unverified a key this runtime cannot read proves nothing here
533
+ return intrinsic.keyEquals(requested, openingKey);
534
+ }
535
+
536
+ /** @internal The public half of the key that OPENED the challenge. The recipient kind comes from the
537
+ * decryption that succeeded rather than from the caller's object, so key material the envelope never
538
+ * exercised is not read as the key that answered it. A key whose public half this runtime cannot derive
539
+ * yields null, and the request is then refused rather than accepted on an unproven key. */
540
+ async function _openingPublicKey(recipient, recipientType) {
541
+ if (!ASYMMETRIC_RECIPIENT[recipientType]) return null;
542
+ try {
543
+ return _createPublicKey({
544
+ key: await key.publicFromPrivate(recipient.key), format: "der", type: "spki",
545
+ });
546
+ } catch (_e) { return null; } // allow:swallow-unverified a key whose public half cannot be derived proves nothing here
547
+ }
548
+
549
+ async function _recoverProofValue(decoded, recipient, witnessHash) {
550
+ /** @internal The count is read off the parse rather than re-walked here, so the structure the parser
551
+ * accepted and the structure this loop iterates cannot disagree. */
552
+ var count = decoded.cmsRecipientCount;
553
+ if (count > POP_MAX_RECIPIENTS) {
554
+ throw E("cmc/bad-pop-challenge",
555
+ "the challenge's enveloped proof value names " + count + " recipients; a challenge is addressed to " +
556
+ "the key being certified, and at most " + POP_MAX_RECIPIENTS + " are tried (RFC 5272 sec. 6.7)");
557
+ }
558
+ for (var i = 0; i < count; i++) {
559
+ var opened;
560
+ try {
561
+ opened = await cmsDecrypt.decrypt(decoded.cms, recipient, { recipientIndex: i });
562
+ } catch (_e) {
563
+ continue; // allow:swallow-unverified this recipient is not addressed to the supplied key; the witness decides
564
+ }
565
+ var candidate = opened.content;
566
+ var computed = nodeCrypto.createHash(witnessHash).update(candidate).digest();
567
+ var matches = computed.length === decoded.witness.length &&
568
+ guard.crypto.constantTimeEqual(computed, decoded.witness);
569
+ guard.secret.zeroize(computed, CmcError, "cmc/bad-input", "the computed witness");
570
+ /** @internal The kind of recipient that actually opened it travels with the value, so what proved
571
+ * possession is decided by the decryption that happened rather than by the caller's object. */
572
+ if (matches) return { value: candidate, recipientType: opened.recipientType };
573
+ guard.secret.zeroize(candidate, CmcError, "cmc/bad-input", "a rejected proof value");
574
+ }
575
+ throw E("cmc/pop-failed",
576
+ "no recipient of the challenge yielded a proof value hashing to the witness it carries, so this " +
577
+ "client cannot prove possession and the enrollment is abandoned (RFC 5272 sec. 6.7 step 3)");
578
+ }
579
+
580
+ /** @internal The client half of RFC 5272 sec. 6.7: decrypt the challenge, confirm it is the value the
581
+ * server hashed into the witness, and answer with a MAC over the request it carried. The witness check
582
+ * is not separable from the answer, because a client that cannot confirm the value "MUST abort the
583
+ * enrollment process" rather than prove possession of something it could not read. */
584
+ async function answerPopChallenge(popChallenge, ids) {
585
+ if (!popChallenge || typeof popChallenge !== "object" || _isArray(popChallenge) || _isBuffer(popChallenge)) {
586
+ throw E("cmc/bad-input", "popChallenge is { challenge, recipient } (RFC 5272 sec. 6.7)");
587
+ }
588
+ guard.identifier.assertKnownKeys(popChallenge, KNOWN_POP_CHALLENGE_KEYS, E, "cmc/bad-input",
589
+ "unknown popChallenge field ");
590
+ var recipient = popChallenge.recipient;
591
+ if (!recipient || typeof recipient !== "object" || _isArray(recipient) || _isBuffer(recipient)) {
592
+ throw E("cmc/bad-input",
593
+ "popChallenge.recipient is the CMS key material that opens the challenge, in the shape " +
594
+ "pki.cms.decrypt takes (RFC 5272 sec. 6.7)");
595
+ }
596
+ /** @internal The challenge arrives from the authority, so bytes that are not DER at all are a
597
+ * malformed challenge like any other and carry the same code rather than the codec's. */
598
+ var challengeBytes = _der(popChallenge.challenge, "the popChallenge challenge");
599
+ var challengeNode;
600
+ try { challengeNode = asn1.decode(challengeBytes); }
601
+ catch (e) {
602
+ throw E("cmc/bad-pop-challenge",
603
+ "the popChallenge challenge is the DER of an EncryptedPOP control value: " +
604
+ ((e && e.message) || "malformed") + " (RFC 5272 sec. 6.7)", e);
605
+ }
606
+ var decoded = cmcFmt.parseEncryptedPop(challengeNode);
607
+ var witnessHash = POP_WITNESS_HASH[guard.text.keyOf(decoded.witnessAlgID.oid)];
608
+ var macHash = POP_MAC_HASH[guard.text.keyOf(decoded.thePOPAlgID.oid)];
609
+ if (!witnessHash || !macHash) {
610
+ throw E("cmc/bad-pop-challenge",
611
+ "the challenge names an algorithm this client does not answer under: RFC 5274 sec. 4.2 requires " +
612
+ "SHA-1 or SHA-256 for the witness and HMAC-SHA1 or HMAC-SHA256 for the proof, got " +
613
+ decoded.witnessAlgID.oid + " and " + decoded.thePOPAlgID.oid);
614
+ }
615
+ /** @internal These four algorithms take no parameters: a SHA-2 identifier carries them absent or as
616
+ * NULL (RFC 5754 sec. 2), and the SHA-1 and HMAC identifiers carry NULL. A parameter value that is
617
+ * neither is not one of the algorithms named, and copying it into the answer would send the authority
618
+ * an identifier it cannot read. */
619
+ _assertNoAlgorithmParameters(decoded.witnessAlgID, "witnessAlgID");
620
+ _assertNoAlgorithmParameters(decoded.thePOPAlgID, "thePOPAlgID");
621
+
622
+ var proofValue = null, macKey = null;
623
+ try {
624
+ var recovered = await _recoverProofValue(decoded, recipient, witnessHash);
625
+ proofValue = recovered.value;
626
+ /** @internal "The POP Proof Value is used as the secret value in the HMAC algorithm and the request
627
+ * is used as the data. If the POP Proof Value is greater than 64 bytes, only the first 64 bytes of
628
+ * the POP Proof Value is used as the secret." The data is the TaggedRequest exactly as the
629
+ * challenge carried it, which is the copy the server holds and the reason sec. 6.7 puts the request
630
+ * in the challenge at all: neither side re-encodes it. */
631
+ macKey = _bufferFrom(proofValue.length > POP_MAC_KEY_MAX_BYTES
632
+ ? _subarray(proofValue, 0, POP_MAC_KEY_MAX_BYTES) : proofValue);
633
+ var thePOP = nodeCrypto.createHmac(macHash, macKey).update(decoded.requestBytes).digest();
634
+ return { algBytes: decoded.thePOPAlgID.bytes, thePOP: thePOP, ids: ids,
635
+ recipientSpki: await _openingPublicKey(recipient, recovered.recipientType),
636
+ challengedRequest: _requestIdentity(decoded.request) };
637
+ } finally {
638
+ var owned = [];
639
+ if (proofValue && _isBuffer(proofValue)) guard.list.append(owned, proofValue);
640
+ if (macKey) guard.list.append(owned, macKey);
641
+ if (owned.length) guard.secret.zeroizeAll(owned, CmcError, "cmc/bad-input", "the POP proof value");
642
+ }
643
+ }
644
+
317
645
  function popLinkWitnessV2(secret, R) {
318
646
  _assertSecret(secret, "a POP Link Witness V2 control");
319
647
  var key = nodeCrypto.createHash("sha256").update(secret, "utf8").digest();
@@ -373,7 +701,17 @@ async function _build(spec, signer, opts) {
373
701
  "a renewal request carries no " + forbidden + " control (RFC 5272 sec. 3.2 (a))");
374
702
  }
375
703
  }
376
- var renewalSigners = Array.isArray(signer) ? signer : [signer];
704
+ /** @internal A renewal carries no Identification or Identity Proof control because, in sec. 3.2(a)'s
705
+ * words, "the same information is provided by the use of an existing certificate from a CA when
706
+ * signing the PKI Request". A shared secret authenticates the message without naming any
707
+ * certificate, so a renewal carried that way would assert an identity by nothing at all. */
708
+ if (signer && typeof signer === "object" && !_isArray(signer) && signer.mac != null) {
709
+ throw E("cmc/bad-signer",
710
+ "a renewal request is signed with the certificate being renewed, which is what carries the " +
711
+ "original identity in place of the Identification and Identity Proof controls (RFC 5272 sec. 3.2 (a)); " +
712
+ "a shared secret names no certificate, so it cannot renew");
713
+ }
714
+ var renewalSigners = _isArray(signer) ? signer : [signer];
377
715
  for (var si = 0; si < renewalSigners.length; si++) {
378
716
  var rs = renewalSigners[si];
379
717
  if (rs && rs.cert == null && rs.spki != null) {
@@ -385,6 +723,12 @@ async function _build(spec, signer, opts) {
385
723
  }
386
724
 
387
725
  var controls = [];
726
+ /** @internal The names a caller-written Identification control gives this client, collected as those
727
+ * controls are encoded so the name checked is the name carried (RFC 5272 sec. 6.2.3). */
728
+ var controlIdentifiers = [], controlIdentificationCount = 0, controlIdentifierUnreadable = false;
729
+ /** @internal Which of the controls the builder computes have already arrived hand-encoded, checked
730
+ * against the bytes being encoded rather than a second read of the caller's object. */
731
+ var generatedSeen = intrinsic.create(null);
388
732
  callerControls.forEach(function (c) {
389
733
  if (!c || typeof c !== "object" || c.type == null || c.value == null) {
390
734
  throw E("cmc/bad-input", "each control is { type, value }");
@@ -393,7 +737,27 @@ async function _build(spec, signer, opts) {
393
737
  return "unknown field " + JSON.stringify(k) + " on a control -- a control is { type, value }, " +
394
738
  "and its bodyPartID is allocated by the builder";
395
739
  });
396
- controls.push(encodeControl(_oidOf(c.type), [_der(c.value, "a control value")], ids));
740
+ /** @internal Taken once. A control is a caller object, so reading `type` or `value` a second time
741
+ * can answer differently than it did here, and what this request is checked against has to be what
742
+ * it carries. The Identification name below is read off THESE bytes for the same reason. */
743
+ var controlType = _oidOf(c.type);
744
+ var controlValue = _der(c.value, "a control value");
745
+ _assertNotAlsoComputed(controlType, controlValue, generatedSeen, spec);
746
+ if (controlType === OID_IDENTIFICATION) {
747
+ controlIdentificationCount += 1;
748
+ var idNode;
749
+ try { idNode = asn1.decode(controlValue); }
750
+ catch (_e) { controlIdentifierUnreadable = true; }
751
+ /** @internal The control is a UTF8String (RFC 5272 sec. 6.2.3). Another string type carrying the
752
+ * same characters is a different value on the wire, so it cannot stand in for the control this
753
+ * request needs while a strict authority refuses the message. */
754
+ if (idNode && idNode.tagClass === "universal" && idNode.tagNumber === UTF8_STRING_TAG) {
755
+ guard.list.append(controlIdentifiers, asn1.read.string(idNode));
756
+ } else {
757
+ controlIdentifierUnreadable = true;
758
+ }
759
+ }
760
+ guard.list.append(controls, encodeControl(controlType, [controlValue], ids));
397
761
  });
398
762
 
399
763
  _assertNoDuplicateBinding(callerControls, spec);
@@ -410,6 +774,22 @@ async function _build(spec, signer, opts) {
410
774
  [b.octetString(_der(spec.dataReturn, "spec.dataReturn"))], ids));
411
775
  }
412
776
 
777
+ /** @internal The name the request gives the authority for this client, from whichever of the two
778
+ * places names it. Read before either control is emitted, so the pair can be compared. */
779
+ var macIdentifier = signer && typeof signer === "object" && !_isArray(signer) && signer.mac != null &&
780
+ typeof signer.mac === "object" && typeof signer.mac.identifier === "string" ? signer.mac.identifier : null;
781
+ var identityIdentifier = spec.identityProof && typeof spec.identityProof.identity === "string"
782
+ ? spec.identityProof.identity : null;
783
+
784
+ /** @internal Settled BEFORE the Identity Proof witness is computed, because the presence of an
785
+ * Identification control changes how that witness is derived: with one, the hash is over the secret
786
+ * and the identity together rather than the secret alone (RFC 5272 sec. 6.2.3). A name reaching the
787
+ * request from anywhere therefore has to be known here, or the witness is derived one way and the
788
+ * request carries the control that demands the other. */
789
+ var carriedIdentity = identityIdentifier;
790
+ if (carriedIdentity == null && controlIdentifiers.length > 0) carriedIdentity = controlIdentifiers[0];
791
+ if (carriedIdentity == null) carriedIdentity = macIdentifier;
792
+
413
793
  if (spec.identityProof) {
414
794
  guard.identifier.assertKnownKeys(spec.identityProof, KNOWN_IDENTITY_PROOF_KEYS, E, "cmc/bad-input",
415
795
  function (k) { return "unknown field " + JSON.stringify(k) + " on identityProof -- it is { secret, identity? }"; });
@@ -419,7 +799,88 @@ async function _build(spec, signer, opts) {
419
799
  }
420
800
  if (identity != null) controls.push(encodeControl(OID_IDENTIFICATION, [b.utf8(identity)], ids));
421
801
  controls.push(encodeControl(OID_IDENTITY_PROOF_V2,
422
- [identityProofV2(spec.identityProof.secret, reqSequenceBytes, identity)], ids));
802
+ [identityProofV2(spec.identityProof.secret, reqSequenceBytes, carriedIdentity)], ids));
803
+ }
804
+
805
+ /** @internal A PasswordRecipientInfo names no recipient, so an authority holding secrets for many
806
+ * clients has nothing in the carrier to select one by. The Identification control is what carries
807
+ * that name: it "assists the server in locating the shared-secret" (RFC 5272 sec. 6.2.3), so a
808
+ * request authenticated by a secret says whose secret it is. Two names for one client is refused
809
+ * rather than picked between. */
810
+ if (macIdentifier != null) {
811
+ var named = [];
812
+ for (var nc = 0; nc < controlIdentifiers.length; nc++) guard.list.append(named, controlIdentifiers[nc]);
813
+ if (identityIdentifier != null) guard.list.append(named, identityIdentifier);
814
+ for (var ni = 0; ni < named.length; ni++) {
815
+ if (named[ni] !== macIdentifier) {
816
+ throw E("cmc/bad-input",
817
+ "the shared-secret identifier and the Identification the request already carries name the same " +
818
+ "client to the authority, so a request cannot carry two different ones (RFC 5272 sec. 6.2.3)");
819
+ }
820
+ }
821
+ if (controlIdentifierUnreadable) {
822
+ throw E("cmc/bad-input",
823
+ "the Identification control this request carries does not read as the UTF8String the client is " +
824
+ "named by, so it cannot be confirmed to name the client whose secret authenticates the request (RFC 5272 sec. 6.2.3)");
825
+ }
826
+ /** @internal One client, one shared secret. The carrier's MAC and an Identity Proof witness are both
827
+ * keyed from "the shared-secret" (RFC 5272 sec. 3.2 and 6.2.1), so two different ones would leave an
828
+ * authority able to check one and not the other, and unable to tell which credential is the client's. */
829
+ if (spec.identityProof && !_sameSecret(signer.mac.secret, spec.identityProof.secret)) {
830
+ throw E("cmc/bad-input",
831
+ "the shared secret that authenticates this request and the one its Identity Proof is computed " +
832
+ "from are the same secret, so a request cannot carry two different ones (RFC 5272 sec. 3.2 / 6.2.1)");
833
+ }
834
+ /** @internal One request names one client once. Two controls saying the same thing still leave an
835
+ * authority choosing between them, so the request carries exactly one however it was assembled. */
836
+ var alreadyNamed = controlIdentificationCount + (identityIdentifier == null ? 0 : 1);
837
+ if (alreadyNamed > 1) {
838
+ throw E("cmc/bad-input",
839
+ "a request names its client once, and this one carries " + alreadyNamed + " Identification controls " +
840
+ "(RFC 5272 sec. 6.2.3)");
841
+ }
842
+ if (alreadyNamed === 0) {
843
+ guard.list.append(controls, encodeControl(OID_IDENTIFICATION, [b.utf8(macIdentifier)], ids));
844
+ }
845
+ }
846
+
847
+ /** @internal The answer to a challenge-response proof of possession (RFC 5272 sec. 6.7 step 4). Its
848
+ * bodyPartID names the certification request in THIS request, not the one the challenge quoted, so
849
+ * the server can tie the proof to what is being asked for now. With more than one request there is
850
+ * no single answer to give, and guessing which one the proof covers is not this side's call. */
851
+ var popProvenRequest = null;
852
+ if (spec.popChallenge) {
853
+ if (encodedRequests.length !== 1) {
854
+ throw E("cmc/bad-input",
855
+ "a challenge-response proof of possession answers for one certification request, and this " +
856
+ "request carries " + encodedRequests.length + " (RFC 5272 sec. 6.7)");
857
+ }
858
+ var answered = await answerPopChallenge(spec.popChallenge, ids);
859
+ /** @internal The proof covers the request the CHALLENGE carried, and the control names the request
860
+ * in THIS message. If those are not the same certification request, the message proves possession
861
+ * of a key for a request it does not contain, so the two are compared on everything but the body
862
+ * part identifier, which the new message is entitled to renumber. */
863
+ var outgoing = cmcFmt.parseTaggedRequest(asn1.decode(encodedRequests[0].der));
864
+ var outgoingId = _requestIdentity(outgoing);
865
+ if (!answered.challengedRequest || !outgoingId ||
866
+ answered.challengedRequest.arm !== outgoingId.arm ||
867
+ answered.challengedRequest.bytes.length !== outgoingId.bytes.length ||
868
+ !guard.crypto.constantTimeEqual(answered.challengedRequest.bytes, outgoingId.bytes)) {
869
+ throw E("cmc/bad-input",
870
+ "the challenge answers for a different certification request than this message carries, so the " +
871
+ "proof would not cover the request it names (RFC 5272 sec. 6.7 step 4)");
872
+ }
873
+ /** @internal What the challenge proved possession of. A request signed with id-alg-noSignature has
874
+ * no signature to prove it, and App. C.1 sends exactly that case here, so this is the request that
875
+ * is then allowed to carry one. Opening the challenge shows possession of the key it was encrypted
876
+ * to, which sec. 6.7 says is the key in the request; a challenge opened by any other key says
877
+ * nothing about the key being certified, so it does not stand in for the missing signature. */
878
+ popProvenRequest = _provesRequestKey(answered.recipientSpki, outgoing) ? outgoingId.bytes : null;
879
+ guard.list.append(controls, encodeControl(OID_DECRYPTED_POP, [b.sequence([
880
+ b.integer(BigInt(encodedRequests[0].bodyPartID)),
881
+ b.raw(answered.algBytes),
882
+ b.octetString(answered.thePOP),
883
+ ])], ids));
423
884
  }
424
885
 
425
886
  if (spec.popLink) {
@@ -450,6 +911,27 @@ async function _build(spec, signer, opts) {
450
911
  (e && e.message ? e.message : "malformed"), e);
451
912
  }
452
913
 
914
+ /** @internal RFC 5272 sec. 3.2 gives a Full PKI Request two carriers, and a request uses one: a
915
+ * SignedData signed by a key, or an AuthenticatedData whose MAC a shared secret keys. A client
916
+ * enrolling under a secret has no key to sign with, so naming both is a contradiction rather than a
917
+ * preference. */
918
+ if (signer && typeof signer === "object" && !_isArray(signer) && signer.mac != null) {
919
+ if (signer.key != null || signer.cert != null || signer.spki != null || signer.keyIdentifier != null) {
920
+ throw E("cmc/bad-input", "a request is carried by a signature or by a shared secret, not both (RFC 5272 sec. 3.2)");
921
+ }
922
+ guard.identifier.assertKnownKeys(signer, KNOWN_MAC_CARRIER_KEYS, E, "cmc/bad-input", "unknown shared-secret carrier field ");
923
+ var macBytes = macPassword(signer.mac);
924
+ try {
925
+ await _assertTcrPops(requests, popProvenRequest);
926
+ /** @internal The Password Recipient Info option, which sec. 3.2(a) requires, is what the CMS
927
+ * producer selects for a password recipient, and it generates the MAC key sec. 3.2(b) calls for. */
928
+ return await cmsEncrypt.authenticate(pkiData, [{ password: macBytes }],
929
+ { contentType: "id-cct-PKIData", pem: opts.pem });
930
+ } finally {
931
+ guard.secret.zeroize(macBytes, CmcError, "cmc/bad-input", "the derived shared-secret key");
932
+ }
933
+ }
934
+
453
935
  _assertKeyOnlySigner(signer, requests);
454
936
 
455
937
  var ownedKeyBytes = [];
@@ -459,7 +941,7 @@ async function _build(spec, signer, opts) {
459
941
  ownedKeyBytes.length = 0;
460
942
  }
461
943
  try {
462
- await _assertTcrPops(requests);
944
+ await _assertTcrPops(requests, popProvenRequest);
463
945
  return await cmsSign.sign(pkiData, copiedSigner, { eContentType: "id-cct-PKIData", pem: opts.pem });
464
946
  } finally {
465
947
  _wipeOwnedKeys();
@@ -582,4 +1064,4 @@ function _csrKeyIdentity(csrDer) {
582
1064
  return { ski: found, spki: (spki && spki.bytes) || null };
583
1065
  }
584
1066
 
585
- module.exports = { build: build };
1067
+ module.exports = { build: build, macPassword: macPassword };