@fedify/fedify 2.1.1 → 2.1.3
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/{accept-D7sAxyNa.js → accept-Dd__NiUL.mjs} +10 -8
- 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-f3m3epl3.js → assert_not_equals--wG9hV7u.mjs} +6 -13
- package/dist/{assert_rejects-0h7I2Esa.js → assert_rejects-B-qJtC9Z.mjs} +6 -11
- package/dist/{assert_throws-rjdMBf31.js → assert_throws-4NwKEy2q.mjs} +5 -10
- package/dist/{builder-DbKYZdSy.js → builder-BKo51w-F.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-BxMZiQaD.d.ts → client-AtlibPOU.d.ts} +1 -1
- package/dist/{client-CoCIaTNO.js → client-DEpOVgY1.mjs} +9 -13
- package/dist/{client-C97KOq3x.d.cts → client-z-8dc-e1.d.cts} +1 -1
- package/dist/{collection-CSzG2j1P.js → collection-BD6-SZ6O.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-DyJjQQ_H.d.ts → context-BOiMZBu5.d.ts} +9 -18
- package/dist/{context-BcqA-0BL.d.cts → context-BhZVy7RB.d.cts} +9 -18
- package/dist/{context-Aqenou7c.js → context-Juj6bdHC.mjs} +7 -11
- package/dist/deno-D5r_9RvZ.mjs +8 -0
- package/dist/{docloader-Ck0SCLXX.js → docloader-B9CXCw8i.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} +21 -44
- 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} +69 -131
- package/dist/federation/idempotency.test.d.mts +2 -0
- package/dist/federation/{idempotency.test.js → idempotency.test.mjs} +31 -63
- 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 -19
- 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} +173 -262
- 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 -30
- package/dist/federation/webfinger.test.d.mts +2 -0
- package/dist/federation/{webfinger.test.js → webfinger.test.mjs} +22 -56
- package/dist/{http-DFzT4YFG.js → http-B53alCGi.mjs} +23 -43
- package/dist/{http-ca2xny58.cjs → http-BngkmEhl.cjs} +177 -302
- package/dist/{http-BudnHZE2.d.cts → http-CrGuipxe.d.cts} +1 -6
- package/dist/{http-EUQ6crVa.js → http-PS3wuU8D.js} +53 -184
- package/dist/{http-Dax_FIBo.d.ts → http-aQzN9Ayi.d.ts} +1 -6
- package/dist/{inbox-BMLz_-pL.js → inbox-CHsLu5ai.mjs} +18 -26
- package/dist/{key-CypuWa94.js → key-D9Np_ZXl.mjs} +29 -37
- package/dist/{keycache-CpGWAUbj.js → keycache-CCSwkQcY.mjs} +5 -10
- package/dist/{keys-BFve7QQv.js → keys-BAK-tUlf.mjs} +5 -9
- 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-Bw2F2ABq.js → kv-cache-B01V7s3h.mjs} +4 -8
- package/dist/{kv-cache-SKgbvvu4.js → kv-cache-B2Qi5MGv.js} +6 -13
- package/dist/{kv-cache-BBJFLMW5.cjs → kv-cache-YCtINZK4.cjs} +27 -34
- package/dist/{kv-QzKcOQgP.js → kv-tL2TOE9X.mjs} +6 -10
- package/dist/{ld-CXLtTc0G.js → ld-BaxRFhDd.mjs} +17 -31
- package/dist/{middleware-CL6XaAFy.cjs → middleware-Bsv-7iX7.cjs} +532 -587
- package/dist/middleware-C37OmOz_.mjs +5 -0
- package/dist/middleware-CelV2xrI.cjs +4 -0
- package/dist/{middleware-CvS6hWm3.js → middleware-Dn1kk96N.js} +335 -382
- package/dist/{middleware-BHJ0xm0L.js → middleware-dFn6ozt5.mjs} +282 -317
- package/dist/{mod-Bx9jcLB8.d.cts → mod-B505FZBC.d.cts} +3 -3
- package/dist/{mod-em2Il1eD.d.cts → mod-Bp_CzKd4.d.cts} +2 -2
- package/dist/{mod-Cs2dYEwI.d.ts → mod-D7PAuO6k.d.ts} +3 -3
- package/dist/{mod-D6MdymW7.d.ts → mod-DKOAow7a.d.ts} +2 -2
- package/dist/{mod-Coe7KEgX.d.cts → mod-DoJBjjnO.d.cts} +2 -2
- package/dist/{mod-D6dOd--H.d.ts → mod-DvxszxXC.d.ts} +2 -2
- package/dist/mod.cjs +29 -74
- package/dist/mod.d.cts +11 -14
- package/dist/mod.d.ts +11 -15
- package/dist/mod.js +17 -71
- package/dist/{negotiation-BlAuS_nr.js → negotiation-DnsfFF8I.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 -43
- 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} +124 -178
- 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-CwMai3jn.js → owner-dxM51u36.mjs} +11 -16
- package/dist/{proof-ZuJBOUoi.js → proof-CH5U0k7G.mjs} +21 -33
- package/dist/{proof-sCID81Ua.cjs → proof-D39qiki3.cjs} +133 -157
- package/dist/{proof-6Zw1FW7t.js → proof-Dpgqx9RS.js} +32 -58
- package/dist/{retry-mqLf4b-R.js → retry-B_E3V_Dx.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-BW73dy6Q.js → send-D1-4ZnQq.mjs} +8 -13
- package/dist/sig/accept.test.d.mts +2 -0
- package/dist/sig/{accept.test.js → accept.test.mjs} +35 -70
- package/dist/sig/http.test.d.mts +2 -0
- package/dist/sig/{http.test.js → http.test.mjs} +166 -280
- 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-X-_kMxKM.js → std__assert-Duiq_YC9.mjs} +12 -24
- package/dist/testing/{mod.d.ts → mod.d.mts} +26 -90
- 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-DCP0WLdt.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 -25
- 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 +8 -8
- package/dist/compat/transformers.test.d.ts +0 -3
- package/dist/compat/transformers.test.js +0 -88
- package/dist/compat-Bb4NuTUO.js +0 -4
- package/dist/compat-DmDDELst.cjs +0 -4
- package/dist/deno-DH972JvX.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-B8FJuMM0.js +0 -27
- package/dist/middleware-BVp930fR.js +0 -12
- package/dist/middleware-BvGP-uXy.cjs +0 -12
- package/dist/mod-B7QkWzrL.d.cts +0 -80
- package/dist/mod-Bh8mqlYw.d.cts +0 -9
- package/dist/mod-D6HodEq7.d.ts +0 -7
- package/dist/mod-SMHOMNpZ.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/accept.test.d.ts +0 -3
- 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,37 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import "../
|
|
10
|
-
import "../
|
|
11
|
-
import "../
|
|
12
|
-
import {
|
|
13
|
-
import { createRfc9421SignatureBase, doubleKnock, formatRfc9421Signature, formatRfc9421SignatureParameters, parseRfc9421Signature, parseRfc9421SignatureInput, signRequest, timingSafeEqual, verifyRequest, verifyRequestDetailed } from "../http-DFzT4YFG.js";
|
|
14
|
-
import { assertExists, assertStringIncludes } from "../std__assert-X-_kMxKM.js";
|
|
15
|
-
import { assertFalse, assertRejects } from "../assert_rejects-0h7I2Esa.js";
|
|
16
|
-
import { assertThrows } from "../assert_throws-rjdMBf31.js";
|
|
17
|
-
import "../assert_not_equals-f3m3epl3.js";
|
|
18
|
-
import { rsaPrivateKey2, rsaPublicKey1, rsaPublicKey2, rsaPublicKey5 } from "../keys-BFve7QQv.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-D9Np_ZXl.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, u as verifyRequestDetailed } from "../http-B53alCGi.mjs";
|
|
12
|
+
import { i as rsaPrivateKey2, l as rsaPublicKey5, o as rsaPublicKey1, s as rsaPublicKey2 } from "../keys-BAK-tUlf.mjs";
|
|
19
13
|
import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
|
|
20
14
|
import { FetchError, exportSpki } from "@fedify/vocab-runtime";
|
|
21
15
|
import { encodeBase64 } from "byte-encodings/base64";
|
|
22
|
-
|
|
23
16
|
//#region src/sig/http.test.ts
|
|
24
17
|
test("signRequest() [draft-cavage]", async () => {
|
|
25
|
-
|
|
18
|
+
assertEquals(await verifyRequest(await signRequest(new Request("https://example.com/", {
|
|
26
19
|
method: "POST",
|
|
27
20
|
body: "Hello, world!",
|
|
28
21
|
headers: {
|
|
29
22
|
"Content-Type": "text/plain; charset=utf-8",
|
|
30
23
|
Accept: "text/plain"
|
|
31
24
|
}
|
|
32
|
-
})
|
|
33
|
-
const signed = await signRequest(request, rsaPrivateKey2, new URL("https://example.com/key2"));
|
|
34
|
-
assertEquals(await verifyRequest(signed, {
|
|
25
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2")), {
|
|
35
26
|
contextLoader: mockDocumentLoader,
|
|
36
27
|
documentLoader: mockDocumentLoader
|
|
37
28
|
}), rsaPublicKey2);
|
|
@@ -57,8 +48,8 @@ test("verifyRequest() [draft-cavage]", async () => {
|
|
|
57
48
|
get(keyId) {
|
|
58
49
|
return Promise.resolve(cache[keyId.href]);
|
|
59
50
|
},
|
|
60
|
-
set(keyId, key
|
|
61
|
-
cache[keyId.href] = key
|
|
51
|
+
set(keyId, key) {
|
|
52
|
+
cache[keyId.href] = key;
|
|
62
53
|
return Promise.resolve();
|
|
63
54
|
}
|
|
64
55
|
}
|
|
@@ -146,7 +137,7 @@ test("verifyRequest() [draft-cavage]", async () => {
|
|
|
146
137
|
currentTime: Temporal.Instant.from("2025-01-01T00:00:00.0000Z"),
|
|
147
138
|
timeWindow: false
|
|
148
139
|
}), rsaPublicKey1);
|
|
149
|
-
|
|
140
|
+
assert(await verifyRequest(new Request("https://c27a97f98d5f.ngrok.app/i/inbox", {
|
|
150
141
|
method: "POST",
|
|
151
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\"}",
|
|
152
143
|
headers: {
|
|
@@ -156,12 +147,10 @@ test("verifyRequest() [draft-cavage]", async () => {
|
|
|
156
147
|
Digest: "SHA-256=YZyjeVQW5GwliJowASkteBJhFBTq3eQk/AMqRETc//A=",
|
|
157
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==\""
|
|
158
149
|
}
|
|
159
|
-
})
|
|
160
|
-
const options2 = {
|
|
150
|
+
}), {
|
|
161
151
|
...options,
|
|
162
152
|
currentTime: Temporal.Instant.from("2025-08-25T12:58:14Z")
|
|
163
|
-
};
|
|
164
|
-
assert(await verifyRequest(request2, options2) != null);
|
|
153
|
+
}) != null);
|
|
165
154
|
});
|
|
166
155
|
test("verifyRequestDetailed() classifies malformed signatures as invalid", async () => {
|
|
167
156
|
const draftMissingKeyId = await verifyRequestDetailed(new Request("https://example.com/", {
|
|
@@ -214,7 +203,7 @@ test("verifyRequestDetailed() classifies malformed signatures as invalid", async
|
|
|
214
203
|
test("verifyRequestDetailed() records failure details on span", async () => {
|
|
215
204
|
const [tracerProvider, exporter] = createTestTracerProvider();
|
|
216
205
|
const keyId = new URL("https://gone.example/actors/alice#main-key");
|
|
217
|
-
|
|
206
|
+
assertFalse((await verifyRequestDetailed(await signRequest(new Request("https://example.com/inbox", {
|
|
218
207
|
method: "POST",
|
|
219
208
|
headers: {
|
|
220
209
|
"Content-Type": "application/activity+json",
|
|
@@ -225,16 +214,14 @@ test("verifyRequestDetailed() records failure details on span", async () => {
|
|
|
225
214
|
type: "Create",
|
|
226
215
|
actor: "https://gone.example/actors/alice"
|
|
227
216
|
})
|
|
228
|
-
}), rsaPrivateKey2, keyId)
|
|
229
|
-
const result = await verifyRequestDetailed(request, {
|
|
217
|
+
}), rsaPrivateKey2, keyId), {
|
|
230
218
|
tracerProvider,
|
|
231
219
|
contextLoader: mockDocumentLoader,
|
|
232
220
|
documentLoader(url) {
|
|
233
221
|
if (url === keyId.href) throw new FetchError(keyId, `HTTP 410: ${keyId.href}`, new Response(null, { status: 410 }));
|
|
234
222
|
return mockDocumentLoader(url);
|
|
235
223
|
}
|
|
236
|
-
});
|
|
237
|
-
assertFalse(result.verified);
|
|
224
|
+
})).verified);
|
|
238
225
|
const spans = exporter.getSpans("http_signatures.verify");
|
|
239
226
|
assertEquals(spans.length, 1);
|
|
240
227
|
const span = spans[0];
|
|
@@ -282,9 +269,7 @@ test("signRequest() and verifyRequest() [rfc9421] implementation", async () => {
|
|
|
282
269
|
const contentDigest = signed.headers.get("Content-Digest");
|
|
283
270
|
assertExists(contentDigest);
|
|
284
271
|
assert(contentDigest.startsWith("sha-256=:"), "Content-Digest should use RFC 9421 format");
|
|
285
|
-
|
|
286
|
-
const expectedDigestBase64 = encodeBase64(expectedDigest);
|
|
287
|
-
assertEquals(contentDigest, `sha-256=:${expectedDigestBase64}:`, "Content-Digest should have correct value");
|
|
272
|
+
assertEquals(contentDigest, `sha-256=:${encodeBase64(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(requestBody)))}:`, "Content-Digest should have correct value");
|
|
288
273
|
const signature = signed.headers.get("Signature");
|
|
289
274
|
assertExists(signature);
|
|
290
275
|
const sigFormat = /^sig1=:([A-Za-z0-9+/]+=*):/;
|
|
@@ -305,25 +290,22 @@ test("signRequest() and verifyRequest() [rfc9421] implementation", async () => {
|
|
|
305
290
|
assertEquals(parsedSig.sig1.byteLength > 0, true, "Signature value should be a non-empty Uint8Array");
|
|
306
291
|
const verifyHeaders = new Headers();
|
|
307
292
|
for (const [name, value] of signed.headers.entries()) if (name !== "Signature" && name !== "Signature-Input") verifyHeaders.set(name, value);
|
|
308
|
-
const
|
|
293
|
+
const reconstructedBase = createRfc9421SignatureBase(new Request(request.url, {
|
|
309
294
|
method: request.method,
|
|
310
295
|
headers: verifyHeaders
|
|
311
|
-
});
|
|
312
|
-
const reconstructedBase = createRfc9421SignatureBase(reconstructedRequest, parsedInput.sig1.components, parsedInput.sig1.parameters);
|
|
296
|
+
}), parsedInput.sig1.components, parsedInput.sig1.parameters);
|
|
313
297
|
const signatureBytes = new Uint8Array(parsedSig.sig1);
|
|
314
|
-
|
|
315
|
-
assert(signatureVerifies, "Manual verification of signature should succeed");
|
|
298
|
+
assert(await crypto.subtle.verify("RSASSA-PKCS1-v1_5", rsaPublicKey2.publicKey, signatureBytes, new TextEncoder().encode(reconstructedBase)), "Manual verification of signature should succeed");
|
|
316
299
|
});
|
|
317
300
|
test("createRfc9421SignatureBase()", () => {
|
|
318
|
-
|
|
301
|
+
assertEquals(createRfc9421SignatureBase(new Request("https://example.com/path?query=value", {
|
|
319
302
|
method: "POST",
|
|
320
303
|
headers: {
|
|
321
304
|
Host: "example.com",
|
|
322
305
|
Date: "Tue, 05 Mar 2024 07:49:44 GMT",
|
|
323
306
|
"Content-Type": "text/plain"
|
|
324
307
|
}
|
|
325
|
-
})
|
|
326
|
-
const components = [
|
|
308
|
+
}), [
|
|
327
309
|
{
|
|
328
310
|
value: "@method",
|
|
329
311
|
params: {}
|
|
@@ -340,21 +322,17 @@ test("createRfc9421SignatureBase()", () => {
|
|
|
340
322
|
value: "date",
|
|
341
323
|
params: {}
|
|
342
324
|
}
|
|
343
|
-
]
|
|
344
|
-
const created = 1709626184;
|
|
345
|
-
const signatureBase = createRfc9421SignatureBase(request, components, formatRfc9421SignatureParameters({
|
|
325
|
+
], formatRfc9421SignatureParameters({
|
|
346
326
|
algorithm: "rsa-v1_5-sha256",
|
|
347
327
|
keyId: new URL("https://example.com/key"),
|
|
348
|
-
created
|
|
349
|
-
}))
|
|
350
|
-
const expected = [
|
|
328
|
+
created: 1709626184
|
|
329
|
+
})), [
|
|
351
330
|
`"@method": POST`,
|
|
352
331
|
`"@target-uri": https://example.com/path?query=value`,
|
|
353
332
|
`"host": example.com`,
|
|
354
333
|
`"date": Tue, 05 Mar 2024 07:49:44 GMT`,
|
|
355
334
|
`"@signature-params": ("@method" "@target-uri" "host" "date");alg="rsa-v1_5-sha256";keyid="https://example.com/key";created=1709626184`
|
|
356
|
-
].join("\n");
|
|
357
|
-
assertEquals(signatureBase, expected);
|
|
335
|
+
].join("\n"));
|
|
358
336
|
});
|
|
359
337
|
test("formatRfc9421Signature()", () => {
|
|
360
338
|
const signature = new Uint8Array([
|
|
@@ -365,7 +343,7 @@ test("formatRfc9421Signature()", () => {
|
|
|
365
343
|
]);
|
|
366
344
|
const keyId = new URL("https://example.com/key");
|
|
367
345
|
const algorithm = "rsa-v1_5-sha256";
|
|
368
|
-
const
|
|
346
|
+
const [signatureInput, signatureHeader] = formatRfc9421Signature(signature, [
|
|
369
347
|
{
|
|
370
348
|
"value": "@method",
|
|
371
349
|
params: {}
|
|
@@ -378,19 +356,16 @@ test("formatRfc9421Signature()", () => {
|
|
|
378
356
|
"value": "host",
|
|
379
357
|
params: {}
|
|
380
358
|
}
|
|
381
|
-
]
|
|
382
|
-
const created = 1709626184;
|
|
383
|
-
const [signatureInput, signatureHeader] = formatRfc9421Signature(signature, components, formatRfc9421SignatureParameters({
|
|
359
|
+
], formatRfc9421SignatureParameters({
|
|
384
360
|
algorithm,
|
|
385
361
|
keyId,
|
|
386
|
-
created
|
|
362
|
+
created: 1709626184
|
|
387
363
|
}));
|
|
388
364
|
assertEquals(signatureInput, `sig1=("@method" "@target-uri" "host");alg="rsa-v1_5-sha256";keyid="https://example.com/key";created=1709626184`);
|
|
389
365
|
assertEquals(signatureHeader, `sig1=:AQIDBA==:`);
|
|
390
366
|
});
|
|
391
367
|
test("parseRfc9421SignatureInput()", () => {
|
|
392
|
-
const
|
|
393
|
-
const parsed = parseRfc9421SignatureInput(signatureInput);
|
|
368
|
+
const parsed = parseRfc9421SignatureInput(`sig1=("@method" "@target-uri" "host" "date");keyid="https://example.com/key";alg="rsa-v1_5-sha256";created=1709626184`);
|
|
394
369
|
assertEquals(parsed.sig1.keyId, "https://example.com/key");
|
|
395
370
|
assertEquals(parsed.sig1.alg, "rsa-v1_5-sha256");
|
|
396
371
|
assertEquals(parsed.sig1.created, 1709626184);
|
|
@@ -415,8 +390,7 @@ test("parseRfc9421SignatureInput()", () => {
|
|
|
415
390
|
assertEquals(parsed.sig1.parameters, "keyid=\"https://example.com/key\";alg=\"rsa-v1_5-sha256\";created=1709626184");
|
|
416
391
|
});
|
|
417
392
|
test("parseRfc9421Signature()", () => {
|
|
418
|
-
const
|
|
419
|
-
const parsed = parseRfc9421Signature(signature);
|
|
393
|
+
const parsed = parseRfc9421Signature(`sig1=:AQIDBA==:,sig2=:Zm9vYmFy:`);
|
|
420
394
|
assertExists(parsed.sig1);
|
|
421
395
|
assertExists(parsed.sig2);
|
|
422
396
|
const sig1Bytes = new Uint8Array(parsed.sig1);
|
|
@@ -425,37 +399,33 @@ test("parseRfc9421Signature()", () => {
|
|
|
425
399
|
assertEquals(sig1Bytes[1], 2);
|
|
426
400
|
assertEquals(sig1Bytes[2], 3);
|
|
427
401
|
assertEquals(sig1Bytes[3], 4);
|
|
428
|
-
|
|
429
|
-
assertEquals(sig2Text, "foobar");
|
|
402
|
+
assertEquals(new TextDecoder().decode(parsed.sig2), "foobar");
|
|
430
403
|
});
|
|
431
404
|
test("verifyRequest() [rfc9421] successful GET verification", async () => {
|
|
432
405
|
const currentTimestamp = 1709626184;
|
|
433
406
|
const currentTime = Temporal.Instant.from("2024-03-05T08:09:44Z");
|
|
434
|
-
|
|
407
|
+
assertEquals(await verifyRequest(await signRequest(new Request("https://example.com/api/resource", {
|
|
435
408
|
method: "GET",
|
|
436
409
|
headers: {
|
|
437
410
|
"Accept": "application/json",
|
|
438
411
|
"Host": "example.com",
|
|
439
412
|
"Date": "Tue, 05 Mar 2024 07:49:44 GMT"
|
|
440
413
|
}
|
|
441
|
-
})
|
|
442
|
-
const signedRequest = await signRequest(validRequest, rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
414
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
443
415
|
spec: "rfc9421",
|
|
444
416
|
currentTime
|
|
445
|
-
})
|
|
446
|
-
const verifiedKey = await verifyRequest(signedRequest, {
|
|
417
|
+
}), {
|
|
447
418
|
contextLoader: mockDocumentLoader,
|
|
448
419
|
documentLoader: mockDocumentLoader,
|
|
449
420
|
spec: "rfc9421",
|
|
450
421
|
currentTime: Temporal.Instant.from(`${(/* @__PURE__ */ new Date(currentTimestamp * 1e3)).toISOString()}`)
|
|
451
|
-
});
|
|
452
|
-
assertEquals(verifiedKey, rsaPublicKey2, "Valid signature should verify to the correct public key");
|
|
422
|
+
}), rsaPublicKey2, "Valid signature should verify to the correct public key");
|
|
453
423
|
});
|
|
454
424
|
test("verifyRequest() [rfc9421] manual POST verification", async () => {
|
|
455
425
|
const currentTimestamp = 1709626184;
|
|
456
426
|
const currentTime = Temporal.Instant.from("2024-03-05T08:09:44Z");
|
|
457
427
|
const postBody = "Test content for signature verification";
|
|
458
|
-
const
|
|
428
|
+
const signedPostRequest = await signRequest(new Request("https://example.com/api/resource", {
|
|
459
429
|
method: "POST",
|
|
460
430
|
body: postBody,
|
|
461
431
|
headers: {
|
|
@@ -464,8 +434,7 @@ test("verifyRequest() [rfc9421] manual POST verification", async () => {
|
|
|
464
434
|
"Host": "example.com",
|
|
465
435
|
"Date": "Tue, 05 Mar 2024 07:49:44 GMT"
|
|
466
436
|
}
|
|
467
|
-
})
|
|
468
|
-
const signedPostRequest = await signRequest(postRequest, rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
437
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
469
438
|
spec: "rfc9421",
|
|
470
439
|
currentTime
|
|
471
440
|
});
|
|
@@ -487,61 +456,54 @@ test("verifyRequest() [rfc9421] manual POST verification", async () => {
|
|
|
487
456
|
assertExists(parsedSignature.sig1, "Should have a valid signature value");
|
|
488
457
|
assertEquals(parsedInput.sig1.keyId, "https://example.com/key2", "Signature should have the correct key ID");
|
|
489
458
|
assertEquals(parsedInput.sig1.created, currentTimestamp, "Signature should have the correct timestamp");
|
|
490
|
-
const
|
|
459
|
+
const signatureBase = createRfc9421SignatureBase(new Request("https://example.com/api/resource", {
|
|
491
460
|
method: "POST",
|
|
492
461
|
body: postBody,
|
|
493
462
|
headers: new Headers(signedPostRequest.headers)
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
const signatureVerified = await crypto.subtle.verify("RSASSA-PKCS1-v1_5", rsaPublicKey2.publicKey, parsedSignature.sig1.slice(), new TextEncoder().encode(signatureBase));
|
|
497
|
-
assert(signatureVerified, "Manual verification of POST signature should succeed");
|
|
463
|
+
}), parsedInput.sig1.components, parsedInput.sig1.parameters);
|
|
464
|
+
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");
|
|
498
465
|
});
|
|
499
466
|
test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
500
467
|
const currentTimestamp = 1709626184;
|
|
501
468
|
const currentTime = Temporal.Instant.from("2024-03-05T08:09:44Z");
|
|
502
|
-
const
|
|
469
|
+
const signedRequest = await signRequest(new Request("https://example.com/api/resource", {
|
|
503
470
|
method: "GET",
|
|
504
471
|
headers: {
|
|
505
472
|
"Accept": "application/json",
|
|
506
473
|
"Host": "example.com",
|
|
507
474
|
"Date": "Tue, 05 Mar 2024 07:49:44 GMT"
|
|
508
475
|
}
|
|
509
|
-
})
|
|
510
|
-
const signedRequest = await signRequest(validRequest, rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
476
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
511
477
|
spec: "rfc9421",
|
|
512
478
|
currentTime
|
|
513
479
|
});
|
|
514
480
|
const validSignatureInput = signedRequest.headers.get("Signature-Input") || "";
|
|
515
481
|
const validSignature = signedRequest.headers.get("Signature") || "";
|
|
516
|
-
|
|
482
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
517
483
|
method: "GET",
|
|
518
484
|
headers: new Headers({
|
|
519
485
|
"Accept": "application/json",
|
|
520
486
|
"Host": "example.com",
|
|
521
487
|
"Signature": validSignature
|
|
522
488
|
})
|
|
523
|
-
})
|
|
524
|
-
const missingInputResult = await verifyRequest(missingInputHeader, {
|
|
489
|
+
}), {
|
|
525
490
|
contextLoader: mockDocumentLoader,
|
|
526
491
|
documentLoader: mockDocumentLoader,
|
|
527
492
|
spec: "rfc9421"
|
|
528
|
-
});
|
|
529
|
-
assertEquals(
|
|
530
|
-
const missingSignatureHeader = new Request("https://example.com/api/resource", {
|
|
493
|
+
}), null, "Should fail verification when Signature-Input header is missing");
|
|
494
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
531
495
|
method: "GET",
|
|
532
496
|
headers: new Headers({
|
|
533
497
|
"Accept": "application/json",
|
|
534
498
|
"Host": "example.com",
|
|
535
499
|
"Signature-Input": validSignatureInput
|
|
536
500
|
})
|
|
537
|
-
})
|
|
538
|
-
const missingSignatureResult = await verifyRequest(missingSignatureHeader, {
|
|
501
|
+
}), {
|
|
539
502
|
contextLoader: mockDocumentLoader,
|
|
540
503
|
documentLoader: mockDocumentLoader,
|
|
541
504
|
spec: "rfc9421"
|
|
542
|
-
});
|
|
543
|
-
assertEquals(
|
|
544
|
-
const tamperedRequest = new Request("https://example.com/api/resource", {
|
|
505
|
+
}), null, "Should fail verification when Signature header is missing");
|
|
506
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
545
507
|
method: "GET",
|
|
546
508
|
headers: new Headers({
|
|
547
509
|
"Accept": "application/json",
|
|
@@ -550,14 +512,12 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
550
512
|
"Signature-Input": validSignatureInput,
|
|
551
513
|
"Signature": "sig1=:AAAAAA==:"
|
|
552
514
|
})
|
|
553
|
-
})
|
|
554
|
-
const tamperedResult = await verifyRequest(tamperedRequest, {
|
|
515
|
+
}), {
|
|
555
516
|
contextLoader: mockDocumentLoader,
|
|
556
517
|
documentLoader: mockDocumentLoader,
|
|
557
518
|
spec: "rfc9421"
|
|
558
|
-
});
|
|
559
|
-
assertEquals(
|
|
560
|
-
const expiredRequest = new Request("https://example.com/api/resource", {
|
|
519
|
+
}), null, "Should fail verification when signature is tampered");
|
|
520
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
561
521
|
method: "GET",
|
|
562
522
|
headers: new Headers({
|
|
563
523
|
"Accept": "application/json",
|
|
@@ -566,16 +526,14 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
566
526
|
"Signature-Input": validSignatureInput,
|
|
567
527
|
"Signature": validSignature
|
|
568
528
|
})
|
|
569
|
-
})
|
|
570
|
-
const expiredResult = await verifyRequest(expiredRequest, {
|
|
529
|
+
}), {
|
|
571
530
|
contextLoader: mockDocumentLoader,
|
|
572
531
|
documentLoader: mockDocumentLoader,
|
|
573
532
|
spec: "rfc9421",
|
|
574
533
|
currentTime: Temporal.Instant.from(`${(/* @__PURE__ */ new Date((currentTimestamp + 2592e3) * 1e3)).toISOString()}`),
|
|
575
534
|
timeWindow: { hours: 1 }
|
|
576
|
-
});
|
|
577
|
-
assertEquals(
|
|
578
|
-
const futureRequest = new Request("https://example.com/api/resource", {
|
|
535
|
+
}), null, "Should fail verification when signature timestamp is too old");
|
|
536
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
579
537
|
method: "GET",
|
|
580
538
|
headers: new Headers({
|
|
581
539
|
"Accept": "application/json",
|
|
@@ -584,16 +542,14 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
584
542
|
"Signature-Input": validSignatureInput,
|
|
585
543
|
"Signature": validSignature
|
|
586
544
|
})
|
|
587
|
-
})
|
|
588
|
-
const futureResult = await verifyRequest(futureRequest, {
|
|
545
|
+
}), {
|
|
589
546
|
contextLoader: mockDocumentLoader,
|
|
590
547
|
documentLoader: mockDocumentLoader,
|
|
591
548
|
spec: "rfc9421",
|
|
592
549
|
currentTime: Temporal.Instant.from(`${(/* @__PURE__ */ new Date((currentTimestamp - 2592e3) * 1e3)).toISOString()}`),
|
|
593
550
|
timeWindow: { hours: 1 }
|
|
594
|
-
});
|
|
595
|
-
assertEquals(
|
|
596
|
-
const timeCheckRequest = new Request("https://example.com/api/resource", {
|
|
551
|
+
}), null, "Should fail verification when signature timestamp is in the future");
|
|
552
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
597
553
|
method: "GET",
|
|
598
554
|
headers: new Headers({
|
|
599
555
|
"Accept": "application/json",
|
|
@@ -602,16 +558,14 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
602
558
|
"Signature-Input": validSignatureInput,
|
|
603
559
|
"Signature": validSignature
|
|
604
560
|
})
|
|
605
|
-
})
|
|
606
|
-
const timeDisabledResult = await verifyRequest(timeCheckRequest, {
|
|
561
|
+
}), {
|
|
607
562
|
contextLoader: mockDocumentLoader,
|
|
608
563
|
documentLoader: mockDocumentLoader,
|
|
609
564
|
spec: "rfc9421",
|
|
610
565
|
currentTime: Temporal.Instant.from(`${(/* @__PURE__ */ new Date((currentTimestamp + 31536e3) * 1e3)).toISOString()}`),
|
|
611
566
|
timeWindow: false
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
const postRequest = new Request("https://example.com/api/resource", {
|
|
567
|
+
}), rsaPublicKey2, "Should verify signature when time checking is disabled");
|
|
568
|
+
const freshSignedPostRequest = await signRequest(new Request("https://example.com/api/resource", {
|
|
615
569
|
method: "POST",
|
|
616
570
|
body: "Test content for signature verification",
|
|
617
571
|
headers: {
|
|
@@ -620,15 +574,14 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
620
574
|
"Host": "example.com",
|
|
621
575
|
"Date": "Tue, 05 Mar 2024 07:49:44 GMT"
|
|
622
576
|
}
|
|
623
|
-
})
|
|
624
|
-
const freshSignedPostRequest = await signRequest(postRequest, rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
577
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
625
578
|
spec: "rfc9421",
|
|
626
579
|
currentTime
|
|
627
580
|
});
|
|
628
581
|
const postSignatureInput = freshSignedPostRequest.headers.get("Signature-Input") || "";
|
|
629
582
|
const postSignature = freshSignedPostRequest.headers.get("Signature") || "";
|
|
630
583
|
const postContentDigest = freshSignedPostRequest.headers.get("Content-Digest") || "";
|
|
631
|
-
|
|
584
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
632
585
|
method: "POST",
|
|
633
586
|
body: "This content won't match the digest",
|
|
634
587
|
headers: new Headers({
|
|
@@ -639,22 +592,19 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
639
592
|
"Signature": postSignature,
|
|
640
593
|
"Content-Digest": postContentDigest
|
|
641
594
|
})
|
|
642
|
-
})
|
|
643
|
-
const tamperDigestResult = await verifyRequest(tamperDigestRequest, {
|
|
595
|
+
}), {
|
|
644
596
|
contextLoader: mockDocumentLoader,
|
|
645
597
|
documentLoader: mockDocumentLoader,
|
|
646
598
|
spec: "rfc9421",
|
|
647
599
|
currentTime: Temporal.Instant.from(`${(/* @__PURE__ */ new Date(currentTimestamp * 1e3)).toISOString()}`)
|
|
648
|
-
});
|
|
649
|
-
assertEquals(tamperDigestResult, null, "Should fail verification with invalid Content-Digest");
|
|
600
|
+
}), null, "Should fail verification with invalid Content-Digest");
|
|
650
601
|
const testRequest = new Request("https://example.com/", { headers: new Headers({
|
|
651
602
|
"Date": "Tue, 05 Mar 2024 07:49:44 GMT",
|
|
652
603
|
"Host": "example.com",
|
|
653
604
|
"Signature-Input": `sig1=("@method" "@target-uri" "host" "date");keyid="https://example.com/key";alg="rsa-v1_5-sha256";created=1709626184`,
|
|
654
605
|
"Signature": `sig1=:YXNkZmprc2RmaGprc2RoZmprc2hkZmtqaHNkZg==:`
|
|
655
606
|
}) });
|
|
656
|
-
const
|
|
657
|
-
const parsedInput = parseRfc9421SignatureInput(signatureInput);
|
|
607
|
+
const parsedInput = parseRfc9421SignatureInput(testRequest.headers.get("Signature-Input") || "");
|
|
658
608
|
assertExists(parsedInput.sig1);
|
|
659
609
|
assertEquals(parsedInput.sig1.keyId, "https://example.com/key");
|
|
660
610
|
assertEquals(parsedInput.sig1.alg, "rsa-v1_5-sha256");
|
|
@@ -677,12 +627,10 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
677
627
|
params: {}
|
|
678
628
|
}
|
|
679
629
|
]);
|
|
680
|
-
const
|
|
681
|
-
const parsedSig = parseRfc9421Signature(signature);
|
|
630
|
+
const parsedSig = parseRfc9421Signature(testRequest.headers.get("Signature") || "");
|
|
682
631
|
assertExists(parsedSig.sig1);
|
|
683
632
|
assert(new TextDecoder().decode(parsedSig.sig1).length > 0, "Signature base64 should decode to non-empty string");
|
|
684
|
-
const
|
|
685
|
-
const complexParsedInput = parseRfc9421SignatureInput(complexSignatureInput);
|
|
633
|
+
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");
|
|
686
634
|
assertExists(complexParsedInput.sig1);
|
|
687
635
|
assertEquals(complexParsedInput.sig1.keyId, "https://example.com/key with spaces");
|
|
688
636
|
assertEquals(complexParsedInput.sig1.alg, "rsa-v1_5-sha256");
|
|
@@ -701,16 +649,13 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
701
649
|
assertEquals(multiParsedInput.sig2.alg, "rsa-pss-sha512");
|
|
702
650
|
const multiParsedSig = parseRfc9421Signature(multiSigRequest.headers.get("Signature") || "");
|
|
703
651
|
assertEquals(Object.keys(multiParsedSig).length, 2, "Should parse multiple signature values");
|
|
704
|
-
const
|
|
705
|
-
const parsedInvalidInput = parseRfc9421SignatureInput(invalidInputFormat);
|
|
652
|
+
const parsedInvalidInput = parseRfc9421SignatureInput("this is not a valid signature-input format");
|
|
706
653
|
assertEquals(Object.keys(parsedInvalidInput).length, 0, "Should handle invalid Signature-Input format");
|
|
707
|
-
const
|
|
708
|
-
const parsedInvalidSig = parseRfc9421Signature(invalidSigFormat);
|
|
654
|
+
const parsedInvalidSig = parseRfc9421Signature("this is not a valid signature format");
|
|
709
655
|
assertEquals(Object.keys(parsedInvalidSig).length, 0, "Should handle invalid Signature format");
|
|
710
|
-
const
|
|
711
|
-
const parsedInvalidBase64 = parseRfc9421Signature(invalidBase64Sig);
|
|
656
|
+
const parsedInvalidBase64 = parseRfc9421Signature("sig1=:!@#$%%^&*():");
|
|
712
657
|
assertEquals(Object.keys(parsedInvalidBase64).length, 0, "Should handle invalid base64 in signature");
|
|
713
|
-
|
|
658
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
714
659
|
method: "GET",
|
|
715
660
|
headers: new Headers({
|
|
716
661
|
"Accept": "application/json",
|
|
@@ -719,14 +664,12 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
719
664
|
"Signature-Input": `${validSignatureInput},sig2=("@method" "@target-uri" "host" "date");keyid="https://example.com/invalid-key";alg="rsa-v1_5-sha256";created=${currentTimestamp}`,
|
|
720
665
|
"Signature": `${validSignature},sig2=:AAAAAA==:`
|
|
721
666
|
})
|
|
722
|
-
})
|
|
723
|
-
const mixedResult = await verifyRequest(mixedRequest, {
|
|
667
|
+
}), {
|
|
724
668
|
contextLoader: mockDocumentLoader,
|
|
725
669
|
documentLoader: mockDocumentLoader,
|
|
726
670
|
spec: "rfc9421",
|
|
727
671
|
currentTime: Temporal.Instant.from(`${(/* @__PURE__ */ new Date(currentTimestamp * 1e3)).toISOString()}`)
|
|
728
|
-
});
|
|
729
|
-
assertEquals(mixedResult, rsaPublicKey2, "Should verify when at least one signature is valid");
|
|
672
|
+
}), rsaPublicKey2, "Should verify when at least one signature is valid");
|
|
730
673
|
});
|
|
731
674
|
test("verifyRequest() [rfc9421] test vector from Mastodon", async () => {
|
|
732
675
|
const signedRequest = new Request("https://www.example.com/activitypub/success", {
|
|
@@ -789,14 +732,13 @@ test("doubleKnock() function with successful first attempt", async () => {
|
|
|
789
732
|
const logFunction = (req) => {
|
|
790
733
|
loggedRequest = req;
|
|
791
734
|
};
|
|
792
|
-
|
|
735
|
+
assertEquals((await doubleKnock(request, {
|
|
793
736
|
keyId: rsaPublicKey2.id,
|
|
794
737
|
privateKey: rsaPrivateKey2
|
|
795
738
|
}, {
|
|
796
739
|
specDeterminer,
|
|
797
740
|
log: logFunction
|
|
798
|
-
});
|
|
799
|
-
assertEquals(response.status, 202, "Response status should be 202 Accepted");
|
|
741
|
+
})).status, 202, "Response status should be 202 Accepted");
|
|
800
742
|
assertEquals(requestCount, 1, "Only one request should have been made");
|
|
801
743
|
assertEquals(firstRequestSpec, "rfc9421", "First attempt should use RFC 9421");
|
|
802
744
|
assertEquals(specDeterminer.usedSpec, "rfc9421", "Spec should be remembered");
|
|
@@ -837,11 +779,10 @@ test("doubleKnock() function with fallback to draft-cavage", async () => {
|
|
|
837
779
|
this.rememberedSpec = spec;
|
|
838
780
|
}
|
|
839
781
|
};
|
|
840
|
-
|
|
782
|
+
assertEquals((await doubleKnock(request, {
|
|
841
783
|
keyId: rsaPublicKey2.id,
|
|
842
784
|
privateKey: rsaPrivateKey2
|
|
843
|
-
}, { specDeterminer });
|
|
844
|
-
assertEquals(response.status, 202, "Response status should be 202 Accepted");
|
|
785
|
+
}, { specDeterminer })).status, 202, "Response status should be 202 Accepted");
|
|
845
786
|
assertEquals(requestCount, 2, "Two requests should have been made");
|
|
846
787
|
assertEquals(firstSpec, "rfc9421", "First attempt should use RFC 9421");
|
|
847
788
|
assertEquals(secondSpec, "draft-cavage-http-signatures-12", "Second attempt should use draft-cavage");
|
|
@@ -863,16 +804,14 @@ test("doubleKnock() function with redirect handling", async () => {
|
|
|
863
804
|
responseCodes.push(202);
|
|
864
805
|
return new Response("", { status: 202 });
|
|
865
806
|
});
|
|
866
|
-
|
|
807
|
+
assertEquals((await doubleKnock(new Request("https://example.com/redirect-endpoint", {
|
|
867
808
|
method: "POST",
|
|
868
809
|
body: "Test message that will be redirected",
|
|
869
810
|
headers: { "Content-Type": "text/plain" }
|
|
870
|
-
})
|
|
871
|
-
const response = await doubleKnock(request, {
|
|
811
|
+
}), {
|
|
872
812
|
keyId: rsaPublicKey2.id,
|
|
873
813
|
privateKey: rsaPrivateKey2
|
|
874
|
-
});
|
|
875
|
-
assertEquals(response.status, 202, "Final response status should be 202 Accepted");
|
|
814
|
+
})).status, 202, "Final response status should be 202 Accepted");
|
|
876
815
|
assertEquals(requestedUrls.length, 2, "Two URLs should have been requested");
|
|
877
816
|
assertEquals(requestedUrls[0], "https://example.com/redirect-endpoint", "First request should be to redirect-endpoint");
|
|
878
817
|
assertEquals(requestedUrls[1], "https://example.com/final-endpoint", "Second request should be to final-endpoint");
|
|
@@ -891,16 +830,14 @@ test("doubleKnock() function with both specs rejected", async () => {
|
|
|
891
830
|
else attempts.push("unknown");
|
|
892
831
|
return new Response("Unauthorized", { status: 401 });
|
|
893
832
|
});
|
|
894
|
-
|
|
833
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-rejects-all", {
|
|
895
834
|
method: "POST",
|
|
896
835
|
body: "Test message that will be rejected regardless of signature format",
|
|
897
836
|
headers: { "Content-Type": "text/plain" }
|
|
898
|
-
})
|
|
899
|
-
const response = await doubleKnock(request, {
|
|
837
|
+
}), {
|
|
900
838
|
keyId: rsaPublicKey2.id,
|
|
901
839
|
privateKey: rsaPrivateKey2
|
|
902
|
-
});
|
|
903
|
-
assertEquals(response.status, 401, "Final response status should be 401 Unauthorized");
|
|
840
|
+
})).status, 401, "Final response status should be 401 Unauthorized");
|
|
904
841
|
assertEquals(requestCount, 2, "Two requests should have been made");
|
|
905
842
|
assertEquals(attempts.length, 2, "Two signature attempts should have been made");
|
|
906
843
|
assertEquals(attempts[0], "rfc9421", "First attempt should use RFC 9421");
|
|
@@ -924,16 +861,14 @@ test("doubleKnock() function with specDeterminer choosing draft-cavage first", a
|
|
|
924
861
|
},
|
|
925
862
|
rememberSpec(_origin, _spec) {}
|
|
926
863
|
};
|
|
927
|
-
|
|
864
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-accepts-any", {
|
|
928
865
|
method: "POST",
|
|
929
866
|
body: "Test message with draft-cavage preference",
|
|
930
867
|
headers: { "Content-Type": "text/plain" }
|
|
931
|
-
})
|
|
932
|
-
const response = await doubleKnock(request, {
|
|
868
|
+
}), {
|
|
933
869
|
keyId: rsaPublicKey2.id,
|
|
934
870
|
privateKey: rsaPrivateKey2
|
|
935
|
-
}, { specDeterminer });
|
|
936
|
-
assertEquals(response.status, 202, "Response status should be 202 Accepted");
|
|
871
|
+
}, { specDeterminer })).status, 202, "Response status should be 202 Accepted");
|
|
937
872
|
assertEquals(requestCount, 1, "Only one request should have been made");
|
|
938
873
|
assertEquals(firstSpec, "draft-cavage", "First attempt should use draft-cavage");
|
|
939
874
|
esm_default.hardReset();
|
|
@@ -1044,25 +979,21 @@ test("doubleKnock() async specDeterminer test", async () => {
|
|
|
1044
979
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1045
980
|
}
|
|
1046
981
|
};
|
|
1047
|
-
|
|
982
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-async-determiner", {
|
|
1048
983
|
method: "POST",
|
|
1049
984
|
body: "Test message with async spec determiner",
|
|
1050
985
|
headers: { "Content-Type": "text/plain" }
|
|
1051
|
-
})
|
|
1052
|
-
const response = await doubleKnock(request, {
|
|
986
|
+
}), {
|
|
1053
987
|
keyId: rsaPublicKey2.id,
|
|
1054
988
|
privateKey: rsaPrivateKey2
|
|
1055
|
-
}, { specDeterminer });
|
|
1056
|
-
assertEquals(response.status, 202, "Response status should be 202 Accepted");
|
|
989
|
+
}, { specDeterminer })).status, 202, "Response status should be 202 Accepted");
|
|
1057
990
|
assertEquals(requestCount, 1, "Only one request should have been made");
|
|
1058
991
|
assertEquals(specUsed, "draft-cavage-http-signatures-12", "Should use spec from async determiner");
|
|
1059
992
|
esm_default.hardReset();
|
|
1060
993
|
});
|
|
1061
994
|
test("timingSafeEqual()", async (t) => {
|
|
1062
995
|
await t.step("should return true for equal empty arrays", () => {
|
|
1063
|
-
|
|
1064
|
-
const b = new Uint8Array([]);
|
|
1065
|
-
assert(timingSafeEqual(a, b));
|
|
996
|
+
assert(timingSafeEqual(new Uint8Array([]), new Uint8Array([])));
|
|
1066
997
|
});
|
|
1067
998
|
await t.step("should return true for equal non-empty arrays", async (t2) => {
|
|
1068
999
|
const testCases = [
|
|
@@ -1129,7 +1060,7 @@ test("timingSafeEqual()", async (t) => {
|
|
|
1129
1060
|
assert(timingSafeEqual(arr, arr), "Array should be equal to itself by reference");
|
|
1130
1061
|
});
|
|
1131
1062
|
await t.step("should return false for arrays with same length but different content", async (t2) => {
|
|
1132
|
-
const
|
|
1063
|
+
for (const tc of [
|
|
1133
1064
|
{
|
|
1134
1065
|
a: [
|
|
1135
1066
|
1,
|
|
@@ -1187,13 +1118,12 @@ test("timingSafeEqual()", async (t) => {
|
|
|
1187
1118
|
],
|
|
1188
1119
|
name: "middle byte differs with edge values"
|
|
1189
1120
|
}
|
|
1190
|
-
]
|
|
1191
|
-
for (const tc of testCases) await t2.step(tc.name, () => {
|
|
1121
|
+
]) await t2.step(tc.name, () => {
|
|
1192
1122
|
assertFalse(timingSafeEqual(new Uint8Array(tc.a), new Uint8Array(tc.b)));
|
|
1193
1123
|
});
|
|
1194
1124
|
});
|
|
1195
1125
|
await t.step("should return false for arrays with different lengths", async (t2) => {
|
|
1196
|
-
const
|
|
1126
|
+
for (const tc of [
|
|
1197
1127
|
{
|
|
1198
1128
|
a: [
|
|
1199
1129
|
1,
|
|
@@ -1230,13 +1160,12 @@ test("timingSafeEqual()", async (t) => {
|
|
|
1230
1160
|
b: [],
|
|
1231
1161
|
name: "a non-empty, b empty"
|
|
1232
1162
|
}
|
|
1233
|
-
]
|
|
1234
|
-
for (const tc of testCases) await t2.step(tc.name, () => {
|
|
1163
|
+
]) await t2.step(tc.name, () => {
|
|
1235
1164
|
assertFalse(timingSafeEqual(new Uint8Array(tc.a), new Uint8Array(tc.b)));
|
|
1236
1165
|
});
|
|
1237
1166
|
});
|
|
1238
1167
|
await t.step("should return false where content matches up to shorter length", async (t2) => {
|
|
1239
|
-
const
|
|
1168
|
+
for (const tc of [
|
|
1240
1169
|
{
|
|
1241
1170
|
a: [1, 2],
|
|
1242
1171
|
b: [
|
|
@@ -1265,21 +1194,16 @@ test("timingSafeEqual()", async (t) => {
|
|
|
1265
1194
|
b: [0],
|
|
1266
1195
|
name: "two zeros vs single zero"
|
|
1267
1196
|
}
|
|
1268
|
-
]
|
|
1269
|
-
for (const tc of testCases) await t2.step(tc.name, () => {
|
|
1197
|
+
]) await t2.step(tc.name, () => {
|
|
1270
1198
|
assertFalse(timingSafeEqual(new Uint8Array(tc.a), new Uint8Array(tc.b)));
|
|
1271
1199
|
});
|
|
1272
1200
|
});
|
|
1273
1201
|
await t.step("should correctly handle comparisons involving padding bytes", async (t2) => {
|
|
1274
1202
|
await t2.step("a=[1], b=[1,0] (b longer with trailing zero)", () => {
|
|
1275
|
-
|
|
1276
|
-
const b1 = new Uint8Array([1, 0]);
|
|
1277
|
-
assertFalse(timingSafeEqual(a1, b1));
|
|
1203
|
+
assertFalse(timingSafeEqual(new Uint8Array([1]), new Uint8Array([1, 0])));
|
|
1278
1204
|
});
|
|
1279
1205
|
await t2.step("a=[1,0], b=[1] (a longer with trailing zero)", () => {
|
|
1280
|
-
|
|
1281
|
-
const b2 = new Uint8Array([1]);
|
|
1282
|
-
assertFalse(timingSafeEqual(a2, b2));
|
|
1206
|
+
assertFalse(timingSafeEqual(new Uint8Array([1, 0]), new Uint8Array([1])));
|
|
1283
1207
|
});
|
|
1284
1208
|
});
|
|
1285
1209
|
});
|
|
@@ -1299,20 +1223,18 @@ test("signRequest() [rfc9421] error handling for invalid signature base creation
|
|
|
1299
1223
|
assertExists(signedRequest.headers.get("Signature"));
|
|
1300
1224
|
});
|
|
1301
1225
|
test("verifyRequest() [rfc9421] error handling for invalid signature base creation", async () => {
|
|
1302
|
-
|
|
1226
|
+
assertEquals(await verifyRequest(new Request("https://example.com/test", {
|
|
1303
1227
|
method: "GET",
|
|
1304
1228
|
headers: {
|
|
1305
1229
|
"Accept": "application/json",
|
|
1306
1230
|
"Signature-Input": "sig1=(\"@unsupported\");alg=\"rsa-pss-sha256\";keyid=\"https://example.com/key2\";created=1234567890",
|
|
1307
1231
|
"Signature": "sig1=:invalid_signature_data:"
|
|
1308
1232
|
}
|
|
1309
|
-
})
|
|
1310
|
-
const result = await verifyRequest(request, {
|
|
1233
|
+
}), {
|
|
1311
1234
|
spec: "rfc9421",
|
|
1312
1235
|
documentLoader: mockDocumentLoader,
|
|
1313
1236
|
contextLoader: mockDocumentLoader
|
|
1314
|
-
});
|
|
1315
|
-
assertEquals(result, null, "Verification should fail gracefully for malformed signature inputs");
|
|
1237
|
+
}), null, "Verification should fail gracefully for malformed signature inputs");
|
|
1316
1238
|
});
|
|
1317
1239
|
test("doubleKnock() regression test for TypeError: unusable bug #294", async () => {
|
|
1318
1240
|
esm_default.spyGlobal();
|
|
@@ -1326,16 +1248,14 @@ test("doubleKnock() regression test for TypeError: unusable bug #294", async ()
|
|
|
1326
1248
|
esm_default.post("https://example.com/final-destination", () => {
|
|
1327
1249
|
return new Response("Success", { status: 200 });
|
|
1328
1250
|
});
|
|
1329
|
-
|
|
1251
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-retry-redirect", {
|
|
1330
1252
|
method: "POST",
|
|
1331
1253
|
body: "Test activity content",
|
|
1332
1254
|
headers: { "Content-Type": "application/activity+json" }
|
|
1333
|
-
})
|
|
1334
|
-
const response = await doubleKnock(request, {
|
|
1255
|
+
}), {
|
|
1335
1256
|
keyId: rsaPublicKey2.id,
|
|
1336
1257
|
privateKey: rsaPrivateKey2
|
|
1337
|
-
});
|
|
1338
|
-
assertEquals(response.status, 200);
|
|
1258
|
+
})).status, 200);
|
|
1339
1259
|
assertEquals(requestCount, 2, "Should make 2 requests before redirect");
|
|
1340
1260
|
esm_default.hardReset();
|
|
1341
1261
|
});
|
|
@@ -1350,16 +1270,14 @@ test("doubleKnock() regression test for redirect handling bug", async () => {
|
|
|
1350
1270
|
esm_default.post("https://example.com/final-destination", () => {
|
|
1351
1271
|
return new Response("Success", { status: 200 });
|
|
1352
1272
|
});
|
|
1353
|
-
|
|
1273
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-retry-redirect", {
|
|
1354
1274
|
method: "POST",
|
|
1355
1275
|
body: "Test activity content",
|
|
1356
1276
|
headers: { "Content-Type": "application/activity+json" }
|
|
1357
|
-
})
|
|
1358
|
-
const response = await doubleKnock(request, {
|
|
1277
|
+
}), {
|
|
1359
1278
|
keyId: rsaPublicKey2.id,
|
|
1360
1279
|
privateKey: rsaPrivateKey2
|
|
1361
|
-
});
|
|
1362
|
-
assertEquals(response.status, 200);
|
|
1280
|
+
})).status, 200);
|
|
1363
1281
|
esm_default.hardReset();
|
|
1364
1282
|
});
|
|
1365
1283
|
test("signRequest() and verifyRequest() cancellation", {
|
|
@@ -1405,22 +1323,19 @@ test("signRequest() and verifyRequest() cancellation", {
|
|
|
1405
1323
|
esm_default.hardReset();
|
|
1406
1324
|
});
|
|
1407
1325
|
test("signRequest() with custom label", async () => {
|
|
1408
|
-
const
|
|
1326
|
+
const signed = await signRequest(new Request("https://example.com/api", {
|
|
1409
1327
|
method: "POST",
|
|
1410
1328
|
body: "test",
|
|
1411
1329
|
headers: { "Content-Type": "text/plain" }
|
|
1412
|
-
})
|
|
1413
|
-
const signed = await signRequest(request, rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
1330
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
1414
1331
|
spec: "rfc9421",
|
|
1415
1332
|
rfc9421: { label: "mysig" }
|
|
1416
1333
|
});
|
|
1417
|
-
|
|
1418
|
-
assertStringIncludes(
|
|
1419
|
-
const sig = signed.headers.get("Signature");
|
|
1420
|
-
assertStringIncludes(sig, "mysig=");
|
|
1334
|
+
assertStringIncludes(signed.headers.get("Signature-Input"), "mysig=");
|
|
1335
|
+
assertStringIncludes(signed.headers.get("Signature"), "mysig=");
|
|
1421
1336
|
});
|
|
1422
1337
|
test("signRequest() with custom components", async () => {
|
|
1423
|
-
const
|
|
1338
|
+
const sigInput = (await signRequest(new Request("https://example.com/api", {
|
|
1424
1339
|
method: "POST",
|
|
1425
1340
|
body: "test",
|
|
1426
1341
|
headers: {
|
|
@@ -1428,8 +1343,7 @@ test("signRequest() with custom components", async () => {
|
|
|
1428
1343
|
"Host": "example.com",
|
|
1429
1344
|
"Date": "Tue, 05 Mar 2024 07:49:44 GMT"
|
|
1430
1345
|
}
|
|
1431
|
-
})
|
|
1432
|
-
const signed = await signRequest(request, rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
1346
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
1433
1347
|
spec: "rfc9421",
|
|
1434
1348
|
rfc9421: { components: [
|
|
1435
1349
|
{
|
|
@@ -1445,29 +1359,26 @@ test("signRequest() with custom components", async () => {
|
|
|
1445
1359
|
params: {}
|
|
1446
1360
|
}
|
|
1447
1361
|
] }
|
|
1448
|
-
});
|
|
1449
|
-
const sigInput = signed.headers.get("Signature-Input");
|
|
1362
|
+
})).headers.get("Signature-Input");
|
|
1450
1363
|
assertStringIncludes(sigInput, "\"@method\"");
|
|
1451
1364
|
assertStringIncludes(sigInput, "\"@target-uri\"");
|
|
1452
1365
|
assertStringIncludes(sigInput, "\"@authority\"");
|
|
1453
1366
|
assertStringIncludes(sigInput, "\"content-digest\"");
|
|
1454
1367
|
});
|
|
1455
1368
|
test("signRequest() with nonce and tag", async () => {
|
|
1456
|
-
const
|
|
1369
|
+
const sigInput = (await signRequest(new Request("https://example.com/api", {
|
|
1457
1370
|
method: "GET",
|
|
1458
1371
|
headers: {
|
|
1459
1372
|
"Host": "example.com",
|
|
1460
1373
|
"Date": "Tue, 05 Mar 2024 07:49:44 GMT"
|
|
1461
1374
|
}
|
|
1462
|
-
})
|
|
1463
|
-
const signed = await signRequest(request, rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
1375
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
1464
1376
|
spec: "rfc9421",
|
|
1465
1377
|
rfc9421: {
|
|
1466
1378
|
nonce: "test-nonce-123",
|
|
1467
1379
|
tag: "app-v1"
|
|
1468
1380
|
}
|
|
1469
|
-
});
|
|
1470
|
-
const sigInput = signed.headers.get("Signature-Input");
|
|
1381
|
+
})).headers.get("Signature-Input");
|
|
1471
1382
|
assertStringIncludes(sigInput, "nonce=\"test-nonce-123\"");
|
|
1472
1383
|
assertStringIncludes(sigInput, "tag=\"app-v1\"");
|
|
1473
1384
|
});
|
|
@@ -1477,26 +1388,22 @@ test("formatRfc9421SignatureParameters() escapes nonce and tag", () => {
|
|
|
1477
1388
|
keyId: new URL("https://example.com/key"),
|
|
1478
1389
|
created: 1709626184
|
|
1479
1390
|
};
|
|
1480
|
-
|
|
1391
|
+
assertStringIncludes(formatRfc9421SignatureParameters({
|
|
1481
1392
|
...commonParams,
|
|
1482
1393
|
nonce: "x\\y"
|
|
1483
|
-
});
|
|
1484
|
-
assertStringIncludes(
|
|
1485
|
-
const quoteNonce = formatRfc9421SignatureParameters({
|
|
1394
|
+
}), "nonce=\"x\\\\y\"");
|
|
1395
|
+
assertStringIncludes(formatRfc9421SignatureParameters({
|
|
1486
1396
|
...commonParams,
|
|
1487
1397
|
nonce: "a\"b"
|
|
1488
|
-
});
|
|
1489
|
-
assertStringIncludes(
|
|
1490
|
-
const slashTag = formatRfc9421SignatureParameters({
|
|
1398
|
+
}), "nonce=\"a\\\"b\"");
|
|
1399
|
+
assertStringIncludes(formatRfc9421SignatureParameters({
|
|
1491
1400
|
...commonParams,
|
|
1492
1401
|
tag: "x\\y"
|
|
1493
|
-
});
|
|
1494
|
-
assertStringIncludes(
|
|
1495
|
-
const quoteTag = formatRfc9421SignatureParameters({
|
|
1402
|
+
}), "tag=\"x\\\\y\"");
|
|
1403
|
+
assertStringIncludes(formatRfc9421SignatureParameters({
|
|
1496
1404
|
...commonParams,
|
|
1497
1405
|
tag: "a\"b"
|
|
1498
|
-
});
|
|
1499
|
-
assertStringIncludes(quoteTag, "tag=\"a\\\"b\"");
|
|
1406
|
+
}), "tag=\"a\\\"b\"");
|
|
1500
1407
|
const mixed = formatRfc9421SignatureParameters({
|
|
1501
1408
|
...commonParams,
|
|
1502
1409
|
nonce: "n\"o\\nce",
|
|
@@ -1506,12 +1413,11 @@ test("formatRfc9421SignatureParameters() escapes nonce and tag", () => {
|
|
|
1506
1413
|
assertStringIncludes(mixed, "tag=\"t\\\"ag\\\\value\"");
|
|
1507
1414
|
});
|
|
1508
1415
|
test("signRequest() [rfc9421] accumulates multiple signatures when called sequentially", async () => {
|
|
1509
|
-
const
|
|
1416
|
+
const twiceSigned = await signRequest(await signRequest(new Request("https://example.com/inbox", {
|
|
1510
1417
|
method: "POST",
|
|
1511
1418
|
body: "Hello",
|
|
1512
1419
|
headers: { "Content-Type": "text/plain" }
|
|
1513
|
-
})
|
|
1514
|
-
const onceSigned = await signRequest(request, rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
1420
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
1515
1421
|
spec: "rfc9421",
|
|
1516
1422
|
rfc9421: {
|
|
1517
1423
|
label: "sig1",
|
|
@@ -1523,8 +1429,7 @@ test("signRequest() [rfc9421] accumulates multiple signatures when called sequen
|
|
|
1523
1429
|
params: {}
|
|
1524
1430
|
}]
|
|
1525
1431
|
}
|
|
1526
|
-
})
|
|
1527
|
-
const twiceSigned = await signRequest(onceSigned, rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
1432
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
1528
1433
|
spec: "rfc9421",
|
|
1529
1434
|
rfc9421: {
|
|
1530
1435
|
label: "sig2",
|
|
@@ -1551,20 +1456,17 @@ test("doubleKnock(): Accept-Signature challenge retry succeeds", async () => {
|
|
|
1551
1456
|
status: 401,
|
|
1552
1457
|
headers: { "Accept-Signature": "sig1=(\"@method\" \"@target-uri\" \"@authority\" \"content-digest\");created;nonce=\"challenge-nonce-1\"" }
|
|
1553
1458
|
});
|
|
1554
|
-
|
|
1555
|
-
if (sigInput.includes("challenge-nonce-1")) return new Response("", { status: 202 });
|
|
1459
|
+
if ((req.headers.get("Signature-Input") ?? "").includes("challenge-nonce-1")) return new Response("", { status: 202 });
|
|
1556
1460
|
return new Response("Bad", { status: 400 });
|
|
1557
1461
|
});
|
|
1558
|
-
|
|
1462
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-challenge-ok", {
|
|
1559
1463
|
method: "POST",
|
|
1560
1464
|
body: "Test message",
|
|
1561
1465
|
headers: { "Content-Type": "text/plain" }
|
|
1562
|
-
})
|
|
1563
|
-
const response = await doubleKnock(request, {
|
|
1466
|
+
}), {
|
|
1564
1467
|
keyId: rsaPublicKey2.id,
|
|
1565
1468
|
privateKey: rsaPrivateKey2
|
|
1566
|
-
});
|
|
1567
|
-
assertEquals(response.status, 202);
|
|
1469
|
+
})).status, 202);
|
|
1568
1470
|
assertEquals(requestCount, 2);
|
|
1569
1471
|
esm_default.hardReset();
|
|
1570
1472
|
});
|
|
@@ -1581,16 +1483,14 @@ test("doubleKnock(): unfulfillable Accept-Signature falls to legacy fallback", a
|
|
|
1581
1483
|
if (req.headers.has("Signature") && !req.headers.has("Signature-Input")) return new Response("", { status: 202 });
|
|
1582
1484
|
return new Response("Bad", { status: 400 });
|
|
1583
1485
|
});
|
|
1584
|
-
|
|
1486
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-unfulfillable", {
|
|
1585
1487
|
method: "POST",
|
|
1586
1488
|
body: "Test message",
|
|
1587
1489
|
headers: { "Content-Type": "text/plain" }
|
|
1588
|
-
})
|
|
1589
|
-
const response = await doubleKnock(request, {
|
|
1490
|
+
}), {
|
|
1590
1491
|
keyId: rsaPublicKey2.id,
|
|
1591
1492
|
privateKey: rsaPrivateKey2
|
|
1592
|
-
});
|
|
1593
|
-
assertEquals(response.status, 202);
|
|
1493
|
+
})).status, 202);
|
|
1594
1494
|
assertEquals(requestCount, 2);
|
|
1595
1495
|
esm_default.hardReset();
|
|
1596
1496
|
});
|
|
@@ -1604,16 +1504,14 @@ test("doubleKnock(): no Accept-Signature falls to legacy fallback", async () =>
|
|
|
1604
1504
|
if (req.headers.has("Signature")) return new Response("", { status: 202 });
|
|
1605
1505
|
return new Response("Bad", { status: 400 });
|
|
1606
1506
|
});
|
|
1607
|
-
|
|
1507
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-no-challenge", {
|
|
1608
1508
|
method: "POST",
|
|
1609
1509
|
body: "Test message",
|
|
1610
1510
|
headers: { "Content-Type": "text/plain" }
|
|
1611
|
-
})
|
|
1612
|
-
const response = await doubleKnock(request, {
|
|
1511
|
+
}), {
|
|
1613
1512
|
keyId: rsaPublicKey2.id,
|
|
1614
1513
|
privateKey: rsaPrivateKey2
|
|
1615
|
-
});
|
|
1616
|
-
assertEquals(response.status, 202);
|
|
1514
|
+
})).status, 202);
|
|
1617
1515
|
assertEquals(requestCount, 2);
|
|
1618
1516
|
esm_default.hardReset();
|
|
1619
1517
|
});
|
|
@@ -1631,16 +1529,14 @@ test("doubleKnock(): challenge retry also fails → legacy fallback attempted",
|
|
|
1631
1529
|
if (req.headers.has("Signature") && !req.headers.has("Signature-Input")) return new Response("", { status: 202 });
|
|
1632
1530
|
return new Response("Bad", { status: 400 });
|
|
1633
1531
|
});
|
|
1634
|
-
|
|
1532
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-challenge-fails", {
|
|
1635
1533
|
method: "POST",
|
|
1636
1534
|
body: "Test message",
|
|
1637
1535
|
headers: { "Content-Type": "text/plain" }
|
|
1638
|
-
})
|
|
1639
|
-
const response = await doubleKnock(request, {
|
|
1536
|
+
}), {
|
|
1640
1537
|
keyId: rsaPublicKey2.id,
|
|
1641
1538
|
privateKey: rsaPrivateKey2
|
|
1642
|
-
});
|
|
1643
|
-
assertEquals(response.status, 202);
|
|
1539
|
+
})).status, 202);
|
|
1644
1540
|
assertEquals(requestCount, 3);
|
|
1645
1541
|
esm_default.hardReset();
|
|
1646
1542
|
});
|
|
@@ -1661,16 +1557,14 @@ test("doubleKnock(): challenge retry returns another challenge → not followed"
|
|
|
1661
1557
|
if (req.headers.has("Signature") && !req.headers.has("Signature-Input")) return new Response("", { status: 202 });
|
|
1662
1558
|
return new Response("Bad", { status: 400 });
|
|
1663
1559
|
});
|
|
1664
|
-
|
|
1560
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-challenge-loop", {
|
|
1665
1561
|
method: "POST",
|
|
1666
1562
|
body: "Test message",
|
|
1667
1563
|
headers: { "Content-Type": "text/plain" }
|
|
1668
|
-
})
|
|
1669
|
-
const response = await doubleKnock(request, {
|
|
1564
|
+
}), {
|
|
1670
1565
|
keyId: rsaPublicKey2.id,
|
|
1671
1566
|
privateKey: rsaPrivateKey2
|
|
1672
|
-
});
|
|
1673
|
-
assertEquals(response.status, 202);
|
|
1567
|
+
})).status, 202);
|
|
1674
1568
|
assertEquals(requestCount, 3);
|
|
1675
1569
|
esm_default.hardReset();
|
|
1676
1570
|
});
|
|
@@ -1687,16 +1581,14 @@ test("doubleKnock(): Accept-Signature with unsupported component falls to legacy
|
|
|
1687
1581
|
if (req.headers.has("Signature") && !req.headers.has("Signature-Input")) return new Response("", { status: 202 });
|
|
1688
1582
|
return new Response("Bad", { status: 400 });
|
|
1689
1583
|
});
|
|
1690
|
-
|
|
1584
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-bad-challenge", {
|
|
1691
1585
|
method: "POST",
|
|
1692
1586
|
body: "Test message",
|
|
1693
1587
|
headers: { "Content-Type": "text/plain" }
|
|
1694
|
-
})
|
|
1695
|
-
const response = await doubleKnock(request, {
|
|
1588
|
+
}), {
|
|
1696
1589
|
keyId: rsaPublicKey2.id,
|
|
1697
1590
|
privateKey: rsaPrivateKey2
|
|
1698
|
-
});
|
|
1699
|
-
assertEquals(response.status, 202);
|
|
1591
|
+
})).status, 202);
|
|
1700
1592
|
assertEquals(requestCount, 2);
|
|
1701
1593
|
esm_default.hardReset();
|
|
1702
1594
|
});
|
|
@@ -1713,16 +1605,14 @@ test("doubleKnock(): Accept-Signature with unsupported derived component falls t
|
|
|
1713
1605
|
if (req.headers.has("Signature") && !req.headers.has("Signature-Input")) return new Response("", { status: 202 });
|
|
1714
1606
|
return new Response("Bad", { status: 400 });
|
|
1715
1607
|
});
|
|
1716
|
-
|
|
1608
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-bad-derived", {
|
|
1717
1609
|
method: "POST",
|
|
1718
1610
|
body: "Test message",
|
|
1719
1611
|
headers: { "Content-Type": "text/plain" }
|
|
1720
|
-
})
|
|
1721
|
-
const response = await doubleKnock(request, {
|
|
1612
|
+
}), {
|
|
1722
1613
|
keyId: rsaPublicKey2.id,
|
|
1723
1614
|
privateKey: rsaPrivateKey2
|
|
1724
|
-
});
|
|
1725
|
-
assertEquals(response.status, 202);
|
|
1615
|
+
})).status, 202);
|
|
1726
1616
|
assertEquals(requestCount, 2);
|
|
1727
1617
|
esm_default.hardReset();
|
|
1728
1618
|
});
|
|
@@ -1739,16 +1629,14 @@ test("doubleKnock(): Accept-Signature with multiple entries where first throws f
|
|
|
1739
1629
|
if (req.headers.has("Signature-Input")) return new Response("", { status: 202 });
|
|
1740
1630
|
return new Response("Bad", { status: 400 });
|
|
1741
1631
|
});
|
|
1742
|
-
|
|
1632
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-multi-challenge", {
|
|
1743
1633
|
method: "POST",
|
|
1744
1634
|
body: "Test message",
|
|
1745
1635
|
headers: { "Content-Type": "text/plain" }
|
|
1746
|
-
})
|
|
1747
|
-
const response = await doubleKnock(request, {
|
|
1636
|
+
}), {
|
|
1748
1637
|
keyId: rsaPublicKey2.id,
|
|
1749
1638
|
privateKey: rsaPrivateKey2
|
|
1750
|
-
});
|
|
1751
|
-
assertEquals(response.status, 202);
|
|
1639
|
+
})).status, 202);
|
|
1752
1640
|
assertEquals(requestCount, 2);
|
|
1753
1641
|
esm_default.hardReset();
|
|
1754
1642
|
});
|
|
@@ -1767,18 +1655,16 @@ test("doubleKnock(): Accept-Signature with multiple compatible entries fulfills
|
|
|
1767
1655
|
if (sigInput.includes("sig1=") && sigInput.includes("sig2=") && sig.includes("sig1=") && sig.includes("sig2=")) return new Response("", { status: 202 });
|
|
1768
1656
|
return new Response("Missing signatures", { status: 400 });
|
|
1769
1657
|
});
|
|
1770
|
-
|
|
1658
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-multi-compat", {
|
|
1771
1659
|
method: "POST",
|
|
1772
1660
|
body: "Test message",
|
|
1773
1661
|
headers: { "Content-Type": "text/plain" }
|
|
1774
|
-
})
|
|
1775
|
-
const response = await doubleKnock(request, {
|
|
1662
|
+
}), {
|
|
1776
1663
|
keyId: rsaPublicKey2.id,
|
|
1777
1664
|
privateKey: rsaPrivateKey2
|
|
1778
|
-
});
|
|
1779
|
-
assertEquals(response.status, 202);
|
|
1665
|
+
})).status, 202);
|
|
1780
1666
|
assertEquals(requestCount, 2);
|
|
1781
1667
|
esm_default.hardReset();
|
|
1782
1668
|
});
|
|
1783
|
-
|
|
1784
|
-
|
|
1669
|
+
//#endregion
|
|
1670
|
+
export {};
|