@fedify/fedify 2.3.0-dev.994 → 2.3.0-pr.809.36
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/README.md +16 -7
- package/dist/{assert-DikXweDx.mjs → assert-OguE97r2.mjs} +1 -1
- package/dist/{assert_instance_of-C4Ri6VuN.mjs → assert_instance_of-DBC5X09g.mjs} +1 -1
- package/dist/{assert_not_equals--wG9hV7u.mjs → assert_not_equals-DkVK8oqV.mjs} +1 -1
- package/dist/{assert_rejects-B-qJtC9Z.mjs → assert_rejects-DN60FHPX.mjs} +28 -3
- package/dist/{assert_strict_equals-Dmjbg-bA.mjs → assert_strict_equals-XEgZAlrj.mjs} +1 -1
- package/dist/{assert_throws-4NwKEy2q.mjs → assert_throws-BOkhLGYc.mjs} +1 -1
- package/dist/{builder-yAlpiIqP.mjs → builder-DSZ7FZmg.mjs} +96 -42
- package/dist/{chunk-nlSIicah.js → chunk-CRNNMoPX.js} +2 -2
- package/dist/circuit-breaker-CSWsyoef.mjs +337 -0
- package/dist/{client-z-8dc-e1.d.cts → client-CAM_bQXx.d.cts} +1 -0
- package/dist/{client-AtlibPOU.d.ts → client-CSddvgWN.d.ts} +1 -2
- package/dist/compat/mod.d.cts +2 -1
- package/dist/compat/mod.d.ts +2 -3
- package/dist/compat/mod.js +3 -3
- package/dist/compat/outgoing-jsonld.test.mjs +4 -4
- package/dist/compat/public-audience.test.mjs +4 -4
- package/dist/compat/transformers.test.mjs +5 -5
- package/dist/{context-BzH2-ajs.d.ts → context-BBVLF7lx.d.cts} +342 -269
- package/dist/{context-DJGagtNd.d.cts → context-BU6jSQdo.d.ts} +344 -266
- package/dist/{context-Dk_tacqz.mjs → context-DVoTs_wM.mjs} +64 -5
- package/dist/{deno-XBVlKnOX.mjs → deno-DCdM7d93.mjs} +1 -1
- package/dist/{docloader-DPp-X6gk.mjs → docloader-B7mVwA5_.mjs} +4 -4
- package/dist/{esm-DVILvP5e.mjs → esm-vrlUxr60.mjs} +4 -7
- package/dist/federation/builder.test.mjs +163 -11
- package/dist/federation/circuit-breaker.test.d.mts +2 -0
- package/dist/federation/circuit-breaker.test.mjs +446 -0
- package/dist/federation/collection.test.mjs +3 -3
- package/dist/federation/handler.test.mjs +1770 -29
- package/dist/federation/idempotency.test.mjs +5 -5
- package/dist/federation/inbox.test.mjs +4 -4
- package/dist/federation/keycache.test.mjs +5 -5
- package/dist/federation/kv.test.mjs +2 -2
- package/dist/federation/metrics.test.d.mts +2 -0
- package/dist/federation/metrics.test.mjs +634 -0
- package/dist/federation/middleware.test.mjs +4036 -146
- package/dist/federation/mod.cjs +195 -14
- package/dist/federation/mod.d.cts +6 -4
- package/dist/federation/mod.d.ts +6 -6
- package/dist/federation/mod.js +192 -14
- package/dist/federation/mq.test.mjs +176 -15
- package/dist/federation/negotiation.test.mjs +4 -4
- package/dist/federation/retry.test.mjs +3 -3
- package/dist/federation/router.test.mjs +190 -9
- package/dist/federation/send.test.mjs +153 -15
- package/dist/federation/temporal.test.d.mts +2 -0
- package/dist/federation/temporal.test.mjs +71 -0
- package/dist/federation/webfinger.test.mjs +150 -5
- package/dist/{http-CSlLA54v.mjs → http-BufbkLuW.mjs} +146 -47
- package/dist/{http-B4rrzxtg.js → http-Dh18nwJg.js} +994 -64
- package/dist/{http-aQzN9Ayi.d.ts → http-VyDTd4G3.d.cts} +15 -3
- package/dist/{http-BrGqJDXL.cjs → http-gOiudB3g.cjs} +1099 -61
- package/dist/{http-CrGuipxe.d.cts → http-lf8Hsd91.d.ts} +15 -1
- package/dist/{key-Bzx--sHD.mjs → key-DZdKLJXT.mjs} +43 -18
- package/dist/{kv-CbLNp3zQ.d.cts → kv-D6hNiMTK.d.ts} +1 -0
- package/dist/{kv-cache-DI-Sk7-I.cjs → kv-cache-BuK_nlpY.cjs} +20 -3
- package/dist/{kv-cache-U__xU4qR.mjs → kv-cache-C7SQnkGI.mjs} +21 -3
- package/dist/{kv-cache-EMIqoIuB.js → kv-cache-DNDSb6Po.js} +20 -3
- package/dist/{kv-GFYnFoOl.d.ts → kv-gJ8LYbxX.d.cts} +1 -3
- package/dist/ld-0Lsznacf.mjs +626 -0
- package/dist/metrics-BHeWd24f.mjs +815 -0
- package/dist/{middleware-zjZ6AFCW.mjs → middleware-BNANvDh2.mjs} +1 -1
- package/dist/{middleware-nidH7VZH.js → middleware-BgzRkDcm.js} +2260 -556
- package/dist/{middleware-An4DjES4.cjs → middleware-D-iES1nQ.cjs} +2318 -623
- package/dist/{middleware-K1g6bEkV.mjs → middleware-EV6J_eAc.mjs} +1639 -373
- package/dist/{mod-CR8soWa9.d.ts → mod-B0hW12_O.d.cts} +26 -4
- package/dist/mod-C0F6kvgS.d.cts +182 -0
- package/dist/{mod-Cr3f-ACa.d.cts → mod-COIAjwRS.d.ts} +26 -2
- package/dist/{mod-CMEbIaNh.d.cts → mod-DFvNJcNb.d.ts} +56 -4
- package/dist/mod-vPYVoa5n.d.ts +182 -0
- package/dist/{mod-CLgIXe9w.d.ts → mod-yvIXFAEi.d.cts} +56 -6
- package/dist/mod.cjs +9 -6
- package/dist/mod.d.cts +12 -10
- package/dist/mod.d.ts +12 -12
- package/dist/mod.js +11 -11
- package/dist/mq-D-nlpY04.d.ts +208 -0
- package/dist/mq-D8uSFzxe.d.cts +208 -0
- package/dist/{negotiation-SQvQgUqe.mjs → negotiation-DDstyBvc.mjs} +29 -0
- package/dist/nodeinfo/client.test.mjs +4 -4
- package/dist/nodeinfo/handler.test.mjs +4 -4
- package/dist/nodeinfo/mod.d.cts +2 -1
- package/dist/nodeinfo/mod.d.ts +2 -3
- package/dist/nodeinfo/mod.js +3 -3
- package/dist/nodeinfo/types.test.mjs +3 -3
- package/dist/otel/exporter.test.mjs +28 -25
- package/dist/otel/mod.cjs +6 -5
- package/dist/otel/mod.d.cts +5 -3
- package/dist/otel/mod.d.ts +5 -5
- package/dist/otel/mod.js +8 -7
- package/dist/{outgoing-jsonld-CNmZLixq.mjs → outgoing-jsonld-L_DbOaFe.mjs} +2 -2
- package/dist/{owner-C-tqfvxn.mjs → owner-Cl-1cAR8.mjs} +2 -2
- package/dist/{owner-CptqhsOy.d.cts → owner-CnngXDNJ.d.ts} +2 -1
- package/dist/{owner-74ARJ5TL.d.ts → owner-DEvZuyOE.d.cts} +2 -3
- package/dist/{proof-Bbbwf8_x.js → proof-CyXndf6-.js} +378 -15
- package/dist/{proof-BlmRPzrK.mjs → proof-Dnz8jtiQ.mjs} +26 -8
- package/dist/{proof-BW89QMVB.cjs → proof-tz35unOj.cjs} +432 -15
- package/dist/runtime/mod.d.cts +1 -0
- package/dist/runtime/mod.d.ts +1 -2
- package/dist/runtime/mod.js +3 -3
- package/dist/{send-05pJLcb6.mjs → send-DIbBYN5P.mjs} +72 -26
- package/dist/sig/http.test.mjs +356 -33
- package/dist/sig/key.test.mjs +103 -6
- package/dist/sig/ld.test.mjs +696 -7
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +4 -3
- package/dist/sig/mod.d.ts +4 -5
- package/dist/sig/mod.js +4 -4
- package/dist/sig/owner.test.mjs +6 -6
- package/dist/sig/proof.test.mjs +169 -8
- package/dist/{std__assert-CRDpx_HF.mjs → std__assert-BBjXFNOb.mjs} +5 -30
- package/dist/temporal-BEwob1Vg.mjs +95 -0
- package/dist/testing/mod.d.mts +110 -10
- package/dist/testing/mod.mjs +1 -2
- package/dist/{transformers-ve6e2xcg.js → transformers-BGMIq1cs.js} +2 -2
- package/dist/{types-hvL8ElAs.js → types-CAY3OdLq.js} +2 -2
- package/dist/utils/docloader.test.mjs +6 -6
- package/dist/utils/kv-cache.test.mjs +67 -2
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +2 -1
- package/dist/utils/mod.d.ts +2 -3
- package/dist/utils/mod.js +3 -3
- package/dist/vocab/cjs.test.mjs +1 -1
- package/dist/vocab/mod.d.cts +1 -0
- package/dist/vocab/mod.d.ts +1 -2
- package/dist/vocab/mod.js +2 -2
- package/package.json +16 -16
- package/dist/ld-DR78beiv.mjs +0 -279
- package/dist/middleware-BFBaR0mF.cjs +0 -4
- package/dist/mod-2d12ffz3.d.ts +0 -64
- package/dist/mod-D35TRn09.d.cts +0 -62
- package/dist/router-CrMLXoOr.mjs +0 -114
- package/dist/{activity-listener-ell7W1s9.mjs → activity-listener-tztVvlNb.mjs} +0 -0
- package/dist/{assert_equals-Ew3jOFa3.mjs → assert_equals-C-ZRDbaf.mjs} +0 -0
- package/dist/{client-D_1QpnWt.mjs → client-ByXmQhYD.mjs} +1 -1
- package/dist/{collection-D-HqUuA2.mjs → collection-Cc3DVAhE.mjs} +0 -0
- package/dist/{keycache-EGATflN-.mjs → keycache-BeU0LCII.mjs} +0 -0
- package/dist/{public-audience-DYFHzm_c.mjs → public-audience-Cvbr2Gzt.mjs} +1 -1
- /package/dist/{retry-bMXBL97A.mjs → retry-CXg_MBI-.mjs} +0 -0
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
4
|
+
import { n as version, t as name } from "./deno-DCdM7d93.mjs";
|
|
5
|
+
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-BHeWd24f.mjs";
|
|
5
6
|
import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-CPkZzmGN.mjs";
|
|
6
|
-
import { o as validateCryptoKey, r as fetchKeyDetailed } from "./key-
|
|
7
|
+
import { o as validateCryptoKey, r as fetchKeyDetailed } from "./key-DZdKLJXT.mjs";
|
|
8
|
+
import { getLogger } from "@logtape/logtape";
|
|
7
9
|
import { CryptographicKey } from "@fedify/vocab";
|
|
8
10
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
9
11
|
import { FetchError } from "@fedify/vocab-runtime";
|
|
10
|
-
import { getLogger } from "@logtape/logtape";
|
|
11
12
|
import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
|
|
12
13
|
import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
|
|
13
14
|
import { encodeHex } from "byte-encodings/hex";
|
|
14
15
|
import { Item, decodeDict, encodeItem } from "structured-field-values";
|
|
15
16
|
//#region src/sig/http.ts
|
|
16
17
|
const DEFAULT_MAX_REDIRECTION = 20;
|
|
18
|
+
const DOUBLE_KNOCK_TRANSPORT_RETRY_DELAY_MS = 100;
|
|
17
19
|
/**
|
|
18
20
|
* Signs a request using the given private key.
|
|
19
21
|
* @param request The request to sign.
|
|
@@ -316,6 +318,27 @@ function parseKeyId(value) {
|
|
|
316
318
|
function getKeyFetchErrorName(error) {
|
|
317
319
|
return error.name || error.constructor.name || "Error";
|
|
318
320
|
}
|
|
321
|
+
/**
|
|
322
|
+
* Known draft-cavage `algorithm` parameter values, used to keep the
|
|
323
|
+
* `http_signatures.algorithm` metric attribute on a bounded set. The header
|
|
324
|
+
* field is attacker-controlled and not used to select the verification
|
|
325
|
+
* algorithm, so unknown values are dropped from the metric to prevent
|
|
326
|
+
* cardinality blow-up.
|
|
327
|
+
*/
|
|
328
|
+
const DRAFT_KNOWN_ALGORITHMS = new Set([
|
|
329
|
+
"ecdsa-sha256",
|
|
330
|
+
"ecdsa-sha384",
|
|
331
|
+
"ecdsa-sha512",
|
|
332
|
+
"ed25519",
|
|
333
|
+
"hs2019",
|
|
334
|
+
"rsa-sha1",
|
|
335
|
+
"rsa-sha256",
|
|
336
|
+
"rsa-sha512"
|
|
337
|
+
]);
|
|
338
|
+
function classifyHttpVerifyResult(result) {
|
|
339
|
+
if (result.verified) return "verified";
|
|
340
|
+
return result.reason.type === "noSignature" ? "missing" : "rejected";
|
|
341
|
+
}
|
|
319
342
|
function recordVerificationResult(span, result) {
|
|
320
343
|
span.setAttribute("http_signatures.verified", result.verified);
|
|
321
344
|
if (result.verified === true) return;
|
|
@@ -359,27 +382,37 @@ async function verifyRequestDetailed(request, options = {}) {
|
|
|
359
382
|
span.setAttribute(ATTR_URL_FULL, request.url);
|
|
360
383
|
for (const [name, value] of request.headers) span.setAttribute(ATTR_HTTP_REQUEST_HEADER(name), value);
|
|
361
384
|
}
|
|
385
|
+
const start = performance.now();
|
|
386
|
+
const metricsContext = {};
|
|
387
|
+
let result;
|
|
388
|
+
let threw = false;
|
|
362
389
|
try {
|
|
363
390
|
let spec = options.spec;
|
|
364
391
|
if (spec == null) spec = request.headers.has("Signature-Input") ? "rfc9421" : "draft-cavage-http-signatures-12";
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
else result = await verifyRequestDraft(request, span, options);
|
|
392
|
+
if (spec === "rfc9421") result = await verifyRequestRfc9421(request, span, metricsContext, options);
|
|
393
|
+
else result = await verifyRequestDraft(request, span, metricsContext, options);
|
|
368
394
|
recordVerificationResult(span, result);
|
|
369
395
|
if (!result.verified) span.setStatus({ code: SpanStatusCode.ERROR });
|
|
370
396
|
return result;
|
|
371
397
|
} catch (error) {
|
|
398
|
+
threw = true;
|
|
372
399
|
span.setStatus({
|
|
373
400
|
code: SpanStatusCode.ERROR,
|
|
374
401
|
message: String(error)
|
|
375
402
|
});
|
|
376
403
|
throw error;
|
|
377
404
|
} finally {
|
|
405
|
+
const classified = threw ? "error" : classifyHttpVerifyResult(result);
|
|
406
|
+
const failureReason = result != null && !result.verified && result.reason.type !== "noSignature" ? result.reason.type : void 0;
|
|
407
|
+
getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "http", classified, {
|
|
408
|
+
algorithm: metricsContext.algorithm,
|
|
409
|
+
failureReason
|
|
410
|
+
});
|
|
378
411
|
span.end();
|
|
379
412
|
}
|
|
380
413
|
});
|
|
381
414
|
}
|
|
382
|
-
async function verifyRequestDraft(request, span, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, tracerProvider } = {}) {
|
|
415
|
+
async function verifyRequestDraft(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
|
|
383
416
|
const logger = getLogger([
|
|
384
417
|
"fedify",
|
|
385
418
|
"sig",
|
|
@@ -527,13 +560,18 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
|
|
|
527
560
|
const keyIdUrl = parseKeyId(keyId);
|
|
528
561
|
if (keyIdUrl == null) return invalidSignatureResult(null);
|
|
529
562
|
span?.setAttribute("http_signatures.key_id", keyId);
|
|
530
|
-
if ("algorithm" in sigValues)
|
|
531
|
-
|
|
563
|
+
if ("algorithm" in sigValues) {
|
|
564
|
+
span?.setAttribute("http_signatures.algorithm", sigValues.algorithm);
|
|
565
|
+
const normalizedAlgorithm = sigValues.algorithm.toLowerCase();
|
|
566
|
+
if (DRAFT_KNOWN_ALGORITHMS.has(normalizedAlgorithm)) metricsContext.algorithm = normalizedAlgorithm;
|
|
567
|
+
}
|
|
568
|
+
const { key, cached, fetchError } = await measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyIdUrl, CryptographicKey, {
|
|
532
569
|
documentLoader,
|
|
533
570
|
contextLoader,
|
|
534
571
|
keyCache,
|
|
535
|
-
tracerProvider
|
|
536
|
-
|
|
572
|
+
tracerProvider,
|
|
573
|
+
meterProvider
|
|
574
|
+
}));
|
|
537
575
|
if (fetchError != null) return keyFetchErrorResult(keyIdUrl, fetchError);
|
|
538
576
|
if (key == null) return invalidSignatureResult(keyIdUrl);
|
|
539
577
|
const headerNames = headers.split(/\s+/g);
|
|
@@ -555,7 +593,7 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
|
|
|
555
593
|
signature,
|
|
556
594
|
message
|
|
557
595
|
});
|
|
558
|
-
return await
|
|
596
|
+
return await verifyRequestDraft(originalRequest, span, metricsContext, {
|
|
559
597
|
documentLoader,
|
|
560
598
|
contextLoader,
|
|
561
599
|
timeWindow,
|
|
@@ -563,7 +601,9 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
|
|
|
563
601
|
keyCache: {
|
|
564
602
|
get: () => Promise.resolve(void 0),
|
|
565
603
|
set: async (keyId, key) => await keyCache?.set(keyId, key)
|
|
566
|
-
}
|
|
604
|
+
},
|
|
605
|
+
meterProvider,
|
|
606
|
+
tracerProvider
|
|
567
607
|
});
|
|
568
608
|
}
|
|
569
609
|
logger.debug("Failed to verify with the fetched key {keyId}; signature {signature} is invalid. Check if the key is correct or if the signed message is correct. The message to sign is:\n{message}", {
|
|
@@ -639,7 +679,7 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
|
|
|
639
679
|
}
|
|
640
680
|
return false;
|
|
641
681
|
}
|
|
642
|
-
async function verifyRequestRfc9421(request, span, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, tracerProvider } = {}) {
|
|
682
|
+
async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
|
|
643
683
|
const logger = getLogger([
|
|
644
684
|
"fedify",
|
|
645
685
|
"sig",
|
|
@@ -673,9 +713,14 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
673
713
|
return invalidSignatureResult(null);
|
|
674
714
|
}
|
|
675
715
|
let failure = noSignatureResult();
|
|
716
|
+
let failureAlgorithm;
|
|
717
|
+
const setFailure = (result, algorithm) => {
|
|
718
|
+
failure = result;
|
|
719
|
+
failureAlgorithm = algorithm;
|
|
720
|
+
};
|
|
676
721
|
for (const sigName of signatureNames) {
|
|
677
722
|
if (!signatures[sigName]) {
|
|
678
|
-
|
|
723
|
+
setFailure(invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId)));
|
|
679
724
|
continue;
|
|
680
725
|
}
|
|
681
726
|
const sigInput = signatureInputs[sigName];
|
|
@@ -686,7 +731,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
686
731
|
signatureName: sigName,
|
|
687
732
|
signatureInput: signatureInputHeader
|
|
688
733
|
});
|
|
689
|
-
|
|
734
|
+
setFailure(invalidSignatureResult(null));
|
|
690
735
|
continue;
|
|
691
736
|
}
|
|
692
737
|
if (!sigInput.created) {
|
|
@@ -694,7 +739,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
694
739
|
signatureName: sigName,
|
|
695
740
|
signatureInput: signatureInputHeader
|
|
696
741
|
});
|
|
697
|
-
|
|
742
|
+
setFailure(invalidSignatureResult(keyId));
|
|
698
743
|
continue;
|
|
699
744
|
}
|
|
700
745
|
const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
|
|
@@ -706,14 +751,14 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
706
751
|
created: signatureCreated.toString(),
|
|
707
752
|
now: now.toString()
|
|
708
753
|
});
|
|
709
|
-
|
|
754
|
+
setFailure(invalidSignatureResult(keyId));
|
|
710
755
|
continue;
|
|
711
756
|
} else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
|
|
712
757
|
logger.debug("Failed to verify; signature created time is too far in the past.", {
|
|
713
758
|
created: signatureCreated.toString(),
|
|
714
759
|
now: now.toString()
|
|
715
760
|
});
|
|
716
|
-
|
|
761
|
+
setFailure(invalidSignatureResult(keyId));
|
|
717
762
|
continue;
|
|
718
763
|
}
|
|
719
764
|
}
|
|
@@ -721,34 +766,35 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
721
766
|
const contentDigestHeader = request.headers.get("Content-Digest");
|
|
722
767
|
if (!contentDigestHeader) {
|
|
723
768
|
logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
|
|
724
|
-
|
|
769
|
+
setFailure(invalidSignatureResult(keyId));
|
|
725
770
|
continue;
|
|
726
771
|
}
|
|
727
772
|
if (!await verifyRfc9421ContentDigest(contentDigestHeader, await request.arrayBuffer())) {
|
|
728
773
|
logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
|
|
729
|
-
|
|
774
|
+
setFailure(invalidSignatureResult(keyId));
|
|
730
775
|
continue;
|
|
731
776
|
}
|
|
732
777
|
}
|
|
733
778
|
span?.setAttribute("http_signatures.key_id", sigInput.keyId);
|
|
734
779
|
span?.setAttribute("http_signatures.created", sigInput.created.toString());
|
|
735
780
|
if (keyId == null) {
|
|
736
|
-
|
|
781
|
+
setFailure(invalidSignatureResult(null));
|
|
737
782
|
continue;
|
|
738
783
|
}
|
|
739
|
-
const { key, cached, fetchError } = await fetchKeyDetailed(keyId, CryptographicKey, {
|
|
784
|
+
const { key, cached, fetchError } = await measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyId, CryptographicKey, {
|
|
740
785
|
documentLoader,
|
|
741
786
|
contextLoader,
|
|
742
787
|
keyCache,
|
|
743
|
-
tracerProvider
|
|
744
|
-
|
|
788
|
+
tracerProvider,
|
|
789
|
+
meterProvider
|
|
790
|
+
}));
|
|
745
791
|
if (fetchError != null) {
|
|
746
|
-
|
|
792
|
+
setFailure(keyFetchErrorResult(keyId, fetchError));
|
|
747
793
|
continue;
|
|
748
794
|
}
|
|
749
795
|
if (!key) {
|
|
750
796
|
logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
|
|
751
|
-
|
|
797
|
+
setFailure(invalidSignatureResult(keyId));
|
|
752
798
|
continue;
|
|
753
799
|
}
|
|
754
800
|
let alg = sigInput.alg?.toLowerCase();
|
|
@@ -760,12 +806,13 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
760
806
|
}
|
|
761
807
|
if (alg) span?.setAttribute("http_signatures.algorithm", alg);
|
|
762
808
|
const algorithm = alg && rfc9421AlgorithmMap[alg];
|
|
809
|
+
const candidateAlgorithm = algorithm ? alg : void 0;
|
|
763
810
|
if (!algorithm) {
|
|
764
811
|
logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
|
|
765
812
|
algorithm: sigInput.alg,
|
|
766
813
|
supported: Object.keys(rfc9421AlgorithmMap)
|
|
767
814
|
});
|
|
768
|
-
|
|
815
|
+
setFailure(invalidSignatureResult(keyId));
|
|
769
816
|
continue;
|
|
770
817
|
}
|
|
771
818
|
let signatureBase;
|
|
@@ -776,20 +823,22 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
776
823
|
error,
|
|
777
824
|
signatureInput: sigInput
|
|
778
825
|
});
|
|
779
|
-
|
|
826
|
+
setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
|
|
780
827
|
continue;
|
|
781
828
|
}
|
|
782
829
|
const signatureBaseBytes = new TextEncoder().encode(signatureBase);
|
|
783
830
|
span?.setAttribute("http_signatures.signature", encodeHex(sigBytes));
|
|
784
831
|
try {
|
|
785
|
-
if (await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes))
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
832
|
+
if (await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes)) {
|
|
833
|
+
metricsContext.algorithm = candidateAlgorithm;
|
|
834
|
+
return {
|
|
835
|
+
verified: true,
|
|
836
|
+
key,
|
|
837
|
+
signatureLabel: sigName
|
|
838
|
+
};
|
|
839
|
+
} else if (cached) {
|
|
791
840
|
logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
|
|
792
|
-
return await
|
|
841
|
+
return await verifyRequestRfc9421(originalRequest, span, metricsContext, {
|
|
793
842
|
documentLoader,
|
|
794
843
|
contextLoader,
|
|
795
844
|
timeWindow,
|
|
@@ -798,14 +847,16 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
798
847
|
get: () => Promise.resolve(void 0),
|
|
799
848
|
set: async (keyId, key) => await keyCache?.set(keyId, key)
|
|
800
849
|
},
|
|
801
|
-
spec: "rfc9421"
|
|
850
|
+
spec: "rfc9421",
|
|
851
|
+
meterProvider,
|
|
852
|
+
tracerProvider
|
|
802
853
|
});
|
|
803
854
|
} else {
|
|
804
855
|
logger.debug("Failed to verify signature with fetched key {keyId}; signature invalid.", {
|
|
805
856
|
keyId: sigInput.keyId,
|
|
806
857
|
signatureBase
|
|
807
858
|
});
|
|
808
|
-
|
|
859
|
+
setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
|
|
809
860
|
}
|
|
810
861
|
} catch (error) {
|
|
811
862
|
logger.debug("Error during signature verification: {error}", {
|
|
@@ -813,9 +864,10 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
813
864
|
keyId: sigInput.keyId,
|
|
814
865
|
algorithm: sigInput.alg
|
|
815
866
|
});
|
|
816
|
-
|
|
867
|
+
setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
|
|
817
868
|
}
|
|
818
869
|
}
|
|
870
|
+
metricsContext.algorithm = failureAlgorithm;
|
|
819
871
|
return failure;
|
|
820
872
|
}
|
|
821
873
|
/**
|
|
@@ -842,6 +894,59 @@ function createRedirectRequest(request, location, body) {
|
|
|
842
894
|
cache: request.cache
|
|
843
895
|
});
|
|
844
896
|
}
|
|
897
|
+
async function fetchDoubleKnockRequest(request, signedRequest, signal) {
|
|
898
|
+
const maxAttempts = request.method === "GET" || request.method === "HEAD" ? 2 : 1;
|
|
899
|
+
for (let attempt = 1;; attempt++) try {
|
|
900
|
+
return await fetch(signedRequest, {
|
|
901
|
+
redirect: "manual",
|
|
902
|
+
signal
|
|
903
|
+
});
|
|
904
|
+
} catch (error) {
|
|
905
|
+
const abortedSignal = getAbortedSignal(signal, request.signal, signedRequest.signal);
|
|
906
|
+
if (abortedSignal != null) throw getAbortReason(abortedSignal);
|
|
907
|
+
if (isAbortError(error)) throw error;
|
|
908
|
+
if (attempt >= maxAttempts) throw createFetchError(request.url, error);
|
|
909
|
+
await sleep(DOUBLE_KNOCK_TRANSPORT_RETRY_DELAY_MS, signal, request.signal, signedRequest.signal);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
function createFetchError(url, cause) {
|
|
913
|
+
const error = new FetchError(url, cause instanceof Error ? cause.message : String(cause));
|
|
914
|
+
error.cause = cause;
|
|
915
|
+
return error;
|
|
916
|
+
}
|
|
917
|
+
function isAbortError(error) {
|
|
918
|
+
return error instanceof Error && error.name === "AbortError";
|
|
919
|
+
}
|
|
920
|
+
async function sleep(ms, ...signals) {
|
|
921
|
+
const abortSignals = signals.filter((signal) => signal != null);
|
|
922
|
+
const abortedSignal = getAbortedSignal(...abortSignals);
|
|
923
|
+
if (abortedSignal != null) throw getAbortReason(abortedSignal);
|
|
924
|
+
if (abortSignals.length < 1) {
|
|
925
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
await new Promise((resolve, reject) => {
|
|
929
|
+
const removeAbortListeners = () => {
|
|
930
|
+
for (const signal of abortSignals) signal.removeEventListener("abort", handleAbort);
|
|
931
|
+
};
|
|
932
|
+
const timeout = setTimeout(() => {
|
|
933
|
+
removeAbortListeners();
|
|
934
|
+
resolve();
|
|
935
|
+
}, ms);
|
|
936
|
+
function handleAbort(event) {
|
|
937
|
+
clearTimeout(timeout);
|
|
938
|
+
removeAbortListeners();
|
|
939
|
+
reject(getAbortReason(event.currentTarget));
|
|
940
|
+
}
|
|
941
|
+
for (const signal of abortSignals) signal.addEventListener("abort", handleAbort, { once: true });
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
function getAbortedSignal(...signals) {
|
|
945
|
+
return signals.find((signal) => signal?.aborted);
|
|
946
|
+
}
|
|
947
|
+
function getAbortReason(signal) {
|
|
948
|
+
return signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
|
|
949
|
+
}
|
|
845
950
|
/**
|
|
846
951
|
* Performs a double-knock request to the given URL. For the details of
|
|
847
952
|
* double-knocking, see
|
|
@@ -868,10 +973,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
|
|
|
868
973
|
body
|
|
869
974
|
});
|
|
870
975
|
log?.(signedRequest);
|
|
871
|
-
let response = await
|
|
872
|
-
redirect: "manual",
|
|
873
|
-
signal
|
|
874
|
-
});
|
|
976
|
+
let response = await fetchDoubleKnockRequest(request, signedRequest, signal);
|
|
875
977
|
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
876
978
|
if (redirected >= maximumRedirection) throw new FetchError(request.url, `Too many redirections (${redirected + 1})`);
|
|
877
979
|
const redirectRequest = createRedirectRequest(request, response.headers.get("Location"), body);
|
|
@@ -946,10 +1048,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
|
|
|
946
1048
|
body
|
|
947
1049
|
});
|
|
948
1050
|
log?.(signedRequest);
|
|
949
|
-
response = await
|
|
950
|
-
redirect: "manual",
|
|
951
|
-
signal
|
|
952
|
-
});
|
|
1051
|
+
response = await fetchDoubleKnockRequest(request, signedRequest, signal);
|
|
953
1052
|
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
954
1053
|
if (redirected >= maximumRedirection) throw new FetchError(request.url, `Too many redirections (${redirected + 1})`);
|
|
955
1054
|
const redirectRequest = createRedirectRequest(request, response.headers.get("Location"), body);
|