@fedify/fedify 2.2.0-dev.610 → 2.2.0-dev.622
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -5
- 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-Bkqx5fo0.js → builder-DcSpny3g.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-D682wzlW.mjs +8 -0
- package/dist/{docloader-ORTT1bPi.js → docloader-CCWf4tNV.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-BDf1pBmE.cjs → http-BQccxQlj.cjs} +177 -302
- package/dist/{http-By9CCocC.js → http-CNsnyqrO.mjs} +23 -43
- package/dist/{http-BudnHZE2.d.cts → http-CrGuipxe.d.cts} +1 -6
- package/dist/{http-DLQMXj_1.js → http-DhwEMhtv.js} +53 -184
- package/dist/{http-Dax_FIBo.d.ts → http-aQzN9Ayi.d.ts} +1 -6
- package/dist/{inbox-CTJeEur6.js → inbox-DegXbbbS.mjs} +18 -26
- package/dist/{key-OaS_196P.js → key-vL60OvqM.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-COnBmjEC.js → kv-cache-CTj3iCix.js} +6 -13
- package/dist/{kv-cache-DG491NbS.cjs → kv-cache-DxdXJNbj.cjs} +27 -34
- package/dist/{kv-QzKcOQgP.js → kv-tL2TOE9X.mjs} +6 -10
- package/dist/{ld-5udwE1JY.js → ld-C3CO00YY.mjs} +17 -31
- package/dist/{middleware-3FSu2a2W.js → middleware-BmsVSOeS.js} +335 -382
- package/dist/middleware-C7shNcsp.cjs +4 -0
- package/dist/{middleware-CFuio6Y_.cjs → middleware-Cx0Ny6_7.cjs} +532 -587
- package/dist/middleware-DqVGYk56.mjs +5 -0
- package/dist/{middleware-DRWIaDqh.js → middleware-Du-vh7I_.mjs} +282 -317
- package/dist/{mod-em2Il1eD.d.cts → mod-Bp_CzKd4.d.cts} +2 -2
- package/dist/{mod-DCbh1JQ5.d.ts → mod-CLgIXe9w.d.ts} +3 -3
- package/dist/{mod-jfnweK2w.d.cts → mod-CMEbIaNh.d.cts} +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-DuRG_QYg.js → owner-DF320w6K.mjs} +11 -16
- package/dist/{proof-DG0_Hm4d.js → proof-BCWk5oas.js} +32 -58
- package/dist/{proof-DFffIwhK.js → proof-IyDwwmzL.mjs} +21 -33
- package/dist/{proof-WJjJMjvC.cjs → proof-WhNxSv_N.cjs} +133 -157
- 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-CJAMZwWu.js → send-CJQubr5t.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-CMcbjYDs.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-BzcyJjau.js +0 -12
- package/dist/middleware-HlNnBIDX.js +0 -27
- package/dist/middleware-i-8IYJwY.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-By9CCocC.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-vL60OvqM.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-CNsnyqrO.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();
|
|
@@ -1064,25 +999,21 @@ test("doubleKnock() async specDeterminer test", async () => {
|
|
|
1064
999
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1065
1000
|
}
|
|
1066
1001
|
};
|
|
1067
|
-
|
|
1002
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-async-determiner", {
|
|
1068
1003
|
method: "POST",
|
|
1069
1004
|
body: "Test message with async spec determiner",
|
|
1070
1005
|
headers: { "Content-Type": "text/plain" }
|
|
1071
|
-
})
|
|
1072
|
-
const response = await doubleKnock(request, {
|
|
1006
|
+
}), {
|
|
1073
1007
|
keyId: rsaPublicKey2.id,
|
|
1074
1008
|
privateKey: rsaPrivateKey2
|
|
1075
|
-
}, { specDeterminer });
|
|
1076
|
-
assertEquals(response.status, 202, "Response status should be 202 Accepted");
|
|
1009
|
+
}, { specDeterminer })).status, 202, "Response status should be 202 Accepted");
|
|
1077
1010
|
assertEquals(requestCount, 1, "Only one request should have been made");
|
|
1078
1011
|
assertEquals(specUsed, "draft-cavage-http-signatures-12", "Should use spec from async determiner");
|
|
1079
1012
|
esm_default.hardReset();
|
|
1080
1013
|
});
|
|
1081
1014
|
test("timingSafeEqual()", async (t) => {
|
|
1082
1015
|
await t.step("should return true for equal empty arrays", () => {
|
|
1083
|
-
|
|
1084
|
-
const b = new Uint8Array([]);
|
|
1085
|
-
assert(timingSafeEqual(a, b));
|
|
1016
|
+
assert(timingSafeEqual(new Uint8Array([]), new Uint8Array([])));
|
|
1086
1017
|
});
|
|
1087
1018
|
await t.step("should return true for equal non-empty arrays", async (t2) => {
|
|
1088
1019
|
const testCases = [
|
|
@@ -1149,7 +1080,7 @@ test("timingSafeEqual()", async (t) => {
|
|
|
1149
1080
|
assert(timingSafeEqual(arr, arr), "Array should be equal to itself by reference");
|
|
1150
1081
|
});
|
|
1151
1082
|
await t.step("should return false for arrays with same length but different content", async (t2) => {
|
|
1152
|
-
const
|
|
1083
|
+
for (const tc of [
|
|
1153
1084
|
{
|
|
1154
1085
|
a: [
|
|
1155
1086
|
1,
|
|
@@ -1207,13 +1138,12 @@ test("timingSafeEqual()", async (t) => {
|
|
|
1207
1138
|
],
|
|
1208
1139
|
name: "middle byte differs with edge values"
|
|
1209
1140
|
}
|
|
1210
|
-
]
|
|
1211
|
-
for (const tc of testCases) await t2.step(tc.name, () => {
|
|
1141
|
+
]) await t2.step(tc.name, () => {
|
|
1212
1142
|
assertFalse(timingSafeEqual(new Uint8Array(tc.a), new Uint8Array(tc.b)));
|
|
1213
1143
|
});
|
|
1214
1144
|
});
|
|
1215
1145
|
await t.step("should return false for arrays with different lengths", async (t2) => {
|
|
1216
|
-
const
|
|
1146
|
+
for (const tc of [
|
|
1217
1147
|
{
|
|
1218
1148
|
a: [
|
|
1219
1149
|
1,
|
|
@@ -1250,13 +1180,12 @@ test("timingSafeEqual()", async (t) => {
|
|
|
1250
1180
|
b: [],
|
|
1251
1181
|
name: "a non-empty, b empty"
|
|
1252
1182
|
}
|
|
1253
|
-
]
|
|
1254
|
-
for (const tc of testCases) await t2.step(tc.name, () => {
|
|
1183
|
+
]) await t2.step(tc.name, () => {
|
|
1255
1184
|
assertFalse(timingSafeEqual(new Uint8Array(tc.a), new Uint8Array(tc.b)));
|
|
1256
1185
|
});
|
|
1257
1186
|
});
|
|
1258
1187
|
await t.step("should return false where content matches up to shorter length", async (t2) => {
|
|
1259
|
-
const
|
|
1188
|
+
for (const tc of [
|
|
1260
1189
|
{
|
|
1261
1190
|
a: [1, 2],
|
|
1262
1191
|
b: [
|
|
@@ -1285,21 +1214,16 @@ test("timingSafeEqual()", async (t) => {
|
|
|
1285
1214
|
b: [0],
|
|
1286
1215
|
name: "two zeros vs single zero"
|
|
1287
1216
|
}
|
|
1288
|
-
]
|
|
1289
|
-
for (const tc of testCases) await t2.step(tc.name, () => {
|
|
1217
|
+
]) await t2.step(tc.name, () => {
|
|
1290
1218
|
assertFalse(timingSafeEqual(new Uint8Array(tc.a), new Uint8Array(tc.b)));
|
|
1291
1219
|
});
|
|
1292
1220
|
});
|
|
1293
1221
|
await t.step("should correctly handle comparisons involving padding bytes", async (t2) => {
|
|
1294
1222
|
await t2.step("a=[1], b=[1,0] (b longer with trailing zero)", () => {
|
|
1295
|
-
|
|
1296
|
-
const b1 = new Uint8Array([1, 0]);
|
|
1297
|
-
assertFalse(timingSafeEqual(a1, b1));
|
|
1223
|
+
assertFalse(timingSafeEqual(new Uint8Array([1]), new Uint8Array([1, 0])));
|
|
1298
1224
|
});
|
|
1299
1225
|
await t2.step("a=[1,0], b=[1] (a longer with trailing zero)", () => {
|
|
1300
|
-
|
|
1301
|
-
const b2 = new Uint8Array([1]);
|
|
1302
|
-
assertFalse(timingSafeEqual(a2, b2));
|
|
1226
|
+
assertFalse(timingSafeEqual(new Uint8Array([1, 0]), new Uint8Array([1])));
|
|
1303
1227
|
});
|
|
1304
1228
|
});
|
|
1305
1229
|
});
|
|
@@ -1319,20 +1243,18 @@ test("signRequest() [rfc9421] error handling for invalid signature base creation
|
|
|
1319
1243
|
assertExists(signedRequest.headers.get("Signature"));
|
|
1320
1244
|
});
|
|
1321
1245
|
test("verifyRequest() [rfc9421] error handling for invalid signature base creation", async () => {
|
|
1322
|
-
|
|
1246
|
+
assertEquals(await verifyRequest(new Request("https://example.com/test", {
|
|
1323
1247
|
method: "GET",
|
|
1324
1248
|
headers: {
|
|
1325
1249
|
"Accept": "application/json",
|
|
1326
1250
|
"Signature-Input": "sig1=(\"@unsupported\");alg=\"rsa-pss-sha256\";keyid=\"https://example.com/key2\";created=1234567890",
|
|
1327
1251
|
"Signature": "sig1=:invalid_signature_data:"
|
|
1328
1252
|
}
|
|
1329
|
-
})
|
|
1330
|
-
const result = await verifyRequest(request, {
|
|
1253
|
+
}), {
|
|
1331
1254
|
spec: "rfc9421",
|
|
1332
1255
|
documentLoader: mockDocumentLoader,
|
|
1333
1256
|
contextLoader: mockDocumentLoader
|
|
1334
|
-
});
|
|
1335
|
-
assertEquals(result, null, "Verification should fail gracefully for malformed signature inputs");
|
|
1257
|
+
}), null, "Verification should fail gracefully for malformed signature inputs");
|
|
1336
1258
|
});
|
|
1337
1259
|
test("doubleKnock() regression test for TypeError: unusable bug #294", async () => {
|
|
1338
1260
|
esm_default.spyGlobal();
|
|
@@ -1346,16 +1268,14 @@ test("doubleKnock() regression test for TypeError: unusable bug #294", async ()
|
|
|
1346
1268
|
esm_default.post("https://example.com/final-destination", () => {
|
|
1347
1269
|
return new Response("Success", { status: 200 });
|
|
1348
1270
|
});
|
|
1349
|
-
|
|
1271
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-retry-redirect", {
|
|
1350
1272
|
method: "POST",
|
|
1351
1273
|
body: "Test activity content",
|
|
1352
1274
|
headers: { "Content-Type": "application/activity+json" }
|
|
1353
|
-
})
|
|
1354
|
-
const response = await doubleKnock(request, {
|
|
1275
|
+
}), {
|
|
1355
1276
|
keyId: rsaPublicKey2.id,
|
|
1356
1277
|
privateKey: rsaPrivateKey2
|
|
1357
|
-
});
|
|
1358
|
-
assertEquals(response.status, 200);
|
|
1278
|
+
})).status, 200);
|
|
1359
1279
|
assertEquals(requestCount, 2, "Should make 2 requests before redirect");
|
|
1360
1280
|
esm_default.hardReset();
|
|
1361
1281
|
});
|
|
@@ -1370,16 +1290,14 @@ test("doubleKnock() regression test for redirect handling bug", async () => {
|
|
|
1370
1290
|
esm_default.post("https://example.com/final-destination", () => {
|
|
1371
1291
|
return new Response("Success", { status: 200 });
|
|
1372
1292
|
});
|
|
1373
|
-
|
|
1293
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-retry-redirect", {
|
|
1374
1294
|
method: "POST",
|
|
1375
1295
|
body: "Test activity content",
|
|
1376
1296
|
headers: { "Content-Type": "application/activity+json" }
|
|
1377
|
-
})
|
|
1378
|
-
const response = await doubleKnock(request, {
|
|
1297
|
+
}), {
|
|
1379
1298
|
keyId: rsaPublicKey2.id,
|
|
1380
1299
|
privateKey: rsaPrivateKey2
|
|
1381
|
-
});
|
|
1382
|
-
assertEquals(response.status, 200);
|
|
1300
|
+
})).status, 200);
|
|
1383
1301
|
esm_default.hardReset();
|
|
1384
1302
|
});
|
|
1385
1303
|
test("signRequest() and verifyRequest() cancellation", {
|
|
@@ -1425,22 +1343,19 @@ test("signRequest() and verifyRequest() cancellation", {
|
|
|
1425
1343
|
esm_default.hardReset();
|
|
1426
1344
|
});
|
|
1427
1345
|
test("signRequest() with custom label", async () => {
|
|
1428
|
-
const
|
|
1346
|
+
const signed = await signRequest(new Request("https://example.com/api", {
|
|
1429
1347
|
method: "POST",
|
|
1430
1348
|
body: "test",
|
|
1431
1349
|
headers: { "Content-Type": "text/plain" }
|
|
1432
|
-
})
|
|
1433
|
-
const signed = await signRequest(request, rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
1350
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
1434
1351
|
spec: "rfc9421",
|
|
1435
1352
|
rfc9421: { label: "mysig" }
|
|
1436
1353
|
});
|
|
1437
|
-
|
|
1438
|
-
assertStringIncludes(
|
|
1439
|
-
const sig = signed.headers.get("Signature");
|
|
1440
|
-
assertStringIncludes(sig, "mysig=");
|
|
1354
|
+
assertStringIncludes(signed.headers.get("Signature-Input"), "mysig=");
|
|
1355
|
+
assertStringIncludes(signed.headers.get("Signature"), "mysig=");
|
|
1441
1356
|
});
|
|
1442
1357
|
test("signRequest() with custom components", async () => {
|
|
1443
|
-
const
|
|
1358
|
+
const sigInput = (await signRequest(new Request("https://example.com/api", {
|
|
1444
1359
|
method: "POST",
|
|
1445
1360
|
body: "test",
|
|
1446
1361
|
headers: {
|
|
@@ -1448,8 +1363,7 @@ test("signRequest() with custom components", async () => {
|
|
|
1448
1363
|
"Host": "example.com",
|
|
1449
1364
|
"Date": "Tue, 05 Mar 2024 07:49:44 GMT"
|
|
1450
1365
|
}
|
|
1451
|
-
})
|
|
1452
|
-
const signed = await signRequest(request, rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
1366
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
1453
1367
|
spec: "rfc9421",
|
|
1454
1368
|
rfc9421: { components: [
|
|
1455
1369
|
{
|
|
@@ -1465,29 +1379,26 @@ test("signRequest() with custom components", async () => {
|
|
|
1465
1379
|
params: {}
|
|
1466
1380
|
}
|
|
1467
1381
|
] }
|
|
1468
|
-
});
|
|
1469
|
-
const sigInput = signed.headers.get("Signature-Input");
|
|
1382
|
+
})).headers.get("Signature-Input");
|
|
1470
1383
|
assertStringIncludes(sigInput, "\"@method\"");
|
|
1471
1384
|
assertStringIncludes(sigInput, "\"@target-uri\"");
|
|
1472
1385
|
assertStringIncludes(sigInput, "\"@authority\"");
|
|
1473
1386
|
assertStringIncludes(sigInput, "\"content-digest\"");
|
|
1474
1387
|
});
|
|
1475
1388
|
test("signRequest() with nonce and tag", async () => {
|
|
1476
|
-
const
|
|
1389
|
+
const sigInput = (await signRequest(new Request("https://example.com/api", {
|
|
1477
1390
|
method: "GET",
|
|
1478
1391
|
headers: {
|
|
1479
1392
|
"Host": "example.com",
|
|
1480
1393
|
"Date": "Tue, 05 Mar 2024 07:49:44 GMT"
|
|
1481
1394
|
}
|
|
1482
|
-
})
|
|
1483
|
-
const signed = await signRequest(request, rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
1395
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
1484
1396
|
spec: "rfc9421",
|
|
1485
1397
|
rfc9421: {
|
|
1486
1398
|
nonce: "test-nonce-123",
|
|
1487
1399
|
tag: "app-v1"
|
|
1488
1400
|
}
|
|
1489
|
-
});
|
|
1490
|
-
const sigInput = signed.headers.get("Signature-Input");
|
|
1401
|
+
})).headers.get("Signature-Input");
|
|
1491
1402
|
assertStringIncludes(sigInput, "nonce=\"test-nonce-123\"");
|
|
1492
1403
|
assertStringIncludes(sigInput, "tag=\"app-v1\"");
|
|
1493
1404
|
});
|
|
@@ -1497,26 +1408,22 @@ test("formatRfc9421SignatureParameters() escapes nonce and tag", () => {
|
|
|
1497
1408
|
keyId: new URL("https://example.com/key"),
|
|
1498
1409
|
created: 1709626184
|
|
1499
1410
|
};
|
|
1500
|
-
|
|
1411
|
+
assertStringIncludes(formatRfc9421SignatureParameters({
|
|
1501
1412
|
...commonParams,
|
|
1502
1413
|
nonce: "x\\y"
|
|
1503
|
-
});
|
|
1504
|
-
assertStringIncludes(
|
|
1505
|
-
const quoteNonce = formatRfc9421SignatureParameters({
|
|
1414
|
+
}), "nonce=\"x\\\\y\"");
|
|
1415
|
+
assertStringIncludes(formatRfc9421SignatureParameters({
|
|
1506
1416
|
...commonParams,
|
|
1507
1417
|
nonce: "a\"b"
|
|
1508
|
-
});
|
|
1509
|
-
assertStringIncludes(
|
|
1510
|
-
const slashTag = formatRfc9421SignatureParameters({
|
|
1418
|
+
}), "nonce=\"a\\\"b\"");
|
|
1419
|
+
assertStringIncludes(formatRfc9421SignatureParameters({
|
|
1511
1420
|
...commonParams,
|
|
1512
1421
|
tag: "x\\y"
|
|
1513
|
-
});
|
|
1514
|
-
assertStringIncludes(
|
|
1515
|
-
const quoteTag = formatRfc9421SignatureParameters({
|
|
1422
|
+
}), "tag=\"x\\\\y\"");
|
|
1423
|
+
assertStringIncludes(formatRfc9421SignatureParameters({
|
|
1516
1424
|
...commonParams,
|
|
1517
1425
|
tag: "a\"b"
|
|
1518
|
-
});
|
|
1519
|
-
assertStringIncludes(quoteTag, "tag=\"a\\\"b\"");
|
|
1426
|
+
}), "tag=\"a\\\"b\"");
|
|
1520
1427
|
const mixed = formatRfc9421SignatureParameters({
|
|
1521
1428
|
...commonParams,
|
|
1522
1429
|
nonce: "n\"o\\nce",
|
|
@@ -1526,12 +1433,11 @@ test("formatRfc9421SignatureParameters() escapes nonce and tag", () => {
|
|
|
1526
1433
|
assertStringIncludes(mixed, "tag=\"t\\\"ag\\\\value\"");
|
|
1527
1434
|
});
|
|
1528
1435
|
test("signRequest() [rfc9421] accumulates multiple signatures when called sequentially", async () => {
|
|
1529
|
-
const
|
|
1436
|
+
const twiceSigned = await signRequest(await signRequest(new Request("https://example.com/inbox", {
|
|
1530
1437
|
method: "POST",
|
|
1531
1438
|
body: "Hello",
|
|
1532
1439
|
headers: { "Content-Type": "text/plain" }
|
|
1533
|
-
})
|
|
1534
|
-
const onceSigned = await signRequest(request, rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
1440
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
1535
1441
|
spec: "rfc9421",
|
|
1536
1442
|
rfc9421: {
|
|
1537
1443
|
label: "sig1",
|
|
@@ -1543,8 +1449,7 @@ test("signRequest() [rfc9421] accumulates multiple signatures when called sequen
|
|
|
1543
1449
|
params: {}
|
|
1544
1450
|
}]
|
|
1545
1451
|
}
|
|
1546
|
-
})
|
|
1547
|
-
const twiceSigned = await signRequest(onceSigned, rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
1452
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
1548
1453
|
spec: "rfc9421",
|
|
1549
1454
|
rfc9421: {
|
|
1550
1455
|
label: "sig2",
|
|
@@ -1571,20 +1476,17 @@ test("doubleKnock(): Accept-Signature challenge retry succeeds", async () => {
|
|
|
1571
1476
|
status: 401,
|
|
1572
1477
|
headers: { "Accept-Signature": "sig1=(\"@method\" \"@target-uri\" \"@authority\" \"content-digest\");created;nonce=\"challenge-nonce-1\"" }
|
|
1573
1478
|
});
|
|
1574
|
-
|
|
1575
|
-
if (sigInput.includes("challenge-nonce-1")) return new Response("", { status: 202 });
|
|
1479
|
+
if ((req.headers.get("Signature-Input") ?? "").includes("challenge-nonce-1")) return new Response("", { status: 202 });
|
|
1576
1480
|
return new Response("Bad", { status: 400 });
|
|
1577
1481
|
});
|
|
1578
|
-
|
|
1482
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-challenge-ok", {
|
|
1579
1483
|
method: "POST",
|
|
1580
1484
|
body: "Test message",
|
|
1581
1485
|
headers: { "Content-Type": "text/plain" }
|
|
1582
|
-
})
|
|
1583
|
-
const response = await doubleKnock(request, {
|
|
1486
|
+
}), {
|
|
1584
1487
|
keyId: rsaPublicKey2.id,
|
|
1585
1488
|
privateKey: rsaPrivateKey2
|
|
1586
|
-
});
|
|
1587
|
-
assertEquals(response.status, 202);
|
|
1489
|
+
})).status, 202);
|
|
1588
1490
|
assertEquals(requestCount, 2);
|
|
1589
1491
|
esm_default.hardReset();
|
|
1590
1492
|
});
|
|
@@ -1601,16 +1503,14 @@ test("doubleKnock(): unfulfillable Accept-Signature falls to legacy fallback", a
|
|
|
1601
1503
|
if (req.headers.has("Signature") && !req.headers.has("Signature-Input")) return new Response("", { status: 202 });
|
|
1602
1504
|
return new Response("Bad", { status: 400 });
|
|
1603
1505
|
});
|
|
1604
|
-
|
|
1506
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-unfulfillable", {
|
|
1605
1507
|
method: "POST",
|
|
1606
1508
|
body: "Test message",
|
|
1607
1509
|
headers: { "Content-Type": "text/plain" }
|
|
1608
|
-
})
|
|
1609
|
-
const response = await doubleKnock(request, {
|
|
1510
|
+
}), {
|
|
1610
1511
|
keyId: rsaPublicKey2.id,
|
|
1611
1512
|
privateKey: rsaPrivateKey2
|
|
1612
|
-
});
|
|
1613
|
-
assertEquals(response.status, 202);
|
|
1513
|
+
})).status, 202);
|
|
1614
1514
|
assertEquals(requestCount, 2);
|
|
1615
1515
|
esm_default.hardReset();
|
|
1616
1516
|
});
|
|
@@ -1624,16 +1524,14 @@ test("doubleKnock(): no Accept-Signature falls to legacy fallback", async () =>
|
|
|
1624
1524
|
if (req.headers.has("Signature")) return new Response("", { status: 202 });
|
|
1625
1525
|
return new Response("Bad", { status: 400 });
|
|
1626
1526
|
});
|
|
1627
|
-
|
|
1527
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-no-challenge", {
|
|
1628
1528
|
method: "POST",
|
|
1629
1529
|
body: "Test message",
|
|
1630
1530
|
headers: { "Content-Type": "text/plain" }
|
|
1631
|
-
})
|
|
1632
|
-
const response = await doubleKnock(request, {
|
|
1531
|
+
}), {
|
|
1633
1532
|
keyId: rsaPublicKey2.id,
|
|
1634
1533
|
privateKey: rsaPrivateKey2
|
|
1635
|
-
});
|
|
1636
|
-
assertEquals(response.status, 202);
|
|
1534
|
+
})).status, 202);
|
|
1637
1535
|
assertEquals(requestCount, 2);
|
|
1638
1536
|
esm_default.hardReset();
|
|
1639
1537
|
});
|
|
@@ -1651,16 +1549,14 @@ test("doubleKnock(): challenge retry also fails → legacy fallback attempted",
|
|
|
1651
1549
|
if (req.headers.has("Signature") && !req.headers.has("Signature-Input")) return new Response("", { status: 202 });
|
|
1652
1550
|
return new Response("Bad", { status: 400 });
|
|
1653
1551
|
});
|
|
1654
|
-
|
|
1552
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-challenge-fails", {
|
|
1655
1553
|
method: "POST",
|
|
1656
1554
|
body: "Test message",
|
|
1657
1555
|
headers: { "Content-Type": "text/plain" }
|
|
1658
|
-
})
|
|
1659
|
-
const response = await doubleKnock(request, {
|
|
1556
|
+
}), {
|
|
1660
1557
|
keyId: rsaPublicKey2.id,
|
|
1661
1558
|
privateKey: rsaPrivateKey2
|
|
1662
|
-
});
|
|
1663
|
-
assertEquals(response.status, 202);
|
|
1559
|
+
})).status, 202);
|
|
1664
1560
|
assertEquals(requestCount, 3);
|
|
1665
1561
|
esm_default.hardReset();
|
|
1666
1562
|
});
|
|
@@ -1681,16 +1577,14 @@ test("doubleKnock(): challenge retry returns another challenge → not followed"
|
|
|
1681
1577
|
if (req.headers.has("Signature") && !req.headers.has("Signature-Input")) return new Response("", { status: 202 });
|
|
1682
1578
|
return new Response("Bad", { status: 400 });
|
|
1683
1579
|
});
|
|
1684
|
-
|
|
1580
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-challenge-loop", {
|
|
1685
1581
|
method: "POST",
|
|
1686
1582
|
body: "Test message",
|
|
1687
1583
|
headers: { "Content-Type": "text/plain" }
|
|
1688
|
-
})
|
|
1689
|
-
const response = await doubleKnock(request, {
|
|
1584
|
+
}), {
|
|
1690
1585
|
keyId: rsaPublicKey2.id,
|
|
1691
1586
|
privateKey: rsaPrivateKey2
|
|
1692
|
-
});
|
|
1693
|
-
assertEquals(response.status, 202);
|
|
1587
|
+
})).status, 202);
|
|
1694
1588
|
assertEquals(requestCount, 3);
|
|
1695
1589
|
esm_default.hardReset();
|
|
1696
1590
|
});
|
|
@@ -1707,16 +1601,14 @@ test("doubleKnock(): Accept-Signature with unsupported component falls to legacy
|
|
|
1707
1601
|
if (req.headers.has("Signature") && !req.headers.has("Signature-Input")) return new Response("", { status: 202 });
|
|
1708
1602
|
return new Response("Bad", { status: 400 });
|
|
1709
1603
|
});
|
|
1710
|
-
|
|
1604
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-bad-challenge", {
|
|
1711
1605
|
method: "POST",
|
|
1712
1606
|
body: "Test message",
|
|
1713
1607
|
headers: { "Content-Type": "text/plain" }
|
|
1714
|
-
})
|
|
1715
|
-
const response = await doubleKnock(request, {
|
|
1608
|
+
}), {
|
|
1716
1609
|
keyId: rsaPublicKey2.id,
|
|
1717
1610
|
privateKey: rsaPrivateKey2
|
|
1718
|
-
});
|
|
1719
|
-
assertEquals(response.status, 202);
|
|
1611
|
+
})).status, 202);
|
|
1720
1612
|
assertEquals(requestCount, 2);
|
|
1721
1613
|
esm_default.hardReset();
|
|
1722
1614
|
});
|
|
@@ -1733,16 +1625,14 @@ test("doubleKnock(): Accept-Signature with unsupported derived component falls t
|
|
|
1733
1625
|
if (req.headers.has("Signature") && !req.headers.has("Signature-Input")) return new Response("", { status: 202 });
|
|
1734
1626
|
return new Response("Bad", { status: 400 });
|
|
1735
1627
|
});
|
|
1736
|
-
|
|
1628
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-bad-derived", {
|
|
1737
1629
|
method: "POST",
|
|
1738
1630
|
body: "Test message",
|
|
1739
1631
|
headers: { "Content-Type": "text/plain" }
|
|
1740
|
-
})
|
|
1741
|
-
const response = await doubleKnock(request, {
|
|
1632
|
+
}), {
|
|
1742
1633
|
keyId: rsaPublicKey2.id,
|
|
1743
1634
|
privateKey: rsaPrivateKey2
|
|
1744
|
-
});
|
|
1745
|
-
assertEquals(response.status, 202);
|
|
1635
|
+
})).status, 202);
|
|
1746
1636
|
assertEquals(requestCount, 2);
|
|
1747
1637
|
esm_default.hardReset();
|
|
1748
1638
|
});
|
|
@@ -1759,16 +1649,14 @@ test("doubleKnock(): Accept-Signature with multiple entries where first throws f
|
|
|
1759
1649
|
if (req.headers.has("Signature-Input")) return new Response("", { status: 202 });
|
|
1760
1650
|
return new Response("Bad", { status: 400 });
|
|
1761
1651
|
});
|
|
1762
|
-
|
|
1652
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-multi-challenge", {
|
|
1763
1653
|
method: "POST",
|
|
1764
1654
|
body: "Test message",
|
|
1765
1655
|
headers: { "Content-Type": "text/plain" }
|
|
1766
|
-
})
|
|
1767
|
-
const response = await doubleKnock(request, {
|
|
1656
|
+
}), {
|
|
1768
1657
|
keyId: rsaPublicKey2.id,
|
|
1769
1658
|
privateKey: rsaPrivateKey2
|
|
1770
|
-
});
|
|
1771
|
-
assertEquals(response.status, 202);
|
|
1659
|
+
})).status, 202);
|
|
1772
1660
|
assertEquals(requestCount, 2);
|
|
1773
1661
|
esm_default.hardReset();
|
|
1774
1662
|
});
|
|
@@ -1787,18 +1675,16 @@ test("doubleKnock(): Accept-Signature with multiple compatible entries fulfills
|
|
|
1787
1675
|
if (sigInput.includes("sig1=") && sigInput.includes("sig2=") && sig.includes("sig1=") && sig.includes("sig2=")) return new Response("", { status: 202 });
|
|
1788
1676
|
return new Response("Missing signatures", { status: 400 });
|
|
1789
1677
|
});
|
|
1790
|
-
|
|
1678
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-multi-compat", {
|
|
1791
1679
|
method: "POST",
|
|
1792
1680
|
body: "Test message",
|
|
1793
1681
|
headers: { "Content-Type": "text/plain" }
|
|
1794
|
-
})
|
|
1795
|
-
const response = await doubleKnock(request, {
|
|
1682
|
+
}), {
|
|
1796
1683
|
keyId: rsaPublicKey2.id,
|
|
1797
1684
|
privateKey: rsaPrivateKey2
|
|
1798
|
-
});
|
|
1799
|
-
assertEquals(response.status, 202);
|
|
1685
|
+
})).status, 202);
|
|
1800
1686
|
assertEquals(requestCount, 2);
|
|
1801
1687
|
esm_default.hardReset();
|
|
1802
1688
|
});
|
|
1803
|
-
|
|
1804
|
-
|
|
1689
|
+
//#endregion
|
|
1690
|
+
export {};
|