@fedify/fedify 2.4.0-dev.2169 → 2.4.0-dev.2193
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{builder-B2d_Pt0j.mjs → builder-0onlMY4u.mjs} +26 -6
- package/dist/{circuit-breaker-C5NqoLBJ.mjs → circuit-breaker-XJ53md4S.mjs} +16 -11
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{compound-proof-D28gMFqP.mjs → compound-proof-B-Fk6Slo.mjs} +3 -3
- package/dist/{context-BykNUf04.d.ts → context-9T--NSo4.d.ts} +55 -1
- package/dist/{context-L0Fnib9j.d.cts → context-CDcAgm4l.d.cts} +55 -1
- package/dist/{deno-DU8dvnWk.mjs → deno-DB_Sh0LK.mjs} +1 -1
- package/dist/{docloader-CghLH7Zi.mjs → docloader-DpWYYY6a.mjs} +25 -17
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/circuit-breaker.test.mjs +50 -1
- package/dist/federation/compound-fanout.test.mjs +3 -3
- package/dist/federation/handler-compound-proof.test.mjs +52 -3
- package/dist/federation/handler.test.mjs +5 -5
- package/dist/federation/hashlink-media.test.mjs +2 -2
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/keycache.test.mjs +1 -1
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +182 -13
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +2 -2
- package/dist/federation/mod.d.ts +2 -2
- package/dist/federation/mod.js +1 -1
- package/dist/federation/outgoing-proof.test.mjs +5 -5
- package/dist/federation/portable-actor.test.mjs +3 -3
- package/dist/federation/portable-chain.test.mjs +3 -3
- package/dist/federation/portable-collection.test.mjs +4 -4
- package/dist/federation/portable-gateway-keys.test.mjs +6 -6
- package/dist/federation/portable-inbox.test.mjs +35 -5
- package/dist/federation/portable-parse-uri.test.mjs +1 -1
- package/dist/federation/portable-route-activity.test.d.mts +2 -0
- package/dist/federation/portable-route-activity.test.mjs +259 -0
- package/dist/federation/portable.test.mjs +49 -5
- package/dist/federation/send.test.mjs +3 -3
- package/dist/federation/tasks/enqueue.test.mjs +1 -1
- package/dist/federation/tasks/tasks.test.mjs +2 -2
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +1 -1
- package/dist/{federation-DrNgkLfJ.cjs → federation-BsGo0EH2.cjs} +76 -35
- package/dist/{http-5CJBdU8n.cjs → http-BEivI0TS.cjs} +279 -87
- package/dist/{http-BGpexPO7.js → http-BGmxtpo2.js} +263 -89
- package/dist/{http-Cok5-vIC.d.ts → http-BrwKINQn.d.cts} +29 -1
- package/dist/{http-DC1cqAQ2.d.cts → http-DGx0KOws.d.ts} +29 -1
- package/dist/{http-Bd6rZlJX.mjs → http-DQf9Mw4V.mjs} +180 -92
- package/dist/{key-gIhVZO3p.mjs → key-DA3Yx8p2.mjs} +4 -4
- package/dist/{key-owner-evidence-Bo3yaeL5.mjs → key-owner-evidence-APB6NpSk.mjs} +1 -1
- package/dist/{keycache-PiOM9pM0.mjs → keycache-Da8A8mZa.mjs} +1 -1
- package/dist/{kv-cache-YRi_g8GB.cjs → kv-cache-1hafSoqH.cjs} +23 -15
- package/dist/{kv-cache-iR55fqlk.js → kv-cache-DduYhpee.js} +24 -16
- package/dist/{kv-cache-D0qjSeWY.mjs → kv-cache-OFBvrKh3.mjs} +1 -1
- package/dist/{ld-BsTkRrwF.mjs → ld-C15yzuY6.mjs} +3 -3
- package/dist/{metrics-jUDFfVgi.mjs → metrics-B1H23mlq.mjs} +1 -1
- package/dist/{middleware-Dq-OCmb6.mjs → middleware-BZQnf0nI.mjs} +1 -1
- package/dist/{middleware-Coqgp-K0.mjs → middleware-BqqycC0P.mjs} +52 -36
- package/dist/{middleware-DlxMxVp5.js → middleware-ClaUFaLb.js} +77 -36
- package/dist/{mod-DK0y9fd-.d.ts → mod-B3OBLj5g.d.ts} +1 -1
- package/dist/{mod-B5z_q9TH.d.ts → mod-C5Mwx5ty.d.ts} +5 -1
- package/dist/{mod-CPPqkyV_.d.cts → mod-Cy9VNHVw.d.ts} +14 -3
- package/dist/{mod-BLOXGs6m.d.cts → mod-D58KCqUg.d.cts} +1 -1
- package/dist/{mod--jv6vyrP.d.ts → mod-DoTWIc7F.d.cts} +14 -3
- package/dist/{mod-BA_F2x53.d.cts → mod-Dp3Ij7sw.d.cts} +5 -1
- package/dist/mod.cjs +4 -4
- package/dist/mod.d.cts +5 -5
- package/dist/mod.d.ts +5 -5
- package/dist/mod.js +4 -4
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/{owner-D3GoetXS.mjs → owner-BVpZBM-V.mjs} +5 -5
- package/dist/{portable-collection-CnlCamup.cjs → portable-collection-BFSShzwh.cjs} +1 -1
- package/dist/{portable-collection-Bkdh_WpL.js → portable-collection-D535cSz_.js} +1 -1
- package/dist/{portable-collection-C60_acmx.mjs → portable-collection-Dn2GdZdI.mjs} +2 -2
- package/dist/{portable-key-DyF5AnZo.mjs → portable-key-Bmwmq7Y6.mjs} +2 -2
- package/dist/{portable-key-id-Bg259c4Z.mjs → portable-key-id-Ca30-hOa.mjs} +45 -1
- package/dist/{proof-CQAYqWdj.mjs → proof-DrJWKeOl.mjs} +49 -7
- package/dist/{send-DjkQrxt5.mjs → send-CT3a-0Vd.mjs} +3 -3
- package/dist/sig/compound-proof-discovery.test.mjs +1 -1
- package/dist/sig/compound-proof-producer.test.mjs +1 -1
- package/dist/sig/compound-proof-verification.test.mjs +2 -2
- package/dist/sig/compound-proof.test.mjs +2 -2
- package/dist/sig/http.test.mjs +495 -23
- package/dist/sig/key-owner-evidence.test.mjs +2 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +2 -2
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +2 -2
- package/dist/sig/mod.d.ts +2 -2
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/portable-collection.test.mjs +1 -1
- package/dist/sig/portable-key-id.test.d.mts +2 -0
- package/dist/sig/portable-key-id.test.mjs +67 -0
- package/dist/sig/portable-key.test.mjs +7 -7
- package/dist/sig/proof.test.mjs +141 -7
- package/dist/{temporal-oRKP803M.mjs → temporal-DNqluyEc.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +52 -0
- package/dist/utils/docloader.test.mjs +93 -2
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +7 -7
|
@@ -16,7 +16,7 @@ json_canon = require_rolldown_runtime.__toESM(json_canon, 1);
|
|
|
16
16
|
let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
|
|
17
17
|
//#region deno.json
|
|
18
18
|
var name = "@fedify/fedify";
|
|
19
|
-
var version = "2.4.0-dev.
|
|
19
|
+
var version = "2.4.0-dev.2193+402149b5";
|
|
20
20
|
//#endregion
|
|
21
21
|
//#region src/sig/portable-key-id.ts
|
|
22
22
|
const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
|
|
@@ -117,6 +117,50 @@ function getCanonicalPortableId(id) {
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
/**
|
|
120
|
+
* Gets the [FEP-fe34] origin of an ID for same-origin checks. The origin of
|
|
121
|
+
* an `ap:` or `ap+ef61:` URI, or of a compatible identifier on any gateway,
|
|
122
|
+
* is the DID of its canonical portable ID, as FEP-ef61 treats a compatible
|
|
123
|
+
* identifier as the portable ID it stands for; unlike `getFe34Origin()`,
|
|
124
|
+
* which gives a compatible identifier the web origin of its gateway. The
|
|
125
|
+
* origin of any other HTTP(S) URL is its web origin, and that of a DID URL
|
|
126
|
+
* is its DID.
|
|
127
|
+
*
|
|
128
|
+
* Only the parsed `URL` is validated, so dot segments that URL parsing has
|
|
129
|
+
* already resolved cannot be detected here.
|
|
130
|
+
*
|
|
131
|
+
* [FEP-fe34]: https://w3id.org/fep/fe34
|
|
132
|
+
* @returns The origin, or `null` if the ID has none that is supported, or is
|
|
133
|
+
* a malformed portable ID or compatible identifier.
|
|
134
|
+
* @internal
|
|
135
|
+
*/
|
|
136
|
+
function getAuthenticationOrigin(id) {
|
|
137
|
+
try {
|
|
138
|
+
if (isPortableId(id)) {
|
|
139
|
+
const canonical = getCanonicalPortableId(id);
|
|
140
|
+
return canonical == null ? null : (0, _fedify_vocab_runtime.getFe34Origin)(canonical);
|
|
141
|
+
}
|
|
142
|
+
return (0, _fedify_vocab_runtime.getFe34Origin)(id);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (error instanceof TypeError) return null;
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Checks whether two IDs identify the same object. Two portable IDs, i.e.,
|
|
150
|
+
* `ap:` or `ap+ef61:` URIs or compatible identifiers, are compared by their
|
|
151
|
+
* canonical portable IDs, so that the same object on different gateways and
|
|
152
|
+
* with different location hints matches. Other IDs have to be equal as
|
|
153
|
+
* they are.
|
|
154
|
+
* @internal
|
|
155
|
+
*/
|
|
156
|
+
function isSameObjectId(a, b) {
|
|
157
|
+
const aPortable = isPortableId(a);
|
|
158
|
+
if (aPortable !== isPortableId(b)) return false;
|
|
159
|
+
if (!aPortable) return a.href === b.href;
|
|
160
|
+
const canonical = getCanonicalPortableId(a);
|
|
161
|
+
return canonical != null && canonical === getCanonicalPortableId(b);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
120
164
|
* Gets the canonical portable ID of an `ap:` or `ap+ef61:` URI or
|
|
121
165
|
* a compatible identifier as it is written, before any URL parsing.
|
|
122
166
|
* Parsing an ID into a `URL` resolves dot segments in its path, which would
|
|
@@ -7429,6 +7473,7 @@ const FEP_2277_COLLECTION_PROPERTIES = [
|
|
|
7429
7473
|
"prev",
|
|
7430
7474
|
"current"
|
|
7431
7475
|
].map((property) => AS_NAMESPACE + property);
|
|
7476
|
+
const FEP_EF61_GATEWAYS = "https://w3id.org/fep/ef61/gateways";
|
|
7432
7477
|
const PORTABLE_OBJECT_ID_PATTERN = /^ap(?:\+ef61)?:\/\//i;
|
|
7433
7478
|
const FUNCTIONAL_PROOF_PROPERTIES = [
|
|
7434
7479
|
`${SECURITY_NAMESPACE}cryptosuite`,
|
|
@@ -7455,6 +7500,35 @@ function hasValidPortableProofShape(proofValue) {
|
|
|
7455
7500
|
});
|
|
7456
7501
|
}
|
|
7457
7502
|
/**
|
|
7503
|
+
* Checks whether an expanded portable actor node has the `gateways` that
|
|
7504
|
+
* FEP-ef61 requires: a non-empty list whose items are all HTTP(S) URIs with
|
|
7505
|
+
* an empty path, query, and fragment. Like the generated vocabulary decoder,
|
|
7506
|
+
* this also tolerates a plain set of values instead of a `@list`, and gateway
|
|
7507
|
+
* strings given as `@value`s instead of `@id`s.
|
|
7508
|
+
*/
|
|
7509
|
+
function hasValidPortableActorGateways(node) {
|
|
7510
|
+
const values = node[FEP_EF61_GATEWAYS];
|
|
7511
|
+
if (!Array.isArray(values)) return false;
|
|
7512
|
+
let items = values;
|
|
7513
|
+
if (values.some((value) => isJsonLdNode(value) && "@list" in value)) {
|
|
7514
|
+
if (values.length !== 1) return false;
|
|
7515
|
+
items = values[0]["@list"];
|
|
7516
|
+
}
|
|
7517
|
+
if (!Array.isArray(items) || items.length < 1) return false;
|
|
7518
|
+
return items.every((item) => {
|
|
7519
|
+
if (!isJsonLdNode(item)) return false;
|
|
7520
|
+
const gateway = typeof item["@id"] === "string" ? item["@id"] : item["@value"];
|
|
7521
|
+
if (typeof gateway !== "string") return false;
|
|
7522
|
+
try {
|
|
7523
|
+
(0, _fedify_vocab_runtime.parseGatewayUrl)(gateway);
|
|
7524
|
+
} catch (error) {
|
|
7525
|
+
if (error instanceof TypeError) return false;
|
|
7526
|
+
throw error;
|
|
7527
|
+
}
|
|
7528
|
+
return true;
|
|
7529
|
+
});
|
|
7530
|
+
}
|
|
7531
|
+
/**
|
|
7458
7532
|
* Classifies an expanded JSON-LD node into an FEP-2277 core type by the
|
|
7459
7533
|
* properties it has.
|
|
7460
7534
|
* @internal
|
|
@@ -7658,6 +7732,15 @@ async function preparePortableObjectProof(jsonLd, options) {
|
|
|
7658
7732
|
}
|
|
7659
7733
|
};
|
|
7660
7734
|
}
|
|
7735
|
+
if (objectType === "actor" && !hasValidPortableActorGateways(root)) return {
|
|
7736
|
+
prepared: false,
|
|
7737
|
+
root,
|
|
7738
|
+
objectType,
|
|
7739
|
+
result: {
|
|
7740
|
+
verified: false,
|
|
7741
|
+
reason: { type: "invalidGateways" }
|
|
7742
|
+
}
|
|
7743
|
+
};
|
|
7661
7744
|
return {
|
|
7662
7745
|
prepared: true,
|
|
7663
7746
|
root,
|
|
@@ -7711,7 +7794,10 @@ async function verifyPortableObjectProofPolicy(jsonLd, key, options = {}) {
|
|
|
7711
7794
|
*
|
|
7712
7795
|
* Every proof must use a DID URL whose DID matches the portable object's
|
|
7713
7796
|
* authority, i.e., the DID of its canonical portable ID, and every proof must
|
|
7714
|
-
* pass {@link verifyProof}.
|
|
7797
|
+
* pass {@link verifyProof}. A portable actor must also have a non-empty
|
|
7798
|
+
* `gateways` list whose items are all HTTP(S) URIs with an empty path, query,
|
|
7799
|
+
* and fragment; otherwise, it is rejected with the `invalidGateways` reason
|
|
7800
|
+
* before its proofs are verified. The proofs are verified over the document as
|
|
7715
7801
|
* given; a compatible identifier is never rewritten into a portable ID.
|
|
7716
7802
|
*
|
|
7717
7803
|
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
@@ -8626,16 +8712,19 @@ function parseDraftSignature(signature) {
|
|
|
8626
8712
|
* Lists the HTTP Signatures that a request claims to carry, without verifying
|
|
8627
8713
|
* any of them. Signatures whose key IDs are not URLs are left out.
|
|
8628
8714
|
* @param request The request.
|
|
8715
|
+
* @param maxSignatures The maximum number of RFC 9421 signatures to consider,
|
|
8716
|
+
* counted the same way as by {@link verifyRequest}, so
|
|
8717
|
+
* that the signatures it ignores are not listed either.
|
|
8629
8718
|
* @returns The signatures; RFC 9421 ones if the request has
|
|
8630
8719
|
* a `Signature-Input` header, or a draft-cavage one otherwise.
|
|
8631
8720
|
* @internal
|
|
8632
8721
|
*/
|
|
8633
|
-
function listRequestSignatures(request) {
|
|
8722
|
+
function listRequestSignatures(request, maxSignatures = 3) {
|
|
8634
8723
|
const signatureInput = request.headers.get("Signature-Input");
|
|
8635
8724
|
if (signatureInput != null) {
|
|
8636
8725
|
const signatures = [];
|
|
8637
|
-
const inputs = parseRfc9421SignatureInput(signatureInput);
|
|
8638
|
-
for (const [label, input] of
|
|
8726
|
+
const inputs = Object.entries(parseRfc9421SignatureInput(signatureInput));
|
|
8727
|
+
for (const [label, input] of inputs.slice(0, maxSignatures)) {
|
|
8639
8728
|
const keyId = parseKeyId(input.keyId);
|
|
8640
8729
|
if (keyId == null) continue;
|
|
8641
8730
|
signatures.push({
|
|
@@ -8763,6 +8852,18 @@ async function signRequestRfc9421(request, privateKey, keyId, span, currentTime,
|
|
|
8763
8852
|
body
|
|
8764
8853
|
});
|
|
8765
8854
|
}
|
|
8855
|
+
/**
|
|
8856
|
+
* Checks that a maximum number of signatures to verify is a positive safe
|
|
8857
|
+
* integer or `Infinity`.
|
|
8858
|
+
* @param value The value to check.
|
|
8859
|
+
* @param name The name of the option, used in the error message.
|
|
8860
|
+
* @throws {RangeError} Thrown if the value is not valid.
|
|
8861
|
+
* @internal
|
|
8862
|
+
*/
|
|
8863
|
+
function validateMaxSignatures(value, name) {
|
|
8864
|
+
if (value === Infinity || Number.isSafeInteger(value) && value >= 1) return;
|
|
8865
|
+
throw new RangeError(`${name} must be a positive integer or Infinity.`);
|
|
8866
|
+
}
|
|
8766
8867
|
const supportedHashAlgorithms = {
|
|
8767
8868
|
"sha": "SHA-1",
|
|
8768
8869
|
"sha-256": "SHA-256",
|
|
@@ -8855,6 +8956,8 @@ function recordVerificationResult(span, result) {
|
|
|
8855
8956
|
* @param options Options for verifying the request.
|
|
8856
8957
|
* @returns The public key of the verified signature, or `null` if the signature
|
|
8857
8958
|
* could not be verified.
|
|
8959
|
+
* @throws {RangeError} Thrown if {@link VerifyRequestOptions.maxSignatures}
|
|
8960
|
+
* is not a positive integer or `Infinity`.
|
|
8858
8961
|
*/
|
|
8859
8962
|
async function verifyRequest(request, options = {}) {
|
|
8860
8963
|
const result = await verifyRequestDetailed(request, options);
|
|
@@ -8867,9 +8970,12 @@ async function verifyRequest(request, options = {}) {
|
|
|
8867
8970
|
* @param request The request to verify.
|
|
8868
8971
|
* @param options Options for verifying the request.
|
|
8869
8972
|
* @returns The verified public key, or a structured verification failure.
|
|
8973
|
+
* @throws {RangeError} Thrown if {@link VerifyRequestOptions.maxSignatures}
|
|
8974
|
+
* is not a positive integer or `Infinity`.
|
|
8870
8975
|
* @since 2.1.0
|
|
8871
8976
|
*/
|
|
8872
8977
|
async function verifyRequestDetailed(request, options = {}) {
|
|
8978
|
+
if (options.maxSignatures !== void 0) validateMaxSignatures(options.maxSignatures, "maxSignatures");
|
|
8873
8979
|
return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("http_signatures.verify", async (span) => {
|
|
8874
8980
|
if (span.isRecording()) {
|
|
8875
8981
|
span.setAttribute(_opentelemetry_semantic_conventions.ATTR_HTTP_REQUEST_METHOD, request.method);
|
|
@@ -9178,7 +9284,100 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
|
|
|
9178
9284
|
}
|
|
9179
9285
|
return false;
|
|
9180
9286
|
}
|
|
9181
|
-
|
|
9287
|
+
/**
|
|
9288
|
+
* Verifies one RFC 9421 signature of a request with a key.
|
|
9289
|
+
* @param request The request, whose body may be consumed already.
|
|
9290
|
+
* @param sigInput The parsed `Signature-Input` member of the signature.
|
|
9291
|
+
* @param sigBytes The bytes of the signature.
|
|
9292
|
+
* @param key The public key named by the signature.
|
|
9293
|
+
* @param span The span to record the algorithm and the signature on.
|
|
9294
|
+
* @returns Whether the signature is verified, and the algorithm of the
|
|
9295
|
+
* signature if it is supported.
|
|
9296
|
+
*/
|
|
9297
|
+
async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span) {
|
|
9298
|
+
const logger = (0, _logtape_logtape.getLogger)([
|
|
9299
|
+
"fedify",
|
|
9300
|
+
"sig",
|
|
9301
|
+
"http"
|
|
9302
|
+
]);
|
|
9303
|
+
let alg = sigInput.alg?.toLowerCase();
|
|
9304
|
+
if (alg == null) {
|
|
9305
|
+
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";
|
|
9306
|
+
else if (key.publicKey.algorithm.name === "RSA-PSS") alg = "rsa-pss-sha512";
|
|
9307
|
+
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";
|
|
9308
|
+
else if (key.publicKey.algorithm.name === "Ed25519") alg = "ed25519";
|
|
9309
|
+
}
|
|
9310
|
+
if (alg) span?.setAttribute("http_signatures.algorithm", alg);
|
|
9311
|
+
const algorithm = alg && rfc9421AlgorithmMap[alg];
|
|
9312
|
+
if (!algorithm) {
|
|
9313
|
+
logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
|
|
9314
|
+
algorithm: sigInput.alg,
|
|
9315
|
+
supported: Object.keys(rfc9421AlgorithmMap)
|
|
9316
|
+
});
|
|
9317
|
+
return {
|
|
9318
|
+
verified: false,
|
|
9319
|
+
mismatched: false
|
|
9320
|
+
};
|
|
9321
|
+
}
|
|
9322
|
+
const metricAlgorithm = alg;
|
|
9323
|
+
let signatureBase;
|
|
9324
|
+
try {
|
|
9325
|
+
signatureBase = createRfc9421SignatureBase(request, sigInput.components, sigInput.parameters);
|
|
9326
|
+
} catch (error) {
|
|
9327
|
+
logger.debug("Failed to create signature base for verification: {error}", {
|
|
9328
|
+
error,
|
|
9329
|
+
signatureInput: sigInput
|
|
9330
|
+
});
|
|
9331
|
+
return {
|
|
9332
|
+
verified: false,
|
|
9333
|
+
algorithm: metricAlgorithm,
|
|
9334
|
+
mismatched: false
|
|
9335
|
+
};
|
|
9336
|
+
}
|
|
9337
|
+
const signatureBaseBytes = new TextEncoder().encode(signatureBase);
|
|
9338
|
+
span?.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(sigBytes));
|
|
9339
|
+
const algorithmName = typeof algorithm === "string" ? algorithm : algorithm.name;
|
|
9340
|
+
if (key.publicKey.algorithm.name !== algorithmName) {
|
|
9341
|
+
logger.debug("Failed to verify; key {keyId} is not a key for algorithm {algorithm}.", {
|
|
9342
|
+
keyId: sigInput.keyId,
|
|
9343
|
+
algorithm: alg
|
|
9344
|
+
});
|
|
9345
|
+
return {
|
|
9346
|
+
verified: false,
|
|
9347
|
+
algorithm: metricAlgorithm,
|
|
9348
|
+
mismatched: true
|
|
9349
|
+
};
|
|
9350
|
+
}
|
|
9351
|
+
let verified;
|
|
9352
|
+
try {
|
|
9353
|
+
verified = await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes);
|
|
9354
|
+
} catch (error) {
|
|
9355
|
+
logger.debug("Error during signature verification: {error}", {
|
|
9356
|
+
error,
|
|
9357
|
+
keyId: sigInput.keyId,
|
|
9358
|
+
algorithm: sigInput.alg
|
|
9359
|
+
});
|
|
9360
|
+
return {
|
|
9361
|
+
verified: false,
|
|
9362
|
+
algorithm: metricAlgorithm,
|
|
9363
|
+
mismatched: false
|
|
9364
|
+
};
|
|
9365
|
+
}
|
|
9366
|
+
if (verified) return {
|
|
9367
|
+
verified: true,
|
|
9368
|
+
algorithm: metricAlgorithm
|
|
9369
|
+
};
|
|
9370
|
+
logger.debug("Failed to verify signature with key {keyId}; signature invalid.", {
|
|
9371
|
+
keyId: sigInput.keyId,
|
|
9372
|
+
signatureBase
|
|
9373
|
+
});
|
|
9374
|
+
return {
|
|
9375
|
+
verified: false,
|
|
9376
|
+
algorithm: metricAlgorithm,
|
|
9377
|
+
mismatched: true
|
|
9378
|
+
};
|
|
9379
|
+
}
|
|
9380
|
+
async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, maxSignatures = 3, meterProvider, tracerProvider } = {}) {
|
|
9182
9381
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
9183
9382
|
"fedify",
|
|
9184
9383
|
"sig",
|
|
@@ -9191,7 +9390,6 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
|
|
|
9191
9390
|
logger.error("Failed to verify; the request body is locked.", { url: request.url });
|
|
9192
9391
|
return noSignatureResult();
|
|
9193
9392
|
}
|
|
9194
|
-
const originalRequest = request;
|
|
9195
9393
|
request = request.clone();
|
|
9196
9394
|
const signatureInputHeader = request.headers.get("Signature-Input");
|
|
9197
9395
|
if (!signatureInputHeader) {
|
|
@@ -9211,6 +9409,29 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
|
|
|
9211
9409
|
logger.debug("Failed to verify; no valid signatures found in Signature-Input header.", { header: signatureInputHeader });
|
|
9212
9410
|
return invalidSignatureResult(null);
|
|
9213
9411
|
}
|
|
9412
|
+
if (signatureNames.length > maxSignatures) {
|
|
9413
|
+
logger.debug("Verifying only the first {maxSignatures} of the {count} signatures in the Signature-Input header; the rest are ignored.", {
|
|
9414
|
+
maxSignatures,
|
|
9415
|
+
count: signatureNames.length
|
|
9416
|
+
});
|
|
9417
|
+
signatureNames.splice(maxSignatures);
|
|
9418
|
+
}
|
|
9419
|
+
const keyLookups = /* @__PURE__ */ new Map();
|
|
9420
|
+
const refreshedKeyIds = /* @__PURE__ */ new Set();
|
|
9421
|
+
const lookUpKey = (keyId, keyCache) => measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyId, _fedify_vocab.CryptographicKey, {
|
|
9422
|
+
documentLoader,
|
|
9423
|
+
contextLoader,
|
|
9424
|
+
keyCache,
|
|
9425
|
+
tracerProvider,
|
|
9426
|
+
meterProvider,
|
|
9427
|
+
portableKeyResolvers: createPortableKeyResolvers({
|
|
9428
|
+
documentLoader,
|
|
9429
|
+
contextLoader,
|
|
9430
|
+
keyCache,
|
|
9431
|
+
tracerProvider,
|
|
9432
|
+
meterProvider
|
|
9433
|
+
})
|
|
9434
|
+
}));
|
|
9214
9435
|
let failure = noSignatureResult();
|
|
9215
9436
|
let failureAlgorithm;
|
|
9216
9437
|
const setFailure = (result, algorithm) => {
|
|
@@ -9284,94 +9505,40 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
|
|
|
9284
9505
|
setFailure(invalidSignatureResult(null));
|
|
9285
9506
|
continue;
|
|
9286
9507
|
}
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
tracerProvider,
|
|
9292
|
-
meterProvider,
|
|
9293
|
-
portableKeyResolvers: createPortableKeyResolvers({
|
|
9294
|
-
documentLoader,
|
|
9295
|
-
contextLoader,
|
|
9296
|
-
keyCache,
|
|
9297
|
-
tracerProvider,
|
|
9298
|
-
meterProvider
|
|
9299
|
-
})
|
|
9300
|
-
}));
|
|
9301
|
-
if (fetchError != null) {
|
|
9302
|
-
setFailure(keyFetchErrorResult(keyId, fetchError));
|
|
9303
|
-
continue;
|
|
9304
|
-
}
|
|
9305
|
-
if (!key) {
|
|
9306
|
-
logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
|
|
9307
|
-
setFailure(invalidSignatureResult(keyId));
|
|
9308
|
-
continue;
|
|
9309
|
-
}
|
|
9310
|
-
let alg = sigInput.alg?.toLowerCase();
|
|
9311
|
-
if (alg == null) {
|
|
9312
|
-
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";
|
|
9313
|
-
else if (key.publicKey.algorithm.name === "RSA-PSS") alg = "rsa-pss-sha512";
|
|
9314
|
-
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";
|
|
9315
|
-
else if (key.publicKey.algorithm.name === "Ed25519") alg = "ed25519";
|
|
9508
|
+
let lookup = keyLookups.get(keyId.href);
|
|
9509
|
+
if (lookup == null) {
|
|
9510
|
+
lookup = await lookUpKey(keyId, keyCache);
|
|
9511
|
+
keyLookups.set(keyId.href, lookup);
|
|
9316
9512
|
}
|
|
9317
|
-
|
|
9318
|
-
|
|
9319
|
-
|
|
9320
|
-
|
|
9321
|
-
|
|
9322
|
-
|
|
9323
|
-
|
|
9324
|
-
|
|
9325
|
-
|
|
9326
|
-
|
|
9327
|
-
|
|
9328
|
-
|
|
9329
|
-
|
|
9330
|
-
|
|
9331
|
-
} catch (error) {
|
|
9332
|
-
logger.debug("Failed to create signature base for verification: {error}", {
|
|
9333
|
-
error,
|
|
9334
|
-
signatureInput: sigInput
|
|
9335
|
-
});
|
|
9336
|
-
setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
|
|
9337
|
-
continue;
|
|
9338
|
-
}
|
|
9339
|
-
const signatureBaseBytes = new TextEncoder().encode(signatureBase);
|
|
9340
|
-
span?.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(sigBytes));
|
|
9341
|
-
try {
|
|
9342
|
-
if (await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes)) {
|
|
9343
|
-
metricsContext.algorithm = candidateAlgorithm;
|
|
9513
|
+
while (true) {
|
|
9514
|
+
const { key, cached, fetchError } = lookup;
|
|
9515
|
+
if (fetchError != null) {
|
|
9516
|
+
setFailure(keyFetchErrorResult(keyId, fetchError));
|
|
9517
|
+
break;
|
|
9518
|
+
}
|
|
9519
|
+
if (!key) {
|
|
9520
|
+
logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
|
|
9521
|
+
setFailure(invalidSignatureResult(keyId));
|
|
9522
|
+
break;
|
|
9523
|
+
}
|
|
9524
|
+
const result = await verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span);
|
|
9525
|
+
if (result.verified) {
|
|
9526
|
+
metricsContext.algorithm = result.algorithm;
|
|
9344
9527
|
return {
|
|
9345
9528
|
verified: true,
|
|
9346
9529
|
key,
|
|
9347
9530
|
signatureLabel: sigName
|
|
9348
9531
|
};
|
|
9349
|
-
}
|
|
9532
|
+
}
|
|
9533
|
+
if (result.mismatched && cached && !refreshedKeyIds.has(keyId.href)) {
|
|
9350
9534
|
logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
currentTime,
|
|
9356
|
-
keyCache: bypassKeyCacheReads(keyCache),
|
|
9357
|
-
spec: "rfc9421",
|
|
9358
|
-
meterProvider,
|
|
9359
|
-
tracerProvider
|
|
9360
|
-
});
|
|
9361
|
-
} else {
|
|
9362
|
-
logger.debug("Failed to verify signature with fetched key {keyId}; signature invalid.", {
|
|
9363
|
-
keyId: sigInput.keyId,
|
|
9364
|
-
signatureBase
|
|
9365
|
-
});
|
|
9366
|
-
setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
|
|
9535
|
+
refreshedKeyIds.add(keyId.href);
|
|
9536
|
+
lookup = await lookUpKey(keyId, bypassKeyCacheReads(keyCache));
|
|
9537
|
+
keyLookups.set(keyId.href, lookup);
|
|
9538
|
+
continue;
|
|
9367
9539
|
}
|
|
9368
|
-
|
|
9369
|
-
|
|
9370
|
-
error,
|
|
9371
|
-
keyId: sigInput.keyId,
|
|
9372
|
-
algorithm: sigInput.alg
|
|
9373
|
-
});
|
|
9374
|
-
setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
|
|
9540
|
+
setFailure(invalidSignatureResult(keyId), result.algorithm);
|
|
9541
|
+
break;
|
|
9375
9542
|
}
|
|
9376
9543
|
}
|
|
9377
9544
|
metricsContext.algorithm = failureAlgorithm;
|
|
@@ -9471,16 +9638,19 @@ async function doubleKnock(request, identity, options = {}) {
|
|
|
9471
9638
|
}
|
|
9472
9639
|
async function doubleKnockInternal(request, identity, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
|
|
9473
9640
|
const { specDeterminer, log, tracerProvider, signal } = options;
|
|
9641
|
+
signal?.throwIfAborted();
|
|
9474
9642
|
const maximumRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
9475
9643
|
visited.add(request.url);
|
|
9476
9644
|
const origin = new URL(request.url).origin;
|
|
9477
9645
|
const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
|
|
9646
|
+
signal?.throwIfAborted();
|
|
9478
9647
|
const body = options.body !== void 0 ? options.body : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
|
|
9479
9648
|
let signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
9480
9649
|
spec: firstTrySpec,
|
|
9481
9650
|
tracerProvider,
|
|
9482
9651
|
body
|
|
9483
9652
|
});
|
|
9653
|
+
signal?.throwIfAborted();
|
|
9484
9654
|
log?.(signedRequest);
|
|
9485
9655
|
let response = await fetchDoubleKnockRequest(request, signedRequest, signal);
|
|
9486
9656
|
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
@@ -9505,6 +9675,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
|
|
|
9505
9675
|
let fulfilled = false;
|
|
9506
9676
|
let challengeRequest;
|
|
9507
9677
|
for (const entry of entries) {
|
|
9678
|
+
signal?.throwIfAborted();
|
|
9508
9679
|
const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
|
|
9509
9680
|
if (rfc9421 == null) continue;
|
|
9510
9681
|
logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
|
|
@@ -9527,6 +9698,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
|
|
|
9527
9698
|
}
|
|
9528
9699
|
}
|
|
9529
9700
|
if (fulfilled && challengeRequest != null) {
|
|
9701
|
+
signal?.throwIfAborted();
|
|
9530
9702
|
signedRequest = challengeRequest;
|
|
9531
9703
|
log?.(signedRequest);
|
|
9532
9704
|
response = await fetch(signedRequest, {
|
|
@@ -9551,11 +9723,13 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
|
|
|
9551
9723
|
status: response.status,
|
|
9552
9724
|
statusText: response.statusText
|
|
9553
9725
|
});
|
|
9726
|
+
signal?.throwIfAborted();
|
|
9554
9727
|
signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
9555
9728
|
spec,
|
|
9556
9729
|
tracerProvider,
|
|
9557
9730
|
body
|
|
9558
9731
|
});
|
|
9732
|
+
signal?.throwIfAborted();
|
|
9559
9733
|
log?.(signedRequest);
|
|
9560
9734
|
response = await fetchDoubleKnockRequest(request, signedRequest, signal);
|
|
9561
9735
|
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
@@ -9718,6 +9892,12 @@ Object.defineProperty(exports, "generateCryptoKeyPair", {
|
|
|
9718
9892
|
return generateCryptoKeyPair;
|
|
9719
9893
|
}
|
|
9720
9894
|
});
|
|
9895
|
+
Object.defineProperty(exports, "getAuthenticationOrigin", {
|
|
9896
|
+
enumerable: true,
|
|
9897
|
+
get: function() {
|
|
9898
|
+
return getAuthenticationOrigin;
|
|
9899
|
+
}
|
|
9900
|
+
});
|
|
9721
9901
|
Object.defineProperty(exports, "getCanonicalPortableId", {
|
|
9722
9902
|
enumerable: true,
|
|
9723
9903
|
get: function() {
|
|
@@ -9850,6 +10030,12 @@ Object.defineProperty(exports, "isPortableUri", {
|
|
|
9850
10030
|
return isPortableUri;
|
|
9851
10031
|
}
|
|
9852
10032
|
});
|
|
10033
|
+
Object.defineProperty(exports, "isSameObjectId", {
|
|
10034
|
+
enumerable: true,
|
|
10035
|
+
get: function() {
|
|
10036
|
+
return isSameObjectId;
|
|
10037
|
+
}
|
|
10038
|
+
});
|
|
9853
10039
|
Object.defineProperty(exports, "isSamePublicKey", {
|
|
9854
10040
|
enumerable: true,
|
|
9855
10041
|
get: function() {
|
|
@@ -10012,6 +10198,12 @@ Object.defineProperty(exports, "validateCryptoKey", {
|
|
|
10012
10198
|
return validateCryptoKey;
|
|
10013
10199
|
}
|
|
10014
10200
|
});
|
|
10201
|
+
Object.defineProperty(exports, "validateMaxSignatures", {
|
|
10202
|
+
enumerable: true,
|
|
10203
|
+
get: function() {
|
|
10204
|
+
return validateMaxSignatures;
|
|
10205
|
+
}
|
|
10206
|
+
});
|
|
10015
10207
|
Object.defineProperty(exports, "verifyCompactJsonLd", {
|
|
10016
10208
|
enumerable: true,
|
|
10017
10209
|
get: function() {
|