@blamejs/pki 0.5.7 → 0.5.8
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 +405 -386
- package/MIGRATING.md +43 -0
- package/README.md +12 -12
- package/lib/acme.js +31 -31
- package/lib/asn1-der.js +10 -10
- package/lib/attrcert-sign.js +19 -19
- package/lib/byte-reader.js +6 -6
- package/lib/byte-writer.js +5 -5
- package/lib/cbor-det.js +27 -24
- package/lib/cmc-build.js +97 -92
- package/lib/cmc-verify.js +106 -30
- package/lib/cmp-build.js +25 -25
- package/lib/cmp-session.js +70 -70
- package/lib/cmp-verify.js +71 -57
- package/lib/cms-compress.js +7 -7
- package/lib/cms-decrypt.js +90 -72
- package/lib/cms-encrypt.js +32 -32
- package/lib/cms-sign.js +74 -55
- package/lib/cms-verify.js +97 -75
- package/lib/composite-sig.js +13 -13
- package/lib/constants.js +4 -4
- package/lib/crl-sign.js +22 -22
- package/lib/crl-verify.js +7 -6
- package/lib/crmf-sign.js +14 -14
- package/lib/csr-sign.js +8 -8
- package/lib/ct.js +37 -37
- package/lib/edwards-point.js +7 -7
- package/lib/est.js +98 -55
- package/lib/framework-error.js +5 -5
- package/lib/guard-all.js +3 -3
- package/lib/guard-async.js +4 -4
- package/lib/guard-bytes.js +79 -79
- package/lib/guard-compress.js +17 -17
- package/lib/guard-crypto.js +1 -1
- package/lib/guard-encoding.js +15 -15
- package/lib/guard-header.js +3 -3
- package/lib/guard-identifier.js +16 -16
- package/lib/guard-json.js +15 -15
- package/lib/guard-limits.js +7 -7
- package/lib/guard-name.js +81 -16
- package/lib/guard-parsed.js +80 -80
- package/lib/guard-range.js +19 -19
- package/lib/guard-secret.js +11 -10
- package/lib/guard-text.js +6 -6
- package/lib/guard-time.js +10 -10
- package/lib/hpke.js +18 -17
- package/lib/http-digest.js +35 -35
- package/lib/http-retry-after.js +13 -13
- package/lib/http-transport.js +20 -19
- package/lib/inspect.js +53 -53
- package/lib/ip-utils.js +2 -2
- package/lib/jose.js +13 -13
- package/lib/key.js +16 -16
- package/lib/lint.js +51 -51
- package/lib/merkle.js +51 -36
- package/lib/mime.js +18 -18
- package/lib/ocsp-verify.js +10 -10
- package/lib/ocsp.js +13 -13
- package/lib/oid.js +29 -29
- package/lib/path-validate.js +114 -113
- package/lib/pbes2.js +16 -16
- package/lib/pkcs12-build.js +53 -53
- package/lib/pki-build.js +21 -19
- package/lib/rc2.js +1 -1
- package/lib/rfc3339.js +5 -5
- package/lib/schema-all.js +31 -31
- package/lib/schema-attrcert.js +12 -12
- package/lib/schema-c509.js +144 -142
- package/lib/schema-cmc.js +58 -58
- package/lib/schema-cmp.js +43 -43
- package/lib/schema-cms.js +45 -45
- package/lib/schema-crl.js +7 -7
- package/lib/schema-crmf.js +28 -28
- package/lib/schema-csr.js +12 -12
- package/lib/schema-csrattrs.js +16 -16
- package/lib/schema-engine.js +18 -18
- package/lib/schema-ocsp.js +15 -15
- package/lib/schema-pkcs12.js +20 -20
- package/lib/schema-pkcs8.js +2 -2
- package/lib/schema-pkix.js +131 -126
- package/lib/schema-smime.js +19 -19
- package/lib/schema-tsp.js +12 -12
- package/lib/schema-x509.js +3 -3
- package/lib/shbs.js +18 -18
- package/lib/sign-scheme.js +13 -13
- package/lib/sigstore.js +10 -11
- package/lib/sleep.js +1 -1
- package/lib/smime.js +308 -96
- package/lib/tls-cert-compress.js +18 -18
- package/lib/trust.js +27 -27
- package/lib/tsp-sign.js +17 -17
- package/lib/validator-all.js +1 -1
- package/lib/validator-attcert.js +1 -1
- package/lib/validator-cose.js +43 -44
- package/lib/validator-keydesc.js +3 -3
- package/lib/validator-sig.js +13 -13
- package/lib/validator-tls.js +11 -11
- package/lib/validator-tpm.js +20 -19
- package/lib/webauthn-mds.js +66 -66
- package/lib/webauthn.js +33 -33
- package/lib/webcrypto.js +15 -15
- package/lib/x509-sign.js +13 -13
- package/package.json +3 -2
- package/sbom.cdx.json +6 -6
package/lib/est.js
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* The network verbs -- `cacerts`, `simpleenroll`, `simplereenroll` -- run the thin
|
|
14
14
|
* RFC 7030 client: they compose the codecs below over the shared `pki.transport`
|
|
15
15
|
* (a caller MAY inject `opts.transport`; the default is a fail-closed
|
|
16
|
-
* `pki.transport.https`). This module opens no socket itself
|
|
17
|
-
* choke point is `pki.transport
|
|
16
|
+
* `pki.transport.https`). This module opens no socket itself: the sole socket
|
|
17
|
+
* choke point is `pki.transport`, so the verbs stay a thin, fail-closed shell:
|
|
18
18
|
* https-only (`est/insecure-url`), an explicit trust anchor required
|
|
19
19
|
* (`est/no-trust-anchors`), same-origin redirects followed but a downgrade / loop
|
|
20
20
|
* refused, a 202 Retry-After SURFACED and never slept, HTTP Basic answered only
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
* by public-key match. Under them sit the transport-agnostic codecs, validators, and
|
|
23
23
|
* request builders over the shipped CMS / CSR / PKCS#8 / X.509 parsers:
|
|
24
24
|
* `transferDecode` / `transferEncode` are the RFC 8951
|
|
25
|
-
* sec. 3 base64 transfer codec (RFC 4648, and
|
|
26
|
-
* Content-Transfer-Encoding header
|
|
25
|
+
* sec. 3 base64 transfer codec (RFC 4648, and deliberately blind to any
|
|
26
|
+
* Content-Transfer-Encoding header, per errata 5904/5107); `splitMultipartMixed`
|
|
27
27
|
* is the /serverkeygen `multipart/mixed` splitter; `parseCertsOnly` validates a
|
|
28
|
-
* certs-only Simple PKI Response (RFC 5272 sec. 4.1)
|
|
28
|
+
* certs-only Simple PKI Response (RFC 5272 sec. 4.1) over `cms.parse` output;
|
|
29
29
|
* `parseServerKeygenResponse` dispatches the two-part key + certificate
|
|
30
30
|
* response with recipient-arm coherence; `classifyResponse` is the HTTP status
|
|
31
31
|
* / content-type / Retry-After state machine (202 accepted-not-ready surfaces
|
|
@@ -35,20 +35,20 @@
|
|
|
35
35
|
* out-of-band-key identifiers, SMIMECapabilities, and the RFC 9908
|
|
36
36
|
* template-priority enroll plan.
|
|
37
37
|
*
|
|
38
|
-
* Altitude
|
|
39
|
-
* Certificates come back
|
|
40
|
-
* certificates are in any order", RFC 5272 sec. 4.1)
|
|
38
|
+
* Altitude matches the toolkit: structural validation, no crypto verdicts.
|
|
39
|
+
* Certificates come back raw and unordered ("Clients MUST NOT assume the
|
|
40
|
+
* certificates are in any order", RFC 5272 sec. 4.1), so `findIssuedCert` picks
|
|
41
41
|
* the issued certificate by a public-key match, never a positional guess. The
|
|
42
42
|
* serverkeygen encrypted-key part's EnvelopedData is surfaced structurally
|
|
43
|
-
* (ciphertext raw, decryption external). A /fullcmc response is
|
|
43
|
+
* (ciphertext raw, decryption external). A /fullcmc response is classified:
|
|
44
44
|
* a 200 may carry either arm RFC 7030 sec. 4.3.2 permits (`certs-only` or
|
|
45
45
|
* `CMC-response`), and a 404 or 501 is the distinct `not-implemented`
|
|
46
|
-
* verdict
|
|
46
|
+
* verdict, meaning this service is absent, not that the transport faulted. Reading the
|
|
47
47
|
* CMC message itself is the CMC module's job. DER-only where DER,
|
|
48
48
|
* fail-closed everywhere.
|
|
49
49
|
*
|
|
50
50
|
* @card
|
|
51
|
-
* EST (RFC 7030 / 8951 / 9908) client
|
|
51
|
+
* EST (RFC 7030 / 8951 / 9908) client: the cacerts / simpleenroll / simplereenroll
|
|
52
52
|
* verbs over the shared pki.transport, plus the codecs they compose: base64 transfer,
|
|
53
53
|
* multipart splitter, certs-only + serverkeygen validators over CMS, the
|
|
54
54
|
* enroll-attribute builders, and the HTTP response classifier. Fail-closed.
|
|
@@ -88,7 +88,7 @@ var OPERATIONS = ["cacerts", "simpleenroll", "simplereenroll", "fullcmc", "serve
|
|
|
88
88
|
|
|
89
89
|
// ---- the option surface each verb accepts -----------------------------------
|
|
90
90
|
//
|
|
91
|
-
// A misspelled option reads as an omission
|
|
91
|
+
// A misspelled option reads as an omission, never as a value: nothing is out of range and
|
|
92
92
|
// nothing fails to parse, so the caller who asked for something stricter gets the looser default
|
|
93
93
|
// and is told nothing. That is worst here, where the options carry the security posture of a
|
|
94
94
|
// network exchange -- a misspelled `tls` leaves the anchors unset (the no-anchors refusal names
|
|
@@ -97,7 +97,7 @@ var OPERATIONS = ["cacerts", "simpleenroll", "simplereenroll", "fullcmc", "serve
|
|
|
97
97
|
// server-generated private key, and a misspelled `oldCert` fails the re-enrollment outright.
|
|
98
98
|
//
|
|
99
99
|
// Every network verb shares the client surface, because every one of them goes through _client and
|
|
100
|
-
// the redirect / authentication plumbing it drives. The per-verb tables extend it
|
|
100
|
+
// the redirect / authentication plumbing it drives. The per-verb tables extend it instead of
|
|
101
101
|
// restating it, so a key added to the client reaches every verb at once and none of them drifts.
|
|
102
102
|
var CLIENT_OPTS = {
|
|
103
103
|
transport: 1, tls: 1, label: 1, timeout: 1, maxResponseBytes: 1, maxRedirects: 1, now: 1,
|
|
@@ -114,7 +114,7 @@ function _withClient(extra) {
|
|
|
114
114
|
var CACERTS_OPTS = _withClient(null);
|
|
115
115
|
var SIMPLEENROLL_OPTS = _withClient({ strict: 1 });
|
|
116
116
|
var SIMPLEREENROLL_OPTS = _withClient({ strict: 1, oldCert: 1 });
|
|
117
|
-
// expectedRecipientKind is
|
|
117
|
+
// expectedRecipientKind is deliberately absent: it is derived from the CSR's own advertised attribute, never
|
|
118
118
|
// taken from the caller, so listing it would offer a pin that nothing reads.
|
|
119
119
|
var SERVERKEYGEN_OPTS = _withClient({
|
|
120
120
|
requestedEncryption: 1, expectedRecipientKeyId: 1, expectedRecipientIssuerSerial: 1,
|
|
@@ -122,7 +122,7 @@ var SERVERKEYGEN_OPTS = _withClient({
|
|
|
122
122
|
var CSRATTRS_OPTS = _withClient(null);
|
|
123
123
|
var FULLCMC_OPTS = _withClient({
|
|
124
124
|
transactionId: 1, senderNonce: 1, dataReturn: 1,
|
|
125
|
-
responderCerts: 1, responseRecipient: 1, allowUnverifiedResponse: 1,
|
|
125
|
+
responderCerts: 1, responseRecipient: 1, allowUnverifiedResponse: 1, allowUnboundResponse: 1,
|
|
126
126
|
});
|
|
127
127
|
// The two verbs that take options without going near the network.
|
|
128
128
|
var CLASSIFY_OPTS = { op: 1, now: 1 };
|
|
@@ -151,8 +151,8 @@ function _knownOpts(opts, known, verb) {
|
|
|
151
151
|
*
|
|
152
152
|
* Decode an EST payload body (a base64 string or Buffer) to DER. CR/LF/space/tab
|
|
153
153
|
* are stripped anywhere (RFC 8951 sec. 3.1); any other non-alphabet byte fails
|
|
154
|
-
* closed with `est/bad-base64`. A Content-Transfer-Encoding header is
|
|
155
|
-
* (errata 5904/5107). Bounded twice
|
|
154
|
+
* closed with `est/bad-base64`. A Content-Transfer-Encoding header is never read
|
|
155
|
+
* (errata 5904/5107). Bounded twice: the raw length before decode and the
|
|
156
156
|
* decoded DER against `DER_MAX_BYTES` (`est/too-large`).
|
|
157
157
|
*
|
|
158
158
|
* @example
|
|
@@ -276,8 +276,8 @@ function splitMultipartMixed(body, contentType) {
|
|
|
276
276
|
* shipped `cms.parse` output: a SignedData with no eContent and EMPTY
|
|
277
277
|
* signerInfos, carrying at least one plain X.509 certificate (a context-tagged
|
|
278
278
|
* CertificateChoices alternative is rejected `est/bad-certificate-choice`). CRLs
|
|
279
|
-
* MAY be present. Certificates come back
|
|
280
|
-
* sorted
|
|
279
|
+
* MAY be present. Certificates come back raw and in as-received order (never
|
|
280
|
+
* sorted, per RFC 5272 sec. 4.1). A non-conformant response throws a typed
|
|
281
281
|
* `EstError` (`est/not-certs-only`, `est/no-certificates`).
|
|
282
282
|
*
|
|
283
283
|
* @example
|
|
@@ -1135,28 +1135,39 @@ function _certsResult(op, res, opts, csrSpki) {
|
|
|
1135
1135
|
* `application/pkcs7-mime; smime-type=CMC-request`, base64 per RFC 8951, over the shared
|
|
1136
1136
|
* `pki.transport`.
|
|
1137
1137
|
*
|
|
1138
|
-
* A 200 answers with
|
|
1139
|
-
* `smime-type=CMC-response` (a Full PKI Response)
|
|
1138
|
+
* A 200 answers with either `smime-type=certs-only` (a Simple PKI Response) or
|
|
1139
|
+
* `smime-type=CMC-response` (a Full PKI Response). RFC 7030 sec. 4.3.2 names both, and the label
|
|
1140
1140
|
* must agree with the bytes. Either way the result is the `pki.cmc.verify` verdict shape, so a
|
|
1141
1141
|
* caller reads one `outcome` (`issued` / `pending` / `confirm-required` / `pop-required` /
|
|
1142
|
-
* `rejected`) regardless of which arm the server chose.
|
|
1143
|
-
* `dataReturn` to have the exchange bound to the request that was sent.
|
|
1142
|
+
* `rejected`) regardless of which arm the server chose.
|
|
1144
1143
|
*
|
|
1145
|
-
*
|
|
1146
|
-
*
|
|
1144
|
+
* The exchange binding is read out of the request itself, never taken on the caller's word: whatever
|
|
1145
|
+
* Transaction Identifier, Sender Nonce or Data Return the submitted bytes carry is what the response
|
|
1146
|
+
* must echo, and `transactionId` / `senderNonce` / `dataReturn` are a cross-check that is refused if
|
|
1147
|
+
* it disagrees. A request that carries none of the three leaves nothing for the response to echo, so
|
|
1148
|
+
* the answer is refused instead of being accepted as an enrollment result that could be a replay of
|
|
1149
|
+
* any earlier exchange. The code is `cmc/unbound-response` on the `CMC-response` arm, which the CMC
|
|
1150
|
+
* layer interprets, and `est/unbound-response` on the `certs-only` arm, which this verb owns. Build
|
|
1151
|
+
* the request with a `senderNonce` (`pki.cmc.build`) to bind it, or pass `allowUnboundResponse: true` to
|
|
1152
|
+
* accept that it is unbound. The verdict reports which halves ran as `bound` and `boundToRequest`.
|
|
1153
|
+
*
|
|
1154
|
+
* A 404 **or** a 501 is the distinct `est/not-implemented` verdict, since support for this verb is
|
|
1155
|
+
* optional on both sides (sec. 4.3). A 202 surfaces its Retry-After and does not sleep. A
|
|
1147
1156
|
* rejection carries a CMC response (sec. 4.3.2 makes it a MUST), which is decoded and attached to
|
|
1148
|
-
* a typed `est/cmc-failed` as `err.cmc` and `err.httpStatus
|
|
1157
|
+
* a typed `est/cmc-failed` as `err.cmc` and `err.httpStatus`, while a body that cannot be read
|
|
1149
1158
|
* never masks the HTTP fault it arrived with.
|
|
1150
1159
|
*
|
|
1151
|
-
* On the `certs-only` arm the issued certificates are identified by
|
|
1152
|
-
* requests that were submitted
|
|
1153
|
-
* certificates are in any order"
|
|
1154
|
-
* before the exchange reads as `issued
|
|
1155
|
-
* bag that answers only some of them, or none, is a refusal
|
|
1160
|
+
* On the `certs-only` arm the issued certificates are identified by public-key match against the
|
|
1161
|
+
* requests that were submitted, the only identification RFC 5272 sec. 4.1 sanctions, since "the
|
|
1162
|
+
* certificates are in any order". Every certification request in the message must be answered
|
|
1163
|
+
* before the exchange reads as `issued`: a key wanted by N requests needs N certificates, so a
|
|
1164
|
+
* bag that answers only some of them, or none, is a refusal and not a partial success. That arm
|
|
1156
1165
|
* carries no controls, so it cannot echo a Transaction Identifier, Sender Nonce or Data Return: a
|
|
1157
|
-
* request that sent those asked for replay binding it cannot provide
|
|
1158
|
-
* since an old response for the same key still matches
|
|
1159
|
-
* `est/unbound-response
|
|
1166
|
+
* request that sent those asked for replay binding it cannot provide (the key match is not one,
|
|
1167
|
+
* since an old response for the same key still matches), and it is refused as
|
|
1168
|
+
* `est/unbound-response`, never accepted with silently none of what was asked for. A request
|
|
1169
|
+
* that asked for no binding reaches the same refusal on this arm, for the same reason: nothing here
|
|
1170
|
+
* can tie the bag to the exchange, so `allowUnboundResponse: true` is what accepts it. They are
|
|
1160
1171
|
* surfaced as `issuedCertificates` (with `certificate` the first), distinct from `certificates`,
|
|
1161
1172
|
* which is the whole returned bag including any chain. Where the requested keys are distinct that
|
|
1162
1173
|
* list is in request order; where several requests deliberately SHARE one key it is not, and does
|
|
@@ -1170,18 +1181,23 @@ function _certsResult(op, res, opts, csrSpki) {
|
|
|
1170
1181
|
* response is SURFACED, never acted on (RFC 5272 sec. 6.15 makes accepting one a manual decision).
|
|
1171
1182
|
*
|
|
1172
1183
|
* @opts
|
|
1173
|
-
* - `transport` / `tls` / `label` / `timeout` / `maxResponseBytes` / `maxRedirects` / `now
|
|
1174
|
-
* - `transactionId` / `senderNonce` / `dataReturn
|
|
1175
|
-
*
|
|
1184
|
+
* - `transport` / `tls` / `label` / `timeout` / `maxResponseBytes` / `maxRedirects` / `now`, as in pki.est.cacerts.
|
|
1185
|
+
* - `transactionId` / `senderNonce` / `dataReturn`: what the request sent. The values are read out
|
|
1186
|
+
* of the request itself; supplying them here cross-checks that, and a disagreement is refused.
|
|
1187
|
+
* - `responderCerts`: extra certificates for CMC signer lookup, for a response that does not carry
|
|
1176
1188
|
* its own signer; the certificates the response carries are searched either way. The carrier's
|
|
1177
1189
|
* signature MUST be verified (RFC 5272 sec. 3.2.1.3.4), so a `CMC-response` whose signer is found
|
|
1178
1190
|
* nowhere and which does not name the opt-out below is refused.
|
|
1179
1191
|
* - `responseRecipient` -- key material for a response carried in AuthenticatedData, in the shape
|
|
1180
1192
|
* `pki.cms.decrypt` takes. Its MAC is then checked and the verdict reports
|
|
1181
|
-
* `signatureVerified: true`,
|
|
1193
|
+
* `signatureVerified: true`, so the carrier is not reachable only unauthenticated.
|
|
1182
1194
|
* - `allowUnverifiedResponse` -- accept a `CMC-response` whose signer certificate cannot be found,
|
|
1183
1195
|
* without checking its signature; the verdict then reports `signatureVerified: false`. For an
|
|
1184
1196
|
* unauthenticated bootstrap only, and it never excuses a signature that is present and wrong.
|
|
1197
|
+
* - `allowUnboundResponse` -- accept an answer to a request that carried no Transaction Identifier,
|
|
1198
|
+
* Sender Nonce or Data Return, so nothing ties it to this exchange; the verdict then reports
|
|
1199
|
+
* `boundToRequest: false`. A separate question from the one above, because a replayed response
|
|
1200
|
+
* is authentic, so naming one does not name the other.
|
|
1185
1201
|
* - `username` / `password` / `allowCrossOriginRedirect` -- as pki.est.simpleenroll.
|
|
1186
1202
|
* @example
|
|
1187
1203
|
* var pair = await pki.key.generate("Ed25519");
|
|
@@ -1417,6 +1433,12 @@ function _cmcSent(opts, der) {
|
|
|
1417
1433
|
// would exist one layer down and be unreachable from the one operators call.
|
|
1418
1434
|
recipient: _copyRecipient(opts.responseRecipient),
|
|
1419
1435
|
allowUnverified: opts.allowUnverifiedResponse === true,
|
|
1436
|
+
// The binding opt-out travels the same way. Without it the capability would
|
|
1437
|
+
// exist one layer down and be unreachable from the verb operators call: a
|
|
1438
|
+
// request built with no Transaction Identifier, Sender Nonce or Data Return
|
|
1439
|
+
// carries nothing for the response to echo, so pki.cmc.verify would refuse
|
|
1440
|
+
// every answer to it and no option here could say that was intended.
|
|
1441
|
+
allowUnbound: opts.allowUnboundResponse === true,
|
|
1420
1442
|
};
|
|
1421
1443
|
}
|
|
1422
1444
|
|
|
@@ -1681,6 +1703,19 @@ function _fullcmcResult(res, opts, wanted, sent) {
|
|
|
1681
1703
|
"the request carried " + unecho.join(" / ") + ", which a certs-only response has no controls to " +
|
|
1682
1704
|
"echo, so the replay binding it asked for cannot be checked (RFC 5272 sec. 6.6 / 6.4)");
|
|
1683
1705
|
}
|
|
1706
|
+
// Reaching here means the request asked for no binding, so nothing whatever
|
|
1707
|
+
// ties this bag of certificates to this exchange. The public-key correlation
|
|
1708
|
+
// below is explicitly not a binding, since an old response for the same key
|
|
1709
|
+
// matches just as well. pki.cmc.verify takes this posture on the other arm,
|
|
1710
|
+
// and it is taken here too: one arm quietly accepting what the other refuses
|
|
1711
|
+
// would make which rule applied depend on the smime-type the server chose.
|
|
1712
|
+
if (sent.allowUnbound !== true) {
|
|
1713
|
+
throw E("est/unbound-response",
|
|
1714
|
+
"nothing ties this certs-only response to the request just sent: a certs-only body carries no " +
|
|
1715
|
+
"controls at all, so build the request with a `senderNonce` (pki.cmc.build) and use a server that " +
|
|
1716
|
+
"answers with a CMC-response, or pass `allowUnboundResponse: true` to accept a bag of certificates " +
|
|
1717
|
+
"that could answer any earlier request for the same key (RFC 5272 sec. 6.6)");
|
|
1718
|
+
}
|
|
1684
1719
|
var certs = parseCertsOnly(der); // throws est/not-certs-only on a Full PKI Response
|
|
1685
1720
|
// A certs-only body says nothing about WHICH request it answers -- no status,
|
|
1686
1721
|
// no body-part reference. So the issued certificate is identified the one way
|
|
@@ -1699,10 +1734,18 @@ function _fullcmcResult(res, opts, wanted, sent) {
|
|
|
1699
1734
|
// depending on which arm the server happened to choose. RFC 7030 secures this
|
|
1700
1735
|
// arm through the authenticated TLS channel instead, and the certificates stay
|
|
1701
1736
|
// `trusted: false` for the caller to run through pki.path.validate.
|
|
1737
|
+
// `bound` and `boundToRequest` are stated here for the same reason
|
|
1738
|
+
// `signatureVerified` is: leaving them undefined while the CMC-response arm
|
|
1739
|
+
// sets them would give a caller a different kind of answer depending on which
|
|
1740
|
+
// arm the server chose. Every one is false and cannot be otherwise, because a
|
|
1741
|
+
// certs-only body has no controls to echo. That is what the refusal above
|
|
1742
|
+
// makes the caller acknowledge before reading this verdict at all.
|
|
1702
1743
|
return { outcome: "issued", certificate: issued, issuedCertificates: issuedCerts,
|
|
1703
1744
|
certificates: certs.certificates, crls: certs.crls,
|
|
1704
1745
|
controls: [], statuses: [], publishTrustAnchors: null, trusted: false,
|
|
1705
|
-
signatureVerified: false
|
|
1746
|
+
signatureVerified: false,
|
|
1747
|
+
bound: { transactionId: false, senderNonce: false, dataReturn: false, bodyPartIDs: false },
|
|
1748
|
+
boundToRequest: false };
|
|
1706
1749
|
}
|
|
1707
1750
|
// smime-type=cmc-response: the Full PKI Response, interpreted into one verdict.
|
|
1708
1751
|
// pki.cmc.verify owns the transaction binding and the status reduction; this
|
|
@@ -1804,11 +1847,11 @@ function _enroll(op, baseUrl, csrInput, opts) {
|
|
|
1804
1847
|
* (`{ certificates, crls }`), or `{ retry: true, retryAfterSeconds }` on a 202 (surfaced,
|
|
1805
1848
|
* never slept). https-only (`est/insecure-url`); an explicit `opts.tls.anchors` (or an
|
|
1806
1849
|
* `opts.tls.useSystemStore` opt-in) is required (`est/no-trust-anchors`); the returned CA
|
|
1807
|
-
* certificate is
|
|
1850
|
+
* certificate is not auto-trusted: the caller path-validates it and supplies the accepted
|
|
1808
1851
|
* anchor on the next call.
|
|
1809
1852
|
*
|
|
1810
1853
|
* @opts
|
|
1811
|
-
* - `transport
|
|
1854
|
+
* - `transport`: an injected transport(request) -> {status, headers, body, tls}; default pki.transport.https.
|
|
1812
1855
|
* - `tls` -- { anchors, useSystemStore, cert, key, minVersion, servername, checkServerIdentity }.
|
|
1813
1856
|
* - `label` -- an OPTIONAL CA label path segment; `timeout` / `maxResponseBytes` / `maxRedirects` -- budgets.
|
|
1814
1857
|
* - `now` -- receipt time (epoch ms) to render a 202 Retry-After HTTP-date as seconds.
|
|
@@ -1849,12 +1892,12 @@ function cacerts(baseUrl, opts) {
|
|
|
1849
1892
|
* certificates (`chain`), and the raw set (`certificates`); or `{ retry: true,
|
|
1850
1893
|
* retryAfterSeconds }` on a 202. No returned certificate matching the CSR key fails closed
|
|
1851
1894
|
* (`est/issued-cert-not-found`); `opts.strict` requires exactly the issued certificate. A
|
|
1852
|
-
* 401 is answered once with HTTP Basic
|
|
1895
|
+
* 401 is answered once with HTTP Basic only when `opts.username`/`password` are supplied and
|
|
1853
1896
|
* the transport already authenticated the server.
|
|
1854
1897
|
*
|
|
1855
1898
|
* @opts
|
|
1856
|
-
* - `transport` / `tls` / `label` / `timeout` / `maxResponseBytes` / `maxRedirects` / `now
|
|
1857
|
-
* - `strict
|
|
1899
|
+
* - `transport` / `tls` / `label` / `timeout` / `maxResponseBytes` / `maxRedirects` / `now`, as in pki.est.cacerts.
|
|
1900
|
+
* - `strict`: reject an enroll response that carries more than the single issued certificate.
|
|
1858
1901
|
* - `username` / `password` -- HTTP Basic credentials, answered only after server authorization (empty username allowed).
|
|
1859
1902
|
* - `allowCrossOriginRedirect` -- opt in to following a cross-origin redirect on this POST.
|
|
1860
1903
|
* @example
|
|
@@ -1884,7 +1927,7 @@ function simpleenroll(baseUrl, csrInput, opts) {
|
|
|
1884
1927
|
* `/.well-known/est/simplereenroll` and REQUIRES `opts.oldCert` (the certificate being
|
|
1885
1928
|
* renewed). Before anything crosses the wire, `reenrollGuard` enforces that the CSR's
|
|
1886
1929
|
* Subject and SubjectAltName (names and criticality) are byte-identical to `opts.oldCert`
|
|
1887
|
-
* (RFC 7030 sec. 4.2.2)
|
|
1930
|
+
* (RFC 7030 sec. 4.2.2). A mismatch fails closed (`est/reenroll-subject-mismatch` /
|
|
1888
1931
|
* `est/reenroll-san-mismatch`) and the transport is never called. A missing `opts.oldCert`
|
|
1889
1932
|
* is `est/bad-input`.
|
|
1890
1933
|
*
|
|
@@ -2039,14 +2082,14 @@ async function _serverkeygenResult(res, opts, derived) {
|
|
|
2039
2082
|
* application/pkcs10`, identical request encoding to `simpleenroll`) to
|
|
2040
2083
|
* `<baseUrl>/.well-known/est/serverkeygen`. The two-part `multipart/mixed` response is surfaced as
|
|
2041
2084
|
* `{ certificates, privateKey }` (a cleartext PKCS#8 `PrivateKeyInfo`) or `{ certificates,
|
|
2042
|
-
* encryptedKey }` (the CMS `EnvelopedData` the caller decrypts out-of-band with its key-encryption key
|
|
2043
|
-
* the verb
|
|
2044
|
-
* The certificates are
|
|
2045
|
-
* the key so the issued certificate's public key is the generated one, not the throwaway CSR key. A
|
|
2085
|
+
* encryptedKey }` (the CMS `EnvelopedData` the caller decrypts out-of-band with its key-encryption key;
|
|
2086
|
+
* the verb never decrypts, so it is not a decryption oracle), or `{ retry, retryAfterSeconds }` on a 202.
|
|
2087
|
+
* The certificates are raw and unordered: unlike `simpleenroll` no leaf is picked, because the CA generated
|
|
2088
|
+
* the key so the issued certificate's public key is the generated one, not the throwaway CSR key. A cleartext
|
|
2046
2089
|
* key is bound to its certificate before it resolves: the delivered private key's public half MUST match
|
|
2047
|
-
*
|
|
2048
|
-
* one), so a mis-associated key is refused
|
|
2049
|
-
* The encryption requirement + expected recipient are
|
|
2090
|
+
* exactly one returned certificate (`est/key-cert-mismatch` on none, `est/ambiguous-issued-cert` on more than
|
|
2091
|
+
* one), so a mis-associated key is refused, never handed back unusable.
|
|
2092
|
+
* The encryption requirement + expected recipient are derived from the CSR's own DecryptKeyIdentifier /
|
|
2050
2093
|
* AsymmetricDecryptKeyIdentifier attribute; an `opts` value that contradicts the CSR is `est/bad-input`
|
|
2051
2094
|
* (a cleartext-key downgrade cannot slip past). The delivered key's channel is asserted confidentiality-
|
|
2052
2095
|
* bearing (a NULL / anonymous / EXPORT cipher is `est/weak-cipher`). https-only, explicit-anchor, and the
|
|
@@ -2116,10 +2159,10 @@ function _csrattrsResult(res, opts) {
|
|
|
2116
2159
|
*
|
|
2117
2160
|
* Fetch the CA's CSR-attributes policy: GET `<baseUrl>/.well-known/est/csrattrs` (`Accept:
|
|
2118
2161
|
* application/csrattrs`). A 200 body is base64-decoded, parsed as an RFC 9908 `CsrAttrs`, and returned
|
|
2119
|
-
* with a `plan` (`buildEnrollAttributes`) the caller applies to its
|
|
2162
|
+
* with a `plan` (`buildEnrollAttributes`) the caller applies to its next CSR; the verb never auto-applies
|
|
2120
2163
|
* attributes to a CSR (single responsibility). A 204 or 404 is `{ available: false }` (a valid "no specific
|
|
2121
|
-
* attributes")
|
|
2122
|
-
* distinct from an empty HTTP body (`est/empty-body`). Server auth is
|
|
2164
|
+
* attributes") and not an error; an empty SEQUENCE (`30 00`) is a complete empty policy (`attrs.items` empty),
|
|
2165
|
+
* distinct from an empty HTTP body (`est/empty-body`). Server auth is not required for this policy GET but a
|
|
2123
2166
|
* 401 is tolerated (the shared auth path stays live). https-only + explicit-anchor as elsewhere.
|
|
2124
2167
|
*
|
|
2125
2168
|
* @opts
|
package/lib/framework-error.js
CHANGED
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
* `{ name, code, message, permanent, isPkiError: true }`.
|
|
17
17
|
*
|
|
18
18
|
* `code` is a stable, greppable `domain/reason` string
|
|
19
|
-
* (`asn1/indefinite-length`, `x509/not-a-certificate`)
|
|
19
|
+
* (`asn1/indefinite-length`, `x509/not-a-certificate`): safe to switch
|
|
20
20
|
* on and safe to log. Because every failure here is a deterministic
|
|
21
21
|
* verdict on the bytes in hand (a malformed length, an unknown OID
|
|
22
|
-
* shape, a truncated certificate), errors are `permanent: true
|
|
22
|
+
* shape, a truncated certificate), errors are `permanent: true`. The
|
|
23
23
|
* same input will never parse on retry.
|
|
24
24
|
*
|
|
25
25
|
* @card
|
|
@@ -41,7 +41,7 @@ var CODE_SHAPE = /^[a-z0-9-]+\/[a-z0-9-]+$/;
|
|
|
41
41
|
* Base class every toolkit error extends. Provides the unified
|
|
42
42
|
* `instanceof` check plus the `{ name, code, isPkiError }` shape.
|
|
43
43
|
* A supplied `code` must be a `domain/reason` string (lowercase
|
|
44
|
-
* alphanumerics and dashes)
|
|
44
|
+
* alphanumerics and dashes); the construction throws a `TypeError`
|
|
45
45
|
* otherwise, which catches an argument-order swap with the
|
|
46
46
|
* `defineClass` subclasses' `(code, message)` convention at the call
|
|
47
47
|
* site instead of shipping prose into a code-switching consumer.
|
|
@@ -83,10 +83,10 @@ class PkiError extends Error {
|
|
|
83
83
|
* a `.factory` static for the common `var _err = XxxError.factory` shape.
|
|
84
84
|
* The `code` must be a `domain/reason` string (the base-class contract);
|
|
85
85
|
* without `withCause`, a third constructor argument throws a `TypeError`
|
|
86
|
-
*
|
|
86
|
+
* instead of silently discarding a cause the caller meant to thread.
|
|
87
87
|
*
|
|
88
88
|
* @opts
|
|
89
|
-
* withCause: boolean, // default
|
|
89
|
+
* withCause: boolean, // default false; constructor becomes (code, message, cause)
|
|
90
90
|
*
|
|
91
91
|
* @example
|
|
92
92
|
* // throws: my/bad-input -- raising the new error type IS what this shows
|
package/lib/guard-all.js
CHANGED
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
// guard-all -- the guard-family orchestrator (schema-all's role for the guard
|
|
9
9
|
// family). It assembles the per-shape fail-closed guards into one namespaced
|
|
10
10
|
// surface every input boundary composes, so a boundary depends on the family
|
|
11
|
-
//
|
|
11
|
+
// itself, with no guard re-implemented inline:
|
|
12
12
|
//
|
|
13
13
|
// guard.bytes.view / .source -- untrusted byte-source -> Buffer re-view
|
|
14
14
|
// (detached-buffer fail-open defence)
|
|
15
|
-
// guard.text.decode -- byte-source -> string, cap
|
|
15
|
+
// guard.text.decode -- byte-source -> string, cap before copy
|
|
16
16
|
// (parser-DoS string-allocation defence)
|
|
17
17
|
// guard.limits.cap / .depthCap -- config-time resource-cap validation
|
|
18
18
|
// (recursion / allocation DoS defence)
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
//
|
|
48
48
|
// Each shape is enforced by a codebase-patterns detector: the characteristic
|
|
49
49
|
// token of a guard (the Buffer.from(x.buffer, byteOffset) re-view, the
|
|
50
|
-
// timingSafeEqual call, the MAX_DECODE_DEPTH_CEILING check) must appear
|
|
50
|
+
// timingSafeEqual call, the MAX_DECODE_DEPTH_CEILING check) must appear only in
|
|
51
51
|
// its guard module, so a new boundary cannot re-inline the shape and forget the
|
|
52
52
|
// defence.
|
|
53
53
|
|
package/lib/guard-async.js
CHANGED
|
@@ -12,14 +12,14 @@
|
|
|
12
12
|
//
|
|
13
13
|
// pki.acme.newOrder(opts).catch(handleIt);
|
|
14
14
|
//
|
|
15
|
-
// A validation that runs
|
|
15
|
+
// A validation that runs before the promise is created throws straight past that `.catch`, so a
|
|
16
16
|
// misspelled option or a malformed input becomes an uncaught exception in code that already handles
|
|
17
17
|
// errors -- and nothing in the shape of the call tells the caller which verbs do that. Eleven verbs
|
|
18
18
|
// across five modules had it (pki.cms.verify / sign / countersign, pki.ocsp.sign, pki.tsp.sign and
|
|
19
19
|
// six pki.acme verbs), each having grown the same way: a cheap synchronous check added at the top of
|
|
20
20
|
// a function that returns a promise further down.
|
|
21
21
|
//
|
|
22
|
-
// What this does
|
|
22
|
+
// What this does not change is when the work happens. The body still runs synchronously, because
|
|
23
23
|
// several of these verbs must resolve a caller's mutable options object before any turn passes --
|
|
24
24
|
// reading a key, a nonce, or a request's bytes a turn later is a different value than the one that
|
|
25
25
|
// was checked. Only the exit changes: a fault leaves as a rejection instead of a throw.
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
// @enforced-by behavioral -- the rule has no rename-proof code shape (it is the ABSENCE of a wrapper
|
|
28
28
|
// around a synchronous prefix, which no lexical pattern can see). The guard is the derived test
|
|
29
29
|
// test/layer-0-primitives/promise-contract.test.js, which reads every `-> Promise<` @signature out
|
|
30
|
-
// of lib/ and calls each verb to prove the refusal arrives as a rejection
|
|
31
|
-
// scope the day it is documented,
|
|
30
|
+
// of lib/ and calls each verb to prove the refusal arrives as a rejection. A new verb is in
|
|
31
|
+
// scope the day it is documented, so no list here can go stale.
|
|
32
32
|
function deferred(body) {
|
|
33
33
|
try { return Promise.resolve(body()); }
|
|
34
34
|
catch (e) { return Promise.reject(e); }
|