@blamejs/pki 0.5.39 → 0.6.1
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 +23 -1
- package/README.md +7 -6
- package/lib/acme.js +23 -23
- package/lib/cmc-verify.js +2 -2
- package/lib/cmp-build.js +2 -2
- package/lib/cmp-session.js +1 -1
- package/lib/ct.js +279 -22
- package/lib/est.js +11 -11
- package/lib/hpke.js +4 -4
- package/lib/http-transport.js +1 -1
- package/lib/lint.js +3 -3
- package/lib/ocsp-request-verify.js +28 -0
- package/lib/ocsp.js +156 -0
- package/lib/path-validate.js +76 -5
- package/lib/schema-c509.js +2 -2
- package/lib/schema-cmc.js +3 -3
- package/lib/sigstore.js +3 -3
- package/lib/webauthn.js +3 -3
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/ct.js
CHANGED
|
@@ -154,7 +154,7 @@ function _parseSct(r, sctLen) {
|
|
|
154
154
|
* @primitive pki.ct.parseSctList
|
|
155
155
|
* @signature pki.ct.parseSctList(extValue) -> { scts, unknownScts, all }
|
|
156
156
|
* @since 0.1.20
|
|
157
|
-
* @status
|
|
157
|
+
* @status stable
|
|
158
158
|
* @spec RFC 6962, RFC 5246, RFC 8446
|
|
159
159
|
* @related pki.ct.reconstructSignedData, pki.ct.encodeSctList, pki.schema.x509.parse
|
|
160
160
|
*
|
|
@@ -252,7 +252,7 @@ function _u24Bytes(n) {
|
|
|
252
252
|
* @primitive pki.ct.reconstructSignedData
|
|
253
253
|
* @signature pki.ct.reconstructSignedData(entry, sct) -> Buffer
|
|
254
254
|
* @since 0.1.20
|
|
255
|
-
* @status
|
|
255
|
+
* @status stable
|
|
256
256
|
* @spec RFC 6962
|
|
257
257
|
* @related pki.ct.parseSctList
|
|
258
258
|
*
|
|
@@ -378,7 +378,7 @@ function _spkiAlg(spki) {
|
|
|
378
378
|
* @primitive pki.ct.verifySct
|
|
379
379
|
* @signature pki.ct.verifySct(entry, sct, logPublicKey) -> Promise<boolean>
|
|
380
380
|
* @since 0.2.12
|
|
381
|
-
* @status
|
|
381
|
+
* @status stable
|
|
382
382
|
* @spec RFC 6962
|
|
383
383
|
* @defends sct-signature-forgery (CWE-347)
|
|
384
384
|
* @related pki.ct.parseSctList, pki.ct.reconstructSignedData
|
|
@@ -498,7 +498,7 @@ function _encodeSctBody(sct) {
|
|
|
498
498
|
* @primitive pki.ct.encodeSctList
|
|
499
499
|
* @signature pki.ct.encodeSctList(scts) -> Buffer
|
|
500
500
|
* @since 0.2.24
|
|
501
|
-
* @status
|
|
501
|
+
* @status stable
|
|
502
502
|
* @spec RFC 6962, RFC 5246
|
|
503
503
|
* @related pki.ct.parseSctList, pki.ct.signSct
|
|
504
504
|
*
|
|
@@ -573,7 +573,7 @@ function _logKeyMaterial(logKey) {
|
|
|
573
573
|
* @primitive pki.ct.signSct
|
|
574
574
|
* @signature pki.ct.signSct(entry, logKey, opts?) -> Promise<sct>
|
|
575
575
|
* @since 0.2.24
|
|
576
|
-
* @status
|
|
576
|
+
* @status stable
|
|
577
577
|
* @spec RFC 6962
|
|
578
578
|
* @related pki.ct.verifySct, pki.ct.reconstructSignedData, pki.ct.encodeSctList
|
|
579
579
|
*
|
|
@@ -723,7 +723,7 @@ function _logsAgree(a, b) {
|
|
|
723
723
|
* @primitive pki.ct.parseLogList
|
|
724
724
|
* @signature pki.ct.parseLogList(json, opts?) -> { logs, byLogId, version, timestamp }
|
|
725
725
|
* @since 0.2.28
|
|
726
|
-
* @status
|
|
726
|
+
* @status stable
|
|
727
727
|
* @spec RFC 6962
|
|
728
728
|
* @related pki.ct.verifySctWithLogList, pki.ct.verifySct
|
|
729
729
|
*
|
|
@@ -815,7 +815,7 @@ function _resolveNotAfter(entry, opts) {
|
|
|
815
815
|
* @primitive pki.ct.verifySctWithLogList
|
|
816
816
|
* @signature pki.ct.verifySctWithLogList(entry, sct, logList, opts?) -> Promise<boolean>
|
|
817
817
|
* @since 0.2.28
|
|
818
|
-
* @status
|
|
818
|
+
* @status stable
|
|
819
819
|
* @spec RFC 6962
|
|
820
820
|
* @related pki.ct.parseLogList, pki.ct.verifySct
|
|
821
821
|
*
|
|
@@ -848,37 +848,292 @@ function _resolveNotAfter(entry, opts) {
|
|
|
848
848
|
* state: { usable: { timestamp: "2026-01-01T00:00:00Z" } } }] }] }), "utf8"));
|
|
849
849
|
* var ok = await pki.ct.verifySctWithLogList(sctEntry, embeddedSct, logList);
|
|
850
850
|
*/
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
851
|
+
// The pure, NON-THROWING log resolution + state + temporal classifier shared by verifySctWithLogList
|
|
852
|
+
// (which throws the mapped code on a non-ok status) and verifySctList (which records the code into a
|
|
853
|
+
// result row and continues). Returns { status, log?, code?, message? } where status is one of
|
|
854
|
+
// ok | bad-input | not-found | untrusted | temporal. A mis-shaped sct.timestamp (in the retired-log
|
|
855
|
+
// gate) or a malformed opts.certNotAfter (in the temporal gate, via within) still THROWS -- those are
|
|
856
|
+
// caller mis-shapes, not per-SCT trust outcomes (the three-tier rule: bad input throws, a per-SCT
|
|
857
|
+
// verdict is recorded). The state and temporal gate semantics are verbatim from the original
|
|
858
|
+
// verifySctWithLogList body, so its shipped RED vectors are the behavior guard for this refactor.
|
|
859
|
+
function _classifyLog(sct, logList, entry, opts) {
|
|
860
|
+
if (logList == null || typeof logList !== "object" || logList.byLogId == null) return { status: "bad-input", code: "ct/bad-input", message: "logList must be a pki.ct.parseLogList result" };
|
|
861
|
+
if (sct == null || typeof sct !== "object" || typeof sct.logIdHex !== "string") return { status: "bad-input", code: "ct/bad-input", message: "the SCT is missing its logIdHex" };
|
|
855
862
|
var log = logList.byLogId[sct.logIdHex];
|
|
856
|
-
if (log == null)
|
|
863
|
+
if (log == null) return { status: "not-found", code: "ct/log-not-found", message: "no trusted CT log matches the SCT's logId " + sct.logIdHex };
|
|
857
864
|
// State gate (fail-closed): trusted proceed; retired only before its retirement; else untrusted.
|
|
858
865
|
if (!log.state.trusted) {
|
|
859
|
-
if (!log.state.conditional)
|
|
866
|
+
if (!log.state.conditional) return { status: "untrusted", log: log, code: "ct/log-untrusted", message: "the CT log state '" + log.state.name + "' is not trusted" };
|
|
860
867
|
var ts = guard.range.uint64(sct.timestamp, _ctErr, "ct/bad-input", "sct.timestamp");
|
|
861
|
-
if (ts >= BigInt(guard.time.instantOf(log.state.since)))
|
|
868
|
+
if (ts >= BigInt(guard.time.instantOf(log.state.since))) return { status: "untrusted", log: log, code: "ct/log-untrusted", message: "the CT log is retired and the SCT is not timestamped before its retirement (" + log.state.since.toISOString() + ")" };
|
|
862
869
|
}
|
|
863
870
|
// Temporal gate (fail-closed): the covered cert's notAfter must be in [start_inclusive, end_exclusive).
|
|
864
871
|
if (log.temporalInterval) {
|
|
865
872
|
var notAfter = _resolveNotAfter(entry, opts);
|
|
866
|
-
//
|
|
867
|
-
// Date is instanceof Date yet NaN, and
|
|
868
|
-
//
|
|
869
|
-
// inside within (
|
|
873
|
+
// An unresolvable notAfter is a failure for this windowed SCT, never a silent skip. within() then
|
|
874
|
+
// validates notAfter fail-closed before comparing: an Invalid Date is instanceof Date yet NaN, and
|
|
875
|
+
// NaN < x / NaN >= x are both false, so an unvalidated notAfter would BYPASS the containment. A
|
|
876
|
+
// malformed opts.certNotAfter throws inside within (a caller mis-shape); an out-of-window one is
|
|
877
|
+
// this recorded temporal status.
|
|
878
|
+
if (notAfter == null) return { status: "temporal", log: log, code: "ct/temporal-interval", message: "the windowed CT log has no resolvable covered-certificate notAfter (pass a valid opts.certNotAfter)" };
|
|
870
879
|
if (!guard.time.within(notAfter, log.temporalInterval.startInclusive, log.temporalInterval.endExclusive, _ctErr, "ct/temporal-interval", "the covered certificate notAfter (pass a valid opts.certNotAfter)")) {
|
|
871
|
-
|
|
880
|
+
return { status: "temporal", log: log, code: "ct/temporal-interval", message: "the covered certificate's notAfter is outside the CT log's temporal_interval" };
|
|
872
881
|
}
|
|
873
882
|
}
|
|
874
|
-
return
|
|
883
|
+
return { status: "ok", log: log };
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
async function verifySctWithLogList(entry, sct, logList, opts) {
|
|
887
|
+
opts = opts || {};
|
|
888
|
+
var c = _classifyLog(sct, logList, entry, opts);
|
|
889
|
+
if (c.status !== "ok") throw _ctErr(c.code, c.message);
|
|
890
|
+
return verifySct(entry, sct, c.log.key); // the shipped crypto verdict (re-checks the logId binding)
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// Options verifySctList reads. All optional; the defaults are the RFC 6962 sec. 3.3 floor (>= 1).
|
|
894
|
+
var _VERIFY_SCT_LIST_OPTS = { minScts: 1, minOperators: 1, certNotAfter: 1, at: 1 };
|
|
895
|
+
|
|
896
|
+
// A caller-supplied policy threshold: a positive integer, or null when omitted (defaulted by the caller).
|
|
897
|
+
function _policyCount(v, name) {
|
|
898
|
+
if (v == null) return null;
|
|
899
|
+
if (typeof v !== "number" || !intrinsic.isInteger(v) || v < 1) throw _ctErr("ct/bad-input", name + " must be a positive integer");
|
|
900
|
+
return v;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* @primitive pki.ct.verifySctList
|
|
905
|
+
* @signature pki.ct.verifySctList(entry, list, logList, opts?) -> Promise<verdict>
|
|
906
|
+
* @since 0.6.1
|
|
907
|
+
* @status stable
|
|
908
|
+
* @spec RFC 6962
|
|
909
|
+
* @related pki.ct.verifySct, pki.ct.verifySctWithLogList, pki.ct.parseLogList, pki.ct.parseSctList
|
|
910
|
+
*
|
|
911
|
+
* Render a certificate-level Certificate Transparency verdict over the SET of SCTs a certificate carries
|
|
912
|
+
* (RFC 6962 sec. 3.3), rather than one SCT at a time. Each SCT is resolved to a trusted log through
|
|
913
|
+
* `logList` and verified with the shipped `verifySct`; the verdict reports how many distinct trusted
|
|
914
|
+
* logs verified an SCT (`validScts`, so a duplicated SCT cannot inflate the count), from how many
|
|
915
|
+
* distinct trusted operators, and whether that meets the caller's CT policy. The RFC floor is one valid
|
|
916
|
+
* SCT (sec. 3.3), so `minScts` and `minOperators` both default to 1; the distinct-operator axis is a
|
|
917
|
+
* browser CT policy layered on top of the RFC, so the caller sets the threshold and the verdict surfaces
|
|
918
|
+
* the counts rather than hardcoding a browser's numbers.
|
|
919
|
+
*
|
|
920
|
+
* `entry` is the shared log-entry context for every SCT (`{ entryType: 0, leafCert }` for SCTs delivered
|
|
921
|
+
* over TLS/OCSP, or `{ entryType: 1, tbsCertificate, issuerKeyHash }` for embedded SCTs; the shape
|
|
922
|
+
* `pki.ct.x509CertEntry` produces). `list` is a `parseSctList` result, its `.scts` array, or an array of
|
|
923
|
+
* decoded v1 SCTs; unknown-version entries are counted `unknownScts` and never as valid. `logList` is a
|
|
924
|
+
* `parseLogList` result. A policy shortfall is a verdict (`policyOk: false`), not a throw; a per-SCT
|
|
925
|
+
* trust or crypto failure is recorded into its result row (`valid: false`, with a `code` for a
|
|
926
|
+
* structural or trust exclusion, without one for a cryptographic mismatch) and the loop continues; only
|
|
927
|
+
* a mis-shaped `entry` / `list` / `logList` / `opts` throws a typed `CtError`.
|
|
928
|
+
*
|
|
929
|
+
* @opts
|
|
930
|
+
* - `minScts` (int, default 1) -- required count of valid SCTs from trusted, in-window logs.
|
|
931
|
+
* - `minOperators` (int, default 1) -- required count of distinct trusted operators among them.
|
|
932
|
+
* - `certNotAfter` (Date) -- the covered certificate's notAfter for each windowed log's temporal gate;
|
|
933
|
+
* auto-derived from `entry.leafCert` for entryType 0, required for a precert entry against a windowed log.
|
|
934
|
+
* - `at` (Date) -- the validation time; when supplied, an SCT whose timestamp is later than it is
|
|
935
|
+
* rejected and not counted (RFC 6962 sec. 5.2, a client rejects a future-dated SCT). The
|
|
936
|
+
* pki.path.validate CT gate passes its opts.time here.
|
|
937
|
+
* @example
|
|
938
|
+
* // requires: logListJsonBytes -- the CT log-list JSON a distributor publishes (the log_list.json browsers consume)
|
|
939
|
+
* // requires: sctExtValue -- a certificate's SCT-list extension value (a signedCertificateTimestampList extension)
|
|
940
|
+
* // requires: certDer -- the DER of the certificate the SCTs cover
|
|
941
|
+
* var logList = pki.ct.parseLogList(logListJsonBytes);
|
|
942
|
+
* var list = pki.ct.parseSctList(sctExtValue);
|
|
943
|
+
* var verdict = await pki.ct.verifySctList({ entryType: 0, leafCert: certDer }, list, logList,
|
|
944
|
+
* { minScts: 2, minOperators: 2 });
|
|
945
|
+
* verdict.policyOk; // did the certificate meet the CT policy?
|
|
946
|
+
* verdict.operators; // the distinct trusted operators whose logs verified an SCT
|
|
947
|
+
*/
|
|
948
|
+
async function verifySctList(entry, list, logList, opts) {
|
|
949
|
+
opts = guard.identifier.optionsObject(opts, _ctErr, "ct/bad-input", "pki.ct.verifySctList options");
|
|
950
|
+
guard.identifier.assertKnownKeys(opts, _VERIFY_SCT_LIST_OPTS, _ctErr, "ct/bad-input", "pki.ct.verifySctList has an unknown option: ");
|
|
951
|
+
var minScts = _policyCount(opts.minScts, "opts.minScts"); if (minScts == null) minScts = 1;
|
|
952
|
+
var minOperators = _policyCount(opts.minOperators, "opts.minOperators"); if (minOperators == null) minOperators = 1;
|
|
953
|
+
if (logList == null || typeof logList !== "object" || logList.byLogId == null) throw _ctErr("ct/bad-input", "logList must be a pki.ct.parseLogList result");
|
|
954
|
+
if (entry == null || (entry.entryType !== 0 && entry.entryType !== 1)) throw _ctErr("ct/bad-entry-type", "entryType must be x509_entry(0) or precert_entry(1) (RFC 6962 sec. 3.1)");
|
|
955
|
+
// The validation time, when supplied, arms the RFC 6962 sec. 5.2 rule that a future-dated SCT is
|
|
956
|
+
// rejected: a log cannot have issued an SCT after the moment we verify it, so its timestamp must not
|
|
957
|
+
// be later than `at`. Held as a millisecond Number and compared against the SCT's BigInt uint64
|
|
958
|
+
// timestamp (a BigInt-vs-Number relational comparison is exact).
|
|
959
|
+
var atMs = null;
|
|
960
|
+
if (opts.at != null) { guard.time.assertValid(opts.at, _ctErr, "ct/bad-input", "opts.at (the validation time)"); atMs = guard.time.instantOf(opts.at); }
|
|
961
|
+
// A present certNotAfter must be a valid Date: a non-Date is a caller mis-shape that throws, never a
|
|
962
|
+
// value silently ignored (entryType 0 would fall back to the certificate's own notAfter) or demoted to
|
|
963
|
+
// a per-SCT temporal failure (entryType 1) -- the malformed-options contract, matching opts.at.
|
|
964
|
+
if (opts.certNotAfter != null) guard.time.assertValid(opts.certNotAfter, _ctErr, "ct/bad-input", "opts.certNotAfter (the covered certificate's notAfter)");
|
|
965
|
+
// Validate the shared entry's required fields up front: a mis-shaped entry is a caller error that
|
|
966
|
+
// throws (not a per-SCT row or a verdict rendered without examining it), even when the SCT list is
|
|
967
|
+
// empty. The same fields reconstructSignedData reads for the preimage.
|
|
968
|
+
if (entry.entryType === 0) { _toBuffer(entry.leafCert, "entry.leafCert"); }
|
|
969
|
+
else {
|
|
970
|
+
if (_toBuffer(entry.issuerKeyHash, "entry.issuerKeyHash").length !== 32) throw _ctErr("ct/bad-issuer-key-hash", "entry.issuerKeyHash must be exactly 32 bytes (SHA-256 of the issuer SPKI, RFC 6962 sec. 3.2)");
|
|
971
|
+
_toBuffer(entry.tbsCertificate, "entry.tbsCertificate");
|
|
972
|
+
}
|
|
973
|
+
// Normalize the list into the known v1 SCTs plus the count of forward-compat unknown-version entries
|
|
974
|
+
// (M7 -- their body layout is undefined, so they are unverifiable, never a valid SCT, never a throw).
|
|
975
|
+
var scts, unknownScts;
|
|
976
|
+
if (list != null && intrinsic.isArray(list.scts)) { scts = list.scts; unknownScts = intrinsic.isArray(list.unknownScts) ? list.unknownScts.length : 0; }
|
|
977
|
+
else if (intrinsic.isArray(list)) { scts = list; unknownScts = 0; }
|
|
978
|
+
else throw _ctErr("ct/bad-input", "list must be a pki.ct.parseSctList result, its .scts array, or an array of decoded v1 SCTs");
|
|
979
|
+
// Bound the per-SCT verification work a caller-supplied array can demand to the same cap parseSctList
|
|
980
|
+
// enforces on wire input, so a bare oversized array cannot force an unbounded run of signature checks.
|
|
981
|
+
if (scts.length > C.LIMITS.SCT_MAX_COUNT) throw _ctErr("ct/too-many-scts", "the SCT list has " + scts.length + " entries, exceeding the SCT_MAX_COUNT cap of " + C.LIMITS.SCT_MAX_COUNT + " (RFC 6962 sec. 3.3)");
|
|
982
|
+
|
|
983
|
+
var results = [], validScts = 0, operators = [], seenOps = intrinsic.create(null), seenLogs = intrinsic.create(null);
|
|
984
|
+
for (var i = 0; i < scts.length; i++) {
|
|
985
|
+
var sct = scts[i];
|
|
986
|
+
var row = { logIdHex: (sct && typeof sct.logIdHex === "string") ? sct.logIdHex : null, valid: false,
|
|
987
|
+
operator: null, logState: null,
|
|
988
|
+
timestamp: (sct && typeof sct.timestamp === "bigint") ? sct.timestamp : null, code: undefined, reason: undefined };
|
|
989
|
+
// Classification can THROW for a malformed per-SCT field (a bad timestamp on a retired-log match):
|
|
990
|
+
// that is a recorded per-SCT outcome, never a thrown aggregate abort, so it matches the usable-log
|
|
991
|
+
// malformed-SCT path below and one bad SCT cannot sink the verdict or a valid sibling.
|
|
992
|
+
var c;
|
|
993
|
+
try { c = _classifyLog(sct, logList, entry, opts); }
|
|
994
|
+
catch (e) {
|
|
995
|
+
if (!(e && e.isPkiError)) throw e;
|
|
996
|
+
row.code = e.code; row.reason = e.message; intrinsic.push(results, row); continue;
|
|
997
|
+
}
|
|
998
|
+
if (c.log) { row.operator = c.log.operator; row.logState = c.log.state.name; }
|
|
999
|
+
if (c.status !== "ok") { row.code = c.code; row.reason = c.message; intrinsic.push(results, row); continue; }
|
|
1000
|
+
// A trust exclusion is a recorded code above; the crypto check below is false-is-a-verdict. But a
|
|
1001
|
+
// structurally-defective SCT from a trusted log (a malformed signature encoding, an unsupported hash
|
|
1002
|
+
// or curve) makes verifySct THROW a typed CtError -- for the aggregate that is a recorded per-SCT
|
|
1003
|
+
// outcome (M10, the verdict-collector tier), never a thrown call that would sink the verdict or a
|
|
1004
|
+
// valid sibling. A non-PkiError (an unexpected bug) is re-thrown, never swallowed into a verdict.
|
|
1005
|
+
var ok;
|
|
1006
|
+
try { ok = await verifySct(entry, sct, c.log.key); }
|
|
1007
|
+
catch (e) {
|
|
1008
|
+
if (!(e && e.isPkiError)) throw e;
|
|
1009
|
+
row.code = e.code; row.reason = e.message; intrinsic.push(results, row); continue;
|
|
1010
|
+
}
|
|
1011
|
+
// RFC 6962 sec. 5.2: reject a future-dated SCT even when its signature verifies -- a log cannot have
|
|
1012
|
+
// issued it after the validation time, so it is recorded as a rejection and never counted. An SCT
|
|
1013
|
+
// dated after the certificate's OWN notAfter is deliberately NOT rejected: RFC 6962 sec. 5.1 permits
|
|
1014
|
+
// a log to accept an already-expired certificate, so such an SCT is legitimate.
|
|
1015
|
+
if (ok === true && atMs != null && sct.timestamp > atMs) {
|
|
1016
|
+
row.code = "ct/future-timestamp"; row.reason = "the SCT timestamp is later than the validation time (RFC 6962 sec. 5.2)";
|
|
1017
|
+
intrinsic.push(results, row); continue;
|
|
1018
|
+
}
|
|
1019
|
+
row.valid = ok === true;
|
|
1020
|
+
// Count each distinct trusted log once: a duplicated SCT, or a second SCT from the same log, is one
|
|
1021
|
+
// attestation, so a repeat cannot inflate validScts past a minScts policy (the count is over the set
|
|
1022
|
+
// of logs, not raw SCT copies). The duplicate row is still recorded for diagnostics.
|
|
1023
|
+
if (row.valid && !seenLogs[sct.logIdHex]) {
|
|
1024
|
+
seenLogs[sct.logIdHex] = true;
|
|
1025
|
+
validScts++;
|
|
1026
|
+
if (c.log.operator != null && !seenOps[c.log.operator]) { seenOps[c.log.operator] = true; intrinsic.push(operators, c.log.operator); }
|
|
1027
|
+
}
|
|
1028
|
+
intrinsic.push(results, row);
|
|
1029
|
+
}
|
|
1030
|
+
var operatorCount = operators.length;
|
|
1031
|
+
var policyOk = validScts >= minScts && operatorCount >= minOperators;
|
|
1032
|
+
return {
|
|
1033
|
+
policyOk: policyOk, totalScts: scts.length + unknownScts, validScts: validScts, unknownScts: unknownScts,
|
|
1034
|
+
operatorCount: operatorCount, operators: operators, required: { minScts: minScts, minOperators: minOperators },
|
|
1035
|
+
results: results,
|
|
1036
|
+
reason: policyOk ? null : ("CT policy not met: " + validScts + " valid SCT(s) from " + operatorCount + " distinct operator(s); require at least " + minScts + " SCT(s) from at least " + minOperators + " operator(s)"),
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// A cert / issuer argument as a parsed x509: a DER Buffer, a PEM string, or an unmodified
|
|
1041
|
+
// pki.schema.x509.parse result. Routed through guard.parsed (provenance-based, getter-free) so a
|
|
1042
|
+
// caller object cannot spoof "already parsed" through a lying accessor; a malformed one is a typed
|
|
1043
|
+
// ct/bad-cert-entry, never a silently-skipped input.
|
|
1044
|
+
function _asParsedCert(v, field) {
|
|
1045
|
+
// Inline require (circular load with schema-x509 -> schema-pkix), matching _resolveNotAfter above.
|
|
1046
|
+
var x509 = require("./schema-x509.js"); // allow:inline-require -- circular load (see _resolveNotAfter)
|
|
1047
|
+
return guard.parsed.acceptDerived(v, "certificate", x509.parse, _ctErr, "ct/bad-cert-entry", field);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/**
|
|
1051
|
+
* @primitive pki.ct.x509CertEntry
|
|
1052
|
+
* @signature pki.ct.x509CertEntry(cert, issuer) -> { entryType, tbsCertificate, issuerKeyHash }
|
|
1053
|
+
* @since 0.6.1
|
|
1054
|
+
* @status stable
|
|
1055
|
+
* @spec RFC 6962
|
|
1056
|
+
* @related pki.ct.verifySctList, pki.ct.reconstructSignedData, pki.ct.verifySct
|
|
1057
|
+
*
|
|
1058
|
+
* Reconstruct the precertificate log entry from a FINAL certificate carrying embedded SCTs, so those
|
|
1059
|
+
* SCTs can be verified end to end (RFC 6962 sec. 3.2): "reconstruct this TBSCertificate from the final
|
|
1060
|
+
* certificate by extracting the TBSCertificate from it and deleting the SCT extension". Returns the
|
|
1061
|
+
* `{ entryType: 1, tbsCertificate, issuerKeyHash }` entry `pki.ct.verifySctList` / `reconstructSignedData`
|
|
1062
|
+
* consume, where `tbsCertificate` is the leaf TBS with ONLY the signedCertificateTimestampList extension
|
|
1063
|
+
* removed and `issuerKeyHash` is `SHA-256(issuer SubjectPublicKeyInfo DER)` (32 bytes). The signed
|
|
1064
|
+
* `tbs_certificate` an SCT covers is also without the CT poison extension (RFC 6962 sec. 3.2, "without
|
|
1065
|
+
* the signature and the poison extension"), which sits in the same position the SCT list takes in the
|
|
1066
|
+
* final certificate, so deleting the SCT-list extension IS the whole reconstruction: no poison extension
|
|
1067
|
+
* is re-added. (The Precertificate Signing Certificate case, where the precert issuer differs from the
|
|
1068
|
+
* final issuer, is out of scope; the common flow issues both from the one CA.)
|
|
1069
|
+
*
|
|
1070
|
+
* The removal is byte surgery on the CA-signed bytes, never a value re-serialization: the extensions
|
|
1071
|
+
* list and the two enclosing containers are rebuilt from every OTHER element's raw DER, so the only
|
|
1072
|
+
* bytes that change are the removed extension and the recomputed canonical DER length prefixes. `cert`
|
|
1073
|
+
* and `issuer` each accept a DER `Buffer`, a PEM string, or a `pki.schema.x509.parse` result. A
|
|
1074
|
+
* certificate with no extensions or no SCT-list extension is `ct/no-sct-extension`; an argument that is
|
|
1075
|
+
* not a certificate is `ct/bad-cert-entry`, and malformed certificate bytes carry the X.509 parse fault.
|
|
1076
|
+
*
|
|
1077
|
+
* @example
|
|
1078
|
+
* // requires: leafCertDer -- the DER of a final certificate carrying embedded SCTs
|
|
1079
|
+
* // requires: issuerCertDer -- the DER of the certificate that issued it
|
|
1080
|
+
* // requires: sctExtValue -- the leaf's signedCertificateTimestampList extension value
|
|
1081
|
+
* // requires: logList -- a pki.ct.parseLogList result
|
|
1082
|
+
* var entry = pki.ct.x509CertEntry(leafCertDer, issuerCertDer);
|
|
1083
|
+
* var verdict = await pki.ct.verifySctList(entry, pki.ct.parseSctList(sctExtValue), logList,
|
|
1084
|
+
* { certNotAfter: pki.schema.x509.parse(leafCertDer).validity.notAfter });
|
|
1085
|
+
*/
|
|
1086
|
+
// The issuer contributes only its SubjectPublicKeyInfo (issuerKeyHash = SHA-256 of it), so it accepts
|
|
1087
|
+
// anything x509CertEntry's cert does PLUS a bare { subjectPublicKeyInfo: { bytes } } -- a trust anchor
|
|
1088
|
+
// carries its key without a full certificate, and requiring a whole cert there would refuse a leaf
|
|
1089
|
+
// validated directly under an anchor.
|
|
1090
|
+
function _issuerSpki(v, field) {
|
|
1091
|
+
if (v != null && typeof v === "object" && !intrinsic.isBuffer(v) && v.subjectPublicKeyInfo != null && v.subjectPublicKeyInfo.bytes != null) {
|
|
1092
|
+
// Validate the bare-form bytes as a BufferSource so a malformed value fails through the typed CT
|
|
1093
|
+
// error contract, not a native TypeError from the SHA-256 update downstream.
|
|
1094
|
+
return guard.bytes.view(v.subjectPublicKeyInfo.bytes, CtError, "ct/bad-cert-entry", field + " subjectPublicKeyInfo.bytes");
|
|
1095
|
+
}
|
|
1096
|
+
return _asParsedCert(v, field).subjectPublicKeyInfo.bytes;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
function x509CertEntry(cert, issuer) {
|
|
1100
|
+
var leaf = _asParsedCert(cert, "cert"), issSpki = _issuerSpki(issuer, "issuer");
|
|
1101
|
+
var tbs = asn1.decode(leaf.tbsBytes);
|
|
1102
|
+
// extensions [3] EXPLICIT is the last TBSCertificate field (context-constructed tag 3).
|
|
1103
|
+
var wrapIdx = -1;
|
|
1104
|
+
for (var i = 0; i < tbs.children.length; i++) {
|
|
1105
|
+
if (tbs.children[i].tagClass === "context" && tbs.children[i].tagNumber === 3) { wrapIdx = i; break; }
|
|
1106
|
+
}
|
|
1107
|
+
if (wrapIdx < 0) throw _ctErr("ct/no-sct-extension", "the certificate has no extensions, so it carries no embedded SCT list (RFC 6962 sec. 3.2)");
|
|
1108
|
+
var extsSeq = tbs.children[wrapIdx].children[0]; // the SEQUENCE OF Extension
|
|
1109
|
+
var sctOid = asn1.build.oid(oid.byName("signedCertificateTimestampList"));
|
|
1110
|
+
var kept = [], removed = 0;
|
|
1111
|
+
for (var j = 0; j < extsSeq.children.length; j++) {
|
|
1112
|
+
var ext = extsSeq.children[j];
|
|
1113
|
+
// ext.children[0] is the extnID OID node; compare its exact TLV bytes to the SCT-list OID.
|
|
1114
|
+
if (ext.children[0] && intrinsic.bufferEquals(ext.children[0].bytes, sctOid)) { removed++; continue; }
|
|
1115
|
+
intrinsic.push(kept, ext.bytes);
|
|
1116
|
+
}
|
|
1117
|
+
if (removed === 0) throw _ctErr("ct/no-sct-extension", "the certificate carries no signedCertificateTimestampList extension (RFC 6962 sec. 3.2)");
|
|
1118
|
+
// Rebuild the TBSCertificate SEQUENCE from raw child bytes; canonical DER lengths reproduce the
|
|
1119
|
+
// original framing minus the removed extension. When the SCT-list was the ONLY extension, `kept` is
|
|
1120
|
+
// empty and the extensions field is dropped ENTIRELY -- RFC 5280 Extensions is SIZE (1..MAX) and the
|
|
1121
|
+
// [3] field is OPTIONAL, so an empty [3] SEQUENCE {} would be invalid and would not match the signed
|
|
1122
|
+
// precert TBS, whose extensions field is likewise absent once its sole poison extension is removed.
|
|
1123
|
+
var tbsChildren = [];
|
|
1124
|
+
for (var k = 0; k < tbs.children.length; k++) {
|
|
1125
|
+
if (k !== wrapIdx) { intrinsic.push(tbsChildren, tbs.children[k].bytes); continue; }
|
|
1126
|
+
if (kept.length > 0) intrinsic.push(tbsChildren, asn1.build.explicit(3, asn1.build.sequence(kept)));
|
|
1127
|
+
}
|
|
1128
|
+
var issuerKeyHash = nodeCrypto.createHash("sha256").update(issSpki).digest();
|
|
1129
|
+
return { entryType: 1, tbsCertificate: asn1.build.sequence(tbsChildren), issuerKeyHash: issuerKeyHash };
|
|
875
1130
|
}
|
|
876
1131
|
|
|
877
1132
|
/**
|
|
878
1133
|
* @primitive pki.ct.verifyLogListSignature
|
|
879
1134
|
* @signature pki.ct.verifyLogListSignature(json, signature, publicKey) -> Promise<boolean>
|
|
880
1135
|
* @since 0.2.29
|
|
881
|
-
* @status
|
|
1136
|
+
* @status stable
|
|
882
1137
|
* @spec RFC 6962, RFC 8017
|
|
883
1138
|
* @related pki.ct.parseLogList, pki.ct.verifySct
|
|
884
1139
|
*
|
|
@@ -1019,7 +1274,7 @@ function _fetchBody(transport, url, req, label) {
|
|
|
1019
1274
|
* @primitive pki.ct.fetchLogList
|
|
1020
1275
|
* @signature pki.ct.fetchLogList(opts) -> Promise<{ logs, byLogId, version, timestamp, raw, status, contentType, tls }>
|
|
1021
1276
|
* @since 0.3.21
|
|
1022
|
-
* @status
|
|
1277
|
+
* @status stable
|
|
1023
1278
|
* @spec RFC 6962
|
|
1024
1279
|
* @related pki.ct.parseLogList, pki.ct.verifyLogListSignature, pki.ct.verifySctWithLogList
|
|
1025
1280
|
*
|
|
@@ -1113,6 +1368,8 @@ module.exports = {
|
|
|
1113
1368
|
signSct: signSct,
|
|
1114
1369
|
parseLogList: parseLogList,
|
|
1115
1370
|
verifySctWithLogList: verifySctWithLogList,
|
|
1371
|
+
verifySctList: verifySctList,
|
|
1372
|
+
x509CertEntry: x509CertEntry,
|
|
1116
1373
|
verifyLogListSignature: verifyLogListSignature,
|
|
1117
1374
|
fetchLogList: fetchLogList,
|
|
1118
1375
|
HASH_ALGORITHMS: HASH_ALGORITHMS,
|
package/lib/est.js
CHANGED
|
@@ -162,7 +162,7 @@ function _knownOpts(opts, known, verb) {
|
|
|
162
162
|
* @primitive pki.est.transferDecode
|
|
163
163
|
* @signature pki.est.transferDecode(body) -> Buffer
|
|
164
164
|
* @since 0.1.24
|
|
165
|
-
* @status
|
|
165
|
+
* @status stable
|
|
166
166
|
* @spec RFC 8951, RFC 4648
|
|
167
167
|
* @related pki.est.transferEncode
|
|
168
168
|
*
|
|
@@ -200,7 +200,7 @@ function transferDecode(body) {
|
|
|
200
200
|
* @primitive pki.est.transferEncode
|
|
201
201
|
* @signature pki.est.transferEncode(der) -> string
|
|
202
202
|
* @since 0.1.24
|
|
203
|
-
* @status
|
|
203
|
+
* @status stable
|
|
204
204
|
* @spec RFC 8951, RFC 4648
|
|
205
205
|
* @related pki.est.transferDecode
|
|
206
206
|
*
|
|
@@ -389,7 +389,7 @@ function splitMultipartMixed(body, contentType) {
|
|
|
389
389
|
* @primitive pki.est.parseCertsOnly
|
|
390
390
|
* @signature pki.est.parseCertsOnly(der) -> { certificates, crls }
|
|
391
391
|
* @since 0.1.24
|
|
392
|
-
* @status
|
|
392
|
+
* @status stable
|
|
393
393
|
* @spec RFC 7030, RFC 5272, RFC 5652
|
|
394
394
|
* @related pki.est.findIssuedCert, pki.schema.cms.parse
|
|
395
395
|
*
|
|
@@ -695,7 +695,7 @@ var NOT_IMPLEMENTED_OPS = { fullcmc: 1 };
|
|
|
695
695
|
* @primitive pki.est.classifyResponse
|
|
696
696
|
* @signature pki.est.classifyResponse(status, headers, body, opts?) -> verdict
|
|
697
697
|
* @since 0.1.24
|
|
698
|
-
* @status
|
|
698
|
+
* @status stable
|
|
699
699
|
* @spec RFC 7030, RFC 8951
|
|
700
700
|
* @related pki.est.paths, pki.est.parseCertsOnly
|
|
701
701
|
*
|
|
@@ -796,7 +796,7 @@ function classifyResponse(status, headers, body, opts) {
|
|
|
796
796
|
* @primitive pki.est.paths
|
|
797
797
|
* @signature pki.est.paths(baseUrl, opts?) -> { cacerts, simpleenroll, ... }
|
|
798
798
|
* @since 0.1.24
|
|
799
|
-
* @status
|
|
799
|
+
* @status stable
|
|
800
800
|
* @spec RFC 7030
|
|
801
801
|
* @related pki.est.classifyResponse
|
|
802
802
|
*
|
|
@@ -1310,7 +1310,7 @@ function _certsResult(op, res, opts, csrSpki) {
|
|
|
1310
1310
|
* @primitive pki.est.fullcmc
|
|
1311
1311
|
* @signature pki.est.fullcmc(baseUrl, request, opts?) -> Promise<verdict | { retry, retryAfterSeconds }>
|
|
1312
1312
|
* @since 0.4.16
|
|
1313
|
-
* @status
|
|
1313
|
+
* @status stable
|
|
1314
1314
|
* @spec RFC 7030, RFC 8951, RFC 5273, RFC 5272
|
|
1315
1315
|
* @related pki.cmc.build, pki.cmc.verify, pki.est.simpleenroll
|
|
1316
1316
|
*
|
|
@@ -2024,7 +2024,7 @@ function _enroll(op, baseUrl, csrInput, opts) {
|
|
|
2024
2024
|
* @primitive pki.est.cacerts
|
|
2025
2025
|
* @signature pki.est.cacerts(baseUrl, opts?) -> Promise<{ certificates, crls } | { retry, retryAfterSeconds }>
|
|
2026
2026
|
* @since 0.3.16
|
|
2027
|
-
* @status
|
|
2027
|
+
* @status stable
|
|
2028
2028
|
* @spec RFC 7030, RFC 8951
|
|
2029
2029
|
* @related pki.est.simpleenroll, pki.transport.https
|
|
2030
2030
|
*
|
|
@@ -2068,7 +2068,7 @@ function cacerts(baseUrl, opts) {
|
|
|
2068
2068
|
* @primitive pki.est.simpleenroll
|
|
2069
2069
|
* @signature pki.est.simpleenroll(baseUrl, csr, opts?) -> Promise<{ certificate, chain, certificates } | { retry, retryAfterSeconds }>
|
|
2070
2070
|
* @since 0.3.16
|
|
2071
|
-
* @status
|
|
2071
|
+
* @status stable
|
|
2072
2072
|
* @spec RFC 7030, RFC 5272
|
|
2073
2073
|
* @related pki.csr.sign, pki.est.simplereenroll, pki.est.cacerts
|
|
2074
2074
|
*
|
|
@@ -2106,7 +2106,7 @@ function simpleenroll(baseUrl, csrInput, opts) {
|
|
|
2106
2106
|
* @primitive pki.est.simplereenroll
|
|
2107
2107
|
* @signature pki.est.simplereenroll(baseUrl, csr, opts?) -> Promise<{ certificate, chain, certificates } | { retry, retryAfterSeconds }>
|
|
2108
2108
|
* @since 0.3.16
|
|
2109
|
-
* @status
|
|
2109
|
+
* @status stable
|
|
2110
2110
|
* @spec RFC 7030
|
|
2111
2111
|
* @related pki.est.simpleenroll, pki.est.reenrollGuard
|
|
2112
2112
|
*
|
|
@@ -2285,7 +2285,7 @@ async function _serverkeygenResult(res, opts, derived) {
|
|
|
2285
2285
|
* @primitive pki.est.serverkeygen
|
|
2286
2286
|
* @signature pki.est.serverkeygen(baseUrl, csr, opts?) -> Promise<{ certificates, privateKey } | { certificates, encryptedKey } | { retry, retryAfterSeconds, retryAfterDate }>
|
|
2287
2287
|
* @since 0.3.28
|
|
2288
|
-
* @status
|
|
2288
|
+
* @status stable
|
|
2289
2289
|
* @spec RFC 7030, RFC 8951
|
|
2290
2290
|
* @related pki.est.simpleenroll, pki.est.parseServerKeygenResponse
|
|
2291
2291
|
*
|
|
@@ -2364,7 +2364,7 @@ function _csrattrsResult(res, opts) {
|
|
|
2364
2364
|
* @primitive pki.est.csrattrs
|
|
2365
2365
|
* @signature pki.est.csrattrs(baseUrl, opts?) -> Promise<{ available: true, attrs, plan } | { available: false, attrs: null }>
|
|
2366
2366
|
* @since 0.3.28
|
|
2367
|
-
* @status
|
|
2367
|
+
* @status stable
|
|
2368
2368
|
* @spec RFC 7030, RFC 8951, RFC 9908
|
|
2369
2369
|
* @related pki.est.simpleenroll, pki.est.buildEnrollAttributes
|
|
2370
2370
|
*
|
package/lib/hpke.js
CHANGED
|
@@ -455,7 +455,7 @@ var suites = {
|
|
|
455
455
|
* @primitive pki.hpke.setupS
|
|
456
456
|
* @signature pki.hpke.setupS(suiteIds, recipientPublicKey, opts?) -> { enc, context }
|
|
457
457
|
* @since 0.2.2
|
|
458
|
-
* @status
|
|
458
|
+
* @status stable
|
|
459
459
|
* @spec RFC 9180
|
|
460
460
|
* @related pki.hpke.setupR, pki.hpke.seal
|
|
461
461
|
*
|
|
@@ -495,7 +495,7 @@ function _setupS(ids, pkR, opts) {
|
|
|
495
495
|
* @primitive pki.hpke.setupR
|
|
496
496
|
* @signature pki.hpke.setupR(suiteIds, enc, recipientPrivateKey, opts?) -> context
|
|
497
497
|
* @since 0.2.2
|
|
498
|
-
* @status
|
|
498
|
+
* @status stable
|
|
499
499
|
* @spec RFC 9180
|
|
500
500
|
* @related pki.hpke.setupS, pki.hpke.open
|
|
501
501
|
*
|
|
@@ -538,7 +538,7 @@ function _setupR(ids, enc, skR, opts) {
|
|
|
538
538
|
* @primitive pki.hpke.seal
|
|
539
539
|
* @signature pki.hpke.seal(suiteIds, recipientPublicKey, opts, aad, pt) -> { enc, ct }
|
|
540
540
|
* @since 0.2.2
|
|
541
|
-
* @status
|
|
541
|
+
* @status stable
|
|
542
542
|
* @spec RFC 9180
|
|
543
543
|
* @related pki.hpke.open, pki.hpke.setupS
|
|
544
544
|
*
|
|
@@ -574,7 +574,7 @@ function seal(ids, pkR, opts, aad, pt) {
|
|
|
574
574
|
* @primitive pki.hpke.open
|
|
575
575
|
* @signature pki.hpke.open(suiteIds, enc, recipientPrivateKey, opts, aad, ct) -> pt
|
|
576
576
|
* @since 0.2.2
|
|
577
|
-
* @status
|
|
577
|
+
* @status stable
|
|
578
578
|
* @spec RFC 9180
|
|
579
579
|
* @related pki.hpke.seal, pki.hpke.setupR
|
|
580
580
|
*
|
package/lib/http-transport.js
CHANGED
|
@@ -245,7 +245,7 @@ function _classifyError(e, C) {
|
|
|
245
245
|
* @primitive pki.transport.https
|
|
246
246
|
* @signature pki.transport.https(defaults?) -> transport
|
|
247
247
|
* @since 0.3.16
|
|
248
|
-
* @status
|
|
248
|
+
* @status stable
|
|
249
249
|
* @spec RFC 7030, RFC 8996
|
|
250
250
|
* @defends tls-downgrade (CWE-757), server-impersonation (CWE-297), response-flooding (CWE-770), ssrf (CWE-918)
|
|
251
251
|
* @related pki.est.cacerts, pki.est.simpleenroll
|
package/lib/lint.js
CHANGED
|
@@ -714,7 +714,7 @@ function _applyThreshold(report, severity) {
|
|
|
714
714
|
* @primitive pki.lint.certificate
|
|
715
715
|
* @signature pki.lint.certificate(input, opts?) -> LintReport
|
|
716
716
|
* @since 0.2.10
|
|
717
|
-
* @status
|
|
717
|
+
* @status stable
|
|
718
718
|
* @spec RFC 5280, CA/Browser Forum TLS BR
|
|
719
719
|
* @related pki.schema.x509.parse, pki.inspect.certificate, pki.path.validate
|
|
720
720
|
*
|
|
@@ -772,7 +772,7 @@ function certificate(input, opts) {
|
|
|
772
772
|
* @primitive pki.lint.rules
|
|
773
773
|
* @signature pki.lint.rules(profile?) -> [{id, severity, source, citation}]
|
|
774
774
|
* @since 0.2.10
|
|
775
|
-
* @status
|
|
775
|
+
* @status stable
|
|
776
776
|
* @spec RFC 5280, CA/Browser Forum TLS BR
|
|
777
777
|
*
|
|
778
778
|
* Enumerate the rule registry (all rules, or one profile's). Each entry exposes its
|
|
@@ -790,7 +790,7 @@ function rules(profile) {
|
|
|
790
790
|
* @primitive pki.lint.profiles
|
|
791
791
|
* @signature pki.lint.profiles() -> [string]
|
|
792
792
|
* @since 0.2.10
|
|
793
|
-
* @status
|
|
793
|
+
* @status stable
|
|
794
794
|
* @spec RFC 5280, CA/Browser Forum TLS BR
|
|
795
795
|
*
|
|
796
796
|
* List the known lint-profile names.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
//
|
|
5
|
+
// @internal -- the single signature seam for an inbound signed OCSPRequest (RFC 6960 sec. 4.1.1),
|
|
6
|
+
// composed by pki.ocsp.verifyRequest. It routes the request-signature check through the same
|
|
7
|
+
// certification-path signature engine (its EdDSA low-order-point and algorithm-confusion gates
|
|
8
|
+
// included) that pki.ocsp.verify uses for a response and pki.crl.verify / pki.cms.verify use for
|
|
9
|
+
// theirs, so no second, weaker verifier of an untrusted request can drift. The producing side runs
|
|
10
|
+
// its own check over what it just signed (pki.ocsp.buildRequest's signer path); this is the
|
|
11
|
+
// different question a responder asks about a request someone else produced. The slot, the wiring
|
|
12
|
+
// assertion and the octet-alignment refusal are the shared seam (lib/verify-seam.js). It is not
|
|
13
|
+
// wired into index.js and is reached only by require.
|
|
14
|
+
|
|
15
|
+
var frameworkError = require("./framework-error");
|
|
16
|
+
var seam = require("./verify-seam");
|
|
17
|
+
|
|
18
|
+
var _seam = seam.makeSeam("ocsp-request-verify", frameworkError.OcspError, "ocsp/bad-input");
|
|
19
|
+
|
|
20
|
+
// Verify a signed OCSPRequest's signature over its exact tbsRequest bytes under the SPKI of the
|
|
21
|
+
// requestor certificate the request carries (RFC 6960 sec. 4.1.1). Unlike a CSR there is no
|
|
22
|
+
// self-signature: the verifying key is the requestor's, identified to the responder by
|
|
23
|
+
// requestorName and proven by the certificate embedded in (or supplied alongside) the request.
|
|
24
|
+
function verifyRequestSignature(signatureAlgorithm, signature, signerSpkiBytes, tbsRequestBytes) {
|
|
25
|
+
return _seam.verify(signatureAlgorithm, signature, signerSpkiBytes, tbsRequestBytes);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = { setEngine: _seam.setEngine, verifyRequestSignature: verifyRequestSignature };
|