@blamejs/pki 0.8.31 → 0.8.48
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 +393 -0
- package/README.md +66 -15
- package/bin/pki.js +716 -30
- package/examples/issue-a-certificate.js +90 -0
- package/examples/post-quantum.js +66 -0
- package/examples/read-a-certificate.js +56 -0
- package/examples/revoke-and-check.js +86 -0
- package/examples/sign-and-verify-cms.js +63 -0
- package/index.js +18 -5
- package/lib/acme.js +223 -74
- package/lib/alt-sig.js +441 -0
- package/lib/asn1-der.js +48 -25
- package/lib/attrcert-sign.js +27 -19
- package/lib/cbor-det.js +24 -14
- package/lib/cmc-build.js +9 -2
- package/lib/cmc-verify.js +14 -3
- package/lib/cmp-build.js +17 -5
- package/lib/cmp-session.js +24 -11
- package/lib/cmp-verify.js +28 -19
- package/lib/cms-compress.js +3 -1
- package/lib/cms-decrypt.js +102 -7
- package/lib/cms-encrypt.js +59 -7
- package/lib/cms-sign.js +31 -9
- package/lib/cms-verify.js +22 -18
- package/lib/composite-kem.js +30 -18
- package/lib/composite-sig.js +29 -14
- package/lib/constants.js +26 -0
- package/lib/crl-sign.js +111 -19
- package/lib/crmf-sign.js +167 -15
- package/lib/csr-sign.js +117 -6
- package/lib/ct.js +939 -48
- package/lib/est.js +29 -16
- package/lib/framework-error.js +12 -0
- package/lib/guard-async.js +1 -1
- package/lib/guard-bytes.js +103 -20
- package/lib/guard-crypto.js +205 -3
- package/lib/guard-encoding.js +6 -1
- package/lib/guard-identifier.js +55 -8
- package/lib/guard-intrinsic.js +43 -0
- package/lib/guard-json.js +122 -9
- package/lib/guard-limits.js +12 -1
- package/lib/guard-list.js +63 -8
- package/lib/guard-name.js +167 -20
- package/lib/guard-range.js +43 -5
- package/lib/guard-text.js +41 -0
- package/lib/guard-verdict.js +39 -8
- package/lib/hpke.js +688 -42
- package/lib/http-transport.js +156 -4
- package/lib/identity-match.js +4 -4
- package/lib/inspect.js +33 -6
- package/lib/jose.js +93 -25
- package/lib/key.js +18 -13
- package/lib/lint.js +165 -1
- package/lib/merkle.js +233 -27
- package/lib/ocsp.js +27 -14
- package/lib/oid.js +14 -4
- package/lib/path-validate.js +143 -61
- package/lib/pkcs12-build.js +7 -2
- package/lib/pki-build.js +31 -7
- package/lib/possession.js +575 -0
- package/lib/related-cert.js +708 -0
- package/lib/revocation-fetch.js +6 -6
- package/lib/scep.js +15 -5
- package/lib/schema-c509.js +5 -2
- package/lib/schema-cmc.js +1 -1
- package/lib/schema-cmp.js +8 -8
- package/lib/schema-cms.js +2 -15
- package/lib/schema-csr.js +69 -0
- package/lib/schema-engine.js +10 -3
- package/lib/schema-pkix.js +228 -2
- package/lib/schema-tsp.js +7 -6
- package/lib/sign-scheme.js +22 -9
- package/lib/sigstore.js +684 -168
- package/lib/sleep.js +8 -1
- package/lib/smime.js +22 -1
- package/lib/tlog.js +1235 -0
- package/lib/tls-cert-compress.js +25 -8
- package/lib/tsp-sign.js +120 -35
- package/lib/tuf.js +1395 -0
- package/lib/validator-tpm.js +4 -1
- package/lib/verify-seam.js +7 -1
- package/lib/webauthn-mds.js +41 -4
- package/lib/webauthn.js +43 -25
- package/lib/webcrypto.js +48 -46
- package/lib/x509-sign.js +225 -19
- package/package.json +2 -1
- package/sbom.cdx.json +6 -6
package/lib/http-transport.js
CHANGED
|
@@ -72,6 +72,15 @@ var nodeDns = require("node:dns");
|
|
|
72
72
|
var nodeUtil = require("node:util");
|
|
73
73
|
var constants = require("./constants");
|
|
74
74
|
var guard = require("./guard-all");
|
|
75
|
+
var intrinsic = require("./guard-intrinsic");
|
|
76
|
+
/** @internal `resolve` and `reject` build their promise from the RECEIVER, so reading the global
|
|
77
|
+
* binding at the call hands the construction to whatever that binding holds. A refusal returned as a
|
|
78
|
+
* rejected promise is a verdict: a replacement whose `reject` resolves lets a request the preparation
|
|
79
|
+
* step rejected proceed, and `peerChain`'s refusal to tunnel through a proxy is one of those. Taken at
|
|
80
|
+
* load. */
|
|
81
|
+
var _Promise = intrinsic.Promise;
|
|
82
|
+
var _promiseResolve = intrinsic.uncurry(intrinsic.promiseResolve);
|
|
83
|
+
var _promiseReject = intrinsic.uncurry(intrinsic.promiseReject);
|
|
75
84
|
var frameworkError = require("./framework-error");
|
|
76
85
|
|
|
77
86
|
var TransportError = frameworkError.TransportError;
|
|
@@ -124,6 +133,30 @@ function _tlsInfo(socket) {
|
|
|
124
133
|
return { protocol: proto, cipher: cipher, peerCertificate: peer && peer.raw ? peer.raw : null, tlsUnique: tlsUnique, tlsExporter: tlsExporter };
|
|
125
134
|
}
|
|
126
135
|
|
|
136
|
+
/** @internal The chain the TLS session resolved, leaf first, walked through the detailed peer
|
|
137
|
+
* certificate's `issuerCertificate` links. The walk is bounded twice over: the top certificate
|
|
138
|
+
* issues itself, so the identity comparison ends it, and a cap ends any cycle a runtime could
|
|
139
|
+
* report instead.
|
|
140
|
+
* IT IS NOT THE SAME AS WHAT THE ENDPOINT TRANSMITTED, which this comment used to claim. Node
|
|
141
|
+
* completes `issuerCertificate` from the trust store in scope, so an endpoint that sends its leaf
|
|
142
|
+
* alone still yields a chain whose upper entries came from the configured anchors rather than off
|
|
143
|
+
* the wire. Node exposes no API for the transmitted list, so the distinction cannot be recovered
|
|
144
|
+
* here and is stated instead of guessed at: filtering the entries that match an anchor would drop
|
|
145
|
+
* a root an endpoint really did send. */
|
|
146
|
+
function _peerChain(socket) {
|
|
147
|
+
if (!socket || typeof socket.getPeerCertificate !== "function") return [];
|
|
148
|
+
var out = [];
|
|
149
|
+
var seen = [];
|
|
150
|
+
var cur = socket.getPeerCertificate(true);
|
|
151
|
+
while (cur && Buffer.isBuffer(cur.raw) && out.length < constants.LIMITS.TLS_MAX_PEER_CHAIN) {
|
|
152
|
+
if (seen.indexOf(cur) !== -1) break;
|
|
153
|
+
seen.push(cur);
|
|
154
|
+
out.push(cur.raw);
|
|
155
|
+
cur = cur.issuerCertificate;
|
|
156
|
+
}
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
|
|
127
160
|
function _pemifyAnchor(a) {
|
|
128
161
|
if (typeof a === "string") return a;
|
|
129
162
|
if (Buffer.isBuffer(a)) {
|
|
@@ -557,8 +590,8 @@ function httpsTransport(defaults) {
|
|
|
557
590
|
request = request || {};
|
|
558
591
|
var prep;
|
|
559
592
|
try { prep = _prepare(request); }
|
|
560
|
-
catch (e) { return
|
|
561
|
-
return new
|
|
593
|
+
catch (e) { return _promiseReject(_Promise, e); }
|
|
594
|
+
return new _Promise(function (resolve, reject) {
|
|
562
595
|
var settled = false;
|
|
563
596
|
var req = null;
|
|
564
597
|
var creq = null;
|
|
@@ -625,7 +658,7 @@ function httpsTransport(defaults) {
|
|
|
625
658
|
function _writeBoundBody() {
|
|
626
659
|
if (settled) return;
|
|
627
660
|
var info = _tlsInfo(socket);
|
|
628
|
-
|
|
661
|
+
_promiseResolve(_Promise, undefined).then(function () { return prep.body(info); }).then(function (b) {
|
|
629
662
|
if (settled) return;
|
|
630
663
|
try {
|
|
631
664
|
if (guard.bytes.isByteSource(b)) b = guard.bytes.source(b, E, C("bad-input"), "the channel-binding body");
|
|
@@ -745,7 +778,126 @@ function httpsTransport(defaults) {
|
|
|
745
778
|
});
|
|
746
779
|
};
|
|
747
780
|
_transportFn.blocksPrivateAddresses = true;
|
|
781
|
+
|
|
782
|
+
/** @internal peerChain negotiates TLS and hands back the channel without sending a request. It
|
|
783
|
+
* prepares the connection through the SAME `_prepare` the request path uses, so the anchors, the
|
|
784
|
+
* SNI, the version floor, the identity hook, the private-address block and the URL scheme gate are
|
|
785
|
+
* one policy with two entry points rather than two policies. `tls.connect` takes `host` where
|
|
786
|
+
* `https.request` takes `hostname`, and ignores the method, path and headers `_prepare` also
|
|
787
|
+
* builds. A proxy is refused rather than bypassed: tunnelling belongs to the request path, and
|
|
788
|
+
* connecting directly while a proxy is configured would reach an origin the caller asked not to
|
|
789
|
+
* reach directly. */
|
|
790
|
+
_transportFn.peerChain = function peerChain(request) {
|
|
791
|
+
request = request || {};
|
|
792
|
+
var prep;
|
|
793
|
+
try { prep = _prepare({
|
|
794
|
+
url: request.url,
|
|
795
|
+
method: "GET",
|
|
796
|
+
tls: request.tls,
|
|
797
|
+
timeout: request.timeout,
|
|
798
|
+
blockPrivateAddresses: request.blockPrivateAddresses,
|
|
799
|
+
allowPlaintextHttp: false,
|
|
800
|
+
proxy: request.proxy,
|
|
801
|
+
}); }
|
|
802
|
+
catch (e) { return _promiseReject(_Promise, e); }
|
|
803
|
+
if (prep.proxy) {
|
|
804
|
+
return _promiseReject(_Promise, E(C("bad-proxy"),
|
|
805
|
+
"peerChain opens a direct connection and does not tunnel; fetch through a proxy with the request verb"));
|
|
806
|
+
}
|
|
807
|
+
var o = prep.options;
|
|
808
|
+
var connectOpts = {
|
|
809
|
+
host: o.hostname, port: o.port, minVersion: o.minVersion,
|
|
810
|
+
rejectUnauthorized: o.rejectUnauthorized,
|
|
811
|
+
};
|
|
812
|
+
if (o.servername !== undefined) connectOpts.servername = o.servername;
|
|
813
|
+
if (o.ca !== undefined) connectOpts.ca = o.ca;
|
|
814
|
+
if (o.cert !== undefined) connectOpts.cert = o.cert;
|
|
815
|
+
if (o.key !== undefined) connectOpts.key = o.key;
|
|
816
|
+
if (o.checkServerIdentity !== undefined) connectOpts.checkServerIdentity = o.checkServerIdentity;
|
|
817
|
+
if (o.lookup !== undefined) connectOpts.lookup = o.lookup;
|
|
818
|
+
|
|
819
|
+
return new _Promise(function (resolve, reject) {
|
|
820
|
+
var settled = false;
|
|
821
|
+
var socket;
|
|
822
|
+
var timer = null;
|
|
823
|
+
function done(fn, arg) {
|
|
824
|
+
if (settled) return;
|
|
825
|
+
settled = true;
|
|
826
|
+
if (timer !== null) { clearTimeout(timer); timer = null; }
|
|
827
|
+
try { if (socket) socket.destroy(); } catch (_de) { /* allow:swallow-unverified the socket is being abandoned either way */ }
|
|
828
|
+
fn(arg);
|
|
829
|
+
}
|
|
830
|
+
try { socket = nodeTls.connect(connectOpts); }
|
|
831
|
+
catch (e) {
|
|
832
|
+
reject(E(_classifyError(e, C), "the TLS connection failed: " + e.message, e));
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
/** @internal The budget is an ABSOLUTE deadline, the same timer the request path above uses, and not
|
|
836
|
+
* `socket.setTimeout`: that one bounds INACTIVITY, so what it measures is the gap between bytes
|
|
837
|
+
* rather than the elapsed time a caller asked to be bounded by. A peer that keeps the socket busy
|
|
838
|
+
* without completing the handshake is the case the two differ on. The socket inactivity timeout is
|
|
839
|
+
* kept as well, since it ends a connection that goes silent sooner than the deadline would. */
|
|
840
|
+
timer = setTimeout(function () {
|
|
841
|
+
done(reject, E(C("timeout"), "the TLS handshake did not complete within " + prep.timeout + "ms"));
|
|
842
|
+
}, prep.timeout);
|
|
843
|
+
socket.setTimeout(prep.timeout, function () {
|
|
844
|
+
done(reject, E(C("timeout"), "the TLS handshake did not complete within " + prep.timeout + "ms"));
|
|
845
|
+
});
|
|
846
|
+
socket.once("secureConnect", function () {
|
|
847
|
+
var info = _tlsInfo(socket);
|
|
848
|
+
info.peerChain = _peerChain(socket);
|
|
849
|
+
done(resolve, info);
|
|
850
|
+
});
|
|
851
|
+
socket.once("error", function (e) {
|
|
852
|
+
var code = _classifyError(e, C);
|
|
853
|
+
done(reject, E(code || C("tls-failed"), "the TLS connection failed: " + e.message, e));
|
|
854
|
+
});
|
|
855
|
+
});
|
|
856
|
+
};
|
|
857
|
+
|
|
748
858
|
return _transportFn;
|
|
749
859
|
}
|
|
750
860
|
|
|
751
|
-
|
|
861
|
+
/**
|
|
862
|
+
* @primitive pki.transport.peerChain
|
|
863
|
+
* @signature pki.transport.peerChain(request, defaults?) -> Promise<channel>
|
|
864
|
+
* @since 0.8.32
|
|
865
|
+
* @status stable
|
|
866
|
+
* @spec RFC 8446, RFC 6125
|
|
867
|
+
* @defends server-impersonation (CWE-297), tls-downgrade (CWE-757), ssrf (CWE-918)
|
|
868
|
+
* @related pki.transport.https, pki.path.validate
|
|
869
|
+
*
|
|
870
|
+
* Negotiate TLS with an endpoint and return the channel it established, without sending a
|
|
871
|
+
* request: `{ protocol, cipher, peerCertificate, peerChain, tlsUnique, tlsExporter }`.
|
|
872
|
+
* `peerChain` is the chain the session resolved as DER, leaf first, walked up from the peer
|
|
873
|
+
* certificate and stopping at the one that issues itself. It is not a record of what crossed the
|
|
874
|
+
* wire: the runtime completes the chain from the trust anchors in scope, so an endpoint that sends
|
|
875
|
+
* only its leaf still yields the entries above it, and those came from the configured anchors.
|
|
876
|
+
* Treat the list as the chain this session built, and read `peerCertificate` for the one
|
|
877
|
+
* certificate the endpoint certainly sent. Nothing reaches the application behind the endpoint,
|
|
878
|
+
* which is the difference from making a request for the same purpose.
|
|
879
|
+
*
|
|
880
|
+
* The connection is prepared by the same code `pki.transport.https` prepares a request with,
|
|
881
|
+
* so the trust anchors, the SNI, the version floor, the `checkServerIdentity` hook, the
|
|
882
|
+
* private-address block and the https-only rule are one policy rather than a second copy of
|
|
883
|
+
* one. A request with neither an explicit anchor nor `useSystemStore` is refused
|
|
884
|
+
* (`transport/no-trust-anchors`) before any socket is opened, and an endpoint whose chain does
|
|
885
|
+
* not build to the configured anchors, or whose name does not match the URL, is refused
|
|
886
|
+
* (`transport/server-auth-failed`). A configured proxy is refused (`transport/bad-proxy`)
|
|
887
|
+
* rather than bypassed, since connecting directly would reach an origin the caller asked to
|
|
888
|
+
* reach through the proxy.
|
|
889
|
+
*
|
|
890
|
+
* **A chain this returns is not a validated one.** The handshake checked that it builds to a
|
|
891
|
+
* configured anchor and matches the name in the URL, and checked no revocation status and no
|
|
892
|
+
* policy. `pki.path.validate` is what validates a path.
|
|
893
|
+
*
|
|
894
|
+
* @example
|
|
895
|
+
* var channel = await pki.transport.peerChain(
|
|
896
|
+
* { url: "https://example.com/" }, { tls: { useSystemStore: true } });
|
|
897
|
+
* channel.peerChain.length; // the chain the session resolved
|
|
898
|
+
* pki.schema.x509.parse(channel.peerChain[0]).subject.dn; // the leaf's subject
|
|
899
|
+
* channel.peerCertificate; // the one certificate the endpoint sent
|
|
900
|
+
*/
|
|
901
|
+
function peerChain(request, defaults) { return httpsTransport(defaults || {}).peerChain(request); }
|
|
902
|
+
|
|
903
|
+
module.exports = { https: httpsTransport, peerChain: peerChain, isBlockedIp: _isBlockedIp, _makeGuardedLookup: _makeGuardedLookup, MAX_TIMEOUT: MAX_TIMEOUT, snapshotProxy: snapshotProxy };
|
package/lib/identity-match.js
CHANGED
|
@@ -446,8 +446,8 @@ function _search(refs, entries, wildcardsAllowed) {
|
|
|
446
446
|
pairs.tick();
|
|
447
447
|
var got = _compare(refs[r], entries[e], wildcardsAllowed);
|
|
448
448
|
if (got.disposition === "match") {
|
|
449
|
-
return { matched: true, matchedReference: refs[r].input, matchedEntry: _entryLabel(entries[e]),
|
|
450
|
-
ignored: ignored, reason: null };
|
|
449
|
+
return guard.verdict.of({ matched: true, matchedReference: refs[r].input, matchedEntry: _entryLabel(entries[e]),
|
|
450
|
+
ignored: ignored, reason: null });
|
|
451
451
|
}
|
|
452
452
|
if (got.disposition === "ignored") {
|
|
453
453
|
var key = e + "|" + got.reason;
|
|
@@ -458,10 +458,10 @@ function _search(refs, entries, wildcardsAllowed) {
|
|
|
458
458
|
}
|
|
459
459
|
}
|
|
460
460
|
}
|
|
461
|
-
return { matched: false, matchedReference: null, matchedEntry: null, ignored: ignored,
|
|
461
|
+
return guard.verdict.of({ matched: false, matchedReference: null, matchedEntry: null, ignored: ignored,
|
|
462
462
|
reason: entries.length === 0
|
|
463
463
|
? "the certificate presents no subjectAltName entry, and RFC 9525 sec. 2 forbids reading an identity from the subject"
|
|
464
|
-
: "no presented identifier matched a reference identity" };
|
|
464
|
+
: "no presented identifier matched a reference identity" });
|
|
465
465
|
}
|
|
466
466
|
|
|
467
467
|
/**
|
package/lib/inspect.js
CHANGED
|
@@ -409,6 +409,29 @@ var EXT_RENDERERS = Object.assign(Object.create(null), {
|
|
|
409
409
|
ocspNoCheck: function (decoded, inner) {
|
|
410
410
|
return inner + "OCSP No Check (this is an OCSP responder certificate; do not check its revocation)";
|
|
411
411
|
},
|
|
412
|
+
/** @internal ITU-T X.509 (2019) clause 9.8. The alternative key and signature run to thousands of
|
|
413
|
+
* octets for a post-quantum algorithm, which is the case the extensions exist for, so each is
|
|
414
|
+
* reported by its algorithm and its length rather than dumped whole. The algorithm is what a reader
|
|
415
|
+
* needs: it says which set of algorithms this certificate is migrating to. */
|
|
416
|
+
subjectAltPublicKeyInfo: function (decoded, inner) {
|
|
417
|
+
return inner + "Algorithm: " + (decoded.algorithm.name || decoded.algorithm.oid) + "\n" +
|
|
418
|
+
inner + "Alternative Public Key: " + decoded.subjectAltPublicKey.length + " bytes";
|
|
419
|
+
},
|
|
420
|
+
altSignatureAlgorithm: function (decoded, inner) {
|
|
421
|
+
return inner + (decoded.name || decoded.oid);
|
|
422
|
+
},
|
|
423
|
+
altSignatureValue: function (decoded, inner) {
|
|
424
|
+
return inner + "Alternative Signature: " + decoded.bytes.length + " bytes";
|
|
425
|
+
},
|
|
426
|
+
/** @internal RFC 9763 sec. 4.1 names a second certificate of the same subject by the digest of the
|
|
427
|
+
* whole certificate, so both fields are shown: the digest is only meaningful beside the algorithm
|
|
428
|
+
* that produced it. */
|
|
429
|
+
relatedCertificate: function (decoded, inner) {
|
|
430
|
+
var alg = decoded.hashAlgorithm || decoded.hashAlgorithmOid || "(unnamed algorithm)";
|
|
431
|
+
var value = Buffer.isBuffer(decoded.hashValue) ? decoded.hashValue : Buffer.alloc(0);
|
|
432
|
+
return inner + "Hash Algorithm: " + alg + "\n" +
|
|
433
|
+
inner + "Related Certificate: " + _hexColon(value, { upper: true });
|
|
434
|
+
},
|
|
412
435
|
subjectKeyIdentifier: function (decoded, inner) {
|
|
413
436
|
return inner + _hexColon(Buffer.isBuffer(decoded) ? decoded : (decoded.bytes || Buffer.alloc(0)), { upper: true });
|
|
414
437
|
},
|
|
@@ -419,8 +442,8 @@ var EXT_RENDERERS = Object.assign(Object.create(null), {
|
|
|
419
442
|
decoded.authorityCertIssuer.names.forEach(function (g) { akiLines.push(inner + _gn(g)); });
|
|
420
443
|
}
|
|
421
444
|
if (decoded.authorityCertSerialNumber != null) {
|
|
422
|
-
var sn = (
|
|
423
|
-
|
|
445
|
+
var sn = guard.range.authoredInteger(decoded.authorityCertSerialNumber, _err,
|
|
446
|
+
"inspect/bad-input", "authorityCertSerialNumber").toString(16);
|
|
424
447
|
if (sn.length % 2) sn = "0" + sn;
|
|
425
448
|
akiLines.push(inner + "serial:0x" + sn.toUpperCase());
|
|
426
449
|
}
|
|
@@ -2350,12 +2373,16 @@ function asn1Dump(input, opts) {
|
|
|
2350
2373
|
var der;
|
|
2351
2374
|
try { der = pkix.coerceToDer(input, _INSPECT_ENTRY); }
|
|
2352
2375
|
catch (e) { throw _err("inspect/bad-input", "input must be a DER Buffer or a PEM block", e); }
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2376
|
+
/** @internal Read once, and safe to read off `der` because `pkix.coerceToDer` above has already
|
|
2377
|
+
* normalized the caller's input: an own `length` property shadowing the real byte count cannot reach
|
|
2378
|
+
* here. The cap below is therefore a cap on the bytes the walk decodes. */
|
|
2379
|
+
var derLen = der.length;
|
|
2380
|
+
if (derLen === 0) throw _err("inspect/bad-input", "input is empty");
|
|
2381
|
+
if (derLen > maxBytes) {
|
|
2382
|
+
throw _err("inspect/bad-input", "input " + derLen + " bytes exceeds the cap " + maxBytes);
|
|
2356
2383
|
}
|
|
2357
2384
|
|
|
2358
|
-
var head = ["ASN.1 structure: " +
|
|
2385
|
+
var head = ["ASN.1 structure: " + derLen + " bytes, decoded as DER"];
|
|
2359
2386
|
var node = null;
|
|
2360
2387
|
var refusal = null;
|
|
2361
2388
|
/** @internal The decoder builds the whole node tree before the renderer counts a line, so the
|
package/lib/jose.js
CHANGED
|
@@ -39,6 +39,10 @@
|
|
|
39
39
|
var constants = require("./constants");
|
|
40
40
|
var wcEngine = require("./webcrypto");
|
|
41
41
|
var webcrypto = wcEngine.webcrypto;
|
|
42
|
+
/** @internal The verify operation, captured at load. The object was held and its METHOD fetched per
|
|
43
|
+
* call, which leaves it replaceable: one answering true accepts a JWS whose signer never signed it,
|
|
44
|
+
* including on the embedded-key route this module pins against its thumbprint. */
|
|
45
|
+
var _subtleVerify = require("./guard-intrinsic").uncurry(webcrypto.subtle.verify);
|
|
42
46
|
var frameworkError = require("./framework-error");
|
|
43
47
|
var guard = require("./guard-all");
|
|
44
48
|
var edwardsPoint = require("./edwards-point");
|
|
@@ -121,11 +125,11 @@ function b64uDecode(text) {
|
|
|
121
125
|
* pki.jose.parseJson('{"a":1}'); // -> { a: 1 }
|
|
122
126
|
*/
|
|
123
127
|
function parseJson(input) {
|
|
124
|
-
return guard.json.parse(input, E, {
|
|
128
|
+
return guard.verdict.shield(guard.json.parse(input, E, {
|
|
125
129
|
maxBytes: LIMITS.JSON_MAX_BYTES, maxDepth: LIMITS.JSON_MAX_DEPTH,
|
|
126
130
|
badJson: "jose/bad-json", tooDeep: "jose/too-deep", duplicateMember: "jose/duplicate-member",
|
|
127
131
|
tooLarge: "jose/too-large", badInput: "jose/bad-input", label: "the JSON document",
|
|
128
|
-
});
|
|
132
|
+
}));
|
|
129
133
|
}
|
|
130
134
|
|
|
131
135
|
|
|
@@ -329,9 +333,18 @@ async function verify(jws, opts) {
|
|
|
329
333
|
if (!jws || typeof jws !== "object" || _isArray(jws)) throw E("jose/bad-jws", "a flattened JWS object is required");
|
|
330
334
|
if (_hasOwn(jws, "signatures")) throw E("jose/bad-jws", "the multi-signature signatures member is forbidden");
|
|
331
335
|
if (_hasOwn(jws, "header")) throw E("jose/bad-jws", "the unprotected header member is forbidden");
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
336
|
+
/** @internal The three members are taken ONCE, for the reason `opts` is above: the JWS is the caller's
|
|
337
|
+
* object, and a member reached through an accessor answers every read separately. `protected` decides
|
|
338
|
+
* both the header the profile rules are applied to and the signing input the signature is checked over,
|
|
339
|
+
* and the thumbprint comparison awaits between those two uses, so reading it twice would let the rules
|
|
340
|
+
* be applied to one message and the signature checked over another. Everything below reads these
|
|
341
|
+
* locals. */
|
|
342
|
+
var protectedB64 = jws.protected;
|
|
343
|
+
var signatureB64 = jws.signature;
|
|
344
|
+
var payloadB64 = jws.payload;
|
|
345
|
+
if (typeof protectedB64 !== "string" || typeof signatureB64 !== "string") throw E("jose/bad-jws", "protected and signature must be base64url strings");
|
|
346
|
+
if (typeof payloadB64 !== "string") throw E("jose/bad-jws", "payload must be a base64url string (never detached)");
|
|
347
|
+
var header = parseJson(b64uDecode(protectedB64));
|
|
335
348
|
var profileName = opts.profile || "acme-outer";
|
|
336
349
|
var checked = _checkHeader(header, profileName);
|
|
337
350
|
/** @internal The embedded jwk is the signer's PUBLIC key (RFC 7515 sec. 4.1.3). One carrying
|
|
@@ -342,14 +355,32 @@ async function verify(jws, opts) {
|
|
|
342
355
|
if (opts.key !== undefined && (typeof opts.key !== "object" || opts.key === null || _isArray(opts.key))) {
|
|
343
356
|
throw E("jose/bad-key", "opts.key was supplied but is not a JWK object, so the key this message must be signed under cannot be established");
|
|
344
357
|
}
|
|
345
|
-
|
|
358
|
+
/** @internal The caller's key is COPIED here, before ANY member of it is read, and everything below
|
|
359
|
+
* reads the copy: the thumbprint comparison against the embedded jwk, the key-type check, the Edwards
|
|
360
|
+
* low-order point gate, the signature-width calculation, the import parameters and the algorithm
|
|
361
|
+
* selector. Its members were read many times over, `crv` eight times and `kty` nine on one successful
|
|
362
|
+
* verify, and an accessor answers each read separately.
|
|
363
|
+
*
|
|
364
|
+
* The FIRST read is the one that decides which curve the point gate validates against
|
|
365
|
+
* (`jwk.crv === "Ed25519" ? 6 : 7`), so a `crv` answering `Ed448` once and `Ed25519` afterwards had the
|
|
366
|
+
* point gated under the wrong curve's parameters while an Ed25519 key was imported and verified. That
|
|
367
|
+
* gate is the whole defense against a low-order key verifying a FORGED signature. Copying before the
|
|
368
|
+
* thumbprint comparison rather than after it matters for the same reason: otherwise a key could
|
|
369
|
+
* thumbprint as the embedded one and be copied as a different one.
|
|
370
|
+
*
|
|
371
|
+
* `snapshotDeep` refuses an accessor-backed member rather than reading it once, which is the stronger
|
|
372
|
+
* answer for a value that selects an algorithm. The embedded jwk comes from the parsed header and is
|
|
373
|
+
* this module's own object, so it needs no copy. */
|
|
374
|
+
var suppliedKey = opts.key === undefined ? undefined
|
|
375
|
+
: guard.bytes.snapshotDeep(opts.key, JoseError, "jose/bad-key", "opts.key");
|
|
376
|
+
var jwk = header.jwk || suppliedKey;
|
|
346
377
|
if (!jwk) throw E("jose/bad-key", "a verification key is required (opts.key) when the profile does not embed a jwk");
|
|
347
378
|
var keySource = "embedded-jwk";
|
|
348
|
-
if (
|
|
379
|
+
if (suppliedKey) {
|
|
349
380
|
keySource = "opts.key";
|
|
350
381
|
if (header.jwk) {
|
|
351
382
|
var embeddedTp, suppliedTp;
|
|
352
|
-
try { embeddedTp = await thumbprint(header.jwk); suppliedTp = await thumbprint(
|
|
383
|
+
try { embeddedTp = await thumbprint(header.jwk); suppliedTp = await thumbprint(suppliedKey); }
|
|
353
384
|
catch (e) { throw E("jose/bad-key", "the embedded jwk and opts.key could not be compared as RFC 7638 thumbprints", e); }
|
|
354
385
|
if (embeddedTp !== suppliedTp) {
|
|
355
386
|
throw E("jose/key-mismatch",
|
|
@@ -358,19 +389,19 @@ async function verify(jws, opts) {
|
|
|
358
389
|
", supplied " + suppliedTp + ")");
|
|
359
390
|
}
|
|
360
391
|
}
|
|
361
|
-
jwk =
|
|
392
|
+
jwk = suppliedKey;
|
|
362
393
|
}
|
|
363
394
|
_assertKeyType(checked.algRow, jwk);
|
|
364
|
-
var sig = b64uDecode(
|
|
395
|
+
var sig = b64uDecode(signatureB64);
|
|
365
396
|
var want = _expectedSigBytes(checked.algRow, jwk);
|
|
366
397
|
if (want != null && sig.length !== want) throw E("jose/bad-signature", "the signature length " + sig.length + " is not the " + want + " bytes " + checked.alg + " requires");
|
|
367
|
-
var signingInput = _bufferFrom(
|
|
398
|
+
var signingInput = _bufferFrom(protectedB64 + "." + payloadB64, "ascii");
|
|
368
399
|
var key;
|
|
369
400
|
try { key = await webcrypto.subtle.importKey("jwk", jwk, _importParams(checked.algRow, jwk), false, ["verify"]); }
|
|
370
401
|
catch (e) { throw E("jose/bad-key", "the JWK could not be imported for verification", e); }
|
|
371
|
-
var ok = await webcrypto.subtle
|
|
402
|
+
var ok = await _subtleVerify(webcrypto.subtle, _cryptoAlg(checked.algRow, jwk), key, sig, signingInput);
|
|
372
403
|
if (!ok) throw E("jose/verify-failed", "the JWS signature did not verify");
|
|
373
|
-
return { header: header, payload: b64uDecode(
|
|
404
|
+
return { header: header, payload: b64uDecode(payloadB64), keySource: keySource };
|
|
374
405
|
}
|
|
375
406
|
|
|
376
407
|
|
|
@@ -409,9 +440,23 @@ async function verify(jws, opts) {
|
|
|
409
440
|
*/
|
|
410
441
|
var _SIGN_KEYS = intrinsic.assign(intrinsic.create(null), { protected: 1, payload: 1, key: 1, jwk: 1, profile: 1 });
|
|
411
442
|
var _SIGN_KEYS_EMBEDDED_JWK = intrinsic.assign(intrinsic.create(null), { protected: 1, payload: 1, key: 1, profile: 1 });
|
|
443
|
+
|
|
444
|
+
/** @internal The payload, copied, taken before anything else on the caller's object is read. Every later
|
|
445
|
+
* read of that object is caller code when the field is an accessor, and the protected header is read
|
|
446
|
+
* FIRST and SERIALIZED, which reaches the header object, each of its fields and any `toJSON` on it. Over
|
|
447
|
+
* a view of the payload any of those would replace what gets signed, after the verb had accepted the
|
|
448
|
+
* payload it was handed, so the copy comes before all of them rather than before whichever one is in
|
|
449
|
+
* view. A payload that is not a Buffer is carried through untouched and refused where it always was, so
|
|
450
|
+
* no diagnostic moves ahead of the header's own. */
|
|
451
|
+
function _fixedPayload(supplied) {
|
|
452
|
+
if (!_isBuffer(supplied)) return supplied;
|
|
453
|
+
return guard.bytes.snapshot(supplied, JoseError, "jose/bad-input", "payload");
|
|
454
|
+
}
|
|
455
|
+
|
|
412
456
|
async function sign(opts) {
|
|
413
457
|
opts = opts || {};
|
|
414
458
|
guard.identifier.assertKnownKeys(opts, _SIGN_KEYS, E, "jose/bad-input", "unknown pki.jose.sign option ");
|
|
459
|
+
var fixedPayload = _fixedPayload(opts.payload);
|
|
415
460
|
var headerJson;
|
|
416
461
|
try { headerJson = _stringify(opts.protected); }
|
|
417
462
|
catch (e) { throw E("jose/bad-header", "the protected header must be JSON-serializable", e); }
|
|
@@ -424,13 +469,25 @@ async function sign(opts) {
|
|
|
424
469
|
(k === "jwk" ? "; the protected header already embeds the public JWK" : "");
|
|
425
470
|
});
|
|
426
471
|
}
|
|
427
|
-
var payload =
|
|
472
|
+
var payload = fixedPayload;
|
|
428
473
|
if (!_isBuffer(payload)) throw E("jose/bad-input", "payload must be a Buffer (empty Buffer for POST-as-GET)");
|
|
429
|
-
|
|
430
|
-
|
|
474
|
+
/** @internal ONE read of the caller's signing key, taken before anything inspects it, and every use
|
|
475
|
+
* below reads this binding. It was read four times: the key-like test, twice by the hash-to-algorithm
|
|
476
|
+
* check, and once more to sign with. An accessor answers each read separately, so the check could
|
|
477
|
+
* approve a SHA-256 key while the signature was produced by a SHA-512 one, and the JWS emitted then
|
|
478
|
+
* DECLARES an algorithm its signature was not made under, which is the algorithm confusion the header
|
|
479
|
+
* registry exists to close before any crypto call.
|
|
480
|
+
*
|
|
481
|
+
* The RSA arms are where it showed: the registry's fixed signature widths catch the HMAC and EC arms,
|
|
482
|
+
* and two 2048-bit RSA keys produce the same 256-byte signature, so the width pin below cannot see a
|
|
483
|
+
* substitution between them. */
|
|
484
|
+
var suppliedKey = opts.key;
|
|
485
|
+
if (!wcEngine.isCryptoKeyLike(suppliedKey)) throw E("jose/bad-input", "a private or secret CryptoKey (opts.key) is required");
|
|
431
486
|
var checked = _checkHeader(header, opts.profile || "acme-outer");
|
|
487
|
+
var suppliedAlg = suppliedKey.algorithm;
|
|
488
|
+
var suppliedHash = suppliedAlg && suppliedAlg.hash;
|
|
432
489
|
if ((checked.algRow.kty === "RSA" || checked.algRow.kty === "oct") &&
|
|
433
|
-
(!
|
|
490
|
+
(!suppliedHash || suppliedHash.name !== checked.algRow.hash)) {
|
|
434
491
|
throw E("jose/bad-key", "the signing key's hash does not match the algorithm " + checked.alg);
|
|
435
492
|
}
|
|
436
493
|
if (header.jwk) { _assertKeyType(checked.algRow, header.jwk); assertPublicJwk(header.jwk); }
|
|
@@ -438,15 +495,18 @@ async function sign(opts) {
|
|
|
438
495
|
var payloadB64 = payload.length === 0 ? "" : b64uEncode(payload);
|
|
439
496
|
var signingInput = _bufferFrom(protectedB64 + "." + payloadB64, "ascii");
|
|
440
497
|
var jwk = header.jwk || opts.jwk || {};
|
|
441
|
-
|
|
498
|
+
/** @internal `suppliedKey` is the reading taken at the top, so the key adopted for signing, the key the
|
|
499
|
+
* hash-to-algorithm check approved and the key the expected signature width is computed from are all
|
|
500
|
+
* one key. */
|
|
501
|
+
var signKey = await wcEngine.adoptKey(suppliedKey, null, ["sign"], E, "jose/bad-input");
|
|
442
502
|
var sigBuf = _bufferFrom(await webcrypto.subtle.sign(_cryptoAlg(checked.algRow, jwk, signKey), signKey, signingInput));
|
|
443
|
-
var want = _expectedSigBytes(checked.algRow, jwk,
|
|
503
|
+
var want = _expectedSigBytes(checked.algRow, jwk, suppliedKey);
|
|
444
504
|
if (want != null && sigBuf.length !== want) throw E("jose/bad-key", "the signing key produced a " + sigBuf.length + "-byte signature but " + checked.alg + " requires " + want + " (the key does not match the algorithm)");
|
|
445
505
|
if (header.jwk) {
|
|
446
506
|
var pubKey;
|
|
447
507
|
try { pubKey = await webcrypto.subtle.importKey("jwk", header.jwk, _importParams(checked.algRow, header.jwk), false, ["verify"]); }
|
|
448
508
|
catch (e) { throw E("jose/bad-key", "the embedded jwk could not be imported to confirm it matches the signing key", e); }
|
|
449
|
-
if (!(await webcrypto.subtle
|
|
509
|
+
if (!(await _subtleVerify(webcrypto.subtle, _cryptoAlg(checked.algRow, header.jwk), pubKey, sigBuf, signingInput))) {
|
|
450
510
|
throw E("jose/bad-key", "the embedded jwk does not match the signing key (it cannot verify the produced signature)");
|
|
451
511
|
}
|
|
452
512
|
}
|
|
@@ -485,14 +545,22 @@ var THUMBPRINT_MEMBERS = intrinsic.assign(intrinsic.create(null), {
|
|
|
485
545
|
* await pki.jose.thumbprint(accountJwk); // -> "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs"
|
|
486
546
|
*/
|
|
487
547
|
async function thumbprint(jwk) {
|
|
488
|
-
if (!jwk || typeof jwk !== "object"
|
|
489
|
-
|
|
490
|
-
|
|
548
|
+
if (!jwk || typeof jwk !== "object") throw E("jose/bad-key", "a JWK object with a kty is required");
|
|
549
|
+
/** @internal COPIED before any member is read, because what this returns is an IDENTITY. Each member
|
|
550
|
+
* was read twice, the type check and then the value put into the hash, and `kty` four times; an
|
|
551
|
+
* accessor answers each read separately, so a member could be checked as one value and HASHED as
|
|
552
|
+
* another. The thumbprint is what RFC 7638 identifies a key by: `pki.jose.verify` compares an embedded
|
|
553
|
+
* jwk against `opts.key` with it, and ACME uses it as the account identity in a key authorization. A
|
|
554
|
+
* thumbprint over a value the checks never approved names a key that was never the one validated. */
|
|
555
|
+
var copied = guard.bytes.snapshotDeep(jwk, JoseError, "jose/bad-key", "the JWK");
|
|
556
|
+
if (typeof copied.kty !== "string") throw E("jose/bad-key", "a JWK object with a kty is required");
|
|
557
|
+
var members = THUMBPRINT_MEMBERS[copied.kty];
|
|
558
|
+
if (!members) throw E("jose/bad-key", "unsupported JWK key type " + _stringify(copied.kty));
|
|
491
559
|
var parts = [];
|
|
492
560
|
for (var i = 0; i < members.length; i++) {
|
|
493
561
|
var m = members[i];
|
|
494
|
-
if (typeof
|
|
495
|
-
_push(parts, _stringify(m) + ":" + _stringify(
|
|
562
|
+
if (typeof copied[m] !== "string") throw E("jose/bad-key", "the JWK is missing the required member " + _stringify(m));
|
|
563
|
+
_push(parts, _stringify(m) + ":" + _stringify(copied[m]));
|
|
496
564
|
}
|
|
497
565
|
var canonical = "{" + _join(parts, ",") + "}";
|
|
498
566
|
var digest = _bufferFrom(await webcrypto.subtle.digest("SHA-256", _bufferFrom(canonical, "utf8")));
|
package/lib/key.js
CHANGED
|
@@ -30,6 +30,11 @@ var schemaSec1 = require("./schema-sec1");
|
|
|
30
30
|
var pkix = require("./schema-pkix");
|
|
31
31
|
var schema = require("./schema-engine");
|
|
32
32
|
var webcrypto = require("./webcrypto");
|
|
33
|
+
/** @internal The key-material exporter, captured at load. The signature probe that decides whether two
|
|
34
|
+
* halves are a pair imports and signs with what this returns, so a replacement exporting a different
|
|
35
|
+
* private key makes a mismatched pair report as corresponding: the probe then signs and verifies with
|
|
36
|
+
* a pair that does correspond, and neither half is the one the caller supplied. */
|
|
37
|
+
var _exportAnyKey = webcrypto.exportAnyKey;
|
|
33
38
|
var compositeKem = require("./composite-kem");
|
|
34
39
|
var frameworkError = require("./framework-error");
|
|
35
40
|
var guard = require("./guard-all");
|
|
@@ -123,7 +128,7 @@ function _sec1ToPkcs8(der, curveOid) {
|
|
|
123
128
|
async function _toPrivateKeyDer(input) {
|
|
124
129
|
if (_isCryptoKey(input)) {
|
|
125
130
|
if (input.type !== "private") throw _err("key/bad-input", "a private CryptoKey is required (got a " + input.type + " key)");
|
|
126
|
-
return
|
|
131
|
+
return _exportAnyKey(input, _err, "key/bad-input");
|
|
127
132
|
}
|
|
128
133
|
return pkix.coerceToDer(input, { pemLabel: "PRIVATE KEY", PemError: PemError, ErrorClass: KeyError, prefix: "key" });
|
|
129
134
|
}
|
|
@@ -295,13 +300,13 @@ async function export_(key, opts) {
|
|
|
295
300
|
var fmt = opts.format || "der";
|
|
296
301
|
if (fmt === "jwk") {
|
|
297
302
|
try {
|
|
298
|
-
return await
|
|
303
|
+
return await _exportAnyKey(key, _err, "key/bad-input", "jwk");
|
|
299
304
|
} catch (e) {
|
|
300
305
|
if (e && e.isPkiError) throw e;
|
|
301
306
|
throw _err("key/bad-input", "exporting the key as a JWK failed", e);
|
|
302
307
|
}
|
|
303
308
|
}
|
|
304
|
-
var der = await
|
|
309
|
+
var der = await _exportAnyKey(key, _err, "key/bad-input");
|
|
305
310
|
if (fmt === "der") return der;
|
|
306
311
|
if (fmt === "pem") return pkix.pemEncode(der, opts.label || defaultLabel, PemError);
|
|
307
312
|
if (fmt === "pkcs1") return _toPkcs1(der, key);
|
|
@@ -870,8 +875,8 @@ async function publicFromPrivate(privateKey, opts) {
|
|
|
870
875
|
return opts.pem ? pkix.pemEncode(spki, "PUBLIC KEY", PemError) : spki;
|
|
871
876
|
}
|
|
872
877
|
try {
|
|
873
|
-
var priv =
|
|
874
|
-
spki =
|
|
878
|
+
var priv = guard.crypto.privateKey({ key: der, format: "der", type: "pkcs8" });
|
|
879
|
+
spki = guard.crypto.exportKey(guard.crypto.publicKey(priv), { format: "der", type: "spki" });
|
|
875
880
|
} catch (e) { throw _err("key/bad-input", "could not derive the public key from the private key", e); }
|
|
876
881
|
return opts.pem ? pkix.pemEncode(spki, "PUBLIC KEY", PemError) : spki;
|
|
877
882
|
}
|
|
@@ -946,7 +951,7 @@ function _pairSignOptions(priv, pub) {
|
|
|
946
951
|
/** @internal Null-prototype, because the engine READS this object by name. A plain object would
|
|
947
952
|
* let an inherited `saltLength` reach the signature as a restriction neither half stated. */
|
|
948
953
|
var options = _create(null);
|
|
949
|
-
options.padding =
|
|
954
|
+
options.padding = guard.crypto.padding.RSA_PKCS1_PSS;
|
|
950
955
|
if (salt != null) options.saltLength = salt;
|
|
951
956
|
return { digest: digest, options: options };
|
|
952
957
|
}
|
|
@@ -1006,7 +1011,7 @@ async function correspondsTo(privateKey, spkiDer) {
|
|
|
1006
1011
|
var privAsPkcs3 = _dhAsPkcs3(privDer, 1, "the private key's");
|
|
1007
1012
|
try {
|
|
1008
1013
|
_assertDhWithin(privAsPkcs3, 1, "the private key's");
|
|
1009
|
-
try { priv =
|
|
1014
|
+
try { priv = guard.crypto.privateKey({ key: privAsPkcs3, format: "der", type: "pkcs8" }); }
|
|
1010
1015
|
catch (e) { throw _err("key/bad-input", "the private key could not be read", e); }
|
|
1011
1016
|
} finally { if (privAsPkcs3 !== privDer) guard.secret.zeroize(privAsPkcs3, KeyError, "key/bad-input", "the rewritten private key"); }
|
|
1012
1017
|
var publicOid = _algorithmOidAt(spki, 0);
|
|
@@ -1014,7 +1019,7 @@ async function correspondsTo(privateKey, spkiDer) {
|
|
|
1014
1019
|
if (_isDhOid(_algorithmOidAt(privDer, 1)) !== _isDhOid(publicOid)) return false;
|
|
1015
1020
|
var pubAsPkcs3 = _dhAsPkcs3(spki, 0, "the public key's");
|
|
1016
1021
|
_assertDhWithin(pubAsPkcs3, 0, "the public key's");
|
|
1017
|
-
try { pub =
|
|
1022
|
+
try { pub = guard.crypto.publicKey({ key: pubAsPkcs3, format: "der", type: "spki" }); }
|
|
1018
1023
|
catch (e) { throw _err("key/bad-input", "the public key could not be read", e); }
|
|
1019
1024
|
if (!_sameKeyFamily(priv, pub)) return false;
|
|
1020
1025
|
var kind = guard.text.keyOf(priv.asymmetricKeyType);
|
|
@@ -1029,7 +1034,7 @@ async function correspondsTo(privateKey, spkiDer) {
|
|
|
1029
1034
|
/** @internal Compared as KEYS rather than as encodings: a PKCS #3 DHParameter may carry the
|
|
1030
1035
|
* optional privateValueLength beside the same p and g, and two encodings of one public value
|
|
1031
1036
|
* are one key. The runtime's equality compares the group and the value. */
|
|
1032
|
-
return _keyEquals(
|
|
1037
|
+
return _keyEquals(guard.crypto.publicKey(priv), pub) === true;
|
|
1033
1038
|
}
|
|
1034
1039
|
if (_PAIR_KEM[kind]) {
|
|
1035
1040
|
var sealed = nodeCrypto.encapsulate(pub);
|
|
@@ -1053,8 +1058,8 @@ async function correspondsTo(privateKey, spkiDer) {
|
|
|
1053
1058
|
* structure for an attacker to plant, so what the scalar generates IS the canonical answer and
|
|
1054
1059
|
* comparing against it adds a bound the agreement alone does not give. */
|
|
1055
1060
|
return guard.crypto.constantTimeEqual(
|
|
1056
|
-
|
|
1057
|
-
|
|
1061
|
+
guard.crypto.exportKey(guard.crypto.publicKey(priv), { format: "der", type: "spki" }),
|
|
1062
|
+
guard.crypto.exportKey(pub, { format: "der", type: "spki" }));
|
|
1058
1063
|
}
|
|
1059
1064
|
var how = _pairSignOptions(priv, pub);
|
|
1060
1065
|
if (how === null) {
|
|
@@ -1062,8 +1067,8 @@ async function correspondsTo(privateKey, spkiDer) {
|
|
|
1062
1067
|
}
|
|
1063
1068
|
var signKey = how.options ? _assign(_create(null), { key: priv }, how.options) : priv;
|
|
1064
1069
|
var verifyKey = how.options ? _assign(_create(null), { key: pub }, how.options) : pub;
|
|
1065
|
-
return
|
|
1066
|
-
|
|
1070
|
+
return guard.crypto.verify(how.digest, _PAIR_PROBE, verifyKey,
|
|
1071
|
+
guard.crypto.sign(how.digest, _PAIR_PROBE, signKey)) === true;
|
|
1067
1072
|
} catch (e) {
|
|
1068
1073
|
/** @internal The incompatible-parameters verdict is decided rather than caught, so it is passed
|
|
1069
1074
|
* through with the reason it was raised for. Rewrapping it would tell an operator the pair could
|