@blamejs/pki 0.6.51 → 0.6.53

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.
@@ -18,6 +18,7 @@ var guard = require("./guard-all");
18
18
  var compositeSig = require("./composite-sig");
19
19
  var constants = require("./constants");
20
20
  var webcrypto = require("./webcrypto");
21
+ var key = require("./key");
21
22
  var frameworkError = require("./framework-error");
22
23
  var sleepUtil = require("./sleep");
23
24
  var retryAfter = require("./http-retry-after");
@@ -53,6 +54,7 @@ var RESUMABLE_ARMS = Object.assign(Object.create(null), { ip: 1, cp: 1, kup: 1 }
53
54
  var RESUME_TOKEN_KEYS = Object.assign(Object.create(null), {
54
55
  transactionId: 1, recipNonce: 1, certReqId: 1, arm: 1, polls: 1, requestedSpki: 1,
55
56
  signer: 1, signerCache: 1, chain: 1, caPubs: 1, nextPollAt: 1, implicitConfirm: 1, protection: 1,
57
+ centralKeyGeneration: 1,
56
58
  });
57
59
  var RESUMABLE_PROTECTION = Object.assign(Object.create(null), { signature: 1, mac: 1 });
58
60
 
@@ -60,7 +62,8 @@ var KNOWN_SESSION_OPTS = Object.assign(Object.create(null), {
60
62
  url: 1, key: 1, cert: 1, mac: 1, trustAnchors: 1, intermediates: 1, recipient: 1, sender: 1,
61
63
  extraCerts: 1, implicitConfirm: 1, maxPolls: 1, maxTotalWait: 1, sleep: 1, time: 1,
62
64
  transport: 1, tls: 1, headers: 1, timeout: 1, maxResponseBytes: 1, proxy: 1, pss: 1, digestAlgorithm: 1,
63
- acceptCert: 1, senderKID: 1, recipKID: 1, expectedSender: 1,
65
+ acceptCert: 1, senderKID: 1, recipKID: 1, expectedSender: 1, acceptCentralKeyGeneration: 1,
66
+ authorizedBySharedSecret: 1,
64
67
  });
65
68
 
66
69
  var TRANSCRIPT_RETAIN_RESPONSES = 2;
@@ -87,6 +90,9 @@ var KEM_CIPHERTEXT_ALGORITHMS = Object.assign(Object.create(null));
87
90
  KEM_CIPHERTEXT_ALGORITHMS[oid.byName(n)] = n;
88
91
  });
89
92
  var KNOWN_REVOKE_KEYS = Object.assign(Object.create(null), { certificate: 1, certDetails: 1, reason: 1 });
93
+ /** @internal The keys the CRMF signature proof-of-possession arm takes, which is the only arm a
94
+ * session sends. A key belonging to another arm names that arm whether or not `type` says so. */
95
+ var SESSION_POP_KEYS = Object.assign(Object.create(null), { type: 1, sender: 1 });
90
96
  var RSA_ARCS = ["rsaEncryption", "rsaSignatureWithripemd160", "id-TA-RSA-v1-5-SHA-256",
91
97
  "sigS-ISO9796-1-DFUE", "sigS-ISO9796-2Withrsa", "sigS-ISO9796-2rndWithrsa"].map(function (n) {
92
98
  return oid.toArcs(oid.byName(n)).slice(0, -1);
@@ -341,8 +347,27 @@ function _certConfHash(certDer) {
341
347
  * `CertResponse` is the caller's CRMF request id (`request.ir.certReqId`, ...) when supplied, else the
342
348
  * single-request default. One transaction per session: a second or concurrent `enroll`, or a batched
343
349
  * CRMF request, is refused (a local build error leaves the session retryable). The returned `chain` is the
344
- * issued leaf plus any authenticated `caPubs` the CA delivered (chain material, never trust anchors); a
345
- * server-generated (central key generation) private key is out of scope and the grant is refused.
350
+ * issued leaf plus any authenticated `caPubs` the CA delivered (chain material, never trust anchors).
351
+ *
352
+ * Central key pair generation (RFC 9483 sec. 4.1.6) is available under `opts.acceptCentralKeyGeneration`,
353
+ * for an entity that cannot generate its own key pair. A request in the shape sec. 4.1.6 defines, a
354
+ * `certTemplate` with no `publicKey` or one whose `subjectPublicKey` is a zero-length BIT STRING,
355
+ * carries no proof of possession, since this entity holds no key to prove possession of, and goes out
356
+ * under `cmp2021` because the response will carry an `EnvelopedData` (RFC 9480 sec. 2.20). The session
357
+ * opens the delivered container with the credential it enrolled under, its private key for the key
358
+ * transport and key agreement techniques and its shared secret for the password technique, authorizes
359
+ * the key generation authority against `opts.trustAnchors` at `opts.time`, and surfaces the result on
360
+ * the verdict as `deliveredKey`. A PBMAC1 session may instead authorize that authority by the secret
361
+ * that protects its requests, which sec. 4.1.6 permits a MAC-protected exchange, and states so with
362
+ * `opts.authorizedBySharedSecret`; it is never inferred from an absent `trustAnchors`, since a missing
363
+ * option would then silently drop both the chain check and the `id-kp-cmKGA` assertion. What binds the grant is then the delivered key rather than a
364
+ * requested one: the issued certificate MUST certify it, and a grant whose certificate does not is
365
+ * refused before the `certConf` that would accept it. The package carries exactly one key, which is
366
+ * what sec. 4.1.6 profiles RFC 5958's `SIZE (1..MAX)` down to, because one grant certifies one key and
367
+ * every further key would be one the issued certificate says nothing about. Without the option, a
368
+ * granted `CertResponse` carrying a `privateKey` is refused, and a request that would ask for one is
369
+ * refused before it is sent. A `poll-timeout` on such a transaction resumes like any other; its `resumeToken` records that
370
+ * the grant is bound to the delivered key, and a session resuming it must set the option too.
346
371
  *
347
372
  * `revoke(request)` drives the RFC 9483 sec. 4.2 revocation exchange (`rr` -> `rp`) through the same shell.
348
373
  * The request names the certificate, either as `{ certificate }` or as `{ certDetails: { issuer,
@@ -380,6 +405,8 @@ function _certConfHash(certDer) {
380
405
  * - `expectedSender` -- optional CA signer certificate (DER Buffer / PEM string / already-parsed `pki.schema.x509.parse` object); when set, every signed response's authenticated header sender MUST bind to it under the RFC 5280 sec. 7 subject-or-subjectAltName rule cmp.verify uses, so a re-encoded-but-equivalent DN and an empty-subject CA named only by a directoryName SAN both match. Given as bytes/PEM it also resolves a first response that omits its own extraCerts (a CA that assumes the client already holds its certificate). Absent, the session pins the first signed response's signer certificate and requires every later leg's sender to bind to it, rejecting a switch to a different trusted signer while permitting same-identity certificate/key rotation.
381
406
  * - `implicitConfirm` -- request implicit confirmation (skip the certConf leg when the CA grants it).
382
407
  * - `acceptCert` -- an async policy `(certDer, { status, grantedWithMods }) => boolean` consulted before the certConf; return true to accept, anything else to veto (a `grantedWithMods` certificate the CA changed). A veto sends a rejecting certConf and yields `outcome: "rejected"` with the certificate still surfaced. Incompatible with `implicitConfirm` (no reject leg exists), so the combination throws at construction.
408
+ * - `acceptCentralKeyGeneration` (boolean) -- accept a key pair the authority generates on this entity's behalf (RFC 9483 sec. 4.1.6). It admits a request that names no key to certify, opens the delivered container with the credential the session enrolled under, and surfaces the result as `deliveredKey` on the issued verdict. The issued certificate must certify the delivered key. Without it, such a request and such a grant are both refused.
409
+ * - `authorizedBySharedSecret` (boolean) -- authorize the key generation authority by the secret that protects this session's requests rather than by a chain, which sec. 4.1.6 permits a MAC-protected exchange. PBMAC1 sessions only, and it is the alternative to `opts.trustAnchors` for that authority rather than an addition to it: a delivered key package is authorized one way or the other, and a session accepting central key generation with neither is refused when it is built. It pairs with `opts.trustAnchors`, which on a session also path-validates the issued certificate, a job the exemption does not replace.
383
410
  * - `transport` -- injectable `transport(request) -> Promise<{status, headers, body}>`; default pki.transport.https. It must return a promise of the response; anything else is `cmp/bad-input`.
384
411
  * - `tls` / `headers` / `timeout` / `maxResponseBytes` -- transport config + budgets.
385
412
  * - `proxy` -- reach the CA through a forward HTTP proxy (`{ url, auth?, tls? }`; see pki.transport).
@@ -420,6 +447,24 @@ function session(opts) {
420
447
  if (!hasAnchors) throw _err("cmp/bad-input", "signature protection requires opts.trustAnchors to authenticate the CA's response signer (RFC 9483 sec. 3.2)");
421
448
  }
422
449
  if (isMac && Array.isArray(opts.trustAnchors) && opts.trustAnchors.length === 0) opts.trustAnchors = null;
450
+ if (opts.acceptCentralKeyGeneration != null && typeof opts.acceptCentralKeyGeneration !== "boolean") {
451
+ throw _err("cmp/bad-input", "opts.acceptCentralKeyGeneration is a boolean stating that this caller accepts a key pair the authority generated (RFC 9483 sec. 4.1.6)");
452
+ }
453
+ if (opts.authorizedBySharedSecret != null && typeof opts.authorizedBySharedSecret !== "boolean") {
454
+ throw _err("cmp/bad-input", "opts.authorizedBySharedSecret is a boolean stating that this caller authorizes the key generation authority by the secret that protects its requests (RFC 9483 sec. 4.1.6)");
455
+ }
456
+ if (opts.authorizedBySharedSecret === true && !isMac) {
457
+ throw _err("cmp/bad-input", "opts.authorizedBySharedSecret authorizes the key generation authority by the secret that protected the request, so it applies only to a PBMAC1 session (RFC 9483 sec. 4.1.6)");
458
+ }
459
+ /** @internal A session that accepts a generated key pair states how it authorizes the authority
460
+ * that generates one, and a MAC session holding neither anchors nor the exemption states neither.
461
+ * Every input this needs is here, so it is refused now: the same request refused on the grant would
462
+ * be refused after the authority had generated and delivered the key, spending the session's one
463
+ * transaction slot and discarding the delivery, and the retry costs a second key generation. */
464
+ if (opts.acceptCentralKeyGeneration === true && isMac
465
+ && opts.trustAnchors == null && opts.authorizedBySharedSecret !== true) {
466
+ throw _err("cmp/bad-input", "accepting a centrally generated key pair needs opts.trustAnchors to chain the key generation authority, or opts.authorizedBySharedSecret to authorize it by the secret that protects this session's requests (RFC 9483 sec. 4.1.6)");
467
+ }
423
468
  if (opts.trustAnchors != null && _engine && _engine.toAnchor) _asCertList(opts.trustAnchors).forEach(function (a) { try { _engine.toAnchor(a); } catch (e) { throw _err("cmp/bad-input", "opts.trustAnchors: each entry must be a certificate (DER/PEM/parsed) or a { name, publicKey, algorithm } anchor tuple -- " + ((e && e.message) || e), e); } });
424
469
  if (opts.intermediates != null && _engine && _engine.coerceCert) _asCertList(opts.intermediates).forEach(function (c) { try { _engine.coerceCert(c); } catch (e) { throw _err("cmp/bad-input", "opts.intermediates: each entry must be a certificate (DER/PEM/parsed) -- " + ((e && e.message) || e), e); } });
425
470
  var _proxySnap = httpTransport.snapshotProxy(opts.proxy);
@@ -472,6 +517,10 @@ function session(opts) {
472
517
  var expectedInfoOp = null;
473
518
  var crlQuery = null;
474
519
  var requestedSpki = null;
520
+ /** @internal Whether the request this session sent asked the authority to generate the key pair. It
521
+ * decides what binds the grant: a key this entity already holds, or the delivered private key the
522
+ * grant arrives with (RFC 9483 sec. 4.1.6). */
523
+ var centralKeyGeneration = false;
475
524
  /** @internal What the request asked for, not what this session was configured with. A resumed poll
476
525
  * finishes a transaction whose confirmation was negotiated in a request an earlier process sent, so
477
526
  * the grant is confirmed the way that request asked and not the way this session's options read. */
@@ -520,6 +569,12 @@ function session(opts) {
520
569
  }
521
570
  h.sender = opts.sender != null ? opts.sender : defaultSender;
522
571
  h.recipient = opts.recipient != null ? opts.recipient : NULL_DN;
572
+ /** @internal RFC 9480 sec. 2.20 needs cmp2021 whenever an exchange uses EnvelopedData, "for the
573
+ * request being sent or for the expected response". A central key generation transaction expects
574
+ * one on its grant, so every message in it carries the version, not only the initial request whose
575
+ * body triggers it: a poll asking for that grant expects the EnvelopedData just as the request
576
+ * did, and a resumed poll is the same transaction in a later process. */
577
+ if (centralKeyGeneration) h.pvno = 3;
523
578
  if (opts.senderKID != null) h.senderKID = opts.senderKID;
524
579
  if (opts.recipKID != null) h.recipKID = opts.recipKID;
525
580
  return h;
@@ -762,6 +817,12 @@ function session(opts) {
762
817
  * this transaction has to authenticate it the way the transaction already does. */
763
818
  protection: isSig ? "signature" : "mac",
764
819
  requestedSpki: requestedSpki ? Buffer.from(requestedSpki).toString("base64") : null,
820
+ /** @internal What the grant is held to, when it is not a key this entity already holds. A
821
+ * central key generation request names no key to certify (RFC 9483 sec. 4.1.6), so the binding
822
+ * is the delivered private key the grant arrives with, and the resumed poll applies it the way
823
+ * the first process would have. Recorded so a resumed session cannot silently drop to no
824
+ * binding at all. */
825
+ centralKeyGeneration: centralKeyGeneration,
765
826
  /** @internal The identity the first process pinned on the first response it verified. Restoring it
766
827
  * is what keeps a restart from admitting a DIFFERENT trusted signer, which an uninterrupted
767
828
  * transaction refuses (RFC 9483 sec. 3.1), and it carries the certificate a responder that sent
@@ -832,7 +893,9 @@ function session(opts) {
832
893
  var ckp = resp && resp.certifiedKeyPair;
833
894
  var cert = ckp && ckp.certificate;
834
895
  if (!Buffer.isBuffer(cert)) throw _err("cmp/unexpected-arm", "a granted CertResponse carried no plain issued certificate (an encryptedCert form is out of enrollment v1 scope) (RFC 9810 sec. 5.3.4)");
835
- if (ckp.privateKey != null) throw _err("cmp/unexpected-arm", "a granted CertResponse carried a server-generated privateKey (central key generation is out of enrollment v1 scope; a session enrolls a client-generated key) (RFC 9810 sec. 5.3.4)");
896
+ if (ckp.privateKey != null && !opts.acceptCentralKeyGeneration) {
897
+ throw _err("cmp/unexpected-arm", "a granted CertResponse carried a server-generated privateKey; accepting a key this entity did not generate is a decision the caller states with opts.acceptCentralKeyGeneration (RFC 9483 sec. 4.1.6)");
898
+ }
836
899
  var parsed;
837
900
  try { parsed = x509.parse(cert); }
838
901
  catch (e) { throw _err("cmp/bad-cert-response", "the granted CertResponse's certificate is not a valid X.509 certificate", e); }
@@ -847,6 +910,41 @@ function session(opts) {
847
910
  return cert;
848
911
  }
849
912
 
913
+ /** @internal The two shapes RFC 9483 sec. 4.1.6 gives a request for a centrally generated key: no
914
+ * publicKey at all, or one carrying an algorithm preference over a zero-length subjectPublicKey. A
915
+ * publicKey that is present and carries key bits is an ordinary request for a key this entity
916
+ * already holds. */
917
+ function _requestsCentralKeyGeneration(armSpec) {
918
+ if (!armSpec || typeof armSpec !== "object" || Buffer.isBuffer(armSpec)) return false;
919
+ var pk = armSpec.certTemplate ? armSpec.certTemplate.publicKey : null;
920
+ if (pk == null) return true;
921
+ if (!guard.bytes.isByteSource(pk)) return false;
922
+ var bits;
923
+ try { bits = asn1.read.bitString(asn1.decode(guard.bytes.view(pk, CmpError, "cmp/bad-input", "the certTemplate publicKey")).children[1]); }
924
+ catch (_e) { return false; }
925
+ return bits.bytes.length === 0;
926
+ }
927
+
928
+ /** @internal One copy of the caller's arm, and of the `certTemplate` inside it, so every check below
929
+ * and the message that goes on the wire read the same values. A `p10cr` arm is a byte source rather
930
+ * than a record, and is left to the builder's own snapshot. */
931
+ function _snapshotArm(armSpec) {
932
+ if (!_isRecord(armSpec)) return armSpec;
933
+ var copy = Object.assign({}, armSpec);
934
+ if (_isRecord(copy.certTemplate)) copy.certTemplate = Object.assign({}, copy.certTemplate);
935
+ /** @internal The proof of possession is copied for the same reason the template is: the session
936
+ * decides on `pop.type` which modes it will send, and a field answering one way to that decision
937
+ * and another to the builder would put a request on the wire in a mode the session refused. */
938
+ if (_isRecord(copy.pop)) copy.pop = Object.assign({}, copy.pop);
939
+ return copy;
940
+ }
941
+ /** @internal A record the session copies field by field, as opposed to a byte source, which every
942
+ * byte-taking door snapshots whole. A `p10cr` arm is a CertificationRequest in any of the shapes
943
+ * `guard.bytes` accepts, and copying one field by field would leave a plain object of indices. */
944
+ function _isRecord(v) {
945
+ return !!v && typeof v === "object" && !guard.bytes.isByteSource(v);
946
+ }
947
+
850
948
  function _extractRequestedSpki(arm, armSpec) {
851
949
  if (arm === "p10cr") {
852
950
  if (!guard.bytes.isByteSource(armSpec)) return null;
@@ -875,8 +973,97 @@ function session(opts) {
875
973
  if (!res || res.valid !== true) throw _err("cmp/bad-cert-response", "the issued certificate did not validate to a supplied trust anchor (its signature or chain is invalid) (RFC 5280 sec. 6.1)");
876
974
  }
877
975
 
976
+ /** @internal RFC 9483 sec. 4.1.6: the key management technique that protected the content-encryption
977
+ * key follows the protection of the REQUEST, so the session opens the container with the credential
978
+ * it enrolled under and no other. A signature session holds a private key and reaches a key
979
+ * transport or key agreement container (sec. 4.1.6.1, sec. 4.1.6.2); a MAC session holds the shared
980
+ * secret and reaches a password container (sec. 4.1.6.3). A container built for the other technique
981
+ * does not open, which is the rule enforcing itself. */
982
+ /** @internal A session certificate option takes DER, PEM, or an already-parsed certificate, and the
983
+ * path builder reads all three; `opts.trustAnchors` additionally takes a `{ name, publicKey,
984
+ * algorithm }` tuple. Both pools are forwarded to `pki.cmp.openKeyPackage`, which takes bytes, so a
985
+ * parsed certificate is handed on as the bytes it was parsed from rather than lost on the way: a
986
+ * certificate that goes missing makes the authority's chain unbuildable and reads as an unauthorized
987
+ * authority. An anchor tuple is passed through, since it is what the anchor form is; anything else
988
+ * is named here, because the certificate pool is the one place a value is dropped rather than
989
+ * refused. */
990
+ function _asOpenableCerts(v, label, allowAnchorTuple) {
991
+ return _asCertList(v).map(function (c, i) {
992
+ if (typeof c === "string" || guard.bytes.isByteSource(c)) return c;
993
+ var der = guard.parsed.sourceOf(c);
994
+ if (der != null) return der;
995
+ if (allowAnchorTuple && _isRecord(c)) return c;
996
+ throw _err("cmp/bad-input", label + "[" + i + "] is a parsed certificate this session cannot hand on as bytes; supply it as DER or PEM");
997
+ });
998
+ }
999
+
1000
+ async function _openDeliveredKey(ckp) {
1001
+ /** @internal A transaction that asked the authority to generate the key pair is bound to the key
1002
+ * it delivers, and to nothing else: no requested public key was submitted for the grant to be
1003
+ * held to. A grant carrying only a certificate would therefore be confirmed with nothing checked,
1004
+ * and the entity would hold a certificate for a private key it never received. */
1005
+ if (ckp.privateKey == null && centralKeyGeneration) {
1006
+ throw _err("cmp/bad-cert-response", "this transaction asked the authority to generate the key pair, and the granted CertResponse carries no privateKey -- there is no key to go with the issued certificate, and nothing the grant is bound to (RFC 9483 sec. 4.1.6)");
1007
+ }
1008
+ if (ckp.privateKey == null) return null;
1009
+ /** @internal The sec. 4.1.6 exemption is what the CALLER said, never what this session inferred
1010
+ * from its own configuration. Reading it off an absent `trustAnchors` would turn a missing option
1011
+ * into a decision to authorize the key generation authority by the shared secret alone, silently
1012
+ * dropping both the chain check and the id-kp-cmKGA assertion. `pki.cmp.openKeyPackage` requires
1013
+ * the statement, so a session that inferred it would admit containers the verb refuses. The verb's
1014
+ * own door decides: one of the two rules, never neither and never both. */
1015
+ var open = opts.mac != null
1016
+ ? { password: opts.mac.secret, authorizedBySharedSecret: opts.authorizedBySharedSecret === true }
1017
+ : { key: opts.key, cert: opts.cert };
1018
+ /** @internal On a session `opts.trustAnchors` does two jobs: it authorizes the key generation
1019
+ * authority, and it path-validates the issued certificate. The exemption replaces the first job
1020
+ * only, so the anchors are withheld from the verb, whose door knows the first job alone and
1021
+ * refuses the pair, and they keep the second. A caller therefore states both options to authorize
1022
+ * the authority by the secret and still chain the certificate it was issued. */
1023
+ if (opts.trustAnchors != null && !open.authorizedBySharedSecret) {
1024
+ open.trustAnchors = _asOpenableCerts(opts.trustAnchors, "opts.trustAnchors", true);
1025
+ if (opts.intermediates != null) open.intermediates = _asOpenableCerts(opts.intermediates, "opts.intermediates", false);
1026
+ }
1027
+ /** @internal The instant the session validates every other certificate at governs the authority's
1028
+ * chain too, so a historical enrollment does not judge one certificate at a stated time and
1029
+ * another at the current one. */
1030
+ if (opts.time != null) open.time = opts.time;
1031
+ if (ckp.privateKey.bytes == null) {
1032
+ throw _err("cmp/bad-key-package", "a delivered key package is carried as an EnvelopedData, and this grant carries the legacy RFC 4211 EncryptedValue form (RFC 9483 sec. 4.1.6, RFC 9810 sec. 5.2.2)");
1033
+ }
1034
+ return cmp.openKeyPackage(ckp.privateKey.bytes, open);
1035
+ }
1036
+
1037
+ /** @internal A centrally generated key and the certificate granted alongside it are one pair, and
1038
+ * only the caller can tell: the authority's signature says the key package is authentic, and says
1039
+ * nothing about which certificate it belongs with. An unbound pair confirms an enrollment whose
1040
+ * certificate certifies a key this entity does not hold, so the two are compared here, before the
1041
+ * certConf that accepts the grant (RFC 9483 sec. 4.1.6). */
1042
+ async function _assertDeliveredKeyCertified(deliveredKey, leaf) {
1043
+ /** @internal RFC 9483 sec. 4.1.6 gives the eContent "a sequence of one OneAsymmetricKey element",
1044
+ * and here that count is what the grant is bound to rather than a matter of form: one grant
1045
+ * certifies one key, so every further key in the package is one the issued certificate says
1046
+ * nothing about and this entity would hold uncertified. The standalone `pki.cmp.openKeyPackage`
1047
+ * surfaces a whole RFC 5958 package, which is SIZE (1..MAX), and leaves the binding to its caller;
1048
+ * a session performs the sec. 4.1.6 enrollment, so it holds the profile's count. */
1049
+ if (deliveredKey.keys.length !== 1) {
1050
+ throw _err("cmp/bad-key-package", "a delivered key package carries one key, and this one carries " +
1051
+ deliveredKey.keys.length + "; the issued certificate certifies one of them and says nothing about the rest (RFC 9483 sec. 4.1.6)");
1052
+ }
1053
+ var certified, pair;
1054
+ try { certified = x509.parse(leaf).subjectPublicKeyInfo.bytes; }
1055
+ catch (e) { throw _err("cmp/bad-cert-response", "the issued certificate's public key cannot be compared with the delivered key", e); }
1056
+ try { pair = await key.correspondsTo(deliveredKey.keys[0], certified); }
1057
+ catch (e) { throw _err("cmp/bad-key-package", "the delivered private key and the issued certificate could not be exercised together, so the two cannot be shown to be a pair", e); }
1058
+ if (!pair) {
1059
+ throw _err("cmp/bad-key-package", "the issued certificate does not certify the delivered private key -- the two are not a pair, so the certificate is unusable with the key this enrollment received (RFC 9483 sec. 4.1.6)");
1060
+ }
1061
+ }
1062
+
878
1063
  async function _finish(granted, header) {
879
1064
  var leaf = _leafOf(granted.resp);
1065
+ var deliveredKey = await _openDeliveredKey(granted.resp.certifiedKeyPair);
1066
+ if (deliveredKey !== null) await _assertDeliveredKeyCertified(deliveredKey, leaf);
880
1067
  var chain = [leaf], seenChain = Object.create(null);
881
1068
  caPubsAccum.forEach(function (c) {
882
1069
  if (!Buffer.isBuffer(c)) return;
@@ -897,13 +1084,21 @@ function session(opts) {
897
1084
  return _terminal("issued", {
898
1085
  certificate: certOut, chain: chainOut, status: _statusOf(granted.resp),
899
1086
  confirmed: conf.confirmed, implicitConfirm: conf.implicit, polls: granted.polls || 0,
1087
+ deliveredKey: deliveredKey,
900
1088
  });
901
1089
  }
902
1090
 
1091
+ /** @internal Claim the session's one transaction slot. It is claimed BEFORE the caller's request
1092
+ * object is read, because reading one runs whatever accessors it carries and an accessor can call
1093
+ * back into this session: a slot claimed only after validation would let the re-entrant call open a
1094
+ * transaction that the outer call then writes its own state over. Every entry point pairs this with
1095
+ * a `finally` that releases the slot, so a request refused during validation leaves the session
1096
+ * retryable exactly as it did before. */
903
1097
  function _assertFresh(what) {
904
1098
  if (completed || inFlight) {
905
1099
  throw _err("cmp/bad-input", "this pki.cmp.session transaction is already " + (completed ? "completed" : "in flight") + "; create a new session per " + what + " (RFC 9810 sec. 5.1.1: one transactionID per transaction)");
906
1100
  }
1101
+ inFlight = true;
907
1102
  }
908
1103
 
909
1104
  async function _runOneShot(bodySpec, arm, kind) {
@@ -924,51 +1119,101 @@ function session(opts) {
924
1119
 
925
1120
  async function enroll(request) {
926
1121
  _assertFresh("enrollment");
1122
+ try { return await _enroll(request); }
1123
+ finally {
1124
+ inFlight = false;
1125
+ completed = started;
1126
+ }
1127
+ }
1128
+
1129
+ async function _enroll(request) {
927
1130
  guard.identifier.assertPlainRecord(request, _err, "cmp/bad-input", "enroll(request), a body spec { ir | cr | kur | p10cr },");
1131
+ /** @internal The request's own door, rather than the builder's arm count downstream: the session
1132
+ * sends the ONE arm it read out of this object, so a key the session does not recognize would
1133
+ * otherwise be dropped silently instead of naming the caller's mistake. */
1134
+ guard.identifier.assertKnownKeys(request, ENROLL_ARMS, _err, "cmp/bad-input", "unknown enroll request field ");
928
1135
  var arms = Object.keys(request).filter(function (k) { return request[k] != null; });
929
1136
  if (arms.length !== 1 || !ENROLL_ARMS[arms[0]]) {
930
1137
  throw _err("cmp/bad-input", "enroll(request) must carry EXACTLY ONE enrollment arm (ir / cr / kur / p10cr)");
931
1138
  }
932
- var armSpec = request[arms[0]];
1139
+ /** @internal The arm is copied out of the caller's object ONCE, and everything after this reads and
1140
+ * sends the copy. A request object can carry accessors, so reading a field twice can get two
1141
+ * answers: the request that goes on the wire has to be the one the checks below were applied to,
1142
+ * or a second answer could turn a validated client-generated enrollment into a central key
1143
+ * generation request the session never admitted. The certTemplate is copied for the same reason,
1144
+ * since the field that decides which of the two this is lives in it. */
1145
+ var armSpec = _snapshotArm(request[arms[0]]);
933
1146
  if (armSpec && typeof armSpec === "object" && !Buffer.isBuffer(armSpec) && armSpec.messages != null) {
934
1147
  throw _err("cmp/bad-input", "a batched CRMF request ({ messages: [...] }) is not supported by a session -- submit one certificate request per pki.cmp.session");
935
1148
  }
936
1149
  var reqSpki = _extractRequestedSpki(arms[0], armSpec);
937
- if (arms[0] !== "p10cr" && reqSpki == null) {
938
- throw _err("cmp/bad-input", "an ir / cr / kur enrollment must submit certTemplate.publicKey -- a session enrolls a client-generated key (a raVerified keyless request or central key generation is not supported)");
939
- }
940
- if (arms[0] !== "p10cr" && armSpec != null && armSpec.pop != null && armSpec.pop.type != null && armSpec.pop.type !== "signature") {
941
- throw _err("cmp/bad-input", "a session ir / cr / kur proves possession by signing the CRMF proof of possession; a non-signature POP mode (" + guard.text.showValue(armSpec.pop.type) + ") is not supported (RFC 4211 sec. 4)");
1150
+ /** @internal RFC 9483 sec. 4.1.6: an entity requesting central key generation "MUST omit the
1151
+ * publicKey field from the certTemplate or ... fill the subjectPublicKey sub-field with a
1152
+ * zero-length BIT STRING", and the popo "MUST be absent". A request in that shape asks the
1153
+ * authority to generate the key, so it is admitted only when the caller has said it accepts one. */
1154
+ var central = arms[0] !== "p10cr" && _requestsCentralKeyGeneration(armSpec);
1155
+ if (central && !opts.acceptCentralKeyGeneration) {
1156
+ throw _err("cmp/bad-input", "this request asks the authority to generate the key pair (RFC 9483 sec. 4.1.6: a certTemplate with no publicKey, or a zero-length subjectPublicKey); accepting a key this entity did not generate is a decision the caller states with opts.acceptCentralKeyGeneration");
1157
+ }
1158
+ if (arms[0] !== "p10cr" && reqSpki == null && !central) {
1159
+ throw _err("cmp/bad-input", "an ir / cr / kur enrollment must submit certTemplate.publicKey -- a session enrolls a client-generated key unless the request asks for central key generation (RFC 9483 sec. 4.1.6) and opts.acceptCentralKeyGeneration is set");
1160
+ }
1161
+ if (central && armSpec.pop != null) {
1162
+ throw _err("cmp/bad-input", "a central key generation request carries no proof of possession, since this entity holds no key to prove possession of (RFC 9483 sec. 4.1.6)");
1163
+ }
1164
+ /** @internal A session sends the signature proof of possession and no other, so the arm is read
1165
+ * the way the builder reads it rather than from `type` alone. `pop: { raVerified: true }` names
1166
+ * the raVerified arm without stating a type, and a door that asked only about `type` would pass
1167
+ * it through to be refused downstream, after the request had been assembled. */
1168
+ if (arms[0] !== "p10cr" && armSpec != null && _isRecord(armSpec.pop)) {
1169
+ var popType = armSpec.pop.type;
1170
+ if (popType != null && popType !== "signature") {
1171
+ throw _err("cmp/bad-input", "a session ir / cr / kur proves possession by signing the CRMF proof of possession; a non-signature POP mode (" + guard.text.showValue(popType) + ") is not supported (RFC 4211 sec. 4)");
1172
+ }
1173
+ guard.identifier.assertKnownKeys(armSpec.pop, SESSION_POP_KEYS, _err, "cmp/bad-input",
1174
+ "a session ir / cr / kur proves possession by signing the CRMF proof of possession, so its `pop` names the signature arm only: ");
942
1175
  }
943
- if (isMac && arms[0] !== "p10cr" && (armSpec == null || armSpec.key == null)) {
1176
+ if (isMac && arms[0] !== "p10cr" && !central && (armSpec == null || armSpec.key == null)) {
944
1177
  throw _err("cmp/bad-input", "a MAC-protected ir / cr / kur must carry the requested key's private half as `key` for the CRMF proof of possession -- a signature session reuses opts.key, but a PBMAC1 session has no signing key, so the request would carry no proof of possession (RFC 4211 sec. 4)");
945
1178
  }
946
- inFlight = true;
947
- try {
948
- txnKind = "enroll";
949
- expectedRespArm = RESPONSE_ARM[arms[0]];
950
- var cid = (typeof armSpec === "object" && !Buffer.isBuffer(armSpec)) ? armSpec.certReqId : undefined;
951
- activeCertReqId = _normalizeCertReqId(cid, arms[0] === "p10cr" ? P10CR_CERT_REQ_ID : DEFAULT_CERT_REQ_ID);
952
- requestedSpki = reqSpki;
953
- var initial = await _send(request, arms[0]);
954
- var t = _classify(initial);
955
- var grantHeader = initial.header;
956
- var pollCount = 0;
957
- if (t.state === "waiting") {
958
- var polled = await _pollLoop(t.resp);
959
- pollCount = polled.polls;
960
- if (polled.timeout) return _terminal("poll-timeout", { status: polled.status, polls: pollCount, resumeToken: _resumeToken(pollCount, _pollDueAt(polled.dueIn)) });
961
- t = polled.done;
962
- grantHeader = polled.header;
963
- t.polls = pollCount;
964
- }
965
- if (t.state === "granted") { var r = await _finish({ resp: t.resp, polls: pollCount, code: t.code }, grantHeader); r.polls = pollCount; return r; }
966
- if (t.state === "rejected") return _terminal("rejected", { status: t.status || _statusOf(t.resp), polls: pollCount });
967
- throw _err("cmp/unexpected-arm", t.reason || "the enrollment transaction reached an unexpected state");
968
- } finally {
969
- inFlight = false;
970
- completed = started;
1179
+ /** @internal Everything this request contributes is read out of the caller's object HERE, while no
1180
+ * session state has been written. Reading an object a caller supplied runs whatever accessors it
1181
+ * carries, and one of those can call back into this session; a write placed among those reads
1182
+ * would let the re-entrant call and this one interleave and leave the two variables that bind the
1183
+ * grant, `centralKeyGeneration` and `requestedSpki`, describing different transactions. */
1184
+ var cid = (typeof armSpec === "object" && !Buffer.isBuffer(armSpec)) ? armSpec.certReqId : undefined;
1185
+ var certReqId = _normalizeCertReqId(cid, arms[0] === "p10cr" ? P10CR_CERT_REQ_ID : DEFAULT_CERT_REQ_ID);
1186
+ /** @internal A central key generation request carries no proof of possession at all, so the arm
1187
+ * names a null key to stop `pki.cmp.build` falling back to the message protection key. */
1188
+ if (central) armSpec.key = null;
1189
+ var sendSpec = Object.create(null);
1190
+ sendSpec[arms[0]] = armSpec;
1191
+
1192
+ txnKind = "enroll";
1193
+ expectedRespArm = RESPONSE_ARM[arms[0]];
1194
+ activeCertReqId = certReqId;
1195
+ /** @internal The pair is installed together, from this request alone. A session whose earlier
1196
+ * enroll failed while building is still retryable, so a retry that submits an ordinary
1197
+ * client-generated key must not carry the earlier request's binding into its own transaction:
1198
+ * the zero-length subjectPublicKey form names an algorithm preference and not a key to certify,
1199
+ * so a central request is bound to the delivered private key instead. */
1200
+ centralKeyGeneration = central;
1201
+ requestedSpki = central ? null : reqSpki;
1202
+ var initial = await _send(sendSpec, arms[0]);
1203
+ var t = _classify(initial);
1204
+ var grantHeader = initial.header;
1205
+ var pollCount = 0;
1206
+ if (t.state === "waiting") {
1207
+ var polled = await _pollLoop(t.resp);
1208
+ pollCount = polled.polls;
1209
+ if (polled.timeout) return _terminal("poll-timeout", { status: polled.status, polls: pollCount, resumeToken: _resumeToken(pollCount, _pollDueAt(polled.dueIn)) });
1210
+ t = polled.done;
1211
+ grantHeader = polled.header;
1212
+ t.polls = pollCount;
971
1213
  }
1214
+ if (t.state === "granted") { var r = await _finish({ resp: t.resp, polls: pollCount, code: t.code }, grantHeader); r.polls = pollCount; return r; }
1215
+ if (t.state === "rejected") return _terminal("rejected", { status: t.status || _statusOf(t.resp), polls: pollCount });
1216
+ throw _err("cmp/unexpected-arm", t.reason || "the enrollment transaction reached an unexpected state");
972
1217
  }
973
1218
 
974
1219
  /** @internal The state a poll-timeout handed the caller, read back. It is the caller's own record of
@@ -988,7 +1233,7 @@ function session(opts) {
988
1233
  arm: token.arm, polls: token.polls, requestedSpki: token.requestedSpki, signer: token.signer,
989
1234
  signerCache: token.signerCache, chain: token.chain, caPubs: token.caPubs,
990
1235
  nextPollAt: token.nextPollAt, implicitConfirm: token.implicitConfirm,
991
- protection: token.protection,
1236
+ protection: token.protection, centralKeyGeneration: token.centralKeyGeneration,
992
1237
  };
993
1238
  var txn = _hexField(raw.transactionId, "transactionId");
994
1239
  /** @internal The width a first message opens a transaction with (RFC 9483 sec. 3.1), which is the
@@ -1035,14 +1280,23 @@ function session(opts) {
1035
1280
  * field must not drop the check that field feeds, so the two that bind the transaction are required:
1036
1281
  * the key the grant is held to, and, for a signature session, the identity every response is held
1037
1282
  * to. A shared-secret session pins no certificate, and its secret is the binding instead. */
1038
- if (raw.requestedSpki == null) {
1039
- throw _err("cmp/bad-input", "the resumeToken requestedSpki must name the key the granted certificate certifies -- without it the grant would be bound to nothing");
1283
+ var wasCentral = raw.centralKeyGeneration === true;
1284
+ if (wasCentral && !opts.acceptCentralKeyGeneration) {
1285
+ throw _err("cmp/bad-input", "the resumeToken continues a central key generation transaction, so the session resuming it must also set opts.acceptCentralKeyGeneration (RFC 9483 sec. 4.1.6)");
1286
+ }
1287
+ if (raw.requestedSpki == null && !wasCentral) {
1288
+ throw _err("cmp/bad-input", "the resumeToken requestedSpki must name the key the granted certificate certifies -- without it the grant would be bound to nothing, unless the transaction requested central key generation, where the delivered key is the binding");
1040
1289
  }
1041
- var spki = _b64Field(raw.requestedSpki, "requestedSpki", CERT_MAX_BYTES);
1290
+ if (raw.requestedSpki != null && wasCentral) {
1291
+ throw _err("cmp/bad-input", "the resumeToken names both a requested key and central key generation; a transaction is bound one way or the other, not both");
1292
+ }
1293
+ var spki = wasCentral ? null : _b64Field(raw.requestedSpki, "requestedSpki", CERT_MAX_BYTES);
1042
1294
  /** @internal Read at the door, not when the grant arrives: stored state that is not a public key
1043
1295
  * would otherwise advance the exchange with the authority before it was found to be unusable. */
1044
- try { _spkiKeyIdentity(spki); }
1045
- catch (e) { throw _err("cmp/bad-input", "the resumeToken requestedSpki is not a SubjectPublicKeyInfo", e); }
1296
+ if (spki !== null) {
1297
+ try { _spkiKeyIdentity(spki); }
1298
+ catch (e) { throw _err("cmp/bad-input", "the resumeToken requestedSpki is not a SubjectPublicKeyInfo", e); }
1299
+ }
1046
1300
  var signer = null;
1047
1301
  if (raw.signer != null) signer = _certField(raw.signer, "signer", CERT_MAX_BYTES);
1048
1302
  else if (isSig) {
@@ -1084,7 +1338,7 @@ function session(opts) {
1084
1338
  throw _err("cmp/bad-input", "the resumeToken nextPollAt must be the instant the next poll is due, in milliseconds since the epoch");
1085
1339
  }
1086
1340
  return { transactionID: txn, recipNonce: nonce, certReqId: certReqId, arm: arm,
1087
- implicitConfirm: implicitConfirm,
1341
+ implicitConfirm: implicitConfirm, centralKeyGeneration: wasCentral,
1088
1342
  polls: polls || 0, requestedSpki: spki, signer: signer, signerCache: signerCache,
1089
1343
  chain: chain, caPubs: caPubs,
1090
1344
  nextPollAt: nextPollAt != null ? nextPollAt : null };
@@ -1139,8 +1393,9 @@ function session(opts) {
1139
1393
  * number of processes. */
1140
1394
  async function resumePoll(token) {
1141
1395
  _assertFresh("resumed poll");
1142
- var t = _readResumeToken(token);
1143
- inFlight = true;
1396
+ var t;
1397
+ try { t = _readResumeToken(token); }
1398
+ catch (e) { inFlight = false; completed = started; throw e; }
1144
1399
  try {
1145
1400
  txnKind = "enroll";
1146
1401
  /** @internal Installing the restored state consumes the session, whether or not a request follows:
@@ -1153,6 +1408,7 @@ function session(opts) {
1153
1408
  activeCertReqId = t.certReqId;
1154
1409
  expectedRespArm = t.arm;
1155
1410
  requestedSpki = t.requestedSpki;
1411
+ centralKeyGeneration = t.centralKeyGeneration;
1156
1412
  requestedImplicitConfirm = t.implicitConfirm;
1157
1413
  /** @internal Restoring the pinned identity is what makes a restart no weaker than staying in one
1158
1414
  * process: the resumed responses are held to the signer the first process authenticated, and the
@@ -1248,6 +1504,14 @@ function session(opts) {
1248
1504
 
1249
1505
  async function revoke(request) {
1250
1506
  _assertFresh("revocation");
1507
+ try { return await _revoke(request); }
1508
+ finally {
1509
+ inFlight = false;
1510
+ completed = started;
1511
+ }
1512
+ }
1513
+
1514
+ async function _revoke(request) {
1251
1515
  guard.identifier.assertPlainRecord(request, _err, "cmp/bad-input", "revoke(request), an object { certificate | certDetails, reason? },");
1252
1516
  guard.identifier.assertKnownKeys(request, KNOWN_REVOKE_KEYS, _err, "cmp/bad-input", "unknown revoke request field ");
1253
1517
  if (isMac) throw _err("cmp/bad-input", "a PBMAC1 session cannot revoke: the request must be signature-protected with the certificate being revoked (RFC 9483 sec. 4.2). Use a signature session ({ key, cert }).");
@@ -1278,29 +1542,31 @@ function session(opts) {
1278
1542
  }
1279
1543
  var own = _assertRevokingOwnCertificate(certTemplateDer);
1280
1544
  var body = { rr: [{ certDetails: certTemplateDer, crlEntryDetails: { reason: reasonArg == null ? undefined : reasonArg } }] };
1281
- inFlight = true;
1282
- try {
1283
- var out = await _runOneShot(body, "rr", "revoke");
1284
- if (out.timeout) return _terminal("poll-timeout", { status: out.status, polls: out.polls });
1285
- var t = out.done;
1286
- if (t.state === "granted") {
1287
- return _terminal("revoked", {
1288
- status: t.status, polls: out.polls,
1289
- revokedCerts: _bindRevCerts(t.revCerts, own),
1290
- crls: (t.crls || []).map(_asCrl),
1291
- });
1292
- }
1293
- if (t.state === "rejected") return _terminal("rejected", { status: t.status || null, polls: out.polls });
1294
- throw _err("cmp/unexpected-arm", t.reason || "the revocation transaction reached an unexpected state");
1295
- } finally {
1296
- inFlight = false;
1297
- completed = started;
1545
+ var out = await _runOneShot(body, "rr", "revoke");
1546
+ if (out.timeout) return _terminal("poll-timeout", { status: out.status, polls: out.polls });
1547
+ var t = out.done;
1548
+ if (t.state === "granted") {
1549
+ return _terminal("revoked", {
1550
+ status: t.status, polls: out.polls,
1551
+ revokedCerts: _bindRevCerts(t.revCerts, own),
1552
+ crls: (t.crls || []).map(_asCrl),
1553
+ });
1298
1554
  }
1555
+ if (t.state === "rejected") return _terminal("rejected", { status: t.status || null, polls: out.polls });
1556
+ throw _err("cmp/unexpected-arm", t.reason || "the revocation transaction reached an unexpected state");
1299
1557
  }
1300
1558
 
1301
1559
 
1302
1560
  async function info(request) {
1303
1561
  _assertFresh("support message");
1562
+ try { return await _info(request); }
1563
+ finally {
1564
+ inFlight = false;
1565
+ completed = started;
1566
+ }
1567
+ }
1568
+
1569
+ async function _info(request) {
1304
1570
  guard.identifier.assertPlainRecord(request, _err, "cmp/bad-input", "info(request), an object naming one support operation (caCerts / rootCaCert / certReqTemplate / crlUpdate),");
1305
1571
  guard.identifier.assertKnownKeys(request, INFO_OPS, _err, "cmp/bad-input", "unknown info request field ");
1306
1572
  var keys = Object.keys(request), names = [], values = [];
@@ -1327,31 +1593,25 @@ function session(opts) {
1327
1593
  crlQuery = { issuerName: built.issuerName, thisUpdate: built.thisUpdate, dpn: built.dpn };
1328
1594
  }
1329
1595
  expectedInfoOp = op;
1330
- inFlight = true;
1331
- try {
1332
- var out = await _runOneShot({ genm: [itav] }, "genm", "info");
1333
- if (out.timeout) return _terminal("poll-timeout", { operation: op.name, status: out.status, polls: out.polls });
1334
- var t = out.done;
1335
- if (t.state === "granted") {
1336
- var raw = t.itav.value;
1337
- var value = null;
1338
- if (raw != null) {
1339
- try {
1340
- value = schemaCmp[op.read](raw);
1341
- } catch (e) {
1342
- if (e && e.isCmpError) throw e;
1343
- throw _err("cmp/bad-info-value", "the " + op.name + " response value is malformed -- " + ((e && e.message) || e), e);
1344
- }
1345
- value = await _checkInfoValue(op, value, itav.infoValue);
1596
+ var out = await _runOneShot({ genm: [itav] }, "genm", "info");
1597
+ if (out.timeout) return _terminal("poll-timeout", { operation: op.name, status: out.status, polls: out.polls });
1598
+ var t = out.done;
1599
+ if (t.state === "granted") {
1600
+ var raw = t.itav.value;
1601
+ var value = null;
1602
+ if (raw != null) {
1603
+ try {
1604
+ value = schemaCmp[op.read](raw);
1605
+ } catch (e) {
1606
+ if (e && e.isCmpError) throw e;
1607
+ throw _err("cmp/bad-info-value", "the " + op.name + " response value is malformed -- " + ((e && e.message) || e), e);
1346
1608
  }
1347
- return _terminal("answered", { operation: op.name, present: raw != null, value: value, status: null, polls: out.polls });
1609
+ value = await _checkInfoValue(op, value, itav.infoValue);
1348
1610
  }
1349
- if (t.state === "rejected") return _terminal("rejected", { operation: op.name, status: t.status || null, polls: out.polls });
1350
- throw _err("cmp/unexpected-arm", t.reason || "the support-message transaction reached an unexpected state");
1351
- } finally {
1352
- inFlight = false;
1353
- completed = started;
1611
+ return _terminal("answered", { operation: op.name, present: raw != null, value: value, status: null, polls: out.polls });
1354
1612
  }
1613
+ if (t.state === "rejected") return _terminal("rejected", { operation: op.name, status: t.status || null, polls: out.polls });
1614
+ throw _err("cmp/unexpected-arm", t.reason || "the support-message transaction reached an unexpected state");
1355
1615
  }
1356
1616
 
1357
1617
  function _checkInfoValue(op, value, requestValue) {
@@ -1574,6 +1834,7 @@ module.exports = {
1574
1834
  transfer: cmp.transfer,
1575
1835
  wellKnownUrl: cmp.wellKnownUrl,
1576
1836
  verify: cmp.verify,
1837
+ openKeyPackage: cmp.openKeyPackage,
1577
1838
  session: session,
1578
1839
  setEngine: setEngine, // @internal
1579
1840
  };