@blamejs/pki 0.6.51 → 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 +33 -0
- package/README.md +1 -1
- package/index.js +8 -3
- package/lib/cmp-build.js +23 -2
- package/lib/cmp-session.js +345 -84
- package/lib/cmp-verify.js +314 -0
- 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);
|
|
@@ -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)
|
|
345
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
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
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
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
|
-
|
|
1039
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1045
|
-
|
|
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
|
|
1143
|
-
|
|
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
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
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
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
var
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
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
|
-
|
|
1609
|
+
value = await _checkInfoValue(op, value, itav.infoValue);
|
|
1348
1610
|
}
|
|
1349
|
-
|
|
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
|
};
|