@fedify/fedify 2.0.8 → 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-B5cKln9v.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-CBHh0rC5.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} +11 -15
- 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-C_RwU_oN.js → http-Bz7avX57.js} +25 -156
- package/dist/{http-Cz3MlXAZ.d.cts → http-C_tEAiZj.d.cts} +1 -6
- package/dist/{http-Br3-1dRf.js → http-DI213UHg.mjs} +17 -33
- package/dist/{http-DGs_78tx.cjs → http-DKBDoudA.cjs} +110 -235
- package/dist/{inbox-3bZUqDLE.js → inbox-Bdn-CSRd.mjs} +18 -26
- package/dist/{key-D7Y_J9kt.js → key-DzJf84o7.mjs} +12 -19
- package/dist/{keycache-BASM0rrX.js → keycache-DaQ3ndaJ.mjs} +5 -9
- 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-CMM5VJsc.js → kv-cache-DBd7BezJ.js} +6 -13
- package/dist/{kv-cache-9PANi4tA.cjs → kv-cache-Dj1Q7TiW.cjs} +27 -34
- package/dist/{kv-cache-El7We5sy.js → kv-cache-OWmRLHir.mjs} +4 -8
- package/dist/{ld-Bjq9Z4St.js → ld-DczS1fLK.mjs} +17 -31
- package/dist/middleware-B5CiOImA.mjs +5 -0
- package/dist/{middleware-Bj30TZll.js → middleware-BKNu57ZI.js} +320 -363
- package/dist/middleware-C36TOX-2.cjs +4 -0
- package/dist/{middleware-CQeA5yF7.cjs → middleware-CyjmpK70.cjs} +513 -564
- package/dist/{middleware-DozhKfB6.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-CImU2dKz.js → owner-DXMGUEOr.mjs} +11 -16
- package/dist/{proof-BygvN4r5.js → proof-C-7NljBU.js} +32 -58
- package/dist/{proof-DLL0MLmV.js → proof-CEOujj0L.mjs} +21 -33
- package/dist/{proof-UhA5do8k.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-DbW03azY.js → send-DIfrLTB_.mjs} +8 -13
- package/dist/sig/http.test.d.mts +2 -0
- package/dist/sig/{http.test.js → http.test.mjs} +117 -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-4w047OFk.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-B73ZyDmk.js +0 -12
- package/dist/middleware-Dr61i4Jo.cjs +0 -12
- package/dist/middleware-_1PYruC5.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();
|
|
@@ -918,25 +853,21 @@ test("doubleKnock() async specDeterminer test", async () => {
|
|
|
918
853
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
919
854
|
}
|
|
920
855
|
};
|
|
921
|
-
|
|
856
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-async-determiner", {
|
|
922
857
|
method: "POST",
|
|
923
858
|
body: "Test message with async spec determiner",
|
|
924
859
|
headers: { "Content-Type": "text/plain" }
|
|
925
|
-
})
|
|
926
|
-
const response = await doubleKnock(request, {
|
|
860
|
+
}), {
|
|
927
861
|
keyId: rsaPublicKey2.id,
|
|
928
862
|
privateKey: rsaPrivateKey2
|
|
929
|
-
}, { specDeterminer });
|
|
930
|
-
assertEquals(response.status, 202, "Response status should be 202 Accepted");
|
|
863
|
+
}, { specDeterminer })).status, 202, "Response status should be 202 Accepted");
|
|
931
864
|
assertEquals(requestCount, 1, "Only one request should have been made");
|
|
932
865
|
assertEquals(specUsed, "draft-cavage-http-signatures-12", "Should use spec from async determiner");
|
|
933
866
|
esm_default.hardReset();
|
|
934
867
|
});
|
|
935
868
|
test("timingSafeEqual()", async (t) => {
|
|
936
869
|
await t.step("should return true for equal empty arrays", () => {
|
|
937
|
-
|
|
938
|
-
const b = new Uint8Array([]);
|
|
939
|
-
assert(timingSafeEqual(a, b));
|
|
870
|
+
assert(timingSafeEqual(new Uint8Array([]), new Uint8Array([])));
|
|
940
871
|
});
|
|
941
872
|
await t.step("should return true for equal non-empty arrays", async (t2) => {
|
|
942
873
|
const testCases = [
|
|
@@ -1003,7 +934,7 @@ test("timingSafeEqual()", async (t) => {
|
|
|
1003
934
|
assert(timingSafeEqual(arr, arr), "Array should be equal to itself by reference");
|
|
1004
935
|
});
|
|
1005
936
|
await t.step("should return false for arrays with same length but different content", async (t2) => {
|
|
1006
|
-
const
|
|
937
|
+
for (const tc of [
|
|
1007
938
|
{
|
|
1008
939
|
a: [
|
|
1009
940
|
1,
|
|
@@ -1061,13 +992,12 @@ test("timingSafeEqual()", async (t) => {
|
|
|
1061
992
|
],
|
|
1062
993
|
name: "middle byte differs with edge values"
|
|
1063
994
|
}
|
|
1064
|
-
]
|
|
1065
|
-
for (const tc of testCases) await t2.step(tc.name, () => {
|
|
995
|
+
]) await t2.step(tc.name, () => {
|
|
1066
996
|
assertFalse(timingSafeEqual(new Uint8Array(tc.a), new Uint8Array(tc.b)));
|
|
1067
997
|
});
|
|
1068
998
|
});
|
|
1069
999
|
await t.step("should return false for arrays with different lengths", async (t2) => {
|
|
1070
|
-
const
|
|
1000
|
+
for (const tc of [
|
|
1071
1001
|
{
|
|
1072
1002
|
a: [
|
|
1073
1003
|
1,
|
|
@@ -1104,13 +1034,12 @@ test("timingSafeEqual()", async (t) => {
|
|
|
1104
1034
|
b: [],
|
|
1105
1035
|
name: "a non-empty, b empty"
|
|
1106
1036
|
}
|
|
1107
|
-
]
|
|
1108
|
-
for (const tc of testCases) await t2.step(tc.name, () => {
|
|
1037
|
+
]) await t2.step(tc.name, () => {
|
|
1109
1038
|
assertFalse(timingSafeEqual(new Uint8Array(tc.a), new Uint8Array(tc.b)));
|
|
1110
1039
|
});
|
|
1111
1040
|
});
|
|
1112
1041
|
await t.step("should return false where content matches up to shorter length", async (t2) => {
|
|
1113
|
-
const
|
|
1042
|
+
for (const tc of [
|
|
1114
1043
|
{
|
|
1115
1044
|
a: [1, 2],
|
|
1116
1045
|
b: [
|
|
@@ -1139,21 +1068,16 @@ test("timingSafeEqual()", async (t) => {
|
|
|
1139
1068
|
b: [0],
|
|
1140
1069
|
name: "two zeros vs single zero"
|
|
1141
1070
|
}
|
|
1142
|
-
]
|
|
1143
|
-
for (const tc of testCases) await t2.step(tc.name, () => {
|
|
1071
|
+
]) await t2.step(tc.name, () => {
|
|
1144
1072
|
assertFalse(timingSafeEqual(new Uint8Array(tc.a), new Uint8Array(tc.b)));
|
|
1145
1073
|
});
|
|
1146
1074
|
});
|
|
1147
1075
|
await t.step("should correctly handle comparisons involving padding bytes", async (t2) => {
|
|
1148
1076
|
await t2.step("a=[1], b=[1,0] (b longer with trailing zero)", () => {
|
|
1149
|
-
|
|
1150
|
-
const b1 = new Uint8Array([1, 0]);
|
|
1151
|
-
assertFalse(timingSafeEqual(a1, b1));
|
|
1077
|
+
assertFalse(timingSafeEqual(new Uint8Array([1]), new Uint8Array([1, 0])));
|
|
1152
1078
|
});
|
|
1153
1079
|
await t2.step("a=[1,0], b=[1] (a longer with trailing zero)", () => {
|
|
1154
|
-
|
|
1155
|
-
const b2 = new Uint8Array([1]);
|
|
1156
|
-
assertFalse(timingSafeEqual(a2, b2));
|
|
1080
|
+
assertFalse(timingSafeEqual(new Uint8Array([1, 0]), new Uint8Array([1])));
|
|
1157
1081
|
});
|
|
1158
1082
|
});
|
|
1159
1083
|
});
|
|
@@ -1170,20 +1094,18 @@ test("signRequest() [rfc9421] error handling for invalid signature base creation
|
|
|
1170
1094
|
assertExists(signedRequest.headers.get("Signature"));
|
|
1171
1095
|
});
|
|
1172
1096
|
test("verifyRequest() [rfc9421] error handling for invalid signature base creation", async () => {
|
|
1173
|
-
|
|
1097
|
+
assertEquals(await verifyRequest(new Request("https://example.com/test", {
|
|
1174
1098
|
method: "GET",
|
|
1175
1099
|
headers: {
|
|
1176
1100
|
"Accept": "application/json",
|
|
1177
1101
|
"Signature-Input": "sig1=(\"@unsupported\");alg=\"rsa-pss-sha256\";keyid=\"https://example.com/key2\";created=1234567890",
|
|
1178
1102
|
"Signature": "sig1=:invalid_signature_data:"
|
|
1179
1103
|
}
|
|
1180
|
-
})
|
|
1181
|
-
const result = await verifyRequest(request, {
|
|
1104
|
+
}), {
|
|
1182
1105
|
spec: "rfc9421",
|
|
1183
1106
|
documentLoader: mockDocumentLoader,
|
|
1184
1107
|
contextLoader: mockDocumentLoader
|
|
1185
|
-
});
|
|
1186
|
-
assertEquals(result, null, "Verification should fail gracefully for malformed signature inputs");
|
|
1108
|
+
}), null, "Verification should fail gracefully for malformed signature inputs");
|
|
1187
1109
|
});
|
|
1188
1110
|
test("doubleKnock() regression test for TypeError: unusable bug #294", async () => {
|
|
1189
1111
|
esm_default.spyGlobal();
|
|
@@ -1197,16 +1119,14 @@ test("doubleKnock() regression test for TypeError: unusable bug #294", async ()
|
|
|
1197
1119
|
esm_default.post("https://example.com/final-destination", () => {
|
|
1198
1120
|
return new Response("Success", { status: 200 });
|
|
1199
1121
|
});
|
|
1200
|
-
|
|
1122
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-retry-redirect", {
|
|
1201
1123
|
method: "POST",
|
|
1202
1124
|
body: "Test activity content",
|
|
1203
1125
|
headers: { "Content-Type": "application/activity+json" }
|
|
1204
|
-
})
|
|
1205
|
-
const response = await doubleKnock(request, {
|
|
1126
|
+
}), {
|
|
1206
1127
|
keyId: rsaPublicKey2.id,
|
|
1207
1128
|
privateKey: rsaPrivateKey2
|
|
1208
|
-
});
|
|
1209
|
-
assertEquals(response.status, 200);
|
|
1129
|
+
})).status, 200);
|
|
1210
1130
|
assertEquals(requestCount, 2, "Should make 2 requests before redirect");
|
|
1211
1131
|
esm_default.hardReset();
|
|
1212
1132
|
});
|
|
@@ -1221,16 +1141,14 @@ test("doubleKnock() regression test for redirect handling bug", async () => {
|
|
|
1221
1141
|
esm_default.post("https://example.com/final-destination", () => {
|
|
1222
1142
|
return new Response("Success", { status: 200 });
|
|
1223
1143
|
});
|
|
1224
|
-
|
|
1144
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-retry-redirect", {
|
|
1225
1145
|
method: "POST",
|
|
1226
1146
|
body: "Test activity content",
|
|
1227
1147
|
headers: { "Content-Type": "application/activity+json" }
|
|
1228
|
-
})
|
|
1229
|
-
const response = await doubleKnock(request, {
|
|
1148
|
+
}), {
|
|
1230
1149
|
keyId: rsaPublicKey2.id,
|
|
1231
1150
|
privateKey: rsaPrivateKey2
|
|
1232
|
-
});
|
|
1233
|
-
assertEquals(response.status, 200);
|
|
1151
|
+
})).status, 200);
|
|
1234
1152
|
esm_default.hardReset();
|
|
1235
1153
|
});
|
|
1236
1154
|
test("signRequest() and verifyRequest() cancellation", {
|
|
@@ -1275,5 +1193,5 @@ test("signRequest() and verifyRequest() cancellation", {
|
|
|
1275
1193
|
});
|
|
1276
1194
|
esm_default.hardReset();
|
|
1277
1195
|
});
|
|
1278
|
-
|
|
1279
|
-
|
|
1196
|
+
//#endregion
|
|
1197
|
+
export {};
|