@fedify/fedify 2.0.0-pr.412.1559 → 2.0.0-pr.412.1794

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.
Files changed (204) hide show
  1. package/README.md +3 -0
  2. package/dist/{actor-Cc6B76eG.js → actor-BXHc5r-q.js} +1 -1
  3. package/dist/actor-Ba9Z1eNJ.cjs +42079 -0
  4. package/dist/{actor-C22bXuuC.d.ts → actor-DqFajh9s.d.ts} +2 -2
  5. package/dist/{actor-CTAuCsWy.js → actor-DzhunPC_.js} +7058 -2722
  6. package/dist/actor-f2NtjyCg.d.cts +128 -0
  7. package/dist/{assert_rejects-DiIiJbZn.js → assert_rejects-Ce45JcFg.js} +1 -1
  8. package/dist/{assert_is_error-BPGph1Jx.js → assert_throws-BNXdRGWP.js} +31 -1
  9. package/dist/{builder-BOEBObR8.js → builder-u7usRsT3.js} +13 -8
  10. package/dist/chunk-DqRYRqnO.cjs +34 -0
  11. package/dist/client-94iWEfQa.d.cts +222 -0
  12. package/dist/{client-DF8anIB5.d.ts → client-BsGzbnV-.d.ts} +3 -75
  13. package/dist/{client-De-z2UnN.js → client-pY7-3icS.js} +3 -22
  14. package/dist/compat/mod.cjs +10 -0
  15. package/dist/compat/mod.d.cts +12 -0
  16. package/dist/compat/mod.d.ts +11 -12
  17. package/dist/compat/mod.js +5 -5
  18. package/dist/compat/transformers.test.js +20 -20
  19. package/dist/compat-DmDDELst.cjs +4 -0
  20. package/dist/compat-nxUqe4Z-.js +4 -0
  21. package/dist/{context-9gCpIkiz.d.ts → context-DG0huGW-.d.ts} +184 -127
  22. package/dist/context-DJ8aSy2Q.d.cts +2312 -0
  23. package/dist/{authdocloader-CLgDGafZ.js → docloader-CrbAy9Oc.js} +17 -8
  24. package/dist/{esm-CHdxdkuH.js → esm-C-Qa1zEM.js} +11 -11
  25. package/dist/federation/builder.test.js +9 -12
  26. package/dist/federation/collection.test.js +7 -9
  27. package/dist/federation/handler.test.js +27 -148
  28. package/dist/federation/idempotency.test.js +201 -0
  29. package/dist/federation/inbox.test.js +4 -6
  30. package/dist/federation/keycache.test.js +3 -4
  31. package/dist/federation/kv.test.js +7 -8
  32. package/dist/federation/middleware.test.js +242 -64
  33. package/dist/federation/mod.cjs +27 -0
  34. package/dist/federation/mod.d.cts +12 -0
  35. package/dist/federation/mod.d.ts +12 -13
  36. package/dist/federation/mod.js +13 -15
  37. package/dist/federation/mq.test.js +8 -10
  38. package/dist/federation/negotiation.test.js +26 -0
  39. package/dist/federation/retry.test.js +4 -5
  40. package/dist/federation/router.test.js +6 -8
  41. package/dist/federation/send.test.js +13 -15
  42. package/dist/federation-CRpdnOMS.cjs +244 -0
  43. package/dist/{federation-CMX7WzeL.js → federation-jcR8-ZxP.js} +3 -3
  44. package/dist/fixtures/media.example.com/avatars/test-avatar.jpg.json +6 -0
  45. package/dist/{http-DqSNLFNY.d.ts → http-BbO0ejuk.d.ts} +2 -2
  46. package/dist/{http-l0TEupZK.js → http-CUVx-vzb.js} +260 -10
  47. package/dist/{http-CcdM1brU.js → http-D2kIm9la.js} +3 -3
  48. package/dist/http-Dofes42e.cjs +1106 -0
  49. package/dist/http-M8k5mKc0.d.cts +253 -0
  50. package/dist/{inbox-B6DZbGNf.js → inbox-HjG5peXY.js} +24 -7
  51. package/dist/{key-CzLv1phF.js → key-CxdLUFS6.js} +3 -2
  52. package/dist/{keycache-B8HdZJSt.js → keycache-bU7COwsh.js} +1 -1
  53. package/dist/{keys-zZwiKkfx.js → keys-DMHs8XNn.js} +2 -1
  54. package/dist/kv-Bxr0Q87_.d.cts +81 -0
  55. package/dist/kv-cache-CGXcDejL.js +122 -0
  56. package/dist/kv-cache-DN9pfMBe.js +94 -0
  57. package/dist/kv-cache-Dkvbn6rg.cjs +134 -0
  58. package/dist/{ld-CBcQdZ6N.js → ld-CIhvSCCr.js} +5 -4
  59. package/dist/lookup-CHkCVZTU.js +260 -0
  60. package/dist/lookup-CfFkONZD.cjs +272 -0
  61. package/dist/{type-SK-d7Tbw.js → lookup-DQRtjvb1.js} +7266 -7087
  62. package/dist/middleware-Bot7EEwV.js +26 -0
  63. package/dist/middleware-CFJgmUMW.cjs +15 -0
  64. package/dist/{middleware-BRgu1IH8.js → middleware-CJlj5Olw.js} +86 -181
  65. package/dist/middleware-CjHl2NHA.js +15 -0
  66. package/dist/middleware-D74TgW2E.cjs +4225 -0
  67. package/dist/{middleware-CxEkcFW0.js → middleware-DMk2Mdn9.js} +117 -127
  68. package/dist/mod-0noXw66R.d.cts +107 -0
  69. package/dist/mod-B-hUPT2N.d.cts +1 -0
  70. package/dist/{mod-Drmz72EK.d.ts → mod-B2iOw50L.d.ts} +3 -3
  71. package/dist/mod-B9M-8jm2.d.ts +109 -0
  72. package/dist/{mod-Cxt4Kpf6.d.ts → mod-BlVovdcy.d.ts} +20 -2
  73. package/dist/{mod-Bqxcp7eN.d.ts → mod-BoHnwOCs.d.ts} +2 -2
  74. package/dist/mod-BxRCHTz-.d.cts +307 -0
  75. package/dist/mod-C2tOeRkN.d.cts +1 -0
  76. package/dist/mod-C58MZ7Wx.d.cts +113 -0
  77. package/dist/mod-DJcZDvjA.d.cts +80 -0
  78. package/dist/mod-DgdBYYa0.d.cts +266 -0
  79. package/dist/{mod-DBzN0aCM.d.ts → mod-Ds0mpFZU.d.ts} +1 -1
  80. package/dist/mod.cjs +137 -0
  81. package/dist/mod.d.cts +16 -0
  82. package/dist/mod.d.ts +16 -17
  83. package/dist/mod.js +19 -21
  84. package/dist/mq-DcJPkXD5.d.cts +140 -0
  85. package/dist/negotiation-C4nFufNk.js +71 -0
  86. package/dist/nodeinfo/client.test.js +30 -111
  87. package/dist/nodeinfo/handler.test.js +25 -26
  88. package/dist/nodeinfo/mod.cjs +10 -0
  89. package/dist/nodeinfo/mod.d.cts +3 -0
  90. package/dist/nodeinfo/mod.d.ts +3 -5
  91. package/dist/nodeinfo/mod.js +6 -7
  92. package/dist/nodeinfo/types.test.js +8 -15
  93. package/dist/nodeinfo-BnthBobC.js +4 -0
  94. package/dist/nodeinfo-CdN0rEnZ.cjs +4 -0
  95. package/dist/owner-B4HbyP8s.d.cts +67 -0
  96. package/dist/{owner-VEIjmR8r.js → owner-Dvh7mBvr.js} +3 -2
  97. package/dist/{owner-CQPnQVtf.d.ts → owner-kQRGVXG1.d.ts} +3 -3
  98. package/dist/proof-D0uThUvD.cjs +674 -0
  99. package/dist/{proof-DoSQAGkE.js → proof-Dgy35fzc.js} +3 -3
  100. package/dist/{proof-tgUlT8hw.js → proof-fqzaMJ4g.js} +11 -10
  101. package/dist/{send-BzS7w-QF.js → send-Drp20VO9.js} +2 -2
  102. package/dist/sig/http.test.js +13 -14
  103. package/dist/sig/key.test.js +9 -11
  104. package/dist/sig/ld.test.js +8 -10
  105. package/dist/sig/mod.cjs +28 -0
  106. package/dist/sig/mod.d.cts +6 -0
  107. package/dist/sig/mod.d.ts +5 -7
  108. package/dist/sig/mod.js +8 -10
  109. package/dist/sig/owner.test.js +10 -12
  110. package/dist/sig/proof.test.js +13 -14
  111. package/dist/sig-C34-oHBl.js +4 -0
  112. package/dist/sig-YYj5tCnr.cjs +4 -0
  113. package/dist/testing/docloader.test.js +6 -8
  114. package/dist/testing/mod.d.ts +387 -224
  115. package/dist/testing/mod.js +2 -3
  116. package/dist/{testing-Z2omCvKy.js → testing-g4UC4liW.js} +1 -2
  117. package/dist/{transformers-Dna8Fg7k.js → transformers-BFT6d7J5.js} +3 -3
  118. package/dist/transformers-CoBS-oFG.cjs +116 -0
  119. package/dist/{types-DgPvoUWN.js → types-BtUjyi5y.js} +8 -169
  120. package/dist/{types-BIgY6c-l.js → types-C2XVl6gj.js} +1 -3
  121. package/dist/types-CWgzGaqk.cjs +315 -0
  122. package/dist/{runtime/authdocloader.test.js → utils/docloader.test.js} +14 -15
  123. package/dist/utils/kv-cache.test.js +209 -0
  124. package/dist/utils/mod.cjs +12 -0
  125. package/dist/utils/mod.d.cts +5 -0
  126. package/dist/utils/mod.d.ts +7 -0
  127. package/dist/utils/mod.js +11 -0
  128. package/dist/utils-D-Va7aXC.js +4 -0
  129. package/dist/utils-DyRU1gdZ.cjs +4 -0
  130. package/dist/vocab/actor.test.js +8 -10
  131. package/dist/vocab/lookup.test.js +257 -9
  132. package/dist/vocab/mod.cjs +86 -0
  133. package/dist/vocab/mod.d.cts +4 -0
  134. package/dist/vocab/mod.d.ts +3 -5
  135. package/dist/vocab/mod.js +6 -7
  136. package/dist/vocab/type.test.js +2 -3
  137. package/dist/vocab/vocab.test.js +438 -15
  138. package/dist/{vocab-SOE1ifCr.d.ts → vocab-BCWe1Ih5.d.ts} +292 -21
  139. package/dist/{vocab-CvD6Vbml.js → vocab-BnR3nIU9.js} +26 -17
  140. package/dist/vocab-CeDBzu-f.d.cts +14903 -0
  141. package/dist/{lookup-Bn_HEC_d.js → vocab-DFaWWYDn.cjs} +108 -139
  142. package/dist/webfinger/handler.test.js +24 -25
  143. package/dist/webfinger/lookup.test.js +7 -9
  144. package/dist/webfinger/mod.cjs +8 -0
  145. package/dist/webfinger/mod.d.cts +2 -0
  146. package/dist/webfinger/mod.d.ts +1 -3
  147. package/dist/webfinger/mod.js +5 -6
  148. package/dist/webfinger-C72Y8lrh.js +4 -0
  149. package/dist/webfinger-vAtLmxOF.cjs +4 -0
  150. package/dist/x/cfworkers.cjs +100 -0
  151. package/dist/x/cfworkers.d.cts +59 -0
  152. package/dist/x/cfworkers.d.ts +2 -2
  153. package/dist/x/cfworkers.js +3 -3
  154. package/dist/x/cfworkers.test.js +6 -8
  155. package/dist/x/hono.cjs +61 -0
  156. package/dist/x/hono.d.cts +53 -0
  157. package/dist/x/hono.d.ts +10 -11
  158. package/dist/x/hono.js +3 -3
  159. package/dist/x/sveltekit.cjs +69 -0
  160. package/dist/x/sveltekit.d.cts +45 -0
  161. package/dist/x/sveltekit.d.ts +10 -11
  162. package/dist/x/sveltekit.js +3 -3
  163. package/package.json +79 -23
  164. package/dist/assert_throws-BOO88avQ.js +0 -39
  165. package/dist/authdocloader-BFVqUbyo.js +0 -52
  166. package/dist/compat-Bb5myD13.js +0 -4
  167. package/dist/docloader-CxWcuWqQ.d.ts +0 -221
  168. package/dist/docloader-DEhniCVa.js +0 -4615
  169. package/dist/key-CUZQgVlf.js +0 -10
  170. package/dist/key-Deb0_wWL.js +0 -10
  171. package/dist/key-DxA6xRtZ.js +0 -260
  172. package/dist/lookup-dtdr2ftf.js +0 -131
  173. package/dist/middleware-BE_geSiJ.js +0 -17
  174. package/dist/middleware-BnU6hzVp.js +0 -26
  175. package/dist/mod-TFoH2Ql8.d.ts +0 -104
  176. package/dist/nodeinfo/semver.test.js +0 -143
  177. package/dist/nodeinfo-CyEbLjHs.js +0 -4
  178. package/dist/runtime/docloader.test.js +0 -522
  179. package/dist/runtime/key.test.js +0 -103
  180. package/dist/runtime/langstr.test.d.ts +0 -3
  181. package/dist/runtime/langstr.test.js +0 -39
  182. package/dist/runtime/mod.d.ts +0 -8
  183. package/dist/runtime/mod.js +0 -13
  184. package/dist/runtime/multibase/multibase.test.d.ts +0 -3
  185. package/dist/runtime/multibase/multibase.test.js +0 -358
  186. package/dist/runtime/url.test.d.ts +0 -3
  187. package/dist/runtime/url.test.js +0 -45
  188. package/dist/runtime-BSkOVUWM.js +0 -4
  189. package/dist/semver-dArNLkR9.js +0 -149
  190. package/dist/sig-BXJO--F9.js +0 -4
  191. package/dist/webfinger-C3GIyXIg.js +0 -4
  192. /package/dist/{assert_not_equals-f3m3epl3.js → assert_not_equals-C80BG-_5.js} +0 -0
  193. /package/dist/{collection-CSzG2j1P.js → collection-BzWsN9pB.js} +0 -0
  194. /package/dist/{denokv-Bv33Xxea.js → denokv-CCssOzMJ.js} +0 -0
  195. /package/dist/{nodeinfo/semver.test.d.ts → federation/idempotency.test.d.ts} +0 -0
  196. /package/dist/{runtime/authdocloader.test.d.ts → federation/negotiation.test.d.ts} +0 -0
  197. /package/dist/{kv-C7sopW2E.d.ts → kv-BKNZ-Tb-.d.ts} +0 -0
  198. /package/dist/{mod-1pDWKvUL.d.ts → mod-CVgZgliM.d.ts} +0 -0
  199. /package/dist/{mod-g0xFzAP9.d.ts → mod-xIj-IT58.d.ts} +0 -0
  200. /package/dist/{mq-CRGm1e_F.d.ts → mq-CUKlBw08.d.ts} +0 -0
  201. /package/dist/{retry-D4GJ670a.js → retry-CfF8Gn4d.js} +0 -0
  202. /package/dist/{std__assert-X-_kMxKM.js → std__assert-DWivtrGR.js} +0 -0
  203. /package/dist/{runtime → utils}/docloader.test.d.ts +0 -0
  204. /package/dist/{runtime/key.test.d.ts → utils/kv-cache.test.d.ts} +0 -0
@@ -0,0 +1,1106 @@
1
+
2
+ const { Temporal } = require("@js-temporal/polyfill");
3
+ const { URLPattern } = require("urlpattern-polyfill");
4
+
5
+ const require_chunk = require('./chunk-DqRYRqnO.cjs');
6
+ const require_lookup = require('./lookup-CfFkONZD.cjs');
7
+ const require_actor = require('./actor-Ba9Z1eNJ.cjs');
8
+ const __logtape_logtape = require_chunk.__toESM(require("@logtape/logtape"));
9
+ const __opentelemetry_api = require_chunk.__toESM(require("@opentelemetry/api"));
10
+ const __fedify_vocab_runtime = require_chunk.__toESM(require("@fedify/vocab-runtime"));
11
+ const byte_encodings_hex = require_chunk.__toESM(require("byte-encodings/hex"));
12
+ const __opentelemetry_semantic_conventions = require_chunk.__toESM(require("@opentelemetry/semantic-conventions"));
13
+ const byte_encodings_base64 = require_chunk.__toESM(require("byte-encodings/base64"));
14
+ const structured_field_values = require_chunk.__toESM(require("structured-field-values"));
15
+
16
+ //#region src/sig/key.ts
17
+ /**
18
+ * Checks if the given key is valid and supported. No-op if the key is valid,
19
+ * otherwise throws an error.
20
+ * @param key The key to check.
21
+ * @param type Which type of key to check. If not specified, the key can be
22
+ * either public or private.
23
+ * @throws {TypeError} If the key is invalid or unsupported.
24
+ */
25
+ function validateCryptoKey(key, type) {
26
+ if (type != null && key.type !== type) throw new TypeError(`The key is not a ${type} key.`);
27
+ if (!key.extractable) throw new TypeError("The key is not extractable.");
28
+ if (key.algorithm.name !== "RSASSA-PKCS1-v1_5" && key.algorithm.name !== "Ed25519") throw new TypeError("Currently only RSASSA-PKCS1-v1_5 and Ed25519 keys are supported. More algorithms will be added in the future!");
29
+ if (key.algorithm.name === "RSASSA-PKCS1-v1_5") {
30
+ const algorithm = key.algorithm;
31
+ if (algorithm.hash.name !== "SHA-256") throw new TypeError("For compatibility with the existing Fediverse software (e.g., Mastodon), hash algorithm for RSASSA-PKCS1-v1_5 keys must be SHA-256.");
32
+ }
33
+ }
34
+ /**
35
+ * Generates a key pair which is appropriate for Fedify.
36
+ * @param algorithm The algorithm to use. Currently only RSASSA-PKCS1-v1_5 and
37
+ * Ed25519 are supported.
38
+ * @returns The generated key pair.
39
+ * @throws {TypeError} If the algorithm is unsupported.
40
+ */
41
+ function generateCryptoKeyPair(algorithm) {
42
+ if (algorithm == null) (0, __logtape_logtape.getLogger)([
43
+ "fedify",
44
+ "sig",
45
+ "key"
46
+ ]).warn("No algorithm specified. Using RSASSA-PKCS1-v1_5 by default, but it is recommended to specify the algorithm explicitly as the parameter will be required in the future.");
47
+ if (algorithm == null || algorithm === "RSASSA-PKCS1-v1_5") return crypto.subtle.generateKey({
48
+ name: "RSASSA-PKCS1-v1_5",
49
+ modulusLength: 4096,
50
+ publicExponent: new Uint8Array([
51
+ 1,
52
+ 0,
53
+ 1
54
+ ]),
55
+ hash: "SHA-256"
56
+ }, true, ["sign", "verify"]);
57
+ else if (algorithm === "Ed25519") return crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"]);
58
+ throw new TypeError("Unsupported algorithm: " + algorithm);
59
+ }
60
+ /**
61
+ * Exports a key in JWK format.
62
+ * @param key The key to export. Either public or private key.
63
+ * @returns The exported key in JWK format. The key is suitable for
64
+ * serialization and storage.
65
+ * @throws {TypeError} If the key is invalid or unsupported.
66
+ */
67
+ async function exportJwk(key) {
68
+ validateCryptoKey(key);
69
+ const jwk = await crypto.subtle.exportKey("jwk", key);
70
+ if (jwk.crv === "Ed25519") jwk.alg = "Ed25519";
71
+ return jwk;
72
+ }
73
+ /**
74
+ * Imports a key from JWK format.
75
+ * @param jwk The key in JWK format.
76
+ * @param type Which type of key to import, either `"public"` or `"private"`.
77
+ * @returns The imported key.
78
+ * @throws {TypeError} If the key is invalid or unsupported.
79
+ */
80
+ async function importJwk(jwk, type) {
81
+ let key;
82
+ if (jwk.kty === "RSA" && jwk.alg === "RS256") key = await crypto.subtle.importKey("jwk", jwk, {
83
+ name: "RSASSA-PKCS1-v1_5",
84
+ hash: "SHA-256"
85
+ }, true, type === "public" ? ["verify"] : ["sign"]);
86
+ else if (jwk.kty === "OKP" && jwk.crv === "Ed25519") {
87
+ if (navigator?.userAgent === "Cloudflare-Workers") {
88
+ jwk = { ...jwk };
89
+ delete jwk.alg;
90
+ }
91
+ key = await crypto.subtle.importKey("jwk", jwk, "Ed25519", true, type === "public" ? ["verify"] : ["sign"]);
92
+ } else throw new TypeError("Unsupported JWK format.");
93
+ validateCryptoKey(key, type);
94
+ return key;
95
+ }
96
+ /**
97
+ * Fetches a {@link CryptographicKey} or {@link Multikey} from the given URL.
98
+ * If the given URL contains an {@link Actor} object, it tries to find
99
+ * the corresponding key in the `publicKey` or `assertionMethod` property.
100
+ * @template T The type of the key to fetch. Either {@link CryptographicKey}
101
+ * or {@link Multikey}.
102
+ * @param keyId The URL of the key.
103
+ * @param cls The class of the key to fetch. Either {@link CryptographicKey}
104
+ * or {@link Multikey}.
105
+ * @param options Options for fetching the key. See {@link FetchKeyOptions}.
106
+ * @returns The fetched key or `null` if the key is not found.
107
+ * @since 1.3.0
108
+ */
109
+ function fetchKey(keyId, cls, options = {}) {
110
+ const tracerProvider = options.tracerProvider ?? __opentelemetry_api.trace.getTracerProvider();
111
+ const tracer = tracerProvider.getTracer(require_lookup.deno_default.name, require_lookup.deno_default.version);
112
+ keyId = typeof keyId === "string" ? new URL(keyId) : keyId;
113
+ return tracer.startActiveSpan("activitypub.fetch_key", {
114
+ kind: __opentelemetry_api.SpanKind.CLIENT,
115
+ attributes: {
116
+ "http.method": "GET",
117
+ "url.full": keyId.href,
118
+ "url.scheme": keyId.protocol.replace(/:$/, ""),
119
+ "url.domain": keyId.hostname,
120
+ "url.path": keyId.pathname,
121
+ "url.query": keyId.search.replace(/^\?/, ""),
122
+ "url.fragment": keyId.hash.replace(/^#/, "")
123
+ }
124
+ }, async (span) => {
125
+ try {
126
+ const result = await fetchKeyInternal(keyId, cls, options);
127
+ span.setAttribute("activitypub.actor.key.cached", result.cached);
128
+ return result;
129
+ } catch (e) {
130
+ span.setStatus({
131
+ code: __opentelemetry_api.SpanStatusCode.ERROR,
132
+ message: String(e)
133
+ });
134
+ throw e;
135
+ } finally {
136
+ span.end();
137
+ }
138
+ });
139
+ }
140
+ async function fetchKeyInternal(keyId, cls, { documentLoader, contextLoader, keyCache, tracerProvider } = {}) {
141
+ const logger = (0, __logtape_logtape.getLogger)([
142
+ "fedify",
143
+ "sig",
144
+ "key"
145
+ ]);
146
+ const cacheKey = typeof keyId === "string" ? new URL(keyId) : keyId;
147
+ keyId = typeof keyId === "string" ? keyId : keyId.href;
148
+ if (keyCache != null) {
149
+ const cachedKey = await keyCache.get(cacheKey);
150
+ if (cachedKey instanceof cls && cachedKey.publicKey != null) {
151
+ logger.debug("Key {keyId} found in cache.", { keyId });
152
+ return {
153
+ key: cachedKey,
154
+ cached: true
155
+ };
156
+ } else if (cachedKey === null) {
157
+ logger.debug("Entry {keyId} found in cache, but it is unavailable.", { keyId });
158
+ return {
159
+ key: null,
160
+ cached: true
161
+ };
162
+ }
163
+ }
164
+ logger.debug("Fetching key {keyId} to verify signature...", { keyId });
165
+ let document;
166
+ try {
167
+ const remoteDocument = await (documentLoader ?? (0, __fedify_vocab_runtime.getDocumentLoader)())(keyId);
168
+ document = remoteDocument.document;
169
+ } catch (_) {
170
+ logger.debug("Failed to fetch key {keyId}.", { keyId });
171
+ await keyCache?.set(cacheKey, null);
172
+ return {
173
+ key: null,
174
+ cached: false
175
+ };
176
+ }
177
+ let object;
178
+ try {
179
+ object = await require_actor.Object.fromJsonLd(document, {
180
+ documentLoader,
181
+ contextLoader,
182
+ tracerProvider
183
+ });
184
+ } catch (e) {
185
+ if (!(e instanceof TypeError)) throw e;
186
+ try {
187
+ object = await cls.fromJsonLd(document, {
188
+ documentLoader,
189
+ contextLoader,
190
+ tracerProvider
191
+ });
192
+ } catch (e$1) {
193
+ if (e$1 instanceof TypeError) {
194
+ logger.debug("Failed to verify; key {keyId} returned an invalid object.", { keyId });
195
+ await keyCache?.set(cacheKey, null);
196
+ return {
197
+ key: null,
198
+ cached: false
199
+ };
200
+ }
201
+ throw e$1;
202
+ }
203
+ }
204
+ let key = null;
205
+ if (object instanceof cls) key = object;
206
+ else if (require_actor.isActor(object)) {
207
+ const keys = cls === require_actor.CryptographicKey ? object.getPublicKeys({
208
+ documentLoader,
209
+ contextLoader,
210
+ tracerProvider
211
+ }) : object.getAssertionMethods({
212
+ documentLoader,
213
+ contextLoader,
214
+ tracerProvider
215
+ });
216
+ let length = 0;
217
+ let lastKey = null;
218
+ for await (const k of keys) {
219
+ length++;
220
+ lastKey = k;
221
+ if (k.id?.href === keyId) {
222
+ key = k;
223
+ break;
224
+ }
225
+ }
226
+ const keyIdUrl = new URL(keyId);
227
+ if (key == null && keyIdUrl.hash === "" && length === 1) key = lastKey;
228
+ if (key == null) {
229
+ logger.debug("Failed to verify; object {keyId} returned an {actorType}, but has no key matching {keyId}.", {
230
+ keyId,
231
+ actorType: object.constructor.name
232
+ });
233
+ await keyCache?.set(cacheKey, null);
234
+ return {
235
+ key: null,
236
+ cached: false
237
+ };
238
+ }
239
+ } else {
240
+ logger.debug("Failed to verify; key {keyId} returned an invalid object.", { keyId });
241
+ await keyCache?.set(cacheKey, null);
242
+ return {
243
+ key: null,
244
+ cached: false
245
+ };
246
+ }
247
+ if (key.publicKey == null) {
248
+ logger.debug("Failed to verify; key {keyId} has no publicKeyPem field.", { keyId });
249
+ await keyCache?.set(cacheKey, null);
250
+ return {
251
+ key: null,
252
+ cached: false
253
+ };
254
+ }
255
+ if (keyCache != null) {
256
+ await keyCache.set(cacheKey, key);
257
+ logger.debug("Key {keyId} cached.", { keyId });
258
+ }
259
+ return {
260
+ key,
261
+ cached: false
262
+ };
263
+ }
264
+
265
+ //#endregion
266
+ //#region src/sig/http.ts
267
+ /**
268
+ * Signs a request using the given private key.
269
+ * @param request The request to sign.
270
+ * @param privateKey The private key to use for signing.
271
+ * @param keyId The key ID to use for the signature. It will be used by the
272
+ * verifier.
273
+ * @returns The signed request.
274
+ * @throws {TypeError} If the private key is invalid or unsupported.
275
+ */
276
+ async function signRequest(request, privateKey, keyId, options = {}) {
277
+ validateCryptoKey(privateKey, "private");
278
+ const tracerProvider = options.tracerProvider ?? __opentelemetry_api.trace.getTracerProvider();
279
+ const tracer = tracerProvider.getTracer(require_lookup.deno_default.name, require_lookup.deno_default.version);
280
+ return await tracer.startActiveSpan("http_signatures.sign", async (span) => {
281
+ try {
282
+ const spec = options.spec ?? "draft-cavage-http-signatures-12";
283
+ let signed;
284
+ if (spec === "rfc9421") signed = await signRequestRfc9421(request, privateKey, keyId, span, options.currentTime, options.body);
285
+ else signed = await signRequestDraft(request, privateKey, keyId, span, options.currentTime, options.body);
286
+ if (span.isRecording()) {
287
+ span.setAttribute(__opentelemetry_semantic_conventions.ATTR_HTTP_REQUEST_METHOD, signed.method);
288
+ span.setAttribute(__opentelemetry_semantic_conventions.ATTR_URL_FULL, signed.url);
289
+ for (const [name, value] of signed.headers) span.setAttribute((0, __opentelemetry_semantic_conventions.ATTR_HTTP_REQUEST_HEADER)(name), value);
290
+ span.setAttribute("http_signatures.key_id", keyId.href);
291
+ }
292
+ return signed;
293
+ } catch (error) {
294
+ span.setStatus({
295
+ code: __opentelemetry_api.SpanStatusCode.ERROR,
296
+ message: String(error)
297
+ });
298
+ throw error;
299
+ } finally {
300
+ span.end();
301
+ }
302
+ });
303
+ }
304
+ async function signRequestDraft(request, privateKey, keyId, span, currentTime, bodyBuffer) {
305
+ if (privateKey.algorithm.name !== "RSASSA-PKCS1-v1_5") throw new TypeError("Unsupported algorithm: " + privateKey.algorithm.name);
306
+ const url = new URL(request.url);
307
+ const body = bodyBuffer !== void 0 ? bodyBuffer : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
308
+ const headers = new Headers(request.headers);
309
+ if (!headers.has("Host")) headers.set("Host", url.host);
310
+ if (!headers.has("Digest") && body != null) {
311
+ const digest = await crypto.subtle.digest("SHA-256", body);
312
+ headers.set("Digest", `SHA-256=${(0, byte_encodings_base64.encodeBase64)(digest)}`);
313
+ if (span.isRecording()) span.setAttribute("http_signatures.digest.sha-256", (0, byte_encodings_hex.encodeHex)(digest));
314
+ }
315
+ if (!headers.has("Date")) headers.set("Date", currentTime == null ? (/* @__PURE__ */ new Date()).toUTCString() : new Date(currentTime.toString()).toUTCString());
316
+ const serialized = [["(request-target)", `${request.method.toLowerCase()} ${url.pathname}`], ...headers];
317
+ const headerNames = serialized.map(([name]) => name);
318
+ const message = serialized.map(([name, value]) => `${name}: ${value.trim()}`).join("\n");
319
+ const signature = await crypto.subtle.sign("RSASSA-PKCS1-v1_5", privateKey, new TextEncoder().encode(message));
320
+ const sigHeader = `keyId="${keyId.href}",algorithm="rsa-sha256",headers="${headerNames.join(" ")}",signature="${(0, byte_encodings_base64.encodeBase64)(signature)}"`;
321
+ headers.set("Signature", sigHeader);
322
+ if (span.isRecording()) {
323
+ span.setAttribute("http_signatures.algorithm", "rsa-sha256");
324
+ span.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(signature));
325
+ }
326
+ return new Request(request, {
327
+ headers,
328
+ body
329
+ });
330
+ }
331
+ function formatRfc9421SignatureParameters(params) {
332
+ return `alg="${params.algorithm}";keyid="${params.keyId.href}";created=${params.created}`;
333
+ }
334
+ /**
335
+ * Creates a signature base for a request according to RFC 9421.
336
+ * @param request The request to create a signature base for.
337
+ * @param components The components to include in the signature base.
338
+ * @param parameters The signature parameters to include in the signature base.
339
+ * @returns The signature base as a string.
340
+ */
341
+ function createRfc9421SignatureBase(request, components, parameters) {
342
+ const url = new URL(request.url);
343
+ const baseComponents = [];
344
+ for (const component of components) {
345
+ let value;
346
+ if (component === "@method") value = request.method.toUpperCase();
347
+ else if (component === "@target-uri") value = request.url;
348
+ else if (component === "@authority") value = url.host;
349
+ else if (component === "@scheme") value = url.protocol.slice(0, -1);
350
+ else if (component === "@request-target") value = `${request.method.toLowerCase()} ${url.pathname}${url.search}`;
351
+ else if (component === "@path") value = url.pathname;
352
+ else if (component === "@query") value = url.search.startsWith("?") ? url.search.slice(1) : url.search;
353
+ else if (component === "@query-param") throw new Error("@query-param requires a parameter name");
354
+ else if (component === "@status") throw new Error("@status is only valid for responses");
355
+ else if (component.startsWith("@")) throw new Error(`Unsupported derived component: ${component}`);
356
+ else {
357
+ const header = request.headers.get(component);
358
+ if (header == null) throw new Error(`Missing header: ${component}`);
359
+ value = header;
360
+ }
361
+ baseComponents.push(`"${component}": ${value}`);
362
+ }
363
+ const sigComponents = components.map((c) => `"${c}"`).join(" ");
364
+ baseComponents.push(`"@signature-params": (${sigComponents});${parameters}`);
365
+ return baseComponents.join("\n");
366
+ }
367
+ /**
368
+ * Formats a signature using rfc9421 format.
369
+ * @param signature The raw signature bytes.
370
+ * @param components The components that were signed.
371
+ * @param parameters The signature parameters.
372
+ * @returns The formatted signature string.
373
+ */
374
+ function formatRfc9421Signature(signature, components, parameters) {
375
+ const signatureInputValue = `sig1=("${components.join("\" \"")}");${parameters}`;
376
+ const signatureValue = `sig1=:${(0, byte_encodings_base64.encodeBase64)(signature)}:`;
377
+ return [signatureInputValue, signatureValue];
378
+ }
379
+ /**
380
+ * Parse RFC 9421 Signature-Input header.
381
+ * @param signatureInput The Signature-Input header value.
382
+ * @returns Parsed signature input parameters.
383
+ */
384
+ function parseRfc9421SignatureInput(signatureInput) {
385
+ let dict;
386
+ try {
387
+ dict = (0, structured_field_values.decodeDict)(signatureInput);
388
+ } catch (error) {
389
+ (0, __logtape_logtape.getLogger)([
390
+ "fedify",
391
+ "sig",
392
+ "http"
393
+ ]).debug("Failed to parse Signature-Input header: {signatureInput}", {
394
+ signatureInput,
395
+ error
396
+ });
397
+ return {};
398
+ }
399
+ const result = {};
400
+ for (const [label, item] of Object.entries(dict)) {
401
+ if (!Array.isArray(item.value) || typeof item.params.keyid !== "string" || typeof item.params.created !== "number") continue;
402
+ const components = item.value.map((subitem) => subitem.value).filter((v) => typeof v === "string");
403
+ const params = (0, structured_field_values.encodeItem)(new structured_field_values.Item(0, item.params));
404
+ result[label] = {
405
+ keyId: item.params.keyid,
406
+ alg: item.params.alg,
407
+ created: item.params.created,
408
+ components,
409
+ parameters: params.slice(params.indexOf(";") + 1)
410
+ };
411
+ }
412
+ return result;
413
+ }
414
+ /**
415
+ * Parse RFC 9421 Signature header.
416
+ * @param signature The Signature header value.
417
+ * @returns Parsed signature values.
418
+ */
419
+ function parseRfc9421Signature(signature) {
420
+ let dict;
421
+ try {
422
+ dict = (0, structured_field_values.decodeDict)(signature);
423
+ } catch (error) {
424
+ (0, __logtape_logtape.getLogger)([
425
+ "fedify",
426
+ "sig",
427
+ "http"
428
+ ]).debug("Failed to parse Signature header: {signature}", {
429
+ signature,
430
+ error
431
+ });
432
+ return {};
433
+ }
434
+ const result = {};
435
+ for (const [key, value] of Object.entries(dict)) if (value.value instanceof Uint8Array) result[key] = value.value;
436
+ return result;
437
+ }
438
+ async function signRequestRfc9421(request, privateKey, keyId, span, currentTime, bodyBuffer) {
439
+ if (privateKey.algorithm.name !== "RSASSA-PKCS1-v1_5") throw new TypeError("Unsupported algorithm: " + privateKey.algorithm.name);
440
+ const url = new URL(request.url);
441
+ const body = bodyBuffer !== void 0 ? bodyBuffer : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
442
+ const headers = new Headers(request.headers);
443
+ if (!headers.has("Host")) headers.set("Host", url.host);
444
+ if (!headers.has("Content-Digest") && body != null) {
445
+ const digest = await crypto.subtle.digest("SHA-256", body);
446
+ headers.set("Content-Digest", `sha-256=:${(0, byte_encodings_base64.encodeBase64)(digest)}:`);
447
+ if (span.isRecording()) span.setAttribute("http_signatures.digest.sha-256", (0, byte_encodings_hex.encodeHex)(digest));
448
+ }
449
+ currentTime ??= Temporal.Now.instant();
450
+ const created = currentTime.epochMilliseconds / 1e3 | 0;
451
+ if (!headers.has("Date")) headers.set("Date", new Date(currentTime.toString()).toUTCString());
452
+ const components = [
453
+ "@method",
454
+ "@target-uri",
455
+ "@authority",
456
+ "host",
457
+ "date"
458
+ ];
459
+ if (body != null) components.push("content-digest");
460
+ const signatureParams = formatRfc9421SignatureParameters({
461
+ algorithm: "rsa-v1_5-sha256",
462
+ keyId,
463
+ created
464
+ });
465
+ let signatureBase;
466
+ try {
467
+ signatureBase = createRfc9421SignatureBase(new Request(request.url, {
468
+ method: request.method,
469
+ headers
470
+ }), components, signatureParams);
471
+ } catch (error) {
472
+ throw new TypeError(`Failed to create signature base: ${String(error)}; it is probably a bug in the implementation. Please report it at Fedify's issue tracker.`);
473
+ }
474
+ const signatureBytes = await crypto.subtle.sign("RSASSA-PKCS1-v1_5", privateKey, new TextEncoder().encode(signatureBase));
475
+ const [signatureInput, signature] = formatRfc9421Signature(signatureBytes, components, signatureParams);
476
+ headers.set("Signature-Input", signatureInput);
477
+ headers.set("Signature", signature);
478
+ if (span.isRecording()) {
479
+ span.setAttribute("http_signatures.algorithm", "rsa-v1_5-sha256");
480
+ span.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(signatureBytes));
481
+ span.setAttribute("http_signatures.created", created.toString());
482
+ }
483
+ return new Request(request, {
484
+ headers,
485
+ body
486
+ });
487
+ }
488
+ const supportedHashAlgorithms = {
489
+ "sha": "SHA-1",
490
+ "sha-256": "SHA-256",
491
+ "sha-512": "SHA-512"
492
+ };
493
+ /**
494
+ * Verifies the signature of a request.
495
+ *
496
+ * Note that this function consumes the request body, so it should not be used
497
+ * if the request body is already consumed. Consuming the request body after
498
+ * calling this function is okay, since this function clones the request
499
+ * under the hood.
500
+ *
501
+ * @param request The request to verify.
502
+ * @param options Options for verifying the request.
503
+ * @returns The public key of the verified signature, or `null` if the signature
504
+ * could not be verified.
505
+ */
506
+ async function verifyRequest(request, options = {}) {
507
+ const tracerProvider = options.tracerProvider ?? __opentelemetry_api.trace.getTracerProvider();
508
+ const tracer = tracerProvider.getTracer(require_lookup.deno_default.name, require_lookup.deno_default.version);
509
+ return await tracer.startActiveSpan("http_signatures.verify", async (span) => {
510
+ if (span.isRecording()) {
511
+ span.setAttribute(__opentelemetry_semantic_conventions.ATTR_HTTP_REQUEST_METHOD, request.method);
512
+ span.setAttribute(__opentelemetry_semantic_conventions.ATTR_URL_FULL, request.url);
513
+ for (const [name, value] of request.headers) span.setAttribute((0, __opentelemetry_semantic_conventions.ATTR_HTTP_REQUEST_HEADER)(name), value);
514
+ }
515
+ try {
516
+ let spec = options.spec;
517
+ if (spec == null) spec = request.headers.has("Signature-Input") ? "rfc9421" : "draft-cavage-http-signatures-12";
518
+ let key;
519
+ if (spec === "rfc9421") key = await verifyRequestRfc9421(request, span, options);
520
+ else key = await verifyRequestDraft(request, span, options);
521
+ if (key == null) span.setStatus({ code: __opentelemetry_api.SpanStatusCode.ERROR });
522
+ return key;
523
+ } catch (error) {
524
+ span.setStatus({
525
+ code: __opentelemetry_api.SpanStatusCode.ERROR,
526
+ message: String(error)
527
+ });
528
+ throw error;
529
+ } finally {
530
+ span.end();
531
+ }
532
+ });
533
+ }
534
+ async function verifyRequestDraft(request, span, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, tracerProvider } = {}) {
535
+ const logger = (0, __logtape_logtape.getLogger)([
536
+ "fedify",
537
+ "sig",
538
+ "http"
539
+ ]);
540
+ if (request.bodyUsed) {
541
+ logger.error("Failed to verify; the request body is already consumed.", { url: request.url });
542
+ return null;
543
+ } else if (request.body?.locked) {
544
+ logger.error("Failed to verify; the request body is locked.", { url: request.url });
545
+ return null;
546
+ }
547
+ const originalRequest = request;
548
+ request = request.clone();
549
+ const dateHeader = request.headers.get("Date");
550
+ if (dateHeader == null) {
551
+ logger.debug("Failed to verify; no Date header found.", { headers: Object.fromEntries(request.headers.entries()) });
552
+ return null;
553
+ }
554
+ const sigHeader = request.headers.get("Signature");
555
+ if (sigHeader == null) {
556
+ logger.debug("Failed to verify; no Signature header found.", { headers: Object.fromEntries(request.headers.entries()) });
557
+ return null;
558
+ }
559
+ const digestHeader = request.headers.get("Digest");
560
+ if (request.method !== "GET" && request.method !== "HEAD" && digestHeader == null) {
561
+ logger.debug("Failed to verify; no Digest header found.", { headers: Object.fromEntries(request.headers.entries()) });
562
+ return null;
563
+ }
564
+ let body = null;
565
+ if (digestHeader != null) {
566
+ body = await request.arrayBuffer();
567
+ const digests = digestHeader.split(",").map((pair) => pair.includes("=") ? pair.split("=", 2) : [pair, ""]);
568
+ let matched = false;
569
+ for (let [algo, digestBase64] of digests) {
570
+ algo = algo.trim().toLowerCase();
571
+ if (!(algo in supportedHashAlgorithms)) continue;
572
+ let digest;
573
+ try {
574
+ digest = (0, byte_encodings_base64.decodeBase64)(digestBase64);
575
+ } catch (error) {
576
+ logger.debug("Failed to verify; invalid base64 encoding: {digest}.", {
577
+ digest: digestBase64,
578
+ error
579
+ });
580
+ return null;
581
+ }
582
+ if (span.isRecording()) span.setAttribute(`http_signatures.digest.${algo}`, (0, byte_encodings_hex.encodeHex)(digest));
583
+ const expectedDigest = await crypto.subtle.digest(supportedHashAlgorithms[algo], body);
584
+ if (!timingSafeEqual(digest, new Uint8Array(expectedDigest))) {
585
+ logger.debug("Failed to verify; digest mismatch ({algorithm}): {digest} != {expectedDigest}.", {
586
+ algorithm: algo,
587
+ digest: digestBase64,
588
+ expectedDigest: (0, byte_encodings_base64.encodeBase64)(expectedDigest)
589
+ });
590
+ return null;
591
+ }
592
+ matched = true;
593
+ }
594
+ if (!matched) {
595
+ logger.debug("Failed to verify; no supported digest algorithm found. Supported: {supportedAlgorithms}; found: {algorithms}.", {
596
+ supportedAlgorithms: Object.keys(supportedHashAlgorithms),
597
+ algorithms: digests.map(([algo]) => algo)
598
+ });
599
+ return null;
600
+ }
601
+ }
602
+ const date = Temporal.Instant.from(new Date(dateHeader).toISOString());
603
+ const now = currentTime ?? Temporal.Now.instant();
604
+ if (timeWindow !== false) {
605
+ const tw = timeWindow ?? { hours: 1 };
606
+ if (Temporal.Instant.compare(date, now.add(tw)) > 0) {
607
+ logger.debug("Failed to verify; Date is too far in the future.", {
608
+ date: date.toString(),
609
+ now: now.toString()
610
+ });
611
+ return null;
612
+ } else if (Temporal.Instant.compare(date, now.subtract(tw)) < 0) {
613
+ logger.debug("Failed to verify; Date is too far in the past.", {
614
+ date: date.toString(),
615
+ now: now.toString()
616
+ });
617
+ return null;
618
+ }
619
+ }
620
+ const sigValues = Object.fromEntries(sigHeader.split(",").map((pair) => pair.match(/^\s*([A-Za-z]+)=(?:"([^"]*)"|(\d+))\s*$/)).filter((m) => m != null).map((m) => [m[1], m[2] ?? m[3]]));
621
+ if (!("keyId" in sigValues)) {
622
+ logger.debug("Failed to verify; no keyId field found in the Signature header.", { signature: sigHeader });
623
+ return null;
624
+ } else if (!("headers" in sigValues)) {
625
+ logger.debug("Failed to verify; no headers field found in the Signature header.", { signature: sigHeader });
626
+ return null;
627
+ } else if (!("signature" in sigValues)) {
628
+ logger.debug("Failed to verify; no signature field found in the Signature header.", { signature: sigHeader });
629
+ return null;
630
+ }
631
+ if ("expires" in sigValues) {
632
+ const expiresSeconds = parseInt(sigValues.expires);
633
+ if (!Number.isInteger(expiresSeconds)) {
634
+ logger.debug("Failed to verify; invalid expires field in the Signature header: {expires}.", {
635
+ expires: sigValues.expires,
636
+ signature: sigHeader
637
+ });
638
+ return null;
639
+ }
640
+ const expires = Temporal.Instant.fromEpochMilliseconds(expiresSeconds * 1e3);
641
+ if (Temporal.Instant.compare(now, expires) > 0) {
642
+ logger.debug("Failed to verify; signature expired at {expires} (now: {now}).", {
643
+ expires: expires.toString(),
644
+ now: now.toString(),
645
+ signature: sigHeader
646
+ });
647
+ return null;
648
+ }
649
+ }
650
+ if ("created" in sigValues) {
651
+ const createdSeconds = parseInt(sigValues.created);
652
+ if (!Number.isInteger(createdSeconds)) {
653
+ logger.debug("Failed to verify; invalid created field in the Signature header: {created}.", {
654
+ created: sigValues.created,
655
+ signature: sigHeader
656
+ });
657
+ return null;
658
+ }
659
+ if (timeWindow !== false) {
660
+ const created = Temporal.Instant.fromEpochMilliseconds(createdSeconds * 1e3);
661
+ const tw = timeWindow ?? { minutes: 1 };
662
+ if (Temporal.Instant.compare(created, now.add(tw)) > 0) {
663
+ logger.debug("Failed to verify; created is too far in the future.", {
664
+ created: created.toString(),
665
+ now: now.toString()
666
+ });
667
+ return null;
668
+ } else if (Temporal.Instant.compare(created, now.subtract(tw)) < 0) {
669
+ logger.debug("Failed to verify; created is too far in the past.", {
670
+ created: created.toString(),
671
+ now: now.toString()
672
+ });
673
+ return null;
674
+ }
675
+ }
676
+ }
677
+ const { keyId, headers, signature } = sigValues;
678
+ span?.setAttribute("http_signatures.key_id", keyId);
679
+ if ("algorithm" in sigValues) span?.setAttribute("http_signatures.algorithm", sigValues.algorithm);
680
+ const { key, cached } = await fetchKey(new URL(keyId), require_actor.CryptographicKey, {
681
+ documentLoader,
682
+ contextLoader,
683
+ keyCache,
684
+ tracerProvider
685
+ });
686
+ if (key == null) return null;
687
+ const headerNames = headers.split(/\s+/g);
688
+ if (!headerNames.includes("(request-target)") || !headerNames.includes("date")) {
689
+ logger.debug("Failed to verify; required headers missing in the Signature header: {headers}.", { headers });
690
+ return null;
691
+ }
692
+ if (body != null && !headerNames.includes("digest")) {
693
+ logger.debug("Failed to verify; required headers missing in the Signature header: {headers}.", { headers });
694
+ return null;
695
+ }
696
+ const message = headerNames.map((name) => `${name}: ` + (name === "(request-target)" ? `${request.method.toLowerCase()} ${new URL(request.url).pathname}` : name === "(created)" ? sigValues.created ?? "" : name === "(expires)" ? sigValues.expires ?? "" : name === "host" ? request.headers.get("host") ?? new URL(request.url).host : request.headers.get(name))).join("\n");
697
+ const sig = (0, byte_encodings_base64.decodeBase64)(signature);
698
+ span?.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(sig));
699
+ const verified = await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, sig, new TextEncoder().encode(message));
700
+ if (!verified) {
701
+ if (cached) {
702
+ logger.debug("Failed to verify with the cached key {keyId}; signature {signature} is invalid. Retrying with the freshly fetched key...", {
703
+ keyId,
704
+ signature,
705
+ message
706
+ });
707
+ return await verifyRequest(originalRequest, {
708
+ documentLoader,
709
+ contextLoader,
710
+ timeWindow,
711
+ currentTime,
712
+ keyCache: {
713
+ get: () => Promise.resolve(void 0),
714
+ set: async (keyId$1, key$1) => await keyCache?.set(keyId$1, key$1)
715
+ }
716
+ });
717
+ }
718
+ logger.debug("Failed to verify with the fetched key {keyId}; signature {signature} is invalid. Check if the key is correct or if the signed message is correct. The message to sign is:\n{message}", {
719
+ keyId,
720
+ signature,
721
+ message
722
+ });
723
+ return null;
724
+ }
725
+ return key;
726
+ }
727
+ /**
728
+ * RFC 9421 map of algorithm identifiers to WebCrypto algorithms
729
+ */
730
+ const rfc9421AlgorithmMap = {
731
+ "rsa-v1_5-sha256": {
732
+ name: "RSASSA-PKCS1-v1_5",
733
+ hash: "SHA-256"
734
+ },
735
+ "rsa-v1_5-sha512": {
736
+ name: "RSASSA-PKCS1-v1_5",
737
+ hash: "SHA-512"
738
+ },
739
+ "rsa-pss-sha512": {
740
+ name: "RSA-PSS",
741
+ hash: "SHA-512"
742
+ },
743
+ "ecdsa-p256-sha256": {
744
+ name: "ECDSA",
745
+ hash: "SHA-256"
746
+ },
747
+ "ecdsa-p384-sha384": {
748
+ name: "ECDSA",
749
+ hash: "SHA-384"
750
+ },
751
+ "ed25519": { name: "Ed25519" }
752
+ };
753
+ /**
754
+ * Verifies a Content-Digest header according to RFC 9421.
755
+ * @param digestHeader The Content-Digest header value.
756
+ * @param body The message body to verify against.
757
+ * @returns Whether the digest is valid.
758
+ */
759
+ async function verifyRfc9421ContentDigest(digestHeader, body) {
760
+ const digests = digestHeader.split(",").map((pair) => {
761
+ pair = pair.trim();
762
+ const pos = pair.indexOf("=");
763
+ const algo = pos < 0 ? pair : pair.slice(0, pos);
764
+ const value = pos < 0 ? "" : pair.slice(pos + 1);
765
+ return {
766
+ algo: algo.trim().toLowerCase(),
767
+ value: value.trim()
768
+ };
769
+ });
770
+ for (const { algo, value } of digests) {
771
+ let hashAlgo;
772
+ if (algo === "sha-256") hashAlgo = "SHA-256";
773
+ else if (algo === "sha-512") hashAlgo = "SHA-512";
774
+ else continue;
775
+ const base64Match = value.match(/^:([^:]+):$/);
776
+ if (!base64Match) continue;
777
+ let digest;
778
+ try {
779
+ digest = (0, byte_encodings_base64.decodeBase64)(base64Match[1]);
780
+ } catch {
781
+ continue;
782
+ }
783
+ const calculatedDigest = await crypto.subtle.digest(hashAlgo, body);
784
+ if (timingSafeEqual(digest, new Uint8Array(calculatedDigest))) return true;
785
+ }
786
+ return false;
787
+ }
788
+ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, tracerProvider } = {}) {
789
+ const logger = (0, __logtape_logtape.getLogger)([
790
+ "fedify",
791
+ "sig",
792
+ "http"
793
+ ]);
794
+ if (request.bodyUsed) {
795
+ logger.error("Failed to verify; the request body is already consumed.", { url: request.url });
796
+ return null;
797
+ } else if (request.body?.locked) {
798
+ logger.error("Failed to verify; the request body is locked.", { url: request.url });
799
+ return null;
800
+ }
801
+ const originalRequest = request;
802
+ request = request.clone();
803
+ const signatureInputHeader = request.headers.get("Signature-Input");
804
+ if (!signatureInputHeader) {
805
+ logger.debug("Failed to verify; no Signature-Input header found.", { headers: Object.fromEntries(request.headers.entries()) });
806
+ return null;
807
+ }
808
+ const signatureHeader = request.headers.get("Signature");
809
+ if (!signatureHeader) {
810
+ logger.debug("Failed to verify; no Signature header found.", { headers: Object.fromEntries(request.headers.entries()) });
811
+ return null;
812
+ }
813
+ const signatureInputs = parseRfc9421SignatureInput(signatureInputHeader);
814
+ logger.debug("Parsed Signature-Input header: {signatureInputs}", { signatureInputs });
815
+ const signatures = parseRfc9421Signature(signatureHeader);
816
+ const signatureNames = Object.keys(signatureInputs);
817
+ if (signatureNames.length === 0) {
818
+ logger.debug("Failed to verify; no valid signatures found in Signature-Input header.", { header: signatureInputHeader });
819
+ return null;
820
+ }
821
+ let validKey = null;
822
+ for (const sigName of signatureNames) {
823
+ if (!signatures[sigName]) continue;
824
+ const sigInput = signatureInputs[sigName];
825
+ const sigBytes = signatures[sigName];
826
+ if (!sigInput.keyId) {
827
+ logger.debug("Failed to verify; missing keyId in signature {signatureName}.", {
828
+ signatureName: sigName,
829
+ signatureInput: signatureInputHeader
830
+ });
831
+ continue;
832
+ }
833
+ if (!sigInput.created) {
834
+ logger.debug("Failed to verify; missing created timestamp in signature {signatureName}.", {
835
+ signatureName: sigName,
836
+ signatureInput: signatureInputHeader
837
+ });
838
+ continue;
839
+ }
840
+ const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
841
+ const now = currentTime ?? Temporal.Now.instant();
842
+ if (timeWindow !== false) {
843
+ const tw = timeWindow ?? { hours: 1 };
844
+ if (Temporal.Instant.compare(signatureCreated, now.add(tw)) > 0) {
845
+ logger.debug("Failed to verify; signature created time is too far in the future.", {
846
+ created: signatureCreated.toString(),
847
+ now: now.toString()
848
+ });
849
+ continue;
850
+ } else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
851
+ logger.debug("Failed to verify; signature created time is too far in the past.", {
852
+ created: signatureCreated.toString(),
853
+ now: now.toString()
854
+ });
855
+ continue;
856
+ }
857
+ }
858
+ if (request.method !== "GET" && request.method !== "HEAD" && sigInput.components.includes("content-digest")) {
859
+ const contentDigestHeader = request.headers.get("Content-Digest");
860
+ if (!contentDigestHeader) {
861
+ logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
862
+ continue;
863
+ }
864
+ const body = await request.arrayBuffer();
865
+ const digestValid = await verifyRfc9421ContentDigest(contentDigestHeader, body);
866
+ if (!digestValid) {
867
+ logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
868
+ continue;
869
+ }
870
+ }
871
+ span?.setAttribute("http_signatures.key_id", sigInput.keyId);
872
+ span?.setAttribute("http_signatures.created", sigInput.created.toString());
873
+ const { key, cached } = await fetchKey(new URL(sigInput.keyId), require_actor.CryptographicKey, {
874
+ documentLoader,
875
+ contextLoader,
876
+ keyCache,
877
+ tracerProvider
878
+ });
879
+ if (!key) {
880
+ logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
881
+ continue;
882
+ }
883
+ let alg = sigInput.alg?.toLowerCase();
884
+ if (alg == null) {
885
+ if (key.publicKey.algorithm.name === "RSASSA-PKCS1-v1_5") alg = "hash" in key.publicKey.algorithm ? key.publicKey.algorithm.hash === "SHA-512" ? "rsa-v1_5-sha512" : "rsa-v1_5-sha256" : "rsa-v1_5-sha256";
886
+ else if (key.publicKey.algorithm.name === "RSA-PSS") alg = "rsa-pss-sha512";
887
+ else if (key.publicKey.algorithm.name === "ECDSA") alg = "namedCurve" in key.publicKey.algorithm && key.publicKey.algorithm.namedCurve === "P-256" ? "ecdsa-p256-sha256" : "ecdsa-p384-sha384";
888
+ else if (key.publicKey.algorithm.name === "Ed25519") alg = "ed25519";
889
+ }
890
+ if (alg) span?.setAttribute("http_signatures.algorithm", alg);
891
+ const algorithm = alg && rfc9421AlgorithmMap[alg];
892
+ if (!algorithm) {
893
+ logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
894
+ algorithm: sigInput.alg,
895
+ supported: Object.keys(rfc9421AlgorithmMap)
896
+ });
897
+ continue;
898
+ }
899
+ let signatureBase;
900
+ try {
901
+ signatureBase = createRfc9421SignatureBase(request, sigInput.components, sigInput.parameters);
902
+ } catch (error) {
903
+ logger.debug("Failed to create signature base for verification: {error}", {
904
+ error,
905
+ signatureInput: sigInput
906
+ });
907
+ continue;
908
+ }
909
+ const signatureBaseBytes = new TextEncoder().encode(signatureBase);
910
+ span?.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(sigBytes));
911
+ try {
912
+ const verified = await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes);
913
+ if (verified) {
914
+ validKey = key;
915
+ break;
916
+ } else if (cached) {
917
+ logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
918
+ return await verifyRequest(originalRequest, {
919
+ documentLoader,
920
+ contextLoader,
921
+ timeWindow,
922
+ currentTime,
923
+ keyCache: {
924
+ get: () => Promise.resolve(void 0),
925
+ set: async (keyId, key$1) => await keyCache?.set(keyId, key$1)
926
+ },
927
+ spec: "rfc9421"
928
+ });
929
+ } else logger.debug("Failed to verify signature with fetched key {keyId}; signature invalid.", {
930
+ keyId: sigInput.keyId,
931
+ signatureBase
932
+ });
933
+ } catch (error) {
934
+ logger.debug("Error during signature verification: {error}", {
935
+ error,
936
+ keyId: sigInput.keyId,
937
+ algorithm: sigInput.alg
938
+ });
939
+ }
940
+ }
941
+ return validKey;
942
+ }
943
+ /**
944
+ * Helper function to create a new Request for redirect handling.
945
+ * @param request The original request.
946
+ * @param location The redirect location.
947
+ * @param body The request body as ArrayBuffer or null.
948
+ * @returns A new Request object for the redirect.
949
+ */
950
+ function createRedirectRequest(request, location, body) {
951
+ const url = new URL(location, request.url);
952
+ return new Request(url, {
953
+ method: request.method,
954
+ headers: request.headers,
955
+ body,
956
+ redirect: "manual",
957
+ signal: request.signal,
958
+ mode: request.mode,
959
+ credentials: request.credentials,
960
+ referrer: request.referrer,
961
+ referrerPolicy: request.referrerPolicy,
962
+ integrity: request.integrity,
963
+ keepalive: request.keepalive,
964
+ cache: request.cache
965
+ });
966
+ }
967
+ /**
968
+ * Performs a double-knock request to the given URL. For the details of
969
+ * double-knocking, see
970
+ * <https://swicg.github.io/activitypub-http-signature/#how-to-upgrade-supported-versions>.
971
+ * @param request The request to send.
972
+ * @param identity The identity to use for signing the request.
973
+ * @param options The options for double-knock requests.
974
+ * @returns The response to the request.
975
+ * @since 1.6.0
976
+ */
977
+ async function doubleKnock(request, identity, options = {}) {
978
+ const { specDeterminer, log, tracerProvider, signal } = options;
979
+ const origin = new URL(request.url).origin;
980
+ const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
981
+ const body = options.body !== void 0 ? options.body : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
982
+ let signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
983
+ spec: firstTrySpec,
984
+ tracerProvider,
985
+ body
986
+ });
987
+ log?.(signedRequest);
988
+ let response = await fetch(signedRequest, {
989
+ redirect: "manual",
990
+ signal
991
+ });
992
+ if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
993
+ const location = response.headers.get("Location");
994
+ return doubleKnock(createRedirectRequest(request, location, body), identity, {
995
+ ...options,
996
+ body
997
+ });
998
+ } else if (response.status === 400 || response.status === 401 || response.status > 401) {
999
+ const spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
1000
+ (0, __logtape_logtape.getLogger)([
1001
+ "fedify",
1002
+ "sig",
1003
+ "http"
1004
+ ]).debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
1005
+ spec: firstTrySpec,
1006
+ secondSpec: spec,
1007
+ status: response.status,
1008
+ statusText: response.statusText
1009
+ });
1010
+ signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
1011
+ spec,
1012
+ tracerProvider,
1013
+ body
1014
+ });
1015
+ log?.(signedRequest);
1016
+ response = await fetch(signedRequest, {
1017
+ redirect: "manual",
1018
+ signal
1019
+ });
1020
+ if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
1021
+ const location = response.headers.get("Location");
1022
+ return doubleKnock(createRedirectRequest(request, location, body), identity, {
1023
+ ...options,
1024
+ body
1025
+ });
1026
+ } else if (response.status !== 400 && response.status !== 401) await specDeterminer?.rememberSpec(origin, spec);
1027
+ } else await specDeterminer?.rememberSpec(origin, firstTrySpec);
1028
+ return response;
1029
+ }
1030
+ /**
1031
+ * Performs a timing-safe equality comparison between two `Uint8Array` values.
1032
+ *
1033
+ * This function is designed to take a constant amount of time to execute,
1034
+ * dependent only on the length of the longer of the two arrays,
1035
+ * regardless of where the first difference in bytes occurs. This helps
1036
+ * prevent timing attacks.
1037
+ *
1038
+ * @param a The first bytes.
1039
+ * @param b The second bytes.
1040
+ * @returns `true` if the arrays are of the same length and contain the same
1041
+ * bytes, `false` otherwise.
1042
+ * @since 1.6.0
1043
+ */
1044
+ function timingSafeEqual(a, b) {
1045
+ const lenA = a.length;
1046
+ const lenB = b.length;
1047
+ const commonLength = Math.max(lenA, lenB);
1048
+ let result = 0;
1049
+ for (let i = 0; i < commonLength; i++) {
1050
+ const byteA = i < lenA ? a[i] : 0;
1051
+ const byteB = i < lenB ? b[i] : 0;
1052
+ result |= byteA ^ byteB;
1053
+ }
1054
+ result |= lenA ^ lenB;
1055
+ return result === 0;
1056
+ }
1057
+
1058
+ //#endregion
1059
+ Object.defineProperty(exports, 'doubleKnock', {
1060
+ enumerable: true,
1061
+ get: function () {
1062
+ return doubleKnock;
1063
+ }
1064
+ });
1065
+ Object.defineProperty(exports, 'exportJwk', {
1066
+ enumerable: true,
1067
+ get: function () {
1068
+ return exportJwk;
1069
+ }
1070
+ });
1071
+ Object.defineProperty(exports, 'fetchKey', {
1072
+ enumerable: true,
1073
+ get: function () {
1074
+ return fetchKey;
1075
+ }
1076
+ });
1077
+ Object.defineProperty(exports, 'generateCryptoKeyPair', {
1078
+ enumerable: true,
1079
+ get: function () {
1080
+ return generateCryptoKeyPair;
1081
+ }
1082
+ });
1083
+ Object.defineProperty(exports, 'importJwk', {
1084
+ enumerable: true,
1085
+ get: function () {
1086
+ return importJwk;
1087
+ }
1088
+ });
1089
+ Object.defineProperty(exports, 'signRequest', {
1090
+ enumerable: true,
1091
+ get: function () {
1092
+ return signRequest;
1093
+ }
1094
+ });
1095
+ Object.defineProperty(exports, 'validateCryptoKey', {
1096
+ enumerable: true,
1097
+ get: function () {
1098
+ return validateCryptoKey;
1099
+ }
1100
+ });
1101
+ Object.defineProperty(exports, 'verifyRequest', {
1102
+ enumerable: true,
1103
+ get: function () {
1104
+ return verifyRequest;
1105
+ }
1106
+ });