@blamejs/pki 0.6.28 → 0.6.30
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 +25 -0
- package/README.md +1 -1
- package/lib/acme.js +10 -8
- package/lib/asn1-der.js +9 -7
- package/lib/attrcert-sign.js +8 -8
- package/lib/byte-reader.js +1 -1
- package/lib/byte-writer.js +1 -1
- package/lib/cmc-verify.js +4 -4
- package/lib/cmp-build.js +9 -5
- package/lib/cmp-session.js +9 -4
- package/lib/cms-verify.js +22 -17
- package/lib/composite-kem.js +2 -2
- package/lib/constants.js +1 -0
- package/lib/crl-sign.js +5 -5
- package/lib/crmf-sign.js +3 -3
- package/lib/csr-sign.js +2 -2
- package/lib/ct.js +3 -3
- package/lib/est.js +34 -22
- package/lib/framework-error.js +2 -2
- package/lib/guard-identifier.js +18 -1
- package/lib/hpke.js +2 -2
- package/lib/http-transport.js +82 -10
- package/lib/inspect.js +1 -1
- package/lib/jose.js +1 -1
- package/lib/key.js +7 -7
- package/lib/lint.js +1 -1
- package/lib/merkle.js +2 -6
- package/lib/mime.js +2 -2
- package/lib/ocsp.js +6 -6
- package/lib/oid.js +1 -1
- package/lib/path-validate.js +13 -13
- package/lib/pkcs12-build.js +4 -4
- package/lib/rfc3339.js +2 -2
- package/lib/scep.js +3 -15
- package/lib/schema-all.js +1 -1
- package/lib/schema-attrcert.js +1 -1
- package/lib/schema-cmc.js +2 -2
- package/lib/schema-cmp.js +8 -9
- package/lib/schema-cms.js +3 -3
- package/lib/schema-crmf.js +3 -3
- package/lib/schema-csr.js +1 -1
- package/lib/schema-csrattrs.js +1 -1
- package/lib/schema-engine.js +4 -4
- package/lib/schema-ocsp.js +1 -1
- package/lib/schema-pkcs12.js +2 -2
- package/lib/schema-pkcs8.js +2 -2
- package/lib/schema-smime.js +1 -1
- package/lib/schema-tsp.js +6 -6
- package/lib/schema-x509.js +2 -2
- package/lib/shbs.js +3 -4
- package/lib/sign-scheme.js +1 -1
- package/lib/sigstore.js +2 -2
- package/lib/smime.js +16 -16
- package/lib/tls-cert-compress.js +7 -7
- package/lib/trust.js +5 -5
- package/lib/tsp-sign.js +8 -6
- package/lib/webauthn.js +23 -19
- package/lib/webcrypto.js +3 -3
- package/lib/x509-sign.js +5 -5
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/crl-sign.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* @fullname CRLs (certificate revocation lists): read, sign and shard
|
|
10
10
|
* @intro The X.509 CRL producing side. `pki.crl.sign` builds a `TBSCertList`, signs it, and emits a
|
|
11
11
|
* `CertificateList` (RFC 5280 sec. 5) that `pki.schema.crl.parse`, `pki.path.crlChecker`, and OpenSSL
|
|
12
|
-
* all accept
|
|
12
|
+
* all accept, over any signature algorithm the toolkit registry resolves: RSA (PKCS#1 v1.5 / PSS),
|
|
13
13
|
* ECDSA, EdDSA, ML-DSA, SLH-DSA, and the composite (hybrid) arms. `pki.crl.verify` checks a CRL
|
|
14
14
|
* signature through the one path-validation signature engine, and `pki.crl.isRevoked` looks a serial up
|
|
15
15
|
* in a parsed CRL. Parsing lives at `pki.schema.crl.parse`.
|
|
@@ -413,7 +413,7 @@ function _sign(spec, issuer, opts) {
|
|
|
413
413
|
* @defends crl-forgery (CWE-347)
|
|
414
414
|
* @related pki.schema.crl.parse, pki.crl.verify, pki.path.crlChecker
|
|
415
415
|
*
|
|
416
|
-
* Build, sign, and DER-encode an X.509 certificate revocation list. `spec` describes the CRL
|
|
416
|
+
* Build, sign, and DER-encode an X.509 certificate revocation list. `spec` describes the CRL:
|
|
417
417
|
* `thisUpdate` / `nextUpdate` (`Date`s), an optional `crlNumber`, a `revoked` array (each entry a
|
|
418
418
|
* `serialNumber` + `revocationDate` with an optional `reason` or `invalidityDate`),
|
|
419
419
|
* and an optional `extensions` object (`authorityKeyIdentifier`, `issuingDistributionPoint`,
|
|
@@ -491,8 +491,8 @@ function _issuerMaySign(parsed, cert) {
|
|
|
491
491
|
* Verify a CRL's signature over its exact parsed `tbsCertList` bytes under the issuer public key. `crl`
|
|
492
492
|
* is a DER `Buffer`, a PEM string, or a parsed CRL; `issuer` is `{ cert }` (DER/PEM/parsed), `{ publicKey }`
|
|
493
493
|
* (SPKI DER), or a raw SPKI `Buffer`. Verification composes the one path-validation signature engine
|
|
494
|
-
* `pki.path.crlChecker` uses
|
|
495
|
-
* EdDSA low-order-point gates
|
|
494
|
+
* `pki.path.crlChecker` uses, the same algorithm-confusion (RFC 9814 sec. 4 key-OID == sig-OID) and
|
|
495
|
+
* EdDSA low-order-point gates, so there is no second, weaker CRL verifier. The verdict's `valid` is
|
|
496
496
|
* `false` on any verification fault, and `signatureValid` reports the signature check on its own; malformed
|
|
497
497
|
* input throws a typed `CrlError`.
|
|
498
498
|
*
|
|
@@ -571,7 +571,7 @@ function _serialHexOf(serial) {
|
|
|
571
571
|
* refused rather than answered from (`crl/not-current`): one whose `thisUpdate` is later, one whose
|
|
572
572
|
* `nextUpdate` has passed, and one carrying no `nextUpdate` at all, which states no window and so cannot
|
|
573
573
|
* be told from a replayed copy. Without `opts.time` currency goes unasked and the verb is the structural
|
|
574
|
-
* lookup it has always been
|
|
574
|
+
* lookup it has always been; `null` then means "not listed on this CRL", which is weaker than "not
|
|
575
575
|
* revoked". `pki.path.crlChecker` decides currency against the material it fetched and is the verb to
|
|
576
576
|
* reach for when the answer has to mean the stronger thing.
|
|
577
577
|
*
|
package/lib/crmf-sign.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @title Certificate request messages
|
|
9
9
|
* @fullname CRMF (Certificate Request Message Format, RFC 4211)
|
|
10
10
|
* @intro The RFC 4211 certificate-request-message producing side. `pki.crmf.build` assembles a
|
|
11
|
-
* `CertReqMessages
|
|
11
|
+
* `CertReqMessages`: one or more `CertReqMsg`, each a `CertRequest` (a `CertTemplate` of the requested
|
|
12
12
|
* certificate fields plus optional controls) paired with a proof of possession. The common proof is a
|
|
13
13
|
* `POPOSigningKey` signature over the `CertRequest`, made with the private half of the key being
|
|
14
14
|
* certified (the requester proves possession, exactly as a PKCS#10 CSR does). The message drops into a
|
|
@@ -355,7 +355,7 @@ function _buildProofOfPossession(pop, certReqDer, template, signingKey, opts) {
|
|
|
355
355
|
* subjectKeyIdentifier, or pre-encoded Extension DER), and an optional `version` (2)), optional `controls`
|
|
356
356
|
* and `regInfo` (an object of regToken / authenticator / utf8Pairs / oldCertID / protocolEncrKey, or
|
|
357
357
|
* pre-encoded AttributeTypeAndValue DER), and an optional `pop` selector. `key` (or `{ key }`) is the
|
|
358
|
-
* REQUESTER's private key
|
|
358
|
+
* REQUESTER's private key, the private half of `certTemplate.publicKey`; the message carries a
|
|
359
359
|
* `POPOSigningKey` proof of possession signed with it (verified before the message is returned), exactly
|
|
360
360
|
* as a PKCS#10 CSR proves possession. The signature algorithm is resolved from the requested public key
|
|
361
361
|
* (RSA PKCS#1 v1.5 / PSS, ECDSA, EdDSA, ML-DSA, SLH-DSA, or a composite arm). `key` is optional: omit it
|
|
@@ -373,7 +373,7 @@ function _buildProofOfPossession(pop, certReqDer, template, signingKey, opts) {
|
|
|
373
373
|
* content type is `id-ct-encKeyWithID`. RFC 9810 sec. 5.2.8.3.1 permits it only where archival is
|
|
374
374
|
* intended, so it takes `pop.archive: true`; `pop.privateKey` is the PKCS#8 DER, `pop.recipients` the CMS
|
|
375
375
|
* recipients to envelope it to, and `pop.identifier` (a string or GeneralName) is required, because
|
|
376
|
-
* sec. 4.2.1 makes it mandatory whenever the purpose is proving possession
|
|
376
|
+
* sec. 4.2.1 makes it mandatory whenever the purpose is proving possession: without it a decrypting
|
|
377
377
|
* agent cannot tell whose key it holds. `pop.contentEncryptionAlgorithm` defaults to `aes-256-cbc`; an
|
|
378
378
|
* AEAD algorithm is refused, since it would produce an `AuthEnvelopedData` this field cannot carry.
|
|
379
379
|
*
|
package/lib/csr-sign.js
CHANGED
|
@@ -179,8 +179,8 @@ function _coerceCsr(request) {
|
|
|
179
179
|
* requested extension, so none of them were altered after signing. It says nothing about who the
|
|
180
180
|
* producer is: the key is self-asserted, the name is self-asserted, and a requester free to choose
|
|
181
181
|
* both can prove possession of a key they generated a moment ago under any name they like. Binding
|
|
182
|
-
* that name to an identity is the enrollment protocol's job
|
|
183
|
-
* an out-of-band check
|
|
182
|
+
* that name to an identity is the enrollment protocol's job (`pki.est`, `pki.cmc`, `pki.cmp`, or
|
|
183
|
+
* an out-of-band check), and remains one after this returns `true`.
|
|
184
184
|
*
|
|
185
185
|
* Verification composes the one path-validation signature engine, with the same algorithm-confusion
|
|
186
186
|
* (RFC 9814 sec. 4 key-OID == sig-OID) and EdDSA low-order-point gates, rather than the self-check
|
package/lib/ct.js
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
*
|
|
35
35
|
* @card
|
|
36
36
|
* Parse RFC 6962 Certificate Transparency SCT lists from a certificate or OCSP
|
|
37
|
-
* extension
|
|
37
|
+
* extension: per-SCT logId / timestamp (BigInt) / algorithm / raw signature,
|
|
38
38
|
* the signed-preimage reconstruction surfaced for external verification,
|
|
39
39
|
* bounded TLS-struct decode, fail-closed.
|
|
40
40
|
*/
|
|
@@ -230,13 +230,13 @@ function _u24Bytes(n) {
|
|
|
230
230
|
* `entry` selects the log-entry arm:
|
|
231
231
|
* - `{ entryType: 0, leafCert: <DER Buffer> }`, an SCT delivered over TLS /
|
|
232
232
|
* OCSP, signed over `x509_entry(0)` with the leaf certificate.
|
|
233
|
-
* - `{ entryType: 1, tbsCertificate: <DER Buffer>, issuerKeyHash: <32B> }
|
|
233
|
+
* - `{ entryType: 1, tbsCertificate: <DER Buffer>, issuerKeyHash: <32B> }`,
|
|
234
234
|
* an SCT EMBEDDED in a certificate, signed over `precert_entry(1)` with the
|
|
235
235
|
* issuer key hash + the precertificate TBS (the TBS with only the SCT
|
|
236
236
|
* extension removed). `issuerKeyHash` is SHA-256 of the issuer's SPKI DER.
|
|
237
237
|
*
|
|
238
238
|
* The preimage reuses the parsed SCT's raw `extensions` byte-for-byte and
|
|
239
|
-
* re-emits the fixed-width scalars canonically. This never verifies anything
|
|
239
|
+
* re-emits the fixed-width scalars canonically. This never verifies anything;
|
|
240
240
|
* a verifier hashes the returned bytes and checks the signature with the log's
|
|
241
241
|
* public key (compose `webcrypto`). Throws `CtError` (`ct/bad-entry-type`,
|
|
242
242
|
* `ct/bad-issuer-key-hash`, `ct/bad-tbs-length`) on a malformed entry, and
|
package/lib/est.js
CHANGED
|
@@ -11,15 +11,15 @@
|
|
|
11
11
|
*
|
|
12
12
|
* @intro
|
|
13
13
|
* Enrollment over Secure Transport (RFC 7030, updated by RFC 8951 and RFC 9908).
|
|
14
|
-
* The network verbs
|
|
14
|
+
* The network verbs (`cacerts`, `simpleenroll`, `simplereenroll`) run the thin
|
|
15
15
|
* RFC 7030 client: they compose the codecs below over the shared `pki.transport`
|
|
16
16
|
* (a caller MAY inject `opts.transport`; the default is a fail-closed
|
|
17
17
|
* `pki.transport.https`). This module opens no socket itself: the sole socket
|
|
18
18
|
* choke point is `pki.transport`, so the verbs stay a thin, fail-closed shell:
|
|
19
19
|
* https-only (`est/insecure-url`), an explicit trust anchor required
|
|
20
20
|
* (`est/no-trust-anchors`), same-origin redirects followed but a downgrade / loop
|
|
21
|
-
* refused, a 202 Retry-After SURFACED and never slept, HTTP Basic answered
|
|
22
|
-
* after the transport authenticated the server, and the issued certificate chosen
|
|
21
|
+
* refused, a 202 Retry-After SURFACED and never slept, HTTP Basic or Digest answered
|
|
22
|
+
* only after the transport authenticated the server, and the issued certificate chosen
|
|
23
23
|
* by public-key match. Under them sit the transport-agnostic codecs, validators, and
|
|
24
24
|
* request builders over the shipped CMS / CSR / PKCS#8 / X.509 parsers:
|
|
25
25
|
* `transferDecode` / `transferEncode` are the RFC 8951
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
* `parseServerKeygenResponse` dispatches the two-part key + certificate
|
|
31
31
|
* response with recipient-arm coherence; `classifyResponse` is the HTTP status
|
|
32
32
|
* / content-type / Retry-After state machine (202 accepted-not-ready surfaces
|
|
33
|
-
* `retryAfterSeconds
|
|
33
|
+
* `retryAfterSeconds`, never an internal sleep; 204/404 on /csrattrs is a
|
|
34
34
|
* "none available" verdict, not an error). The builders assemble the CSR
|
|
35
|
-
* attributes EST adds
|
|
35
|
+
* attributes EST adds: a channel-binding challengePassword, the
|
|
36
36
|
* out-of-band-key identifiers, SMIMECapabilities, and the RFC 9908
|
|
37
37
|
* template-priority enroll plan.
|
|
38
38
|
*
|
|
@@ -100,7 +100,7 @@ var OPERATIONS = ["cacerts", "simpleenroll", "simplereenroll", "fullcmc", "serve
|
|
|
100
100
|
|
|
101
101
|
var CLIENT_OPTS = {
|
|
102
102
|
transport: 1, tls: 1, label: 1, timeout: 1, maxResponseBytes: 1, maxRedirects: 1, now: 1,
|
|
103
|
-
auth: 1, username: 1, password: 1, allowCrossOriginRedirect: 1,
|
|
103
|
+
auth: 1, username: 1, password: 1, allowCrossOriginRedirect: 1, proxy: 1,
|
|
104
104
|
};
|
|
105
105
|
function _withClient(extra) {
|
|
106
106
|
var out = {};
|
|
@@ -133,6 +133,9 @@ function _knownOpts(opts, known, verb) {
|
|
|
133
133
|
});
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
function _snapProxy(opts) { return httpTransport.snapshotProxy(opts.proxy); }
|
|
137
|
+
function _rej(e) { return Promise.reject(e); }
|
|
138
|
+
|
|
136
139
|
|
|
137
140
|
/**
|
|
138
141
|
* @primitive pki.est.transferDecode
|
|
@@ -533,7 +536,7 @@ var NOT_IMPLEMENTED_OPS = { fullcmc: 1 };
|
|
|
533
536
|
*
|
|
534
537
|
* Classify an EST HTTP response into a verdict or a typed fault. A 200 requires
|
|
535
538
|
* the operation's exact content-type (`est/bad-content-type`); a 202 requires a
|
|
536
|
-
* Retry-After (absent -> `est/missing-retry-after`)
|
|
539
|
+
* Retry-After (absent -> `est/missing-retry-after`): a delay-seconds value is
|
|
537
540
|
* surfaced as bounded `retryAfterSeconds`, an HTTP-date as absolute
|
|
538
541
|
* `retryAfterDate` (epoch ms; `retryAfterSeconds` too when `opts.now` is given),
|
|
539
542
|
* and any other value is `est/bad-retry-after` (never slept on either way);
|
|
@@ -846,7 +849,7 @@ function _drive(method, url, body, headers, opts, transport, budgets) {
|
|
|
846
849
|
return hh;
|
|
847
850
|
}
|
|
848
851
|
function step() {
|
|
849
|
-
return transport({ method: method, url: url.href, headers: _headersFor(url), body: body, tls: _tlsFor(url), timeout: budgets.timeout, maxResponseBytes: budgets.maxResponseBytes }).then(function (res) {
|
|
852
|
+
return transport({ method: method, url: url.href, headers: _headersFor(url), body: body, tls: _tlsFor(url), proxy: budgets.proxy, timeout: budgets.timeout, maxResponseBytes: budgets.maxResponseBytes }).then(function (res) {
|
|
850
853
|
res = res || {};
|
|
851
854
|
var blen = _bodyLen(res);
|
|
852
855
|
if (blen > budgets.maxResponseBytes) throw E("est/response-too-large", "the response body (" + blen + " bytes) exceeds the " + budgets.maxResponseBytes + "-byte cap (RFC 7030 sec. 6)");
|
|
@@ -909,7 +912,7 @@ function _drive(method, url, body, headers, opts, transport, budgets) {
|
|
|
909
912
|
return step();
|
|
910
913
|
}
|
|
911
914
|
|
|
912
|
-
function _client(op, method, baseUrl, body, headers, opts) {
|
|
915
|
+
function _client(op, method, baseUrl, body, headers, opts, proxySnap) {
|
|
913
916
|
var base;
|
|
914
917
|
try { base = new URL(String(baseUrl)); }
|
|
915
918
|
catch (e) { throw E("est/bad-url", "the EST base URL did not parse: " + guard.text.showValue(baseUrl), e); }
|
|
@@ -924,6 +927,7 @@ function _client(op, method, baseUrl, body, headers, opts) {
|
|
|
924
927
|
}
|
|
925
928
|
var budgets = {
|
|
926
929
|
tls: _tlsForRequest(opts),
|
|
930
|
+
proxy: proxySnap,
|
|
927
931
|
timeout: guard.limits.cap(opts.timeout, "timeout", DEFAULT_TIMEOUT, { E: E, code: "est/bad-input", min: 1, max: MAX_TIMEOUT }),
|
|
928
932
|
maxResponseBytes: guard.limits.cap(opts.maxResponseBytes, "maxResponseBytes", constants.LIMITS.HTTP_MAX_RESPONSE_BYTES, { E: E, code: "est/bad-input", min: 1, max: constants.LIMITS.HTTP_MAX_RESPONSE_BYTES }),
|
|
929
933
|
maxRedirects: guard.limits.cap(opts.maxRedirects, "maxRedirects", 5, { E: E, code: "est/bad-input", min: 0, max: 32 }),
|
|
@@ -1002,7 +1006,7 @@ function _certsResult(op, res, opts, csrSpki) {
|
|
|
1002
1006
|
* surfaced as `issuedCertificates` (with `certificate` the first), distinct from `certificates`,
|
|
1003
1007
|
* which is the whole returned bag including any chain. Where the requested keys are distinct that
|
|
1004
1008
|
* list is in request order; where several requests deliberately SHARE one key it is not, and does
|
|
1005
|
-
* not claim to be
|
|
1009
|
+
* not claim to be: the public key is the only identification sec. 4.1 sanctions, so when it is
|
|
1006
1010
|
* shared nothing in the response says which of those requests a given certificate answers. That arm reports `signatureVerified: false`: a certs-only body is
|
|
1007
1011
|
* a degenerate SignedData with no signers by definition, so its security rests on the authenticated
|
|
1008
1012
|
* TLS channel, not on a signature.
|
|
@@ -1012,7 +1016,7 @@ function _certsResult(op, res, opts, csrSpki) {
|
|
|
1012
1016
|
* response is SURFACED, never acted on (RFC 5272 sec. 6.15 makes accepting one a manual decision).
|
|
1013
1017
|
*
|
|
1014
1018
|
* @opts
|
|
1015
|
-
* - `transport` / `tls` / `label` / `timeout` / `maxResponseBytes` / `maxRedirects` / `now`, as in pki.est.cacerts.
|
|
1019
|
+
* - `transport` / `tls` / `label` / `timeout` / `maxResponseBytes` / `maxRedirects` / `proxy` / `now`, as in pki.est.cacerts.
|
|
1016
1020
|
* - `transactionId` / `senderNonce` / `dataReturn`: what the request sent. The values are read out
|
|
1017
1021
|
* of the request itself; supplying them here cross-checks that, and a disagreement is refused.
|
|
1018
1022
|
* - `responderCerts`: extra certificates for CMC signer lookup, for a response that does not carry
|
|
@@ -1049,16 +1053,18 @@ function fullcmc(baseUrl, request, opts) {
|
|
|
1049
1053
|
try {
|
|
1050
1054
|
if (typeof opts !== "object" || Buffer.isBuffer(opts)) throw E("est/bad-input", "pki.est.fullcmc options must be an object");
|
|
1051
1055
|
_knownOpts(opts, FULLCMC_OPTS, "fullcmc");
|
|
1056
|
+
var proxySnap;
|
|
1057
|
+
try { proxySnap = _snapProxy(opts); } catch (_pe) { throw E("est/bad-input", "the options object could not be read", _pe); }
|
|
1052
1058
|
der = _cmcRequestDer(request);
|
|
1053
1059
|
wanted = _requestedPublicKeys(der);
|
|
1054
1060
|
sent = _cmcSent(opts, der);
|
|
1055
1061
|
opts = _shallowCopy(opts);
|
|
1056
1062
|
var body = transferEncode(der);
|
|
1057
1063
|
return _client("fullcmc", "POST", baseUrl, body,
|
|
1058
|
-
{ accept: "application/pkcs7-mime", "content-type": "application/pkcs7-mime; smime-type=CMC-request" }, opts)
|
|
1064
|
+
{ accept: "application/pkcs7-mime", "content-type": "application/pkcs7-mime; smime-type=CMC-request" }, opts, proxySnap)
|
|
1059
1065
|
.then(function (res) { return _fullcmcResult(res, opts, wanted, sent); });
|
|
1060
1066
|
} catch (e) {
|
|
1061
|
-
return
|
|
1067
|
+
return _rej(e);
|
|
1062
1068
|
}
|
|
1063
1069
|
}
|
|
1064
1070
|
|
|
@@ -1357,11 +1363,11 @@ function _tryDecodeCmcFault(res, sent) {
|
|
|
1357
1363
|
}).then(null, function () { return null; });
|
|
1358
1364
|
}
|
|
1359
1365
|
|
|
1360
|
-
function _enroll(op, baseUrl, csrInput, opts) {
|
|
1366
|
+
function _enroll(op, baseUrl, csrInput, opts, proxySnap) {
|
|
1361
1367
|
var csrDer = _csrDer(csrInput);
|
|
1362
1368
|
var spki = csr.parse(csrDer).subjectPublicKeyInfo;
|
|
1363
1369
|
var body = transferEncode(csrDer);
|
|
1364
|
-
return _client(op, "POST", baseUrl, body, { accept: "application/pkcs7-mime", "content-type": "application/pkcs10" }, opts)
|
|
1370
|
+
return _client(op, "POST", baseUrl, body, { accept: "application/pkcs7-mime", "content-type": "application/pkcs10" }, opts, proxySnap)
|
|
1365
1371
|
.then(function (res) { return _certsResult(op, res, opts, spki); });
|
|
1366
1372
|
}
|
|
1367
1373
|
|
|
@@ -1386,6 +1392,7 @@ function _enroll(op, baseUrl, csrInput, opts) {
|
|
|
1386
1392
|
* - `transport`: an injected transport(request) -> {status, headers, body, tls}; default pki.transport.https.
|
|
1387
1393
|
* - `tls` -- { anchors, useSystemStore, cert, key, minVersion, servername, checkServerIdentity }.
|
|
1388
1394
|
* - `label` -- an OPTIONAL CA label path segment; `timeout` / `maxResponseBytes` / `maxRedirects` -- budgets.
|
|
1395
|
+
* - `proxy` -- reach the CA through a forward HTTP proxy (`{ url, auth?, tls? }`; see pki.transport).
|
|
1389
1396
|
* - `now` -- receipt time (epoch ms) to render a 202 Retry-After HTTP-date as seconds.
|
|
1390
1397
|
* - `auth` -- HTTP authentication: `{ scheme: "basic" | "digest", username, password, allowMD5, allowLegacyQop, maxStaleRetries }`.
|
|
1391
1398
|
* There is no `"auto"`: the scheme is chosen here, not by whatever a server offers. `username` / `password`
|
|
@@ -1399,9 +1406,10 @@ function _enroll(op, baseUrl, csrInput, opts) {
|
|
|
1399
1406
|
*/
|
|
1400
1407
|
function cacerts(baseUrl, opts) {
|
|
1401
1408
|
opts = opts || {};
|
|
1409
|
+
var proxySnap; try { proxySnap = _snapProxy(opts); } catch (_pe) { return _rej(E("est/bad-input", "the options object could not be read", _pe)); }
|
|
1402
1410
|
return Promise.resolve().then(function () {
|
|
1403
1411
|
_knownOpts(opts, CACERTS_OPTS, "cacerts");
|
|
1404
|
-
return _client("cacerts", "GET", baseUrl, null, { accept: "application/pkcs7-mime" }, opts);
|
|
1412
|
+
return _client("cacerts", "GET", baseUrl, null, { accept: "application/pkcs7-mime" }, opts, proxySnap);
|
|
1405
1413
|
}).then(function (res) { return _certsResult("cacerts", res, opts, null); });
|
|
1406
1414
|
}
|
|
1407
1415
|
|
|
@@ -1424,9 +1432,9 @@ function cacerts(baseUrl, opts) {
|
|
|
1424
1432
|
* the transport already authenticated the server.
|
|
1425
1433
|
*
|
|
1426
1434
|
* @opts
|
|
1427
|
-
* - `transport` / `tls` / `label` / `timeout` / `maxResponseBytes` / `maxRedirects` / `now`, as in pki.est.cacerts.
|
|
1435
|
+
* - `transport` / `tls` / `label` / `timeout` / `maxResponseBytes` / `maxRedirects` / `proxy` / `now`, as in pki.est.cacerts.
|
|
1428
1436
|
* - `strict`: reject an enroll response that carries more than the single issued certificate.
|
|
1429
|
-
* - `username` / `password` -- HTTP Basic
|
|
1437
|
+
* - `username` / `password` / `auth` -- HTTP credentials: top-level `username` / `password` mean Basic; `auth` (`{ scheme: "basic" | "digest", username, password, ... }`, as in pki.est.cacerts) selects Basic or Digest. Answered only after server authorization (empty username allowed).
|
|
1430
1438
|
* - `allowCrossOriginRedirect` -- opt in to following a cross-origin redirect on this POST.
|
|
1431
1439
|
* @example
|
|
1432
1440
|
* var req = await pki.csr.sign({ subject: "device.example", subjectPublicKey: signerSpki }, { key: signerKeyPkcs8 });
|
|
@@ -1437,9 +1445,10 @@ function cacerts(baseUrl, opts) {
|
|
|
1437
1445
|
*/
|
|
1438
1446
|
function simpleenroll(baseUrl, csrInput, opts) {
|
|
1439
1447
|
opts = opts || {};
|
|
1448
|
+
var proxySnap; try { proxySnap = _snapProxy(opts); } catch (_pe) { return _rej(E("est/bad-input", "the options object could not be read", _pe)); }
|
|
1440
1449
|
return Promise.resolve().then(function () {
|
|
1441
1450
|
_knownOpts(opts, SIMPLEENROLL_OPTS, "simpleenroll");
|
|
1442
|
-
return _enroll("simpleenroll", baseUrl, csrInput, opts);
|
|
1451
|
+
return _enroll("simpleenroll", baseUrl, csrInput, opts, proxySnap);
|
|
1443
1452
|
});
|
|
1444
1453
|
}
|
|
1445
1454
|
|
|
@@ -1470,11 +1479,12 @@ function simpleenroll(baseUrl, csrInput, opts) {
|
|
|
1470
1479
|
*/
|
|
1471
1480
|
function simplereenroll(baseUrl, csrInput, opts) {
|
|
1472
1481
|
opts = opts || {};
|
|
1482
|
+
var proxySnap; try { proxySnap = _snapProxy(opts); } catch (_pe) { return _rej(E("est/bad-input", "the options object could not be read", _pe)); }
|
|
1473
1483
|
return Promise.resolve().then(function () {
|
|
1474
1484
|
_knownOpts(opts, SIMPLEREENROLL_OPTS, "simplereenroll");
|
|
1475
1485
|
if (!opts.oldCert) throw E("est/bad-input", "simplereenroll requires opts.oldCert (the certificate being renewed, RFC 7030 sec. 4.2.2)");
|
|
1476
1486
|
reenrollGuard(opts.oldCert, _csrDer(csrInput));
|
|
1477
|
-
return _enroll("simplereenroll", baseUrl, csrInput, opts);
|
|
1487
|
+
return _enroll("simplereenroll", baseUrl, csrInput, opts, proxySnap);
|
|
1478
1488
|
});
|
|
1479
1489
|
}
|
|
1480
1490
|
|
|
@@ -1613,6 +1623,7 @@ async function _serverkeygenResult(res, opts, derived) {
|
|
|
1613
1623
|
*/
|
|
1614
1624
|
function serverkeygen(baseUrl, csrInput, opts) {
|
|
1615
1625
|
opts = opts || {};
|
|
1626
|
+
var proxySnap; try { proxySnap = _snapProxy(opts); } catch (_pe) { return _rej(E("est/bad-input", "the options object could not be read", _pe)); }
|
|
1616
1627
|
return Promise.resolve().then(function () {
|
|
1617
1628
|
_knownOpts(opts, SERVERKEYGEN_OPTS, "serverkeygen");
|
|
1618
1629
|
var csrDer = _csrDer(csrInput);
|
|
@@ -1631,7 +1642,7 @@ function serverkeygen(baseUrl, csrInput, opts) {
|
|
|
1631
1642
|
if ((opts.expectedRecipientKeyId !== undefined || opts.expectedRecipientIssuerSerial != null) && !derived.requestedEncryption) {
|
|
1632
1643
|
throw E("est/bad-input", "a recipient expectation (expectedRecipientKeyId / expectedRecipientIssuerSerial) implies an encrypted key, but the CSR advertised no DecryptKeyIdentifier / AsymmetricDecryptKeyIdentifier attribute (RFC 7030 sec. 4.4.1)");
|
|
1633
1644
|
}
|
|
1634
|
-
return _client("serverkeygen", "POST", baseUrl, transferEncode(csrDer), { accept: "multipart/mixed", "content-type": "application/pkcs10" }, opts)
|
|
1645
|
+
return _client("serverkeygen", "POST", baseUrl, transferEncode(csrDer), { accept: "multipart/mixed", "content-type": "application/pkcs10" }, opts, proxySnap)
|
|
1635
1646
|
.then(function (res) { return _serverkeygenResult(res, opts, derived); });
|
|
1636
1647
|
});
|
|
1637
1648
|
}
|
|
@@ -1673,9 +1684,10 @@ function _csrattrsResult(res, opts) {
|
|
|
1673
1684
|
*/
|
|
1674
1685
|
function csrattrs(baseUrl, opts) {
|
|
1675
1686
|
opts = opts || {};
|
|
1687
|
+
var proxySnap; try { proxySnap = _snapProxy(opts); } catch (_pe) { return _rej(E("est/bad-input", "the options object could not be read", _pe)); }
|
|
1676
1688
|
return Promise.resolve().then(function () {
|
|
1677
1689
|
_knownOpts(opts, CSRATTRS_OPTS, "csrattrs");
|
|
1678
|
-
return _client("csrattrs", "GET", baseUrl, null, { accept: "application/csrattrs" }, opts);
|
|
1690
|
+
return _client("csrattrs", "GET", baseUrl, null, { accept: "application/csrattrs" }, opts, proxySnap);
|
|
1679
1691
|
}).then(function (res) { return _csrattrsResult(res, opts); });
|
|
1680
1692
|
}
|
|
1681
1693
|
|
package/lib/framework-error.js
CHANGED
|
@@ -90,8 +90,8 @@ class PkiError extends Error {
|
|
|
90
90
|
* @status stable
|
|
91
91
|
* @spec internal (design: error-class factory)
|
|
92
92
|
*
|
|
93
|
-
* Factory that produces a `PkiError` subclass with the standard shape
|
|
94
|
-
*
|
|
93
|
+
* Factory that produces a `PkiError` subclass with the standard shape. The
|
|
94
|
+
* returned constructor takes
|
|
95
95
|
* `(code, message)`, stamps `name`, sets an `is<Name>` flag, and exposes
|
|
96
96
|
* a `.factory` static for the common `var _err = XxxError.factory` shape.
|
|
97
97
|
* The `code` must be a `domain/reason` string (the base-class contract);
|
package/lib/guard-identifier.js
CHANGED
|
@@ -309,11 +309,27 @@ function _isBuiltinExotic(v) {
|
|
|
309
309
|
t.isBoxedPrimitive(v) || t.isNativeError(v) || t.isArgumentsObject(v) || t.isGeneratorObject(v);
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
function _proxyInChain(v) {
|
|
313
|
+
if (v === null || typeof v !== "object") return false;
|
|
314
|
+
var visited = new _Set();
|
|
315
|
+
for (var o = v; o && !_setHas(visited, o); o = _getPrototypeOf(o)) {
|
|
316
|
+
_setAdd(visited, o);
|
|
317
|
+
if (util.types.isProxy(o)) return true;
|
|
318
|
+
}
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
function isPlainRecord(v) {
|
|
322
|
+
if (v === null || typeof v !== "object") return false;
|
|
323
|
+
if (_proxyInChain(v)) return false;
|
|
324
|
+
if (_isNodeBuffer(v) || _isArray(v)) return false;
|
|
325
|
+
return !_isBuiltinExotic(v);
|
|
326
|
+
}
|
|
312
327
|
// @enforced-by behavioral -- a plain-record refusal has no single rename-proof code shape worth a
|
|
313
328
|
function assertPlainRecord(v, E, code, label) {
|
|
314
329
|
_requireFactory(E);
|
|
315
|
-
if (v === null || typeof v !== "object"
|
|
330
|
+
if (v === null || typeof v !== "object") throw E(code, label + " must be a plain object");
|
|
316
331
|
_refuseUnenumerable(v, E, code, label);
|
|
332
|
+
if (_isNodeBuffer(v) || _isArray(v)) throw E(code, label + " must be a plain object");
|
|
317
333
|
if (_isBuiltinExotic(v)) throw E(code, label + " must be a plain object (a built-in exotic such as a Date or Map is refused)");
|
|
318
334
|
}
|
|
319
335
|
|
|
@@ -378,6 +394,7 @@ module.exports = intrinsic.freeze({
|
|
|
378
394
|
assertCanonicalOid: assertCanonicalOid,
|
|
379
395
|
assertKnownKeys: assertKnownKeys,
|
|
380
396
|
assertPlainRecord: assertPlainRecord,
|
|
397
|
+
isPlainRecord: isPlainRecord,
|
|
381
398
|
optionsObject: optionsObject,
|
|
382
399
|
refuseAccessorFields: refuseAccessorFields,
|
|
383
400
|
readableNames: readableNames,
|
package/lib/hpke.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @nav Protocols
|
|
7
7
|
* @title HPKE
|
|
8
8
|
* @fullname HPKE (Hybrid Public Key Encryption, RFC 9180)
|
|
9
|
-
* @intro Hybrid Public Key Encryption (RFC 9180)
|
|
9
|
+
* @intro Hybrid Public Key Encryption (RFC 9180): the standard construction
|
|
10
10
|
* behind TLS Encrypted Client Hello, MLS, and OHTTP that turns a recipient KEM
|
|
11
11
|
* public key into an encapsulated key plus an AEAD-encrypting context. This is
|
|
12
12
|
* the RFC 9180 base construction: the DHKEM suites P-256, P-521, X25519, and
|
|
@@ -358,7 +358,7 @@ var suites = {
|
|
|
358
358
|
|
|
359
359
|
/**
|
|
360
360
|
* @primitive pki.hpke.setupS
|
|
361
|
-
* @signature pki.hpke.setupS(suiteIds, recipientPublicKey, opts?) -> { enc, context }
|
|
361
|
+
* @signature pki.hpke.setupS(suiteIds, recipientPublicKey, opts?) -> { enc, context, sharedSecret }
|
|
362
362
|
* @since 0.2.2
|
|
363
363
|
* @status stable
|
|
364
364
|
* @spec RFC 9180
|
package/lib/http-transport.js
CHANGED
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
* composes it (or an injected substitute) through one contract:
|
|
17
17
|
* `transport(request) -> Promise<{ status, headers, body, tls }>`. The first three are
|
|
18
18
|
* exactly what a message layer's classifier consumes, so no protocol semantics leak
|
|
19
|
-
* into the socket layer
|
|
19
|
+
* into the socket layer: the transport owns socket lifecycle, the TLS trust policy,
|
|
20
20
|
* the streaming size cap, and the timeout budget; the caller owns HTTP status,
|
|
21
21
|
* content-type, redirect, and authentication decisions. `tls` reports the negotiated
|
|
22
|
-
* channel
|
|
22
|
+
* channel, `{ protocol, cipher, peerCertificate }`, which is the one fact a caller
|
|
23
23
|
* cannot recover from the response bytes. An INJECTED substitute should return it too:
|
|
24
24
|
* `pki.est.serverkeygen` asserts the negotiated cipher can protect the private key it
|
|
25
25
|
* is about to accept, and a transport that reports no cipher is trusted instead of
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
* @card
|
|
41
41
|
* The shared fail-closed node:https transport (est / acme / cmp): explicit trust
|
|
42
42
|
* anchors, rejectUnauthorized always on, a TLS floor, a streaming response-size cap,
|
|
43
|
-
* and a timeout
|
|
43
|
+
* and a timeout, behind one `transport(request) -> {status, headers, body, tls}` seam.
|
|
44
44
|
*/
|
|
45
45
|
|
|
46
46
|
var nodeHttps = require("node:https");
|
|
@@ -48,6 +48,7 @@ var nodeHttp = require("node:http");
|
|
|
48
48
|
var nodeNet = require("node:net");
|
|
49
49
|
var nodeTls = require("node:tls");
|
|
50
50
|
var nodeDns = require("node:dns");
|
|
51
|
+
var nodeUtil = require("node:util");
|
|
51
52
|
var constants = require("./constants");
|
|
52
53
|
var guard = require("./guard-all");
|
|
53
54
|
var frameworkError = require("./framework-error");
|
|
@@ -197,13 +198,70 @@ var PROXY_KEYS = _keySet(["url", "auth", "tls"]);
|
|
|
197
198
|
var PROXY_AUTH_KEYS = _keySet(["scheme", "username", "password"]);
|
|
198
199
|
var PROXY_TLS_KEYS = _keySet(["anchors", "useSystemStore", "servername", "minVersion"]);
|
|
199
200
|
|
|
201
|
+
function _copyAnchor(a) { return (a !== null && typeof a === "object" && nodeUtil.types.isProxy(a)) ? a : (Buffer.isBuffer(a) ? Buffer.from(a) : a); }
|
|
202
|
+
function _noopProxyErr(code, message) { var e = new Error(message); e.code = code; e.isPkiError = true; return e; }
|
|
203
|
+
function _copyableRecord(p) {
|
|
204
|
+
if (!guard.identifier.isPlainRecord(p)) return false;
|
|
205
|
+
var proto = Object.getPrototypeOf(p);
|
|
206
|
+
if (proto !== Object.prototype && proto !== null) return false;
|
|
207
|
+
if (Object.getOwnPropertySymbols(p).length !== 0) return false;
|
|
208
|
+
var ownEnumData = 0;
|
|
209
|
+
var names = Object.getOwnPropertyNames(p);
|
|
210
|
+
for (var i = 0; i < names.length; i++) {
|
|
211
|
+
var d = Object.getOwnPropertyDescriptor(p, names[i]);
|
|
212
|
+
if (!d.enumerable || d.get || d.set) return false;
|
|
213
|
+
ownEnumData++;
|
|
214
|
+
}
|
|
215
|
+
var readable = guard.identifier.readableNames(p, _noopProxyErr, "transport/bad-proxy", "opts.proxy");
|
|
216
|
+
if (readable.length !== ownEnumData) return false;
|
|
217
|
+
for (var j = 0; j < readable.length; j++) {
|
|
218
|
+
var rd = Object.getOwnPropertyDescriptor(p, readable[j]);
|
|
219
|
+
if (!rd || !rd.enumerable || rd.get || rd.set) return false;
|
|
220
|
+
}
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
function _copyableArray(a) {
|
|
224
|
+
if (a === null || typeof a !== "object" || nodeUtil.types.isProxy(a) || !Array.isArray(a) || Object.getPrototypeOf(a) !== Array.prototype) return false;
|
|
225
|
+
if (a.length > constants.LIMITS.PROXY_MAX_TLS_ANCHORS) return false;
|
|
226
|
+
if (Object.getOwnPropertySymbols(a).length !== 0) return false;
|
|
227
|
+
var names = Object.getOwnPropertyNames(a);
|
|
228
|
+
for (var i = 0; i < names.length; i++) {
|
|
229
|
+
var d = Object.getOwnPropertyDescriptor(a, names[i]);
|
|
230
|
+
if (d.get || d.set) return false;
|
|
231
|
+
}
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
function snapshotProxy(p) {
|
|
235
|
+
try {
|
|
236
|
+
if (!_copyableRecord(p)) return p;
|
|
237
|
+
var out = Object.assign(Object.create(null), p);
|
|
238
|
+
if (_copyableRecord(p.auth)) out.auth = Object.assign(Object.create(null), p.auth);
|
|
239
|
+
if (_copyableRecord(p.tls)) {
|
|
240
|
+
out.tls = Object.assign(Object.create(null), p.tls);
|
|
241
|
+
var a = p.tls.anchors;
|
|
242
|
+
if (_copyableArray(a)) {
|
|
243
|
+
var copied = [];
|
|
244
|
+
for (var i = 0; i < a.length; i++) copied[i] = _copyAnchor(a[i]);
|
|
245
|
+
out.tls.anchors = copied;
|
|
246
|
+
} else if (typeof a === "string") out.tls.anchors = a;
|
|
247
|
+
else if (a !== null && typeof a === "object" && !nodeUtil.types.isProxy(a) && Buffer.isBuffer(a)) out.tls.anchors = Buffer.from(a);
|
|
248
|
+
}
|
|
249
|
+
return out;
|
|
250
|
+
} catch (_e) {
|
|
251
|
+
// allow:swallow-unverified an input whose introspection throws (a revoked Proxy, a throwing trap) returns unchanged for _validateProxy to refuse
|
|
252
|
+
return p;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
200
256
|
function _validateProxy(request, defaults, E, C, url) {
|
|
201
257
|
var proxy = request.proxy !== undefined ? request.proxy : defaults.proxy;
|
|
202
258
|
if (proxy === undefined || proxy === null) return null;
|
|
203
259
|
guard.identifier.assertPlainRecord(proxy, E, C("bad-proxy"), "opts.proxy");
|
|
260
|
+
if (!_copyableRecord(proxy)) throw E(C("bad-proxy"), "opts.proxy must be a plain object with only own enumerable data properties");
|
|
204
261
|
guard.identifier.assertKnownKeys(proxy, PROXY_KEYS, E, C("bad-proxy"), "opts.proxy has an unknown option ");
|
|
262
|
+
if (typeof proxy.url !== "string") throw E(C("bad-proxy"), "opts.proxy.url must be a string, got " + guard.text.showValue(proxy.url));
|
|
205
263
|
var purl;
|
|
206
|
-
try { purl = new URL(
|
|
264
|
+
try { purl = new URL(proxy.url); }
|
|
207
265
|
catch (e) { throw E(C("bad-proxy"), "opts.proxy.url did not parse: " + guard.text.showValue(proxy.url), e); }
|
|
208
266
|
if (purl.protocol !== "http:" && purl.protocol !== "https:") throw E(C("bad-proxy"), "opts.proxy.url must be an http or https URL, got " + purl.protocol);
|
|
209
267
|
if (purl.username || purl.password) throw E(C("bad-proxy"), "opts.proxy.url must not carry credentials in its userinfo; supply opts.proxy.auth over an https proxy");
|
|
@@ -214,6 +272,7 @@ function _validateProxy(request, defaults, E, C, url) {
|
|
|
214
272
|
if (proxy.auth !== undefined && proxy.auth !== null) {
|
|
215
273
|
if (!secure) throw E(C("proxy-auth-requires-tls"), "proxy credentials cannot be sent over a plaintext http proxy, where the proxy hop would expose them; use an https proxy URL");
|
|
216
274
|
guard.identifier.assertPlainRecord(proxy.auth, E, C("bad-proxy"), "opts.proxy.auth");
|
|
275
|
+
if (!_copyableRecord(proxy.auth)) throw E(C("bad-proxy"), "opts.proxy.auth must be a plain object with only own enumerable data properties");
|
|
217
276
|
guard.identifier.assertKnownKeys(proxy.auth, PROXY_AUTH_KEYS, E, C("bad-proxy"), "opts.proxy.auth has an unknown option ");
|
|
218
277
|
var scheme = proxy.auth.scheme;
|
|
219
278
|
if (scheme === "digest") throw E(C("proxy-unsupported-scheme"), "Digest proxy authentication is not supported in this release; use 'basic' over an https proxy");
|
|
@@ -228,22 +287,25 @@ function _validateProxy(request, defaults, E, C, url) {
|
|
|
228
287
|
var ptls = proxy.tls;
|
|
229
288
|
if (ptls !== undefined && ptls !== null) {
|
|
230
289
|
guard.identifier.assertPlainRecord(ptls, E, C("bad-proxy"), "opts.proxy.tls");
|
|
290
|
+
if (!_copyableRecord(ptls)) throw E(C("bad-proxy"), "opts.proxy.tls must be a plain object with only own enumerable data properties");
|
|
231
291
|
guard.identifier.assertKnownKeys(ptls, PROXY_TLS_KEYS, E, C("bad-proxy"), "opts.proxy.tls has an unknown option ");
|
|
232
292
|
if (ptls.servername !== undefined && ptls.servername !== null && typeof ptls.servername !== "string") throw E(C("bad-proxy"), "opts.proxy.tls.servername must be a string");
|
|
233
293
|
}
|
|
234
294
|
var pAnchors = ptls ? ptls.anchors : undefined;
|
|
295
|
+
if (pAnchors !== undefined && pAnchors !== null && typeof pAnchors === "object" && nodeUtil.types.isProxy(pAnchors)) throw E(C("bad-proxy"), "opts.proxy.tls.anchors must not be a Proxy");
|
|
235
296
|
var pUseSystem = ptls ? ptls.useSystemStore === true : false;
|
|
236
297
|
var pHasAnchors = pAnchors !== undefined && pAnchors !== null && !(Array.isArray(pAnchors) && pAnchors.length === 0);
|
|
237
298
|
if (!pHasAnchors && !pUseSystem) throw E(C("bad-proxy"), "an https proxy requires explicit tls trust: set proxy.tls.anchors or proxy.tls.useSystemStore");
|
|
238
299
|
if (pHasAnchors) {
|
|
300
|
+
if (Array.isArray(pAnchors) && !_copyableArray(pAnchors)) throw E(C("bad-proxy"), "opts.proxy.tls.anchors must be a plain array of PEM strings or DER Buffers");
|
|
239
301
|
var pAnchorList = Array.isArray(pAnchors) ? pAnchors : [pAnchors];
|
|
240
302
|
for (var pai = 0; pai < pAnchorList.length; pai++) {
|
|
241
303
|
var pan = pAnchorList[pai];
|
|
242
|
-
if (typeof pan !== "string" && !Buffer.isBuffer(pan)) throw E(C("bad-proxy"), "opts.proxy.tls.anchors must be a PEM string or a DER Buffer (or an array of them)");
|
|
304
|
+
if ((pan !== null && typeof pan === "object" && nodeUtil.types.isProxy(pan)) || (typeof pan !== "string" && !Buffer.isBuffer(pan))) throw E(C("bad-proxy"), "opts.proxy.tls.anchors must be a PEM string or a DER Buffer (or an array of them)");
|
|
243
305
|
}
|
|
244
306
|
}
|
|
245
|
-
var pMinVersion = (ptls && ptls.minVersion)
|
|
246
|
-
if (pMinVersion !== "TLSv1.2" && pMinVersion !== "TLSv1.3") throw E(C("bad-proxy"), "proxy.tls.minVersion must be 'TLSv1.2' or 'TLSv1.3', got " + pMinVersion);
|
|
307
|
+
var pMinVersion = (ptls && ptls.minVersion !== undefined && ptls.minVersion !== null) ? ptls.minVersion : DEFAULT_MIN_VERSION;
|
|
308
|
+
if (pMinVersion !== "TLSv1.2" && pMinVersion !== "TLSv1.3") throw E(C("bad-proxy"), "proxy.tls.minVersion must be 'TLSv1.2' or 'TLSv1.3', got " + guard.text.showValue(pMinVersion));
|
|
247
309
|
var pCa = [];
|
|
248
310
|
if (pHasAnchors) pCa = pCa.concat(_pemifyAnchors(pAnchors));
|
|
249
311
|
if (pUseSystem) pCa = pCa.concat(_systemCa());
|
|
@@ -289,7 +351,7 @@ function _proxyErrorClass(e, C) {
|
|
|
289
351
|
* (`transport/server-auth-failed`) all fail closed; `rejectUnauthorized` is always on.
|
|
290
352
|
* A protocol client passes its own error factory (`defaults.E`) + `defaults.errPrefix`
|
|
291
353
|
* to surface domain codes (`est/...`). The transport owns no HTTP/redirect/auth
|
|
292
|
-
* semantics
|
|
354
|
+
* semantics; those live in the message layer that consumes the response triple.
|
|
293
355
|
*
|
|
294
356
|
* @opts
|
|
295
357
|
* - `tls.anchors` -- Explicit trust anchor(s): a DER/PEM Buffer, an array, or PEM string(s) (node `ca`).
|
|
@@ -370,7 +432,17 @@ function httpsTransport(defaults) {
|
|
|
370
432
|
return callerCsi(host, cert2);
|
|
371
433
|
};
|
|
372
434
|
}
|
|
373
|
-
var proxyInfo
|
|
435
|
+
var proxyInfo;
|
|
436
|
+
try { proxyInfo = _validateProxy(request, defaults, E, C, url); }
|
|
437
|
+
catch (e) {
|
|
438
|
+
var typedProxyError = false;
|
|
439
|
+
try {
|
|
440
|
+
var ec = (e instanceof Error) && !nodeUtil.types.isProxy(e) ? e.code : undefined;
|
|
441
|
+
typedProxyError = ec === C("bad-proxy") || ec === C("proxy-auth-requires-tls") || ec === C("proxy-unsupported-scheme");
|
|
442
|
+
} catch (_ie) { /* allow:swallow-unverified reading .code on a hostile error object can throw; treat any such failure as untyped */ }
|
|
443
|
+
if (typedProxyError) throw e;
|
|
444
|
+
throw E(C("bad-proxy"), "opts.proxy could not be validated", e);
|
|
445
|
+
}
|
|
374
446
|
var blockPrivate = (request.blockPrivateAddresses !== undefined ? request.blockPrivateAddresses : defaults.blockPrivateAddresses) === true;
|
|
375
447
|
if (blockPrivate && proxyInfo) throw E(C("bad-proxy"), "blockPrivateAddresses cannot be enforced through a forward proxy, which resolves the origin itself and would reach a private origin the transport never checks; use a direct connection for a private-address-blocked fetch");
|
|
376
448
|
if (blockPrivate) {
|
|
@@ -526,4 +598,4 @@ function httpsTransport(defaults) {
|
|
|
526
598
|
return _transportFn;
|
|
527
599
|
}
|
|
528
600
|
|
|
529
|
-
module.exports = { https: httpsTransport, isBlockedIp: _isBlockedIp, _makeGuardedLookup: _makeGuardedLookup, MAX_TIMEOUT: MAX_TIMEOUT };
|
|
601
|
+
module.exports = { https: httpsTransport, isBlockedIp: _isBlockedIp, _makeGuardedLookup: _makeGuardedLookup, MAX_TIMEOUT: MAX_TIMEOUT, snapshotProxy: snapshotProxy };
|
package/lib/inspect.js
CHANGED
|
@@ -795,7 +795,7 @@ var _INSPECT_BY_FORMAT = { x509: certificate, crl: crlReport, csr: csrReport, cm
|
|
|
795
795
|
* @related pki.schema.detectFormat, pki.inspect.certificate
|
|
796
796
|
*
|
|
797
797
|
* Detect which PKI format `input` (a PEM string or DER Buffer) encodes and render
|
|
798
|
-
* it with the matching report
|
|
798
|
+
* it with the matching report, the inspect analogue of `pki.schema.parse`. Routes
|
|
799
799
|
* a certificate / CRL / CSR / CMS to `certificate` / `crl` / `csr` / `cms`; a
|
|
800
800
|
* detected but out-of-scope format (OCSP, TSP, PKCS#8/#12, CRMF, CMP, ...) throws
|
|
801
801
|
* `inspect/unsupported-format` naming it, and an unrecognized input
|
package/lib/jose.js
CHANGED
|
@@ -294,7 +294,7 @@ function assertPublicJwk(jwk) {
|
|
|
294
294
|
* or the message is refused with `jose/key-mismatch`. They are compared as RFC 7638
|
|
295
295
|
* thumbprints, so member order and members outside the key itself cannot make equal
|
|
296
296
|
* keys look different. Without `opts.key` the embedded `jwk` is used where the
|
|
297
|
-
* profile permits one
|
|
297
|
+
* profile permits one, which verifies that the message is internally consistent,
|
|
298
298
|
* not that any particular signer produced it. `keySource` reports which of the two
|
|
299
299
|
* answered, so a signature checked against a caller-named key is distinguishable
|
|
300
300
|
* from one checked against the key the message brought with it.
|
package/lib/key.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* `publicFromPrivate` over every algorithm the WebCrypto engine drives: RSA, EC, Ed25519/Ed448,
|
|
13
13
|
* X25519/X448, and the FIPS post-quantum ML-DSA / ML-KEM. Unencrypted export / import DELEGATES to the
|
|
14
14
|
* WebCrypto `exportKey` / `importKey` PKCS#8 / SPKI encoders (which already emit each algorithm's
|
|
15
|
-
* `AlgorithmIdentifier.parameters` correctly
|
|
15
|
+
* `AlgorithmIdentifier.parameters` correctly: RSA NULL, EC namedCurve, Ed/X ABSENT), so the wrapper
|
|
16
16
|
* never re-encodes an AlgorithmIdentifier. PBES2 encrypt / decrypt composes the one shared `lib/pbes2.js`
|
|
17
17
|
* home (the same PBKDF2 + AES-CBC primitives `pki.cms` uses). Parsing lives at `pki.schema.pkcs8.parse`.
|
|
18
18
|
* @spec RFC 5958, RFC 8018, RFC 5280
|
|
@@ -73,8 +73,8 @@ async function _toPrivateKeyDer(input) {
|
|
|
73
73
|
* (never encrypt opaque bytes), and the produced `EncryptedPrivateKeyInfo` is re-parsed before return.
|
|
74
74
|
*
|
|
75
75
|
* The PBKDF2 `prf` equal to the default (`hmacWithSHA1`) is omitted from the parameters (X.690 sec. 11.5),
|
|
76
|
-
* `keyLength` is omitted (the AES cipher OID fixes the key size), and the salt / iteration count are bounded
|
|
77
|
-
*
|
|
76
|
+
* `keyLength` is omitted (the AES cipher OID fixes the key size), and the salt / iteration count are bounded,
|
|
77
|
+
* so the output is byte-exact with OpenSSL's `pkcs8 -topk8 -v2`. A bad input throws a typed `KeyError`.
|
|
78
78
|
*
|
|
79
79
|
* @opts
|
|
80
80
|
* - `cipher` (string) -- `aes-256-cbc` (default), `aes-192-cbc`, or `aes-128-cbc`.
|
|
@@ -187,13 +187,13 @@ function _decryptPbes2(encAlg, ciphertext, password, opts) {
|
|
|
187
187
|
*
|
|
188
188
|
* Export an extractable `CryptoKey` to DER (or PEM): a private key as PKCS#8 `OneAsymmetricKey`, a public
|
|
189
189
|
* key as SubjectPublicKeyInfo. The encoding is delegated to the WebCrypto `exportKey` PKCS#8 / SPKI encoder,
|
|
190
|
-
* so the algorithm-specific `AlgorithmIdentifier.parameters` are byte-correct
|
|
190
|
+
* so the algorithm-specific `AlgorithmIdentifier.parameters` are byte-correct: RSA carries an explicit
|
|
191
191
|
* NULL, EC a namedCurve OID, and Ed25519 / Ed448 / X25519 / X448 omit parameters (RFC 8410 sec. 3); the
|
|
192
192
|
* wrapper never re-encodes the AlgorithmIdentifier.
|
|
193
193
|
*
|
|
194
194
|
* The key need not have been created by this toolkit's own WebCrypto: one from the platform's, or from a
|
|
195
|
-
* separately-installed copy of this toolkit, is exported through whichever of them holds its material
|
|
196
|
-
*
|
|
195
|
+
* separately-installed copy of this toolkit, is exported through whichever of them holds its material.
|
|
196
|
+
* A key created non-extractable is refused with that as the reason,
|
|
197
197
|
* as is one whose implementation keeps its material out of this process's reach entirely.
|
|
198
198
|
*
|
|
199
199
|
* @opts
|
|
@@ -231,7 +231,7 @@ async function export_(key, opts) {
|
|
|
231
231
|
* @related pki.key.export, pki.key.decrypt
|
|
232
232
|
*
|
|
233
233
|
* Import a DER / PEM PKCS#8 private key, SPKI public key, or (with `opts.password`) an `ENCRYPTED PRIVATE
|
|
234
|
-
* KEY
|
|
234
|
+
* KEY`, auto-detecting the structure, into a `CryptoKey`. The WebCrypto algorithm is inferred from the
|
|
235
235
|
* key's OID for the algorithms that name exactly one (Ed25519 / Ed448 / X25519 / X448 / ML-DSA / ML-KEM /
|
|
236
236
|
* SLH-DSA); RSA and EC are ambiguous between signing and key agreement, so `opts.algorithm` must be supplied
|
|
237
237
|
* for them (without it the import fails closed). Default key usages follow the algorithm and key type.
|