@fedify/fedify 2.0.7 → 2.0.9
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/dist/{assert-MZs1qjMx.js → assert-ddO5KLpe.mjs} +5 -9
- package/dist/{assert_equals-DSbWqCm3.js → assert_equals-Ew3jOFa3.mjs} +55 -69
- package/dist/{assert_instance_of-DHz7EHNU.js → assert_instance_of-C4Ri6VuN.mjs} +5 -9
- package/dist/{assert_not_equals-C80BG-_5.js → assert_not_equals--wG9hV7u.mjs} +6 -13
- package/dist/{assert_rejects-Ce45JcFg.js → assert_rejects-B-qJtC9Z.mjs} +6 -11
- package/dist/{assert_throws-BNXdRGWP.js → assert_throws-4NwKEy2q.mjs} +5 -10
- package/dist/{builder-DA7Qgx_F.js → builder-DDoQaGOu.mjs} +32 -41
- package/dist/{chunk-CGaQZ11T.cjs → chunk-DDcVe30Y.cjs} +23 -24
- package/dist/{chunk-DJNbSFdH.js → chunk-nlSIicah.js} +8 -8
- package/dist/{client-CoCIaTNO.js → client-A1UrnX6I.mjs} +9 -13
- package/dist/{client-BxMZiQaD.d.ts → client-AtlibPOU.d.ts} +1 -1
- package/dist/{client-C97KOq3x.d.cts → client-z-8dc-e1.d.cts} +1 -1
- package/dist/{collection-CcnIw1qY.js → collection-ChgDTHLz.mjs} +7 -12
- package/dist/compat/mod.cjs +5 -8
- package/dist/compat/mod.d.cts +78 -6
- package/dist/compat/mod.d.ts +78 -6
- package/dist/compat/mod.js +4 -8
- package/dist/compat/transformers.test.mjs +62 -0
- package/dist/{context-D3QkEtZd.d.cts → context-CNIt-Qn7.d.cts} +9 -18
- package/dist/{context-DZJhUmzF.d.ts → context-Dyg7P1qW.d.ts} +9 -18
- package/dist/{context-pa9aIrwp.js → context-Juj6bdHC.mjs} +7 -11
- package/dist/deno-CuVDEdyj.mjs +8 -0
- package/dist/{docloader-CJeSPcS_.js → docloader-BPq9yzC_.mjs} +8 -14
- package/dist/{esm-nLm00z9V.js → esm-DVILvP5e.mjs} +50 -89
- package/dist/federation/builder.test.d.mts +2 -0
- package/dist/federation/{builder.test.js → builder.test.mjs} +19 -38
- package/dist/federation/collection.test.d.mts +2 -0
- package/dist/federation/collection.test.mjs +21 -0
- package/dist/federation/handler.test.d.mts +2 -0
- package/dist/federation/{handler.test.js → handler.test.mjs} +26 -56
- package/dist/federation/idempotency.test.d.mts +2 -0
- package/dist/federation/{idempotency.test.js → idempotency.test.mjs} +31 -62
- package/dist/federation/inbox.test.d.mts +2 -0
- package/dist/federation/{inbox.test.js → inbox.test.mjs} +8 -12
- package/dist/federation/keycache.test.d.mts +2 -0
- package/dist/federation/{keycache.test.js → keycache.test.mjs} +13 -13
- package/dist/federation/kv.test.d.mts +2 -0
- package/dist/federation/{kv.test.js → kv.test.mjs} +11 -22
- package/dist/federation/middleware.test.d.mts +2 -0
- package/dist/federation/{middleware.test.js → middleware.test.mjs} +146 -225
- package/dist/federation/mod.cjs +327 -16
- package/dist/federation/mod.d.cts +3 -6
- package/dist/federation/mod.d.ts +3 -6
- package/dist/federation/mod.js +322 -13
- package/dist/federation/mq.test.d.mts +2 -0
- package/dist/federation/{mq.test.js → mq.test.mjs} +21 -35
- package/dist/federation/negotiation.test.d.mts +2 -0
- package/dist/federation/{negotiation.test.js → negotiation.test.mjs} +9 -16
- package/dist/federation/retry.test.d.mts +2 -0
- package/dist/federation/{retry.test.js → retry.test.mjs} +8 -11
- package/dist/federation/router.test.d.mts +2 -0
- package/dist/federation/{router.test.js → router.test.mjs} +11 -16
- package/dist/federation/send.test.d.mts +2 -0
- package/dist/federation/{send.test.js → send.test.mjs} +22 -29
- package/dist/federation/webfinger.test.d.mts +2 -0
- package/dist/federation/{webfinger.test.js → webfinger.test.mjs} +22 -55
- package/dist/{http-DkHdFfrc.d.ts → http-B2wiNmSo.d.ts} +1 -6
- package/dist/{http-B3vAjAtl.js → http-Bz7avX57.js} +39 -159
- package/dist/{http-Cz3MlXAZ.d.cts → http-C_tEAiZj.d.cts} +1 -6
- package/dist/{http-CjaLjnRN.js → http-DI213UHg.mjs} +31 -35
- package/dist/{http-CQ7TiYUI.cjs → http-DKBDoudA.cjs} +119 -233
- package/dist/{inbox-B33isX44.js → inbox-Bdn-CSRd.mjs} +18 -26
- package/dist/{key-Cga1p73u.js → key-DzJf84o7.mjs} +12 -19
- package/dist/{keycache-DRxpZ5r9.js → keycache-DaQ3ndaJ.mjs} +15 -10
- package/dist/{keys-ZbcByPg9.js → keys-CtZLJq76.mjs} +5 -9
- package/dist/{kv-QzKcOQgP.js → kv-BrZHNugx.mjs} +6 -10
- package/dist/{kv-BL4nlICN.d.cts → kv-CbLNp3zQ.d.cts} +1 -1
- package/dist/{kv-DXEUEP6z.d.ts → kv-GFYnFoOl.d.ts} +1 -1
- package/dist/{kv-cache-CFdy0BpZ.js → kv-cache-DBd7BezJ.js} +6 -13
- package/dist/{kv-cache--qyREO7e.cjs → kv-cache-Dj1Q7TiW.cjs} +27 -34
- package/dist/{kv-cache-El7We5sy.js → kv-cache-OWmRLHir.mjs} +4 -8
- package/dist/{ld-BWSOukKj.js → ld-DczS1fLK.mjs} +17 -31
- package/dist/middleware-B5CiOImA.mjs +5 -0
- package/dist/{middleware-CP7JdsGq.js → middleware-BKNu57ZI.js} +331 -365
- package/dist/middleware-C36TOX-2.cjs +4 -0
- package/dist/{middleware-Scz2k9eL.cjs → middleware-CyjmpK70.cjs} +523 -565
- package/dist/{middleware-BI1VCuPT.js → middleware-DoHz9oIo.mjs} +260 -292
- package/dist/{mod-DPkRU3EK.d.cts → mod-1xhgsHef.d.cts} +2 -2
- package/dist/{mod-DUWcVv49.d.ts → mod-BGtYJZKu.d.ts} +2 -2
- package/dist/{mod-DXsQakeS.d.cts → mod-Bld7oeqf.d.cts} +3 -3
- package/dist/{mod-DnSsduJF.d.ts → mod-BnAKGh2w.d.ts} +2 -2
- package/dist/{mod-CwZXZJ9d.d.ts → mod-DTOUyCce.d.ts} +3 -3
- package/dist/{mod-Di3W5OdP.d.cts → mod-DWoQffTD.d.cts} +2 -2
- package/dist/mod.cjs +29 -68
- package/dist/mod.d.cts +11 -14
- package/dist/mod.d.ts +11 -15
- package/dist/mod.js +17 -65
- package/dist/{negotiation-5NPJL6zp.js → negotiation-BehA2uul.mjs} +7 -11
- package/dist/nodeinfo/client.test.d.mts +2 -0
- package/dist/nodeinfo/{client.test.js → client.test.mjs} +22 -40
- package/dist/nodeinfo/handler.test.d.mts +2 -0
- package/dist/nodeinfo/{handler.test.js → handler.test.mjs} +13 -42
- package/dist/nodeinfo/mod.cjs +5 -8
- package/dist/nodeinfo/mod.d.cts +2 -3
- package/dist/nodeinfo/mod.d.ts +2 -3
- package/dist/nodeinfo/mod.js +4 -8
- package/dist/nodeinfo/types.test.d.mts +2 -0
- package/dist/nodeinfo/{types.test.js → types.test.mjs} +9 -16
- package/dist/otel/exporter.test.d.mts +2 -0
- package/dist/otel/{exporter.test.js → exporter.test.mjs} +117 -169
- package/dist/otel/mod.cjs +15 -20
- package/dist/otel/mod.d.cts +2 -2
- package/dist/otel/mod.d.ts +2 -2
- package/dist/otel/mod.js +8 -14
- package/dist/{owner-gd0Q9FuU.d.ts → owner-74ARJ5TL.d.ts} +1 -1
- package/dist/{owner-1AbPBOOZ.d.cts → owner-CptqhsOy.d.cts} +1 -1
- package/dist/{owner-HASxJJP1.js → owner-DXMGUEOr.mjs} +11 -16
- package/dist/{proof-BwfRl5J4.js → proof-C-7NljBU.js} +33 -59
- package/dist/{proof-BXt2Oi8t.js → proof-CEOujj0L.mjs} +21 -33
- package/dist/{proof-BYZ4hcgN.cjs → proof-DMu-6A_w.cjs} +133 -157
- package/dist/{retry-D4GJ670a.js → retry-Ddbq3AcK.mjs} +4 -7
- package/dist/{router-D9eI0s4b.js → router-CrMLXoOr.mjs} +4 -8
- package/dist/runtime/mod.cjs +11 -13
- package/dist/runtime/mod.d.cts +6 -2
- package/dist/runtime/mod.d.ts +0 -1
- package/dist/runtime/mod.js +4 -7
- package/dist/{send-BF3omx5-.js → send-DIfrLTB_.mjs} +8 -13
- package/dist/sig/http.test.d.mts +2 -0
- package/dist/sig/{http.test.js → http.test.mjs} +160 -199
- package/dist/sig/key.test.d.mts +2 -0
- package/dist/sig/{key.test.js → key.test.mjs} +11 -18
- package/dist/sig/ld.test.d.mts +2 -0
- package/dist/sig/{ld.test.js → ld.test.mjs} +22 -35
- package/dist/sig/mod.cjs +6 -9
- package/dist/sig/mod.d.cts +3 -3
- package/dist/sig/mod.d.ts +3 -3
- package/dist/sig/mod.js +5 -9
- package/dist/sig/owner.test.d.mts +2 -0
- package/dist/sig/{owner.test.js → owner.test.mjs} +19 -34
- package/dist/sig/proof.test.d.mts +2 -0
- package/dist/sig/{proof.test.js → proof.test.mjs} +16 -27
- package/dist/{std__assert-DWivtrGR.js → std__assert-Duiq_YC9.mjs} +12 -24
- package/dist/testing/{mod.d.ts → mod.d.mts} +26 -78
- package/dist/testing/mod.mjs +6 -0
- package/dist/{transformers-3g8GZwkZ.cjs → transformers-NeAONrAq.cjs} +20 -25
- package/dist/{transformers-C3FLHUd6.js → transformers-ve6e2xcg.js} +3 -7
- package/dist/{types-CPz01LGH.js → types-C37hquWI.mjs} +4 -7
- package/dist/{types-Cd_hszr_.cjs → types-KC4QAoxe.cjs} +29 -34
- package/dist/{types-C93Ob9cU.js → types-hvL8ElAs.js} +8 -13
- package/dist/utils/docloader.test.d.mts +2 -0
- package/dist/utils/{docloader.test.js → docloader.test.mjs} +14 -24
- package/dist/utils/kv-cache.test.d.mts +2 -0
- package/dist/utils/{kv-cache.test.js → kv-cache.test.mjs} +25 -40
- package/dist/utils/mod.cjs +5 -9
- package/dist/utils/mod.d.cts +1 -3
- package/dist/utils/mod.d.ts +1 -3
- package/dist/utils/mod.js +4 -9
- package/dist/vocab/cjs.test.d.mts +2 -0
- package/dist/vocab/cjs.test.mjs +14 -0
- package/dist/vocab/mod.cjs +10 -12
- package/dist/vocab/mod.js +3 -5
- package/package.json +7 -7
- package/dist/compat/transformers.test.d.ts +0 -3
- package/dist/compat/transformers.test.js +0 -87
- package/dist/compat-Bb4NuTUO.js +0 -4
- package/dist/compat-DmDDELst.cjs +0 -4
- package/dist/deno-CbQK8e-e.js +0 -121
- package/dist/federation/builder.test.d.ts +0 -3
- package/dist/federation/collection.test.d.ts +0 -3
- package/dist/federation/collection.test.js +0 -32
- package/dist/federation/handler.test.d.ts +0 -3
- package/dist/federation/idempotency.test.d.ts +0 -3
- package/dist/federation/inbox.test.d.ts +0 -3
- package/dist/federation/keycache.test.d.ts +0 -3
- package/dist/federation/kv.test.d.ts +0 -3
- package/dist/federation/middleware.test.d.ts +0 -3
- package/dist/federation/mq.test.d.ts +0 -3
- package/dist/federation/negotiation.test.d.ts +0 -3
- package/dist/federation/retry.test.d.ts +0 -3
- package/dist/federation/router.test.d.ts +0 -3
- package/dist/federation/send.test.d.ts +0 -3
- package/dist/federation/webfinger.test.d.ts +0 -3
- package/dist/federation-Bp3HI26G.cjs +0 -350
- package/dist/federation-DaMfqRm4.js +0 -332
- package/dist/middleware-CcXQzfn8.cjs +0 -12
- package/dist/middleware-CoeaBowW.js +0 -12
- package/dist/middleware-vH2jFwC6.js +0 -26
- package/dist/mod-Bh8mqlYw.d.cts +0 -9
- package/dist/mod-D6HodEq7.d.ts +0 -7
- package/dist/mod-DVwHUI_x.d.cts +0 -80
- package/dist/mod-DosD6NsG.d.ts +0 -82
- package/dist/mod-gq_Xfdz8.d.cts +0 -1
- package/dist/nodeinfo/client.test.d.ts +0 -3
- package/dist/nodeinfo/handler.test.d.ts +0 -3
- package/dist/nodeinfo/types.test.d.ts +0 -3
- package/dist/nodeinfo-DoESQxq5.js +0 -4
- package/dist/nodeinfo-DuMYTpbZ.cjs +0 -4
- package/dist/otel/exporter.test.d.ts +0 -3
- package/dist/runtime-c2Njxsry.cjs +0 -17
- package/dist/runtime-poamPCMb.js +0 -13
- package/dist/sig/http.test.d.ts +0 -3
- package/dist/sig/key.test.d.ts +0 -3
- package/dist/sig/ld.test.d.ts +0 -3
- package/dist/sig/owner.test.d.ts +0 -3
- package/dist/sig/proof.test.d.ts +0 -3
- package/dist/sig-BNhspNOf.js +0 -4
- package/dist/sig-vX39WyWI.cjs +0 -4
- package/dist/testing/mod.js +0 -10
- package/dist/utils/docloader.test.d.ts +0 -3
- package/dist/utils/kv-cache.test.d.ts +0 -3
- package/dist/utils-BQ9KqEK9.cjs +0 -4
- package/dist/utils-Dn5OPdSW.js +0 -4
- /package/dist/{mod-AGjRfPjT.d.ts → compat/transformers.test.d.mts} +0 -0
|
@@ -1,36 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import "../
|
|
10
|
-
import "../
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { assertExists, assertStringIncludes } from "../std__assert-DWivtrGR.js";
|
|
14
|
-
import { assertFalse, assertRejects } from "../assert_rejects-Ce45JcFg.js";
|
|
15
|
-
import { assertThrows } from "../assert_throws-BNXdRGWP.js";
|
|
16
|
-
import "../assert_not_equals-C80BG-_5.js";
|
|
17
|
-
import { rsaPrivateKey2, rsaPublicKey1, rsaPublicKey2, rsaPublicKey5 } from "../keys-ZbcByPg9.js";
|
|
1
|
+
import { Temporal } from "@js-temporal/polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { t as esm_default } from "../esm-DVILvP5e.mjs";
|
|
5
|
+
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
|
|
6
|
+
import { a as assertExists, t as assertStringIncludes } from "../std__assert-Duiq_YC9.mjs";
|
|
7
|
+
import { n as assertFalse, t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
|
|
8
|
+
import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs";
|
|
9
|
+
import { t as assert } from "../assert-ddO5KLpe.mjs";
|
|
10
|
+
import { t as exportJwk } from "../key-DzJf84o7.mjs";
|
|
11
|
+
import { a as parseRfc9421Signature, c as timingSafeEqual, i as formatRfc9421SignatureParameters, l as verifyRequest, n as doubleKnock, o as parseRfc9421SignatureInput, r as formatRfc9421Signature, s as signRequest, t as createRfc9421SignatureBase } from "../http-DI213UHg.mjs";
|
|
12
|
+
import { i as rsaPrivateKey2, l as rsaPublicKey5, o as rsaPublicKey1, s as rsaPublicKey2 } from "../keys-CtZLJq76.mjs";
|
|
18
13
|
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
19
14
|
import { exportSpki } from "@fedify/vocab-runtime";
|
|
20
15
|
import { encodeBase64 } from "byte-encodings/base64";
|
|
21
|
-
|
|
22
16
|
//#region src/sig/http.test.ts
|
|
23
17
|
test("signRequest() [draft-cavage]", async () => {
|
|
24
|
-
|
|
18
|
+
assertEquals(await verifyRequest(await signRequest(new Request("https://example.com/", {
|
|
25
19
|
method: "POST",
|
|
26
20
|
body: "Hello, world!",
|
|
27
21
|
headers: {
|
|
28
22
|
"Content-Type": "text/plain; charset=utf-8",
|
|
29
23
|
Accept: "text/plain"
|
|
30
24
|
}
|
|
31
|
-
})
|
|
32
|
-
const signed = await signRequest(request, rsaPrivateKey2, new URL("https://example.com/key2"));
|
|
33
|
-
assertEquals(await verifyRequest(signed, {
|
|
25
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2")), {
|
|
34
26
|
contextLoader: mockDocumentLoader,
|
|
35
27
|
documentLoader: mockDocumentLoader
|
|
36
28
|
}), rsaPublicKey2);
|
|
@@ -56,8 +48,8 @@ test("verifyRequest() [draft-cavage]", async () => {
|
|
|
56
48
|
get(keyId) {
|
|
57
49
|
return Promise.resolve(cache[keyId.href]);
|
|
58
50
|
},
|
|
59
|
-
set(keyId, key
|
|
60
|
-
cache[keyId.href] = key
|
|
51
|
+
set(keyId, key) {
|
|
52
|
+
cache[keyId.href] = key;
|
|
61
53
|
return Promise.resolve();
|
|
62
54
|
}
|
|
63
55
|
}
|
|
@@ -145,7 +137,7 @@ test("verifyRequest() [draft-cavage]", async () => {
|
|
|
145
137
|
currentTime: Temporal.Instant.from("2025-01-01T00:00:00.0000Z"),
|
|
146
138
|
timeWindow: false
|
|
147
139
|
}), rsaPublicKey1);
|
|
148
|
-
|
|
140
|
+
assert(await verifyRequest(new Request("https://c27a97f98d5f.ngrok.app/i/inbox", {
|
|
149
141
|
method: "POST",
|
|
150
142
|
body: "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://w3id.org/security/v1\"],\"actor\":\"https://oeee.cafe/ap/users/3609fd4e-d51d-4db8-9f04-4189815864dd\",\"object\":{\"actor\":\"https://c27a97f98d5f.ngrok.app/i\",\"object\":\"https://oeee.cafe/ap/users/3609fd4e-d51d-4db8-9f04-4189815864dd\",\"type\":\"Follow\",\"id\":\"https://c27a97f98d5f.ngrok.app/i#follows/https://oeee.cafe/ap/users/3609fd4e-d51d-4db8-9f04-4189815864dd\"},\"type\":\"Accept\",\"id\":\"https://oeee.cafe/objects/0fc2608f-5660-4b91-b8c7-63c0c2ac2e20\"}",
|
|
151
143
|
headers: {
|
|
@@ -155,12 +147,10 @@ test("verifyRequest() [draft-cavage]", async () => {
|
|
|
155
147
|
Digest: "SHA-256=YZyjeVQW5GwliJowASkteBJhFBTq3eQk/AMqRETc//A=",
|
|
156
148
|
Signature: "keyId=\"https://oeee.cafe/ap/users/3609fd4e-d51d-4db8-9f04-4189815864dd#main-key\",algorithm=\"hs2019\",created=\"1756126694\",expires=\"1756130294\",headers=\"(request-target) (created) (expires) content-type date digest host\",signature=\"XFb0jl2uMhE7RhbneE9sK9Zls2qZec8iy6+9O8UgDQeBGJThORFLjXKlps4QO1WAf1YSVB/i5aV6yF+h73Lm3ZiuAJDx1h+00iLsxoYuIw1CZvF0V2jELoo3sQ2/ZzqeoO6H5TbK7tKnU+ulFAPTuJgjIvPwYl11OMRouVS34NiaHP9Yx9pU813TLv37thG/hUKanyq8kk0IJWtDWteY/zxDvzoe7VOkBXVBHslMyrNAI/5JGulVQAQp/E61dJAhTHHIyGxkc/7iutWFZuqFXIiPJ9KR2OuKDj/B32hEzlsf5xH/CjqOJPIg1qMK8FzDiALCq6zjiKIBEnW8HQc/hQ==\""
|
|
157
149
|
}
|
|
158
|
-
})
|
|
159
|
-
const options2 = {
|
|
150
|
+
}), {
|
|
160
151
|
...options,
|
|
161
152
|
currentTime: Temporal.Instant.from("2025-08-25T12:58:14Z")
|
|
162
|
-
};
|
|
163
|
-
assert(await verifyRequest(request2, options2) != null);
|
|
153
|
+
}) != null);
|
|
164
154
|
});
|
|
165
155
|
test("signRequest() and verifyRequest() [rfc9421] implementation", async () => {
|
|
166
156
|
const currentTimestamp = 1709626184;
|
|
@@ -201,9 +191,7 @@ test("signRequest() and verifyRequest() [rfc9421] implementation", async () => {
|
|
|
201
191
|
const contentDigest = signed.headers.get("Content-Digest");
|
|
202
192
|
assertExists(contentDigest);
|
|
203
193
|
assert(contentDigest.startsWith("sha-256=:"), "Content-Digest should use RFC 9421 format");
|
|
204
|
-
|
|
205
|
-
const expectedDigestBase64 = encodeBase64(expectedDigest);
|
|
206
|
-
assertEquals(contentDigest, `sha-256=:${expectedDigestBase64}:`, "Content-Digest should have correct value");
|
|
194
|
+
assertEquals(contentDigest, `sha-256=:${encodeBase64(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(requestBody)))}:`, "Content-Digest should have correct value");
|
|
207
195
|
const signature = signed.headers.get("Signature");
|
|
208
196
|
assertExists(signature);
|
|
209
197
|
const sigFormat = /^sig1=:([A-Za-z0-9+/]+=*):/;
|
|
@@ -224,71 +212,58 @@ test("signRequest() and verifyRequest() [rfc9421] implementation", async () => {
|
|
|
224
212
|
assertEquals(parsedSig.sig1.byteLength > 0, true, "Signature value should be a non-empty Uint8Array");
|
|
225
213
|
const verifyHeaders = new Headers();
|
|
226
214
|
for (const [name, value] of signed.headers.entries()) if (name !== "Signature" && name !== "Signature-Input") verifyHeaders.set(name, value);
|
|
227
|
-
const
|
|
215
|
+
const reconstructedBase = createRfc9421SignatureBase(new Request(request.url, {
|
|
228
216
|
method: request.method,
|
|
229
217
|
headers: verifyHeaders
|
|
230
|
-
});
|
|
231
|
-
const reconstructedBase = createRfc9421SignatureBase(reconstructedRequest, parsedInput.sig1.components, parsedInput.sig1.parameters);
|
|
218
|
+
}), parsedInput.sig1.components, parsedInput.sig1.parameters);
|
|
232
219
|
const signatureBytes = new Uint8Array(parsedSig.sig1);
|
|
233
|
-
|
|
234
|
-
assert(signatureVerifies, "Manual verification of signature should succeed");
|
|
220
|
+
assert(await crypto.subtle.verify("RSASSA-PKCS1-v1_5", rsaPublicKey2.publicKey, signatureBytes, new TextEncoder().encode(reconstructedBase)), "Manual verification of signature should succeed");
|
|
235
221
|
});
|
|
236
222
|
test("createRfc9421SignatureBase()", () => {
|
|
237
|
-
|
|
223
|
+
assertEquals(createRfc9421SignatureBase(new Request("https://example.com/path?query=value", {
|
|
238
224
|
method: "POST",
|
|
239
225
|
headers: {
|
|
240
226
|
Host: "example.com",
|
|
241
227
|
Date: "Tue, 05 Mar 2024 07:49:44 GMT",
|
|
242
228
|
"Content-Type": "text/plain"
|
|
243
229
|
}
|
|
244
|
-
})
|
|
245
|
-
const components = [
|
|
230
|
+
}), [
|
|
246
231
|
"@method",
|
|
247
232
|
"@target-uri",
|
|
248
233
|
"host",
|
|
249
234
|
"date"
|
|
250
|
-
]
|
|
251
|
-
const created = 1709626184;
|
|
252
|
-
const signatureBase = createRfc9421SignatureBase(request, components, formatRfc9421SignatureParameters({
|
|
235
|
+
], formatRfc9421SignatureParameters({
|
|
253
236
|
algorithm: "rsa-v1_5-sha256",
|
|
254
237
|
keyId: new URL("https://example.com/key"),
|
|
255
|
-
created
|
|
256
|
-
}))
|
|
257
|
-
const expected = [
|
|
238
|
+
created: 1709626184
|
|
239
|
+
})), [
|
|
258
240
|
`"@method": POST`,
|
|
259
241
|
`"@target-uri": https://example.com/path?query=value`,
|
|
260
242
|
`"host": example.com`,
|
|
261
243
|
`"date": Tue, 05 Mar 2024 07:49:44 GMT`,
|
|
262
244
|
`"@signature-params": ("@method" "@target-uri" "host" "date");alg="rsa-v1_5-sha256";keyid="https://example.com/key";created=1709626184`
|
|
263
|
-
].join("\n");
|
|
264
|
-
assertEquals(signatureBase, expected);
|
|
245
|
+
].join("\n"));
|
|
265
246
|
});
|
|
266
247
|
test("formatRfc9421Signature()", () => {
|
|
267
|
-
const
|
|
248
|
+
const [signatureInput, signatureHeader] = formatRfc9421Signature(new Uint8Array([
|
|
268
249
|
1,
|
|
269
250
|
2,
|
|
270
251
|
3,
|
|
271
252
|
4
|
|
272
|
-
])
|
|
273
|
-
const keyId = new URL("https://example.com/key");
|
|
274
|
-
const algorithm = "rsa-v1_5-sha256";
|
|
275
|
-
const components = [
|
|
253
|
+
]), [
|
|
276
254
|
"@method",
|
|
277
255
|
"@target-uri",
|
|
278
256
|
"host"
|
|
279
|
-
]
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
keyId,
|
|
284
|
-
created
|
|
257
|
+
], formatRfc9421SignatureParameters({
|
|
258
|
+
algorithm: "rsa-v1_5-sha256",
|
|
259
|
+
keyId: new URL("https://example.com/key"),
|
|
260
|
+
created: 1709626184
|
|
285
261
|
}));
|
|
286
262
|
assertEquals(signatureInput, `sig1=("@method" "@target-uri" "host");alg="rsa-v1_5-sha256";keyid="https://example.com/key";created=1709626184`);
|
|
287
263
|
assertEquals(signatureHeader, `sig1=:AQIDBA==:`);
|
|
288
264
|
});
|
|
289
265
|
test("parseRfc9421SignatureInput()", () => {
|
|
290
|
-
const
|
|
291
|
-
const parsed = parseRfc9421SignatureInput(signatureInput);
|
|
266
|
+
const parsed = parseRfc9421SignatureInput(`sig1=("@method" "@target-uri" "host" "date");keyid="https://example.com/key";alg="rsa-v1_5-sha256";created=1709626184`);
|
|
292
267
|
assertEquals(parsed.sig1.keyId, "https://example.com/key");
|
|
293
268
|
assertEquals(parsed.sig1.alg, "rsa-v1_5-sha256");
|
|
294
269
|
assertEquals(parsed.sig1.created, 1709626184);
|
|
@@ -301,8 +276,7 @@ test("parseRfc9421SignatureInput()", () => {
|
|
|
301
276
|
assertEquals(parsed.sig1.parameters, "keyid=\"https://example.com/key\";alg=\"rsa-v1_5-sha256\";created=1709626184");
|
|
302
277
|
});
|
|
303
278
|
test("parseRfc9421Signature()", () => {
|
|
304
|
-
const
|
|
305
|
-
const parsed = parseRfc9421Signature(signature);
|
|
279
|
+
const parsed = parseRfc9421Signature(`sig1=:AQIDBA==:,sig2=:Zm9vYmFy:`);
|
|
306
280
|
assertExists(parsed.sig1);
|
|
307
281
|
assertExists(parsed.sig2);
|
|
308
282
|
const sig1Bytes = new Uint8Array(parsed.sig1);
|
|
@@ -311,37 +285,33 @@ test("parseRfc9421Signature()", () => {
|
|
|
311
285
|
assertEquals(sig1Bytes[1], 2);
|
|
312
286
|
assertEquals(sig1Bytes[2], 3);
|
|
313
287
|
assertEquals(sig1Bytes[3], 4);
|
|
314
|
-
|
|
315
|
-
assertEquals(sig2Text, "foobar");
|
|
288
|
+
assertEquals(new TextDecoder().decode(parsed.sig2), "foobar");
|
|
316
289
|
});
|
|
317
290
|
test("verifyRequest() [rfc9421] successful GET verification", async () => {
|
|
318
291
|
const currentTimestamp = 1709626184;
|
|
319
292
|
const currentTime = Temporal.Instant.from("2024-03-05T08:09:44Z");
|
|
320
|
-
|
|
293
|
+
assertEquals(await verifyRequest(await signRequest(new Request("https://example.com/api/resource", {
|
|
321
294
|
method: "GET",
|
|
322
295
|
headers: {
|
|
323
296
|
"Accept": "application/json",
|
|
324
297
|
"Host": "example.com",
|
|
325
298
|
"Date": "Tue, 05 Mar 2024 07:49:44 GMT"
|
|
326
299
|
}
|
|
327
|
-
})
|
|
328
|
-
const signedRequest = await signRequest(validRequest, rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
300
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
329
301
|
spec: "rfc9421",
|
|
330
302
|
currentTime
|
|
331
|
-
})
|
|
332
|
-
const verifiedKey = await verifyRequest(signedRequest, {
|
|
303
|
+
}), {
|
|
333
304
|
contextLoader: mockDocumentLoader,
|
|
334
305
|
documentLoader: mockDocumentLoader,
|
|
335
306
|
spec: "rfc9421",
|
|
336
307
|
currentTime: Temporal.Instant.from(`${(/* @__PURE__ */ new Date(currentTimestamp * 1e3)).toISOString()}`)
|
|
337
|
-
});
|
|
338
|
-
assertEquals(verifiedKey, rsaPublicKey2, "Valid signature should verify to the correct public key");
|
|
308
|
+
}), rsaPublicKey2, "Valid signature should verify to the correct public key");
|
|
339
309
|
});
|
|
340
310
|
test("verifyRequest() [rfc9421] manual POST verification", async () => {
|
|
341
311
|
const currentTimestamp = 1709626184;
|
|
342
312
|
const currentTime = Temporal.Instant.from("2024-03-05T08:09:44Z");
|
|
343
313
|
const postBody = "Test content for signature verification";
|
|
344
|
-
const
|
|
314
|
+
const signedPostRequest = await signRequest(new Request("https://example.com/api/resource", {
|
|
345
315
|
method: "POST",
|
|
346
316
|
body: postBody,
|
|
347
317
|
headers: {
|
|
@@ -350,8 +320,7 @@ test("verifyRequest() [rfc9421] manual POST verification", async () => {
|
|
|
350
320
|
"Host": "example.com",
|
|
351
321
|
"Date": "Tue, 05 Mar 2024 07:49:44 GMT"
|
|
352
322
|
}
|
|
353
|
-
})
|
|
354
|
-
const signedPostRequest = await signRequest(postRequest, rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
323
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
355
324
|
spec: "rfc9421",
|
|
356
325
|
currentTime
|
|
357
326
|
});
|
|
@@ -373,61 +342,54 @@ test("verifyRequest() [rfc9421] manual POST verification", async () => {
|
|
|
373
342
|
assertExists(parsedSignature.sig1, "Should have a valid signature value");
|
|
374
343
|
assertEquals(parsedInput.sig1.keyId, "https://example.com/key2", "Signature should have the correct key ID");
|
|
375
344
|
assertEquals(parsedInput.sig1.created, currentTimestamp, "Signature should have the correct timestamp");
|
|
376
|
-
const
|
|
345
|
+
const signatureBase = createRfc9421SignatureBase(new Request("https://example.com/api/resource", {
|
|
377
346
|
method: "POST",
|
|
378
347
|
body: postBody,
|
|
379
348
|
headers: new Headers(signedPostRequest.headers)
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
const signatureVerified = await crypto.subtle.verify("RSASSA-PKCS1-v1_5", rsaPublicKey2.publicKey, parsedSignature.sig1.slice(), new TextEncoder().encode(signatureBase));
|
|
383
|
-
assert(signatureVerified, "Manual verification of POST signature should succeed");
|
|
349
|
+
}), parsedInput.sig1.components, parsedInput.sig1.parameters);
|
|
350
|
+
assert(await crypto.subtle.verify("RSASSA-PKCS1-v1_5", rsaPublicKey2.publicKey, parsedSignature.sig1.slice(), new TextEncoder().encode(signatureBase)), "Manual verification of POST signature should succeed");
|
|
384
351
|
});
|
|
385
352
|
test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
386
353
|
const currentTimestamp = 1709626184;
|
|
387
354
|
const currentTime = Temporal.Instant.from("2024-03-05T08:09:44Z");
|
|
388
|
-
const
|
|
355
|
+
const signedRequest = await signRequest(new Request("https://example.com/api/resource", {
|
|
389
356
|
method: "GET",
|
|
390
357
|
headers: {
|
|
391
358
|
"Accept": "application/json",
|
|
392
359
|
"Host": "example.com",
|
|
393
360
|
"Date": "Tue, 05 Mar 2024 07:49:44 GMT"
|
|
394
361
|
}
|
|
395
|
-
})
|
|
396
|
-
const signedRequest = await signRequest(validRequest, rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
362
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
397
363
|
spec: "rfc9421",
|
|
398
364
|
currentTime
|
|
399
365
|
});
|
|
400
366
|
const validSignatureInput = signedRequest.headers.get("Signature-Input") || "";
|
|
401
367
|
const validSignature = signedRequest.headers.get("Signature") || "";
|
|
402
|
-
|
|
368
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
403
369
|
method: "GET",
|
|
404
370
|
headers: new Headers({
|
|
405
371
|
"Accept": "application/json",
|
|
406
372
|
"Host": "example.com",
|
|
407
373
|
"Signature": validSignature
|
|
408
374
|
})
|
|
409
|
-
})
|
|
410
|
-
const missingInputResult = await verifyRequest(missingInputHeader, {
|
|
375
|
+
}), {
|
|
411
376
|
contextLoader: mockDocumentLoader,
|
|
412
377
|
documentLoader: mockDocumentLoader,
|
|
413
378
|
spec: "rfc9421"
|
|
414
|
-
});
|
|
415
|
-
assertEquals(
|
|
416
|
-
const missingSignatureHeader = new Request("https://example.com/api/resource", {
|
|
379
|
+
}), null, "Should fail verification when Signature-Input header is missing");
|
|
380
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
417
381
|
method: "GET",
|
|
418
382
|
headers: new Headers({
|
|
419
383
|
"Accept": "application/json",
|
|
420
384
|
"Host": "example.com",
|
|
421
385
|
"Signature-Input": validSignatureInput
|
|
422
386
|
})
|
|
423
|
-
})
|
|
424
|
-
const missingSignatureResult = await verifyRequest(missingSignatureHeader, {
|
|
387
|
+
}), {
|
|
425
388
|
contextLoader: mockDocumentLoader,
|
|
426
389
|
documentLoader: mockDocumentLoader,
|
|
427
390
|
spec: "rfc9421"
|
|
428
|
-
});
|
|
429
|
-
assertEquals(
|
|
430
|
-
const tamperedRequest = new Request("https://example.com/api/resource", {
|
|
391
|
+
}), null, "Should fail verification when Signature header is missing");
|
|
392
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
431
393
|
method: "GET",
|
|
432
394
|
headers: new Headers({
|
|
433
395
|
"Accept": "application/json",
|
|
@@ -436,14 +398,12 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
436
398
|
"Signature-Input": validSignatureInput,
|
|
437
399
|
"Signature": "sig1=:AAAAAA==:"
|
|
438
400
|
})
|
|
439
|
-
})
|
|
440
|
-
const tamperedResult = await verifyRequest(tamperedRequest, {
|
|
401
|
+
}), {
|
|
441
402
|
contextLoader: mockDocumentLoader,
|
|
442
403
|
documentLoader: mockDocumentLoader,
|
|
443
404
|
spec: "rfc9421"
|
|
444
|
-
});
|
|
445
|
-
assertEquals(
|
|
446
|
-
const expiredRequest = new Request("https://example.com/api/resource", {
|
|
405
|
+
}), null, "Should fail verification when signature is tampered");
|
|
406
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
447
407
|
method: "GET",
|
|
448
408
|
headers: new Headers({
|
|
449
409
|
"Accept": "application/json",
|
|
@@ -452,16 +412,14 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
452
412
|
"Signature-Input": validSignatureInput,
|
|
453
413
|
"Signature": validSignature
|
|
454
414
|
})
|
|
455
|
-
})
|
|
456
|
-
const expiredResult = await verifyRequest(expiredRequest, {
|
|
415
|
+
}), {
|
|
457
416
|
contextLoader: mockDocumentLoader,
|
|
458
417
|
documentLoader: mockDocumentLoader,
|
|
459
418
|
spec: "rfc9421",
|
|
460
419
|
currentTime: Temporal.Instant.from(`${(/* @__PURE__ */ new Date((currentTimestamp + 2592e3) * 1e3)).toISOString()}`),
|
|
461
420
|
timeWindow: { hours: 1 }
|
|
462
|
-
});
|
|
463
|
-
assertEquals(
|
|
464
|
-
const futureRequest = new Request("https://example.com/api/resource", {
|
|
421
|
+
}), null, "Should fail verification when signature timestamp is too old");
|
|
422
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
465
423
|
method: "GET",
|
|
466
424
|
headers: new Headers({
|
|
467
425
|
"Accept": "application/json",
|
|
@@ -470,16 +428,14 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
470
428
|
"Signature-Input": validSignatureInput,
|
|
471
429
|
"Signature": validSignature
|
|
472
430
|
})
|
|
473
|
-
})
|
|
474
|
-
const futureResult = await verifyRequest(futureRequest, {
|
|
431
|
+
}), {
|
|
475
432
|
contextLoader: mockDocumentLoader,
|
|
476
433
|
documentLoader: mockDocumentLoader,
|
|
477
434
|
spec: "rfc9421",
|
|
478
435
|
currentTime: Temporal.Instant.from(`${(/* @__PURE__ */ new Date((currentTimestamp - 2592e3) * 1e3)).toISOString()}`),
|
|
479
436
|
timeWindow: { hours: 1 }
|
|
480
|
-
});
|
|
481
|
-
assertEquals(
|
|
482
|
-
const timeCheckRequest = new Request("https://example.com/api/resource", {
|
|
437
|
+
}), null, "Should fail verification when signature timestamp is in the future");
|
|
438
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
483
439
|
method: "GET",
|
|
484
440
|
headers: new Headers({
|
|
485
441
|
"Accept": "application/json",
|
|
@@ -488,16 +444,14 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
488
444
|
"Signature-Input": validSignatureInput,
|
|
489
445
|
"Signature": validSignature
|
|
490
446
|
})
|
|
491
|
-
})
|
|
492
|
-
const timeDisabledResult = await verifyRequest(timeCheckRequest, {
|
|
447
|
+
}), {
|
|
493
448
|
contextLoader: mockDocumentLoader,
|
|
494
449
|
documentLoader: mockDocumentLoader,
|
|
495
450
|
spec: "rfc9421",
|
|
496
451
|
currentTime: Temporal.Instant.from(`${(/* @__PURE__ */ new Date((currentTimestamp + 31536e3) * 1e3)).toISOString()}`),
|
|
497
452
|
timeWindow: false
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
const postRequest = new Request("https://example.com/api/resource", {
|
|
453
|
+
}), rsaPublicKey2, "Should verify signature when time checking is disabled");
|
|
454
|
+
const freshSignedPostRequest = await signRequest(new Request("https://example.com/api/resource", {
|
|
501
455
|
method: "POST",
|
|
502
456
|
body: "Test content for signature verification",
|
|
503
457
|
headers: {
|
|
@@ -506,15 +460,14 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
506
460
|
"Host": "example.com",
|
|
507
461
|
"Date": "Tue, 05 Mar 2024 07:49:44 GMT"
|
|
508
462
|
}
|
|
509
|
-
})
|
|
510
|
-
const freshSignedPostRequest = await signRequest(postRequest, rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
463
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
511
464
|
spec: "rfc9421",
|
|
512
465
|
currentTime
|
|
513
466
|
});
|
|
514
467
|
const postSignatureInput = freshSignedPostRequest.headers.get("Signature-Input") || "";
|
|
515
468
|
const postSignature = freshSignedPostRequest.headers.get("Signature") || "";
|
|
516
469
|
const postContentDigest = freshSignedPostRequest.headers.get("Content-Digest") || "";
|
|
517
|
-
|
|
470
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
518
471
|
method: "POST",
|
|
519
472
|
body: "This content won't match the digest",
|
|
520
473
|
headers: new Headers({
|
|
@@ -525,22 +478,19 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
525
478
|
"Signature": postSignature,
|
|
526
479
|
"Content-Digest": postContentDigest
|
|
527
480
|
})
|
|
528
|
-
})
|
|
529
|
-
const tamperDigestResult = await verifyRequest(tamperDigestRequest, {
|
|
481
|
+
}), {
|
|
530
482
|
contextLoader: mockDocumentLoader,
|
|
531
483
|
documentLoader: mockDocumentLoader,
|
|
532
484
|
spec: "rfc9421",
|
|
533
485
|
currentTime: Temporal.Instant.from(`${(/* @__PURE__ */ new Date(currentTimestamp * 1e3)).toISOString()}`)
|
|
534
|
-
});
|
|
535
|
-
assertEquals(tamperDigestResult, null, "Should fail verification with invalid Content-Digest");
|
|
486
|
+
}), null, "Should fail verification with invalid Content-Digest");
|
|
536
487
|
const testRequest = new Request("https://example.com/", { headers: new Headers({
|
|
537
488
|
"Date": "Tue, 05 Mar 2024 07:49:44 GMT",
|
|
538
489
|
"Host": "example.com",
|
|
539
490
|
"Signature-Input": `sig1=("@method" "@target-uri" "host" "date");keyid="https://example.com/key";alg="rsa-v1_5-sha256";created=1709626184`,
|
|
540
491
|
"Signature": `sig1=:YXNkZmprc2RmaGprc2RoZmprc2hkZmtqaHNkZg==:`
|
|
541
492
|
}) });
|
|
542
|
-
const
|
|
543
|
-
const parsedInput = parseRfc9421SignatureInput(signatureInput);
|
|
493
|
+
const parsedInput = parseRfc9421SignatureInput(testRequest.headers.get("Signature-Input") || "");
|
|
544
494
|
assertExists(parsedInput.sig1);
|
|
545
495
|
assertEquals(parsedInput.sig1.keyId, "https://example.com/key");
|
|
546
496
|
assertEquals(parsedInput.sig1.alg, "rsa-v1_5-sha256");
|
|
@@ -551,12 +501,10 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
551
501
|
"host",
|
|
552
502
|
"date"
|
|
553
503
|
]);
|
|
554
|
-
const
|
|
555
|
-
const parsedSig = parseRfc9421Signature(signature);
|
|
504
|
+
const parsedSig = parseRfc9421Signature(testRequest.headers.get("Signature") || "");
|
|
556
505
|
assertExists(parsedSig.sig1);
|
|
557
506
|
assert(new TextDecoder().decode(parsedSig.sig1).length > 0, "Signature base64 should decode to non-empty string");
|
|
558
|
-
const
|
|
559
|
-
const complexParsedInput = parseRfc9421SignatureInput(complexSignatureInput);
|
|
507
|
+
const complexParsedInput = parseRfc9421SignatureInput("sig1=(\"@method\" \"@target-uri\" \"host\" \"content-type\" \"value with \\\"quotes\\\" and spaces\");keyid=\"https://example.com/key with spaces\";alg=\"rsa-v1_5-sha256\";created=1709626184");
|
|
560
508
|
assertExists(complexParsedInput.sig1);
|
|
561
509
|
assertEquals(complexParsedInput.sig1.keyId, "https://example.com/key with spaces");
|
|
562
510
|
assertEquals(complexParsedInput.sig1.alg, "rsa-v1_5-sha256");
|
|
@@ -575,16 +523,13 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
575
523
|
assertEquals(multiParsedInput.sig2.alg, "rsa-pss-sha512");
|
|
576
524
|
const multiParsedSig = parseRfc9421Signature(multiSigRequest.headers.get("Signature") || "");
|
|
577
525
|
assertEquals(Object.keys(multiParsedSig).length, 2, "Should parse multiple signature values");
|
|
578
|
-
const
|
|
579
|
-
const parsedInvalidInput = parseRfc9421SignatureInput(invalidInputFormat);
|
|
526
|
+
const parsedInvalidInput = parseRfc9421SignatureInput("this is not a valid signature-input format");
|
|
580
527
|
assertEquals(Object.keys(parsedInvalidInput).length, 0, "Should handle invalid Signature-Input format");
|
|
581
|
-
const
|
|
582
|
-
const parsedInvalidSig = parseRfc9421Signature(invalidSigFormat);
|
|
528
|
+
const parsedInvalidSig = parseRfc9421Signature("this is not a valid signature format");
|
|
583
529
|
assertEquals(Object.keys(parsedInvalidSig).length, 0, "Should handle invalid Signature format");
|
|
584
|
-
const
|
|
585
|
-
const parsedInvalidBase64 = parseRfc9421Signature(invalidBase64Sig);
|
|
530
|
+
const parsedInvalidBase64 = parseRfc9421Signature("sig1=:!@#$%%^&*():");
|
|
586
531
|
assertEquals(Object.keys(parsedInvalidBase64).length, 0, "Should handle invalid base64 in signature");
|
|
587
|
-
|
|
532
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
588
533
|
method: "GET",
|
|
589
534
|
headers: new Headers({
|
|
590
535
|
"Accept": "application/json",
|
|
@@ -593,14 +538,12 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
593
538
|
"Signature-Input": `${validSignatureInput},sig2=("@method" "@target-uri" "host" "date");keyid="https://example.com/invalid-key";alg="rsa-v1_5-sha256";created=${currentTimestamp}`,
|
|
594
539
|
"Signature": `${validSignature},sig2=:AAAAAA==:`
|
|
595
540
|
})
|
|
596
|
-
})
|
|
597
|
-
const mixedResult = await verifyRequest(mixedRequest, {
|
|
541
|
+
}), {
|
|
598
542
|
contextLoader: mockDocumentLoader,
|
|
599
543
|
documentLoader: mockDocumentLoader,
|
|
600
544
|
spec: "rfc9421",
|
|
601
545
|
currentTime: Temporal.Instant.from(`${(/* @__PURE__ */ new Date(currentTimestamp * 1e3)).toISOString()}`)
|
|
602
|
-
});
|
|
603
|
-
assertEquals(mixedResult, rsaPublicKey2, "Should verify when at least one signature is valid");
|
|
546
|
+
}), rsaPublicKey2, "Should verify when at least one signature is valid");
|
|
604
547
|
});
|
|
605
548
|
test("verifyRequest() [rfc9421] test vector from Mastodon", async () => {
|
|
606
549
|
const signedRequest = new Request("https://www.example.com/activitypub/success", {
|
|
@@ -663,14 +606,13 @@ test("doubleKnock() function with successful first attempt", async () => {
|
|
|
663
606
|
const logFunction = (req) => {
|
|
664
607
|
loggedRequest = req;
|
|
665
608
|
};
|
|
666
|
-
|
|
609
|
+
assertEquals((await doubleKnock(request, {
|
|
667
610
|
keyId: rsaPublicKey2.id,
|
|
668
611
|
privateKey: rsaPrivateKey2
|
|
669
612
|
}, {
|
|
670
613
|
specDeterminer,
|
|
671
614
|
log: logFunction
|
|
672
|
-
});
|
|
673
|
-
assertEquals(response.status, 202, "Response status should be 202 Accepted");
|
|
615
|
+
})).status, 202, "Response status should be 202 Accepted");
|
|
674
616
|
assertEquals(requestCount, 1, "Only one request should have been made");
|
|
675
617
|
assertEquals(firstRequestSpec, "rfc9421", "First attempt should use RFC 9421");
|
|
676
618
|
assertEquals(specDeterminer.usedSpec, "rfc9421", "Spec should be remembered");
|
|
@@ -711,11 +653,10 @@ test("doubleKnock() function with fallback to draft-cavage", async () => {
|
|
|
711
653
|
this.rememberedSpec = spec;
|
|
712
654
|
}
|
|
713
655
|
};
|
|
714
|
-
|
|
656
|
+
assertEquals((await doubleKnock(request, {
|
|
715
657
|
keyId: rsaPublicKey2.id,
|
|
716
658
|
privateKey: rsaPrivateKey2
|
|
717
|
-
}, { specDeterminer });
|
|
718
|
-
assertEquals(response.status, 202, "Response status should be 202 Accepted");
|
|
659
|
+
}, { specDeterminer })).status, 202, "Response status should be 202 Accepted");
|
|
719
660
|
assertEquals(requestCount, 2, "Two requests should have been made");
|
|
720
661
|
assertEquals(firstSpec, "rfc9421", "First attempt should use RFC 9421");
|
|
721
662
|
assertEquals(secondSpec, "draft-cavage-http-signatures-12", "Second attempt should use draft-cavage");
|
|
@@ -737,16 +678,14 @@ test("doubleKnock() function with redirect handling", async () => {
|
|
|
737
678
|
responseCodes.push(202);
|
|
738
679
|
return new Response("", { status: 202 });
|
|
739
680
|
});
|
|
740
|
-
|
|
681
|
+
assertEquals((await doubleKnock(new Request("https://example.com/redirect-endpoint", {
|
|
741
682
|
method: "POST",
|
|
742
683
|
body: "Test message that will be redirected",
|
|
743
684
|
headers: { "Content-Type": "text/plain" }
|
|
744
|
-
})
|
|
745
|
-
const response = await doubleKnock(request, {
|
|
685
|
+
}), {
|
|
746
686
|
keyId: rsaPublicKey2.id,
|
|
747
687
|
privateKey: rsaPrivateKey2
|
|
748
|
-
});
|
|
749
|
-
assertEquals(response.status, 202, "Final response status should be 202 Accepted");
|
|
688
|
+
})).status, 202, "Final response status should be 202 Accepted");
|
|
750
689
|
assertEquals(requestedUrls.length, 2, "Two URLs should have been requested");
|
|
751
690
|
assertEquals(requestedUrls[0], "https://example.com/redirect-endpoint", "First request should be to redirect-endpoint");
|
|
752
691
|
assertEquals(requestedUrls[1], "https://example.com/final-endpoint", "Second request should be to final-endpoint");
|
|
@@ -765,16 +704,14 @@ test("doubleKnock() function with both specs rejected", async () => {
|
|
|
765
704
|
else attempts.push("unknown");
|
|
766
705
|
return new Response("Unauthorized", { status: 401 });
|
|
767
706
|
});
|
|
768
|
-
|
|
707
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-rejects-all", {
|
|
769
708
|
method: "POST",
|
|
770
709
|
body: "Test message that will be rejected regardless of signature format",
|
|
771
710
|
headers: { "Content-Type": "text/plain" }
|
|
772
|
-
})
|
|
773
|
-
const response = await doubleKnock(request, {
|
|
711
|
+
}), {
|
|
774
712
|
keyId: rsaPublicKey2.id,
|
|
775
713
|
privateKey: rsaPrivateKey2
|
|
776
|
-
});
|
|
777
|
-
assertEquals(response.status, 401, "Final response status should be 401 Unauthorized");
|
|
714
|
+
})).status, 401, "Final response status should be 401 Unauthorized");
|
|
778
715
|
assertEquals(requestCount, 2, "Two requests should have been made");
|
|
779
716
|
assertEquals(attempts.length, 2, "Two signature attempts should have been made");
|
|
780
717
|
assertEquals(attempts[0], "rfc9421", "First attempt should use RFC 9421");
|
|
@@ -798,16 +735,14 @@ test("doubleKnock() function with specDeterminer choosing draft-cavage first", a
|
|
|
798
735
|
},
|
|
799
736
|
rememberSpec(_origin, _spec) {}
|
|
800
737
|
};
|
|
801
|
-
|
|
738
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-accepts-any", {
|
|
802
739
|
method: "POST",
|
|
803
740
|
body: "Test message with draft-cavage preference",
|
|
804
741
|
headers: { "Content-Type": "text/plain" }
|
|
805
|
-
})
|
|
806
|
-
const response = await doubleKnock(request, {
|
|
742
|
+
}), {
|
|
807
743
|
keyId: rsaPublicKey2.id,
|
|
808
744
|
privateKey: rsaPrivateKey2
|
|
809
|
-
}, { specDeterminer });
|
|
810
|
-
assertEquals(response.status, 202, "Response status should be 202 Accepted");
|
|
745
|
+
}, { specDeterminer })).status, 202, "Response status should be 202 Accepted");
|
|
811
746
|
assertEquals(requestCount, 1, "Only one request should have been made");
|
|
812
747
|
assertEquals(firstSpec, "draft-cavage", "First attempt should use draft-cavage");
|
|
813
748
|
esm_default.hardReset();
|
|
@@ -855,6 +790,49 @@ test("doubleKnock() complex redirect chain test", async () => {
|
|
|
855
790
|
for (const loggedReq of logs) assert(loggedReq.headers.has("Signature-Input") || loggedReq.headers.has("Signature"), "Each request should be signed with either RFC 9421 or draft-cavage");
|
|
856
791
|
esm_default.hardReset();
|
|
857
792
|
});
|
|
793
|
+
test("doubleKnock() throws on too many redirects", async () => {
|
|
794
|
+
esm_default.spyGlobal();
|
|
795
|
+
let requestCount = 0;
|
|
796
|
+
esm_default.post("begin:https://example.com/too-many-redirects/", (cl) => {
|
|
797
|
+
requestCount++;
|
|
798
|
+
const index = Number(cl.url.split("/").at(-1));
|
|
799
|
+
return Response.redirect(`https://example.com/too-many-redirects/${index + 1}`, 302);
|
|
800
|
+
});
|
|
801
|
+
const request = new Request("https://example.com/too-many-redirects/0", {
|
|
802
|
+
method: "POST",
|
|
803
|
+
body: "Redirect loop",
|
|
804
|
+
headers: { "Content-Type": "text/plain" }
|
|
805
|
+
});
|
|
806
|
+
await assertRejects(() => doubleKnock(request, {
|
|
807
|
+
keyId: rsaPublicKey2.id,
|
|
808
|
+
privateKey: rsaPrivateKey2
|
|
809
|
+
}), Error, "Too many redirections");
|
|
810
|
+
assertEquals(requestCount, 21);
|
|
811
|
+
esm_default.hardReset();
|
|
812
|
+
});
|
|
813
|
+
test("doubleKnock() detects redirect loops", async () => {
|
|
814
|
+
esm_default.spyGlobal();
|
|
815
|
+
let requestCount = 0;
|
|
816
|
+
esm_default.post("https://example.com/redirect-loop-a", () => {
|
|
817
|
+
requestCount++;
|
|
818
|
+
return Response.redirect("https://example.com/redirect-loop-b", 302);
|
|
819
|
+
});
|
|
820
|
+
esm_default.post("https://example.com/redirect-loop-b", () => {
|
|
821
|
+
requestCount++;
|
|
822
|
+
return Response.redirect("https://example.com/redirect-loop-a", 302);
|
|
823
|
+
});
|
|
824
|
+
const request = new Request("https://example.com/redirect-loop-a", {
|
|
825
|
+
method: "POST",
|
|
826
|
+
body: "Redirect loop",
|
|
827
|
+
headers: { "Content-Type": "text/plain" }
|
|
828
|
+
});
|
|
829
|
+
await assertRejects(() => doubleKnock(request, {
|
|
830
|
+
keyId: rsaPublicKey2.id,
|
|
831
|
+
privateKey: rsaPrivateKey2
|
|
832
|
+
}), Error, "Redirect loop detected");
|
|
833
|
+
assertEquals(requestCount, 2);
|
|
834
|
+
esm_default.hardReset();
|
|
835
|
+
});
|
|
858
836
|
test("doubleKnock() async specDeterminer test", async () => {
|
|
859
837
|
esm_default.spyGlobal();
|
|
860
838
|
let requestCount = 0;
|
|
@@ -875,25 +853,21 @@ test("doubleKnock() async specDeterminer test", async () => {
|
|
|
875
853
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
876
854
|
}
|
|
877
855
|
};
|
|
878
|
-
|
|
856
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-async-determiner", {
|
|
879
857
|
method: "POST",
|
|
880
858
|
body: "Test message with async spec determiner",
|
|
881
859
|
headers: { "Content-Type": "text/plain" }
|
|
882
|
-
})
|
|
883
|
-
const response = await doubleKnock(request, {
|
|
860
|
+
}), {
|
|
884
861
|
keyId: rsaPublicKey2.id,
|
|
885
862
|
privateKey: rsaPrivateKey2
|
|
886
|
-
}, { specDeterminer });
|
|
887
|
-
assertEquals(response.status, 202, "Response status should be 202 Accepted");
|
|
863
|
+
}, { specDeterminer })).status, 202, "Response status should be 202 Accepted");
|
|
888
864
|
assertEquals(requestCount, 1, "Only one request should have been made");
|
|
889
865
|
assertEquals(specUsed, "draft-cavage-http-signatures-12", "Should use spec from async determiner");
|
|
890
866
|
esm_default.hardReset();
|
|
891
867
|
});
|
|
892
868
|
test("timingSafeEqual()", async (t) => {
|
|
893
869
|
await t.step("should return true for equal empty arrays", () => {
|
|
894
|
-
|
|
895
|
-
const b = new Uint8Array([]);
|
|
896
|
-
assert(timingSafeEqual(a, b));
|
|
870
|
+
assert(timingSafeEqual(new Uint8Array([]), new Uint8Array([])));
|
|
897
871
|
});
|
|
898
872
|
await t.step("should return true for equal non-empty arrays", async (t2) => {
|
|
899
873
|
const testCases = [
|
|
@@ -960,7 +934,7 @@ test("timingSafeEqual()", async (t) => {
|
|
|
960
934
|
assert(timingSafeEqual(arr, arr), "Array should be equal to itself by reference");
|
|
961
935
|
});
|
|
962
936
|
await t.step("should return false for arrays with same length but different content", async (t2) => {
|
|
963
|
-
const
|
|
937
|
+
for (const tc of [
|
|
964
938
|
{
|
|
965
939
|
a: [
|
|
966
940
|
1,
|
|
@@ -1018,13 +992,12 @@ test("timingSafeEqual()", async (t) => {
|
|
|
1018
992
|
],
|
|
1019
993
|
name: "middle byte differs with edge values"
|
|
1020
994
|
}
|
|
1021
|
-
]
|
|
1022
|
-
for (const tc of testCases) await t2.step(tc.name, () => {
|
|
995
|
+
]) await t2.step(tc.name, () => {
|
|
1023
996
|
assertFalse(timingSafeEqual(new Uint8Array(tc.a), new Uint8Array(tc.b)));
|
|
1024
997
|
});
|
|
1025
998
|
});
|
|
1026
999
|
await t.step("should return false for arrays with different lengths", async (t2) => {
|
|
1027
|
-
const
|
|
1000
|
+
for (const tc of [
|
|
1028
1001
|
{
|
|
1029
1002
|
a: [
|
|
1030
1003
|
1,
|
|
@@ -1061,13 +1034,12 @@ test("timingSafeEqual()", async (t) => {
|
|
|
1061
1034
|
b: [],
|
|
1062
1035
|
name: "a non-empty, b empty"
|
|
1063
1036
|
}
|
|
1064
|
-
]
|
|
1065
|
-
for (const tc of testCases) await t2.step(tc.name, () => {
|
|
1037
|
+
]) await t2.step(tc.name, () => {
|
|
1066
1038
|
assertFalse(timingSafeEqual(new Uint8Array(tc.a), new Uint8Array(tc.b)));
|
|
1067
1039
|
});
|
|
1068
1040
|
});
|
|
1069
1041
|
await t.step("should return false where content matches up to shorter length", async (t2) => {
|
|
1070
|
-
const
|
|
1042
|
+
for (const tc of [
|
|
1071
1043
|
{
|
|
1072
1044
|
a: [1, 2],
|
|
1073
1045
|
b: [
|
|
@@ -1096,21 +1068,16 @@ test("timingSafeEqual()", async (t) => {
|
|
|
1096
1068
|
b: [0],
|
|
1097
1069
|
name: "two zeros vs single zero"
|
|
1098
1070
|
}
|
|
1099
|
-
]
|
|
1100
|
-
for (const tc of testCases) await t2.step(tc.name, () => {
|
|
1071
|
+
]) await t2.step(tc.name, () => {
|
|
1101
1072
|
assertFalse(timingSafeEqual(new Uint8Array(tc.a), new Uint8Array(tc.b)));
|
|
1102
1073
|
});
|
|
1103
1074
|
});
|
|
1104
1075
|
await t.step("should correctly handle comparisons involving padding bytes", async (t2) => {
|
|
1105
1076
|
await t2.step("a=[1], b=[1,0] (b longer with trailing zero)", () => {
|
|
1106
|
-
|
|
1107
|
-
const b1 = new Uint8Array([1, 0]);
|
|
1108
|
-
assertFalse(timingSafeEqual(a1, b1));
|
|
1077
|
+
assertFalse(timingSafeEqual(new Uint8Array([1]), new Uint8Array([1, 0])));
|
|
1109
1078
|
});
|
|
1110
1079
|
await t2.step("a=[1,0], b=[1] (a longer with trailing zero)", () => {
|
|
1111
|
-
|
|
1112
|
-
const b2 = new Uint8Array([1]);
|
|
1113
|
-
assertFalse(timingSafeEqual(a2, b2));
|
|
1080
|
+
assertFalse(timingSafeEqual(new Uint8Array([1, 0]), new Uint8Array([1])));
|
|
1114
1081
|
});
|
|
1115
1082
|
});
|
|
1116
1083
|
});
|
|
@@ -1127,20 +1094,18 @@ test("signRequest() [rfc9421] error handling for invalid signature base creation
|
|
|
1127
1094
|
assertExists(signedRequest.headers.get("Signature"));
|
|
1128
1095
|
});
|
|
1129
1096
|
test("verifyRequest() [rfc9421] error handling for invalid signature base creation", async () => {
|
|
1130
|
-
|
|
1097
|
+
assertEquals(await verifyRequest(new Request("https://example.com/test", {
|
|
1131
1098
|
method: "GET",
|
|
1132
1099
|
headers: {
|
|
1133
1100
|
"Accept": "application/json",
|
|
1134
1101
|
"Signature-Input": "sig1=(\"@unsupported\");alg=\"rsa-pss-sha256\";keyid=\"https://example.com/key2\";created=1234567890",
|
|
1135
1102
|
"Signature": "sig1=:invalid_signature_data:"
|
|
1136
1103
|
}
|
|
1137
|
-
})
|
|
1138
|
-
const result = await verifyRequest(request, {
|
|
1104
|
+
}), {
|
|
1139
1105
|
spec: "rfc9421",
|
|
1140
1106
|
documentLoader: mockDocumentLoader,
|
|
1141
1107
|
contextLoader: mockDocumentLoader
|
|
1142
|
-
});
|
|
1143
|
-
assertEquals(result, null, "Verification should fail gracefully for malformed signature inputs");
|
|
1108
|
+
}), null, "Verification should fail gracefully for malformed signature inputs");
|
|
1144
1109
|
});
|
|
1145
1110
|
test("doubleKnock() regression test for TypeError: unusable bug #294", async () => {
|
|
1146
1111
|
esm_default.spyGlobal();
|
|
@@ -1154,16 +1119,14 @@ test("doubleKnock() regression test for TypeError: unusable bug #294", async ()
|
|
|
1154
1119
|
esm_default.post("https://example.com/final-destination", () => {
|
|
1155
1120
|
return new Response("Success", { status: 200 });
|
|
1156
1121
|
});
|
|
1157
|
-
|
|
1122
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-retry-redirect", {
|
|
1158
1123
|
method: "POST",
|
|
1159
1124
|
body: "Test activity content",
|
|
1160
1125
|
headers: { "Content-Type": "application/activity+json" }
|
|
1161
|
-
})
|
|
1162
|
-
const response = await doubleKnock(request, {
|
|
1126
|
+
}), {
|
|
1163
1127
|
keyId: rsaPublicKey2.id,
|
|
1164
1128
|
privateKey: rsaPrivateKey2
|
|
1165
|
-
});
|
|
1166
|
-
assertEquals(response.status, 200);
|
|
1129
|
+
})).status, 200);
|
|
1167
1130
|
assertEquals(requestCount, 2, "Should make 2 requests before redirect");
|
|
1168
1131
|
esm_default.hardReset();
|
|
1169
1132
|
});
|
|
@@ -1178,16 +1141,14 @@ test("doubleKnock() regression test for redirect handling bug", async () => {
|
|
|
1178
1141
|
esm_default.post("https://example.com/final-destination", () => {
|
|
1179
1142
|
return new Response("Success", { status: 200 });
|
|
1180
1143
|
});
|
|
1181
|
-
|
|
1144
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-retry-redirect", {
|
|
1182
1145
|
method: "POST",
|
|
1183
1146
|
body: "Test activity content",
|
|
1184
1147
|
headers: { "Content-Type": "application/activity+json" }
|
|
1185
|
-
})
|
|
1186
|
-
const response = await doubleKnock(request, {
|
|
1148
|
+
}), {
|
|
1187
1149
|
keyId: rsaPublicKey2.id,
|
|
1188
1150
|
privateKey: rsaPrivateKey2
|
|
1189
|
-
});
|
|
1190
|
-
assertEquals(response.status, 200);
|
|
1151
|
+
})).status, 200);
|
|
1191
1152
|
esm_default.hardReset();
|
|
1192
1153
|
});
|
|
1193
1154
|
test("signRequest() and verifyRequest() cancellation", {
|
|
@@ -1232,5 +1193,5 @@ test("signRequest() and verifyRequest() cancellation", {
|
|
|
1232
1193
|
});
|
|
1233
1194
|
esm_default.hardReset();
|
|
1234
1195
|
});
|
|
1235
|
-
|
|
1236
|
-
|
|
1196
|
+
//#endregion
|
|
1197
|
+
export {};
|