@blamejs/pki 0.6.50 → 0.6.52
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 +52 -0
- package/README.md +1 -1
- package/index.js +8 -3
- package/lib/cmp-build.js +23 -2
- package/lib/cmp-session.js +352 -85
- package/lib/cmp-verify.js +434 -6
- package/lib/guard-name.js +59 -1
- package/lib/guard-parsed.js +17 -1
- package/lib/key.js +159 -0
- package/lib/oid.js +2 -0
- package/lib/path-validate.js +8 -0
- package/lib/schema-cmp.js +1 -1
- package/lib/schema-pkix.js +43 -3
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/cmp-session.js
CHANGED
|
@@ -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);
|
|
@@ -201,8 +207,14 @@ var CAPUBS_MAX = 2 * MAX_EXTRA_CERTS;
|
|
|
201
207
|
var SESSION_MAX_INTERMEDIATES = constants.LIMITS.PATH_BUILD_MAX_CANDIDATES - CAPUBS_MAX - MAX_EXTRA_CERTS;
|
|
202
208
|
var SESSION_MAX_INTERMEDIATES_MAC = constants.LIMITS.PATH_BUILD_MAX_CANDIDATES - CAPUBS_MAX;
|
|
203
209
|
function _isRecoverableVerify(code) {
|
|
210
|
+
/** @internal `cmp/bad-sender-kid` belongs here for the same reason `cmp/signer-cert-not-found` does:
|
|
211
|
+
* a response whose extraCerts carry no certificate matching the declared key identifier is one this
|
|
212
|
+
* session may already hold the signer for. Retrying is safe because the fallback certificate is put
|
|
213
|
+
* through the same identifier check, so the retry can only succeed on a certificate the message
|
|
214
|
+
* actually names. */
|
|
204
215
|
return code === "cmp/signer-cert-not-found" || code === "cmp/protection-failed" ||
|
|
205
|
-
code === "cmp/sender-mismatch" || code === "cmp/untrusted-signer"
|
|
216
|
+
code === "cmp/sender-mismatch" || code === "cmp/untrusted-signer" ||
|
|
217
|
+
code === "cmp/bad-sender-kid";
|
|
206
218
|
}
|
|
207
219
|
function _responseExtraCerts(responseBytes) {
|
|
208
220
|
var extra = schemaCmp.parse(responseBytes).extraCerts;
|
|
@@ -335,8 +347,27 @@ function _certConfHash(certDer) {
|
|
|
335
347
|
* `CertResponse` is the caller's CRMF request id (`request.ir.certReqId`, ...) when supplied, else the
|
|
336
348
|
* single-request default. One transaction per session: a second or concurrent `enroll`, or a batched
|
|
337
349
|
* CRMF request, is refused (a local build error leaves the session retryable). The returned `chain` is the
|
|
338
|
-
* issued leaf plus any authenticated `caPubs` the CA delivered (chain material, never trust anchors)
|
|
339
|
-
*
|
|
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.
|
|
340
371
|
*
|
|
341
372
|
* `revoke(request)` drives the RFC 9483 sec. 4.2 revocation exchange (`rr` -> `rp`) through the same shell.
|
|
342
373
|
* The request names the certificate, either as `{ certificate }` or as `{ certDetails: { issuer,
|
|
@@ -374,6 +405,8 @@ function _certConfHash(certDer) {
|
|
|
374
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.
|
|
375
406
|
* - `implicitConfirm` -- request implicit confirmation (skip the certConf leg when the CA grants it).
|
|
376
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.
|
|
377
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`.
|
|
378
411
|
* - `tls` / `headers` / `timeout` / `maxResponseBytes` -- transport config + budgets.
|
|
379
412
|
* - `proxy` -- reach the CA through a forward HTTP proxy (`{ url, auth?, tls? }`; see pki.transport).
|
|
@@ -414,6 +447,24 @@ function session(opts) {
|
|
|
414
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)");
|
|
415
448
|
}
|
|
416
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
|
+
}
|
|
417
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); } });
|
|
418
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); } });
|
|
419
470
|
var _proxySnap = httpTransport.snapshotProxy(opts.proxy);
|
|
@@ -466,6 +517,10 @@ function session(opts) {
|
|
|
466
517
|
var expectedInfoOp = null;
|
|
467
518
|
var crlQuery = null;
|
|
468
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;
|
|
469
524
|
/** @internal What the request asked for, not what this session was configured with. A resumed poll
|
|
470
525
|
* finishes a transaction whose confirmation was negotiated in a request an earlier process sent, so
|
|
471
526
|
* the grant is confirmed the way that request asked and not the way this session's options read. */
|
|
@@ -514,6 +569,12 @@ function session(opts) {
|
|
|
514
569
|
}
|
|
515
570
|
h.sender = opts.sender != null ? opts.sender : defaultSender;
|
|
516
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;
|
|
517
578
|
if (opts.senderKID != null) h.senderKID = opts.senderKID;
|
|
518
579
|
if (opts.recipKID != null) h.recipKID = opts.recipKID;
|
|
519
580
|
return h;
|
|
@@ -756,6 +817,12 @@ function session(opts) {
|
|
|
756
817
|
* this transaction has to authenticate it the way the transaction already does. */
|
|
757
818
|
protection: isSig ? "signature" : "mac",
|
|
758
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,
|
|
759
826
|
/** @internal The identity the first process pinned on the first response it verified. Restoring it
|
|
760
827
|
* is what keeps a restart from admitting a DIFFERENT trusted signer, which an uninterrupted
|
|
761
828
|
* transaction refuses (RFC 9483 sec. 3.1), and it carries the certificate a responder that sent
|
|
@@ -826,7 +893,9 @@ function session(opts) {
|
|
|
826
893
|
var ckp = resp && resp.certifiedKeyPair;
|
|
827
894
|
var cert = ckp && ckp.certificate;
|
|
828
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)");
|
|
829
|
-
if (ckp.privateKey != null
|
|
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
|
+
}
|
|
830
899
|
var parsed;
|
|
831
900
|
try { parsed = x509.parse(cert); }
|
|
832
901
|
catch (e) { throw _err("cmp/bad-cert-response", "the granted CertResponse's certificate is not a valid X.509 certificate", e); }
|
|
@@ -841,6 +910,41 @@ function session(opts) {
|
|
|
841
910
|
return cert;
|
|
842
911
|
}
|
|
843
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
|
+
|
|
844
948
|
function _extractRequestedSpki(arm, armSpec) {
|
|
845
949
|
if (arm === "p10cr") {
|
|
846
950
|
if (!guard.bytes.isByteSource(armSpec)) return null;
|
|
@@ -869,8 +973,97 @@ function session(opts) {
|
|
|
869
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)");
|
|
870
974
|
}
|
|
871
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
|
+
|
|
872
1063
|
async function _finish(granted, header) {
|
|
873
1064
|
var leaf = _leafOf(granted.resp);
|
|
1065
|
+
var deliveredKey = await _openDeliveredKey(granted.resp.certifiedKeyPair);
|
|
1066
|
+
if (deliveredKey !== null) await _assertDeliveredKeyCertified(deliveredKey, leaf);
|
|
874
1067
|
var chain = [leaf], seenChain = Object.create(null);
|
|
875
1068
|
caPubsAccum.forEach(function (c) {
|
|
876
1069
|
if (!Buffer.isBuffer(c)) return;
|
|
@@ -891,13 +1084,21 @@ function session(opts) {
|
|
|
891
1084
|
return _terminal("issued", {
|
|
892
1085
|
certificate: certOut, chain: chainOut, status: _statusOf(granted.resp),
|
|
893
1086
|
confirmed: conf.confirmed, implicitConfirm: conf.implicit, polls: granted.polls || 0,
|
|
1087
|
+
deliveredKey: deliveredKey,
|
|
894
1088
|
});
|
|
895
1089
|
}
|
|
896
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. */
|
|
897
1097
|
function _assertFresh(what) {
|
|
898
1098
|
if (completed || inFlight) {
|
|
899
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)");
|
|
900
1100
|
}
|
|
1101
|
+
inFlight = true;
|
|
901
1102
|
}
|
|
902
1103
|
|
|
903
1104
|
async function _runOneShot(bodySpec, arm, kind) {
|
|
@@ -918,51 +1119,101 @@ function session(opts) {
|
|
|
918
1119
|
|
|
919
1120
|
async function enroll(request) {
|
|
920
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) {
|
|
921
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 ");
|
|
922
1135
|
var arms = Object.keys(request).filter(function (k) { return request[k] != null; });
|
|
923
1136
|
if (arms.length !== 1 || !ENROLL_ARMS[arms[0]]) {
|
|
924
1137
|
throw _err("cmp/bad-input", "enroll(request) must carry EXACTLY ONE enrollment arm (ir / cr / kur / p10cr)");
|
|
925
1138
|
}
|
|
926
|
-
|
|
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]]);
|
|
927
1146
|
if (armSpec && typeof armSpec === "object" && !Buffer.isBuffer(armSpec) && armSpec.messages != null) {
|
|
928
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");
|
|
929
1148
|
}
|
|
930
1149
|
var reqSpki = _extractRequestedSpki(arms[0], armSpec);
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
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: ");
|
|
936
1175
|
}
|
|
937
|
-
if (isMac && arms[0] !== "p10cr" && (armSpec == null || armSpec.key == null)) {
|
|
1176
|
+
if (isMac && arms[0] !== "p10cr" && !central && (armSpec == null || armSpec.key == null)) {
|
|
938
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)");
|
|
939
1178
|
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
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;
|
|
965
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");
|
|
966
1217
|
}
|
|
967
1218
|
|
|
968
1219
|
/** @internal The state a poll-timeout handed the caller, read back. It is the caller's own record of
|
|
@@ -982,7 +1233,7 @@ function session(opts) {
|
|
|
982
1233
|
arm: token.arm, polls: token.polls, requestedSpki: token.requestedSpki, signer: token.signer,
|
|
983
1234
|
signerCache: token.signerCache, chain: token.chain, caPubs: token.caPubs,
|
|
984
1235
|
nextPollAt: token.nextPollAt, implicitConfirm: token.implicitConfirm,
|
|
985
|
-
protection: token.protection,
|
|
1236
|
+
protection: token.protection, centralKeyGeneration: token.centralKeyGeneration,
|
|
986
1237
|
};
|
|
987
1238
|
var txn = _hexField(raw.transactionId, "transactionId");
|
|
988
1239
|
/** @internal The width a first message opens a transaction with (RFC 9483 sec. 3.1), which is the
|
|
@@ -1029,14 +1280,23 @@ function session(opts) {
|
|
|
1029
1280
|
* field must not drop the check that field feeds, so the two that bind the transaction are required:
|
|
1030
1281
|
* the key the grant is held to, and, for a signature session, the identity every response is held
|
|
1031
1282
|
* to. A shared-secret session pins no certificate, and its secret is the binding instead. */
|
|
1032
|
-
|
|
1033
|
-
|
|
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");
|
|
1034
1289
|
}
|
|
1035
|
-
|
|
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);
|
|
1036
1294
|
/** @internal Read at the door, not when the grant arrives: stored state that is not a public key
|
|
1037
1295
|
* would otherwise advance the exchange with the authority before it was found to be unusable. */
|
|
1038
|
-
|
|
1039
|
-
|
|
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
|
+
}
|
|
1040
1300
|
var signer = null;
|
|
1041
1301
|
if (raw.signer != null) signer = _certField(raw.signer, "signer", CERT_MAX_BYTES);
|
|
1042
1302
|
else if (isSig) {
|
|
@@ -1078,7 +1338,7 @@ function session(opts) {
|
|
|
1078
1338
|
throw _err("cmp/bad-input", "the resumeToken nextPollAt must be the instant the next poll is due, in milliseconds since the epoch");
|
|
1079
1339
|
}
|
|
1080
1340
|
return { transactionID: txn, recipNonce: nonce, certReqId: certReqId, arm: arm,
|
|
1081
|
-
implicitConfirm: implicitConfirm,
|
|
1341
|
+
implicitConfirm: implicitConfirm, centralKeyGeneration: wasCentral,
|
|
1082
1342
|
polls: polls || 0, requestedSpki: spki, signer: signer, signerCache: signerCache,
|
|
1083
1343
|
chain: chain, caPubs: caPubs,
|
|
1084
1344
|
nextPollAt: nextPollAt != null ? nextPollAt : null };
|
|
@@ -1133,8 +1393,9 @@ function session(opts) {
|
|
|
1133
1393
|
* number of processes. */
|
|
1134
1394
|
async function resumePoll(token) {
|
|
1135
1395
|
_assertFresh("resumed poll");
|
|
1136
|
-
var t
|
|
1137
|
-
|
|
1396
|
+
var t;
|
|
1397
|
+
try { t = _readResumeToken(token); }
|
|
1398
|
+
catch (e) { inFlight = false; completed = started; throw e; }
|
|
1138
1399
|
try {
|
|
1139
1400
|
txnKind = "enroll";
|
|
1140
1401
|
/** @internal Installing the restored state consumes the session, whether or not a request follows:
|
|
@@ -1147,6 +1408,7 @@ function session(opts) {
|
|
|
1147
1408
|
activeCertReqId = t.certReqId;
|
|
1148
1409
|
expectedRespArm = t.arm;
|
|
1149
1410
|
requestedSpki = t.requestedSpki;
|
|
1411
|
+
centralKeyGeneration = t.centralKeyGeneration;
|
|
1150
1412
|
requestedImplicitConfirm = t.implicitConfirm;
|
|
1151
1413
|
/** @internal Restoring the pinned identity is what makes a restart no weaker than staying in one
|
|
1152
1414
|
* process: the resumed responses are held to the signer the first process authenticated, and the
|
|
@@ -1242,6 +1504,14 @@ function session(opts) {
|
|
|
1242
1504
|
|
|
1243
1505
|
async function revoke(request) {
|
|
1244
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) {
|
|
1245
1515
|
guard.identifier.assertPlainRecord(request, _err, "cmp/bad-input", "revoke(request), an object { certificate | certDetails, reason? },");
|
|
1246
1516
|
guard.identifier.assertKnownKeys(request, KNOWN_REVOKE_KEYS, _err, "cmp/bad-input", "unknown revoke request field ");
|
|
1247
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 }).");
|
|
@@ -1272,29 +1542,31 @@ function session(opts) {
|
|
|
1272
1542
|
}
|
|
1273
1543
|
var own = _assertRevokingOwnCertificate(certTemplateDer);
|
|
1274
1544
|
var body = { rr: [{ certDetails: certTemplateDer, crlEntryDetails: { reason: reasonArg == null ? undefined : reasonArg } }] };
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
crls: (t.crls || []).map(_asCrl),
|
|
1285
|
-
});
|
|
1286
|
-
}
|
|
1287
|
-
if (t.state === "rejected") return _terminal("rejected", { status: t.status || null, polls: out.polls });
|
|
1288
|
-
throw _err("cmp/unexpected-arm", t.reason || "the revocation transaction reached an unexpected state");
|
|
1289
|
-
} finally {
|
|
1290
|
-
inFlight = false;
|
|
1291
|
-
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
|
+
});
|
|
1292
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");
|
|
1293
1557
|
}
|
|
1294
1558
|
|
|
1295
1559
|
|
|
1296
1560
|
async function info(request) {
|
|
1297
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) {
|
|
1298
1570
|
guard.identifier.assertPlainRecord(request, _err, "cmp/bad-input", "info(request), an object naming one support operation (caCerts / rootCaCert / certReqTemplate / crlUpdate),");
|
|
1299
1571
|
guard.identifier.assertKnownKeys(request, INFO_OPS, _err, "cmp/bad-input", "unknown info request field ");
|
|
1300
1572
|
var keys = Object.keys(request), names = [], values = [];
|
|
@@ -1321,31 +1593,25 @@ function session(opts) {
|
|
|
1321
1593
|
crlQuery = { issuerName: built.issuerName, thisUpdate: built.thisUpdate, dpn: built.dpn };
|
|
1322
1594
|
}
|
|
1323
1595
|
expectedInfoOp = op;
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
var
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
if (e && e.isCmpError) throw e;
|
|
1337
|
-
throw _err("cmp/bad-info-value", "the " + op.name + " response value is malformed -- " + ((e && e.message) || e), e);
|
|
1338
|
-
}
|
|
1339
|
-
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);
|
|
1340
1608
|
}
|
|
1341
|
-
|
|
1609
|
+
value = await _checkInfoValue(op, value, itav.infoValue);
|
|
1342
1610
|
}
|
|
1343
|
-
|
|
1344
|
-
throw _err("cmp/unexpected-arm", t.reason || "the support-message transaction reached an unexpected state");
|
|
1345
|
-
} finally {
|
|
1346
|
-
inFlight = false;
|
|
1347
|
-
completed = started;
|
|
1611
|
+
return _terminal("answered", { operation: op.name, present: raw != null, value: value, status: null, polls: out.polls });
|
|
1348
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");
|
|
1349
1615
|
}
|
|
1350
1616
|
|
|
1351
1617
|
function _checkInfoValue(op, value, requestValue) {
|
|
@@ -1568,6 +1834,7 @@ module.exports = {
|
|
|
1568
1834
|
transfer: cmp.transfer,
|
|
1569
1835
|
wellKnownUrl: cmp.wellKnownUrl,
|
|
1570
1836
|
verify: cmp.verify,
|
|
1837
|
+
openKeyPackage: cmp.openKeyPackage,
|
|
1571
1838
|
session: session,
|
|
1572
1839
|
setEngine: setEngine, // @internal
|
|
1573
1840
|
};
|