@fedify/fedify 2.4.0-dev.2169 → 2.4.0-dev.2190
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-CFF0YNTp.mjs} +3 -3
- 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-F2jjkrt0.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-EopndaNb.mjs} +1 -1
- package/dist/{docloader-CghLH7Zi.mjs → docloader-u6zzOExF.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 +3 -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 +137 -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 +3 -3
- 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-BSS2dUO7.cjs} +38 -21
- package/dist/{http-BGpexPO7.js → http-BqN5kyUf.js} +219 -87
- package/dist/{http-Cok5-vIC.d.ts → http-BrwKINQn.d.cts} +29 -1
- package/dist/{http-5CJBdU8n.cjs → http-CCr2_ipG.cjs} +236 -86
- package/dist/{http-DC1cqAQ2.d.cts → http-DGx0KOws.d.ts} +29 -1
- package/dist/{http-Bd6rZlJX.mjs → http-Y1ObT1A8.mjs} +180 -92
- package/dist/{key-gIhVZO3p.mjs → key-DyXf88bq.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-iR55fqlk.js → kv-cache-C02XitwK.js} +24 -16
- package/dist/{kv-cache-D0qjSeWY.mjs → kv-cache-C8YjNAi_.mjs} +1 -1
- package/dist/{kv-cache-YRi_g8GB.cjs → kv-cache-DUXluAc0.cjs} +23 -15
- package/dist/{ld-BsTkRrwF.mjs → ld-oQj6j_hP.mjs} +3 -3
- package/dist/{metrics-jUDFfVgi.mjs → metrics-zMe834ID.mjs} +1 -1
- package/dist/{middleware-Coqgp-K0.mjs → middleware-Bdu2-OPD.mjs} +37 -25
- package/dist/{middleware-DlxMxVp5.js → middleware-CaR2MRWY.js} +39 -22
- package/dist/{middleware-Dq-OCmb6.mjs → middleware-Ck9yghxs.mjs} +1 -1
- 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--jv6vyrP.d.ts → mod-Ck9_lSJB.d.ts} +1 -1
- package/dist/{mod-BLOXGs6m.d.cts → mod-D58KCqUg.d.cts} +1 -1
- package/dist/{mod-CPPqkyV_.d.cts → mod-DHTuu3l8.d.cts} +1 -1
- 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-Dem4fbwc.mjs} +5 -5
- package/dist/{portable-collection-Bkdh_WpL.js → portable-collection-BEqLrMW3.js} +1 -1
- package/dist/{portable-collection-C60_acmx.mjs → portable-collection-BL6aZlQ0.mjs} +2 -2
- package/dist/{portable-collection-CnlCamup.cjs → portable-collection-DJe_bk50.cjs} +1 -1
- package/dist/{portable-key-DyF5AnZo.mjs → portable-key-Duue9Y2Y.mjs} +2 -2
- package/dist/{portable-key-id-Bg259c4Z.mjs → portable-key-id-Ca30-hOa.mjs} +45 -1
- package/dist/{proof-CQAYqWdj.mjs → proof-C9115DX_.mjs} +5 -5
- package/dist/{send-DjkQrxt5.mjs → send-D0hxFFRB.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 +3 -3
- package/dist/{temporal-oRKP803M.mjs → temporal-DKchpKyp.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.2190+be3a8be0";
|
|
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
|
|
@@ -8626,16 +8670,19 @@ function parseDraftSignature(signature) {
|
|
|
8626
8670
|
* Lists the HTTP Signatures that a request claims to carry, without verifying
|
|
8627
8671
|
* any of them. Signatures whose key IDs are not URLs are left out.
|
|
8628
8672
|
* @param request The request.
|
|
8673
|
+
* @param maxSignatures The maximum number of RFC 9421 signatures to consider,
|
|
8674
|
+
* counted the same way as by {@link verifyRequest}, so
|
|
8675
|
+
* that the signatures it ignores are not listed either.
|
|
8629
8676
|
* @returns The signatures; RFC 9421 ones if the request has
|
|
8630
8677
|
* a `Signature-Input` header, or a draft-cavage one otherwise.
|
|
8631
8678
|
* @internal
|
|
8632
8679
|
*/
|
|
8633
|
-
function listRequestSignatures(request) {
|
|
8680
|
+
function listRequestSignatures(request, maxSignatures = 3) {
|
|
8634
8681
|
const signatureInput = request.headers.get("Signature-Input");
|
|
8635
8682
|
if (signatureInput != null) {
|
|
8636
8683
|
const signatures = [];
|
|
8637
|
-
const inputs = parseRfc9421SignatureInput(signatureInput);
|
|
8638
|
-
for (const [label, input] of
|
|
8684
|
+
const inputs = Object.entries(parseRfc9421SignatureInput(signatureInput));
|
|
8685
|
+
for (const [label, input] of inputs.slice(0, maxSignatures)) {
|
|
8639
8686
|
const keyId = parseKeyId(input.keyId);
|
|
8640
8687
|
if (keyId == null) continue;
|
|
8641
8688
|
signatures.push({
|
|
@@ -8763,6 +8810,18 @@ async function signRequestRfc9421(request, privateKey, keyId, span, currentTime,
|
|
|
8763
8810
|
body
|
|
8764
8811
|
});
|
|
8765
8812
|
}
|
|
8813
|
+
/**
|
|
8814
|
+
* Checks that a maximum number of signatures to verify is a positive safe
|
|
8815
|
+
* integer or `Infinity`.
|
|
8816
|
+
* @param value The value to check.
|
|
8817
|
+
* @param name The name of the option, used in the error message.
|
|
8818
|
+
* @throws {RangeError} Thrown if the value is not valid.
|
|
8819
|
+
* @internal
|
|
8820
|
+
*/
|
|
8821
|
+
function validateMaxSignatures(value, name) {
|
|
8822
|
+
if (value === Infinity || Number.isSafeInteger(value) && value >= 1) return;
|
|
8823
|
+
throw new RangeError(`${name} must be a positive integer or Infinity.`);
|
|
8824
|
+
}
|
|
8766
8825
|
const supportedHashAlgorithms = {
|
|
8767
8826
|
"sha": "SHA-1",
|
|
8768
8827
|
"sha-256": "SHA-256",
|
|
@@ -8855,6 +8914,8 @@ function recordVerificationResult(span, result) {
|
|
|
8855
8914
|
* @param options Options for verifying the request.
|
|
8856
8915
|
* @returns The public key of the verified signature, or `null` if the signature
|
|
8857
8916
|
* could not be verified.
|
|
8917
|
+
* @throws {RangeError} Thrown if {@link VerifyRequestOptions.maxSignatures}
|
|
8918
|
+
* is not a positive integer or `Infinity`.
|
|
8858
8919
|
*/
|
|
8859
8920
|
async function verifyRequest(request, options = {}) {
|
|
8860
8921
|
const result = await verifyRequestDetailed(request, options);
|
|
@@ -8867,9 +8928,12 @@ async function verifyRequest(request, options = {}) {
|
|
|
8867
8928
|
* @param request The request to verify.
|
|
8868
8929
|
* @param options Options for verifying the request.
|
|
8869
8930
|
* @returns The verified public key, or a structured verification failure.
|
|
8931
|
+
* @throws {RangeError} Thrown if {@link VerifyRequestOptions.maxSignatures}
|
|
8932
|
+
* is not a positive integer or `Infinity`.
|
|
8870
8933
|
* @since 2.1.0
|
|
8871
8934
|
*/
|
|
8872
8935
|
async function verifyRequestDetailed(request, options = {}) {
|
|
8936
|
+
if (options.maxSignatures !== void 0) validateMaxSignatures(options.maxSignatures, "maxSignatures");
|
|
8873
8937
|
return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("http_signatures.verify", async (span) => {
|
|
8874
8938
|
if (span.isRecording()) {
|
|
8875
8939
|
span.setAttribute(_opentelemetry_semantic_conventions.ATTR_HTTP_REQUEST_METHOD, request.method);
|
|
@@ -9178,7 +9242,100 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
|
|
|
9178
9242
|
}
|
|
9179
9243
|
return false;
|
|
9180
9244
|
}
|
|
9181
|
-
|
|
9245
|
+
/**
|
|
9246
|
+
* Verifies one RFC 9421 signature of a request with a key.
|
|
9247
|
+
* @param request The request, whose body may be consumed already.
|
|
9248
|
+
* @param sigInput The parsed `Signature-Input` member of the signature.
|
|
9249
|
+
* @param sigBytes The bytes of the signature.
|
|
9250
|
+
* @param key The public key named by the signature.
|
|
9251
|
+
* @param span The span to record the algorithm and the signature on.
|
|
9252
|
+
* @returns Whether the signature is verified, and the algorithm of the
|
|
9253
|
+
* signature if it is supported.
|
|
9254
|
+
*/
|
|
9255
|
+
async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span) {
|
|
9256
|
+
const logger = (0, _logtape_logtape.getLogger)([
|
|
9257
|
+
"fedify",
|
|
9258
|
+
"sig",
|
|
9259
|
+
"http"
|
|
9260
|
+
]);
|
|
9261
|
+
let alg = sigInput.alg?.toLowerCase();
|
|
9262
|
+
if (alg == null) {
|
|
9263
|
+
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";
|
|
9264
|
+
else if (key.publicKey.algorithm.name === "RSA-PSS") alg = "rsa-pss-sha512";
|
|
9265
|
+
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";
|
|
9266
|
+
else if (key.publicKey.algorithm.name === "Ed25519") alg = "ed25519";
|
|
9267
|
+
}
|
|
9268
|
+
if (alg) span?.setAttribute("http_signatures.algorithm", alg);
|
|
9269
|
+
const algorithm = alg && rfc9421AlgorithmMap[alg];
|
|
9270
|
+
if (!algorithm) {
|
|
9271
|
+
logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
|
|
9272
|
+
algorithm: sigInput.alg,
|
|
9273
|
+
supported: Object.keys(rfc9421AlgorithmMap)
|
|
9274
|
+
});
|
|
9275
|
+
return {
|
|
9276
|
+
verified: false,
|
|
9277
|
+
mismatched: false
|
|
9278
|
+
};
|
|
9279
|
+
}
|
|
9280
|
+
const metricAlgorithm = alg;
|
|
9281
|
+
let signatureBase;
|
|
9282
|
+
try {
|
|
9283
|
+
signatureBase = createRfc9421SignatureBase(request, sigInput.components, sigInput.parameters);
|
|
9284
|
+
} catch (error) {
|
|
9285
|
+
logger.debug("Failed to create signature base for verification: {error}", {
|
|
9286
|
+
error,
|
|
9287
|
+
signatureInput: sigInput
|
|
9288
|
+
});
|
|
9289
|
+
return {
|
|
9290
|
+
verified: false,
|
|
9291
|
+
algorithm: metricAlgorithm,
|
|
9292
|
+
mismatched: false
|
|
9293
|
+
};
|
|
9294
|
+
}
|
|
9295
|
+
const signatureBaseBytes = new TextEncoder().encode(signatureBase);
|
|
9296
|
+
span?.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(sigBytes));
|
|
9297
|
+
const algorithmName = typeof algorithm === "string" ? algorithm : algorithm.name;
|
|
9298
|
+
if (key.publicKey.algorithm.name !== algorithmName) {
|
|
9299
|
+
logger.debug("Failed to verify; key {keyId} is not a key for algorithm {algorithm}.", {
|
|
9300
|
+
keyId: sigInput.keyId,
|
|
9301
|
+
algorithm: alg
|
|
9302
|
+
});
|
|
9303
|
+
return {
|
|
9304
|
+
verified: false,
|
|
9305
|
+
algorithm: metricAlgorithm,
|
|
9306
|
+
mismatched: true
|
|
9307
|
+
};
|
|
9308
|
+
}
|
|
9309
|
+
let verified;
|
|
9310
|
+
try {
|
|
9311
|
+
verified = await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes);
|
|
9312
|
+
} catch (error) {
|
|
9313
|
+
logger.debug("Error during signature verification: {error}", {
|
|
9314
|
+
error,
|
|
9315
|
+
keyId: sigInput.keyId,
|
|
9316
|
+
algorithm: sigInput.alg
|
|
9317
|
+
});
|
|
9318
|
+
return {
|
|
9319
|
+
verified: false,
|
|
9320
|
+
algorithm: metricAlgorithm,
|
|
9321
|
+
mismatched: false
|
|
9322
|
+
};
|
|
9323
|
+
}
|
|
9324
|
+
if (verified) return {
|
|
9325
|
+
verified: true,
|
|
9326
|
+
algorithm: metricAlgorithm
|
|
9327
|
+
};
|
|
9328
|
+
logger.debug("Failed to verify signature with key {keyId}; signature invalid.", {
|
|
9329
|
+
keyId: sigInput.keyId,
|
|
9330
|
+
signatureBase
|
|
9331
|
+
});
|
|
9332
|
+
return {
|
|
9333
|
+
verified: false,
|
|
9334
|
+
algorithm: metricAlgorithm,
|
|
9335
|
+
mismatched: true
|
|
9336
|
+
};
|
|
9337
|
+
}
|
|
9338
|
+
async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, maxSignatures = 3, meterProvider, tracerProvider } = {}) {
|
|
9182
9339
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
9183
9340
|
"fedify",
|
|
9184
9341
|
"sig",
|
|
@@ -9191,7 +9348,6 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
|
|
|
9191
9348
|
logger.error("Failed to verify; the request body is locked.", { url: request.url });
|
|
9192
9349
|
return noSignatureResult();
|
|
9193
9350
|
}
|
|
9194
|
-
const originalRequest = request;
|
|
9195
9351
|
request = request.clone();
|
|
9196
9352
|
const signatureInputHeader = request.headers.get("Signature-Input");
|
|
9197
9353
|
if (!signatureInputHeader) {
|
|
@@ -9211,6 +9367,29 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
|
|
|
9211
9367
|
logger.debug("Failed to verify; no valid signatures found in Signature-Input header.", { header: signatureInputHeader });
|
|
9212
9368
|
return invalidSignatureResult(null);
|
|
9213
9369
|
}
|
|
9370
|
+
if (signatureNames.length > maxSignatures) {
|
|
9371
|
+
logger.debug("Verifying only the first {maxSignatures} of the {count} signatures in the Signature-Input header; the rest are ignored.", {
|
|
9372
|
+
maxSignatures,
|
|
9373
|
+
count: signatureNames.length
|
|
9374
|
+
});
|
|
9375
|
+
signatureNames.splice(maxSignatures);
|
|
9376
|
+
}
|
|
9377
|
+
const keyLookups = /* @__PURE__ */ new Map();
|
|
9378
|
+
const refreshedKeyIds = /* @__PURE__ */ new Set();
|
|
9379
|
+
const lookUpKey = (keyId, keyCache) => measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyId, _fedify_vocab.CryptographicKey, {
|
|
9380
|
+
documentLoader,
|
|
9381
|
+
contextLoader,
|
|
9382
|
+
keyCache,
|
|
9383
|
+
tracerProvider,
|
|
9384
|
+
meterProvider,
|
|
9385
|
+
portableKeyResolvers: createPortableKeyResolvers({
|
|
9386
|
+
documentLoader,
|
|
9387
|
+
contextLoader,
|
|
9388
|
+
keyCache,
|
|
9389
|
+
tracerProvider,
|
|
9390
|
+
meterProvider
|
|
9391
|
+
})
|
|
9392
|
+
}));
|
|
9214
9393
|
let failure = noSignatureResult();
|
|
9215
9394
|
let failureAlgorithm;
|
|
9216
9395
|
const setFailure = (result, algorithm) => {
|
|
@@ -9284,94 +9463,40 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
|
|
|
9284
9463
|
setFailure(invalidSignatureResult(null));
|
|
9285
9464
|
continue;
|
|
9286
9465
|
}
|
|
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";
|
|
9316
|
-
}
|
|
9317
|
-
if (alg) span?.setAttribute("http_signatures.algorithm", alg);
|
|
9318
|
-
const algorithm = alg && rfc9421AlgorithmMap[alg];
|
|
9319
|
-
const candidateAlgorithm = algorithm ? alg : void 0;
|
|
9320
|
-
if (!algorithm) {
|
|
9321
|
-
logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
|
|
9322
|
-
algorithm: sigInput.alg,
|
|
9323
|
-
supported: Object.keys(rfc9421AlgorithmMap)
|
|
9324
|
-
});
|
|
9325
|
-
setFailure(invalidSignatureResult(keyId));
|
|
9326
|
-
continue;
|
|
9327
|
-
}
|
|
9328
|
-
let signatureBase;
|
|
9329
|
-
try {
|
|
9330
|
-
signatureBase = createRfc9421SignatureBase(request, sigInput.components, sigInput.parameters);
|
|
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;
|
|
9466
|
+
let lookup = keyLookups.get(keyId.href);
|
|
9467
|
+
if (lookup == null) {
|
|
9468
|
+
lookup = await lookUpKey(keyId, keyCache);
|
|
9469
|
+
keyLookups.set(keyId.href, lookup);
|
|
9338
9470
|
}
|
|
9339
|
-
|
|
9340
|
-
|
|
9341
|
-
|
|
9342
|
-
|
|
9343
|
-
|
|
9471
|
+
while (true) {
|
|
9472
|
+
const { key, cached, fetchError } = lookup;
|
|
9473
|
+
if (fetchError != null) {
|
|
9474
|
+
setFailure(keyFetchErrorResult(keyId, fetchError));
|
|
9475
|
+
break;
|
|
9476
|
+
}
|
|
9477
|
+
if (!key) {
|
|
9478
|
+
logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
|
|
9479
|
+
setFailure(invalidSignatureResult(keyId));
|
|
9480
|
+
break;
|
|
9481
|
+
}
|
|
9482
|
+
const result = await verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span);
|
|
9483
|
+
if (result.verified) {
|
|
9484
|
+
metricsContext.algorithm = result.algorithm;
|
|
9344
9485
|
return {
|
|
9345
9486
|
verified: true,
|
|
9346
9487
|
key,
|
|
9347
9488
|
signatureLabel: sigName
|
|
9348
9489
|
};
|
|
9349
|
-
}
|
|
9490
|
+
}
|
|
9491
|
+
if (result.mismatched && cached && !refreshedKeyIds.has(keyId.href)) {
|
|
9350
9492
|
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);
|
|
9493
|
+
refreshedKeyIds.add(keyId.href);
|
|
9494
|
+
lookup = await lookUpKey(keyId, bypassKeyCacheReads(keyCache));
|
|
9495
|
+
keyLookups.set(keyId.href, lookup);
|
|
9496
|
+
continue;
|
|
9367
9497
|
}
|
|
9368
|
-
|
|
9369
|
-
|
|
9370
|
-
error,
|
|
9371
|
-
keyId: sigInput.keyId,
|
|
9372
|
-
algorithm: sigInput.alg
|
|
9373
|
-
});
|
|
9374
|
-
setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
|
|
9498
|
+
setFailure(invalidSignatureResult(keyId), result.algorithm);
|
|
9499
|
+
break;
|
|
9375
9500
|
}
|
|
9376
9501
|
}
|
|
9377
9502
|
metricsContext.algorithm = failureAlgorithm;
|
|
@@ -9471,16 +9596,19 @@ async function doubleKnock(request, identity, options = {}) {
|
|
|
9471
9596
|
}
|
|
9472
9597
|
async function doubleKnockInternal(request, identity, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
|
|
9473
9598
|
const { specDeterminer, log, tracerProvider, signal } = options;
|
|
9599
|
+
signal?.throwIfAborted();
|
|
9474
9600
|
const maximumRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
9475
9601
|
visited.add(request.url);
|
|
9476
9602
|
const origin = new URL(request.url).origin;
|
|
9477
9603
|
const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
|
|
9604
|
+
signal?.throwIfAborted();
|
|
9478
9605
|
const body = options.body !== void 0 ? options.body : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
|
|
9479
9606
|
let signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
9480
9607
|
spec: firstTrySpec,
|
|
9481
9608
|
tracerProvider,
|
|
9482
9609
|
body
|
|
9483
9610
|
});
|
|
9611
|
+
signal?.throwIfAborted();
|
|
9484
9612
|
log?.(signedRequest);
|
|
9485
9613
|
let response = await fetchDoubleKnockRequest(request, signedRequest, signal);
|
|
9486
9614
|
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
@@ -9505,6 +9633,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
|
|
|
9505
9633
|
let fulfilled = false;
|
|
9506
9634
|
let challengeRequest;
|
|
9507
9635
|
for (const entry of entries) {
|
|
9636
|
+
signal?.throwIfAborted();
|
|
9508
9637
|
const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
|
|
9509
9638
|
if (rfc9421 == null) continue;
|
|
9510
9639
|
logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
|
|
@@ -9527,6 +9656,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
|
|
|
9527
9656
|
}
|
|
9528
9657
|
}
|
|
9529
9658
|
if (fulfilled && challengeRequest != null) {
|
|
9659
|
+
signal?.throwIfAborted();
|
|
9530
9660
|
signedRequest = challengeRequest;
|
|
9531
9661
|
log?.(signedRequest);
|
|
9532
9662
|
response = await fetch(signedRequest, {
|
|
@@ -9551,11 +9681,13 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
|
|
|
9551
9681
|
status: response.status,
|
|
9552
9682
|
statusText: response.statusText
|
|
9553
9683
|
});
|
|
9684
|
+
signal?.throwIfAborted();
|
|
9554
9685
|
signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
9555
9686
|
spec,
|
|
9556
9687
|
tracerProvider,
|
|
9557
9688
|
body
|
|
9558
9689
|
});
|
|
9690
|
+
signal?.throwIfAborted();
|
|
9559
9691
|
log?.(signedRequest);
|
|
9560
9692
|
response = await fetchDoubleKnockRequest(request, signedRequest, signal);
|
|
9561
9693
|
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
@@ -9718,6 +9850,12 @@ Object.defineProperty(exports, "generateCryptoKeyPair", {
|
|
|
9718
9850
|
return generateCryptoKeyPair;
|
|
9719
9851
|
}
|
|
9720
9852
|
});
|
|
9853
|
+
Object.defineProperty(exports, "getAuthenticationOrigin", {
|
|
9854
|
+
enumerable: true,
|
|
9855
|
+
get: function() {
|
|
9856
|
+
return getAuthenticationOrigin;
|
|
9857
|
+
}
|
|
9858
|
+
});
|
|
9721
9859
|
Object.defineProperty(exports, "getCanonicalPortableId", {
|
|
9722
9860
|
enumerable: true,
|
|
9723
9861
|
get: function() {
|
|
@@ -9850,6 +9988,12 @@ Object.defineProperty(exports, "isPortableUri", {
|
|
|
9850
9988
|
return isPortableUri;
|
|
9851
9989
|
}
|
|
9852
9990
|
});
|
|
9991
|
+
Object.defineProperty(exports, "isSameObjectId", {
|
|
9992
|
+
enumerable: true,
|
|
9993
|
+
get: function() {
|
|
9994
|
+
return isSameObjectId;
|
|
9995
|
+
}
|
|
9996
|
+
});
|
|
9853
9997
|
Object.defineProperty(exports, "isSamePublicKey", {
|
|
9854
9998
|
enumerable: true,
|
|
9855
9999
|
get: function() {
|
|
@@ -10012,6 +10156,12 @@ Object.defineProperty(exports, "validateCryptoKey", {
|
|
|
10012
10156
|
return validateCryptoKey;
|
|
10013
10157
|
}
|
|
10014
10158
|
});
|
|
10159
|
+
Object.defineProperty(exports, "validateMaxSignatures", {
|
|
10160
|
+
enumerable: true,
|
|
10161
|
+
get: function() {
|
|
10162
|
+
return validateMaxSignatures;
|
|
10163
|
+
}
|
|
10164
|
+
});
|
|
10015
10165
|
Object.defineProperty(exports, "verifyCompactJsonLd", {
|
|
10016
10166
|
enumerable: true,
|
|
10017
10167
|
get: function() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference lib="esnext.temporal" />
|
|
2
2
|
import { Actor, CryptographicKey, Multikey } from "@fedify/vocab";
|
|
3
|
-
import { DocumentLoader } from "@fedify/vocab-runtime";
|
|
4
3
|
import { MeterProvider, TracerProvider } from "@opentelemetry/api";
|
|
4
|
+
import { DocumentLoader } from "@fedify/vocab-runtime";
|
|
5
5
|
|
|
6
6
|
//#region src/sig/portable-key.d.ts
|
|
7
7
|
/**
|
|
@@ -601,6 +601,30 @@ interface VerifyRequestOptions {
|
|
|
601
601
|
*/
|
|
602
602
|
spec?: HttpMessageSignaturesSpec;
|
|
603
603
|
/**
|
|
604
|
+
* The maximum number of [RFC 9421] signatures of a request to verify.
|
|
605
|
+
* A request can carry several signatures, each of which may make Fedify
|
|
606
|
+
* fetch the key that it names, so only the first ones in the order of
|
|
607
|
+
* the `Signature-Input` header are verified, and the rest are ignored as
|
|
608
|
+
* if they were absent. Every signature among the first ones counts, even
|
|
609
|
+
* if it fails before its key is fetched, e.g., because it is too old, or
|
|
610
|
+
* names the same key as another one. The key of a key ID is looked up
|
|
611
|
+
* only once for all the signatures that name it.
|
|
612
|
+
*
|
|
613
|
+
* It has to be a positive integer, or `Infinity` to verify every
|
|
614
|
+
* signature, which lets a single request make Fedify fetch any number of
|
|
615
|
+
* keys. Draft-cavage HTTP Signatures carry a single signature, so this
|
|
616
|
+
* option does not affect them.
|
|
617
|
+
*
|
|
618
|
+
* Three by default.
|
|
619
|
+
*
|
|
620
|
+
* [RFC 9421]: https://www.rfc-editor.org/rfc/rfc9421
|
|
621
|
+
* @throws {RangeError} Thrown by {@link verifyRequest} and
|
|
622
|
+
* {@link verifyRequestDetailed} if the value is not a positive
|
|
623
|
+
* integer or `Infinity`.
|
|
624
|
+
* @since 2.4.0
|
|
625
|
+
*/
|
|
626
|
+
maxSignatures?: number;
|
|
627
|
+
/**
|
|
604
628
|
* The OpenTelemetry tracer provider. If omitted, the global tracer provider
|
|
605
629
|
* is used.
|
|
606
630
|
* @since 1.3.0
|
|
@@ -651,6 +675,8 @@ type VerifyRequestDetailedResult = {
|
|
|
651
675
|
* @param options Options for verifying the request.
|
|
652
676
|
* @returns The public key of the verified signature, or `null` if the signature
|
|
653
677
|
* could not be verified.
|
|
678
|
+
* @throws {RangeError} Thrown if {@link VerifyRequestOptions.maxSignatures}
|
|
679
|
+
* is not a positive integer or `Infinity`.
|
|
654
680
|
*/
|
|
655
681
|
declare function verifyRequest(request: Request, options?: VerifyRequestOptions): Promise<CryptographicKey | null>;
|
|
656
682
|
/**
|
|
@@ -660,6 +686,8 @@ declare function verifyRequest(request: Request, options?: VerifyRequestOptions)
|
|
|
660
686
|
* @param request The request to verify.
|
|
661
687
|
* @param options Options for verifying the request.
|
|
662
688
|
* @returns The verified public key, or a structured verification failure.
|
|
689
|
+
* @throws {RangeError} Thrown if {@link VerifyRequestOptions.maxSignatures}
|
|
690
|
+
* is not a positive integer or `Infinity`.
|
|
663
691
|
* @since 2.1.0
|
|
664
692
|
*/
|
|
665
693
|
declare function verifyRequestDetailed(request: Request, options?: VerifyRequestOptions): Promise<VerifyRequestDetailedResult>;
|