@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
|
@@ -14,7 +14,7 @@ import serialize from "json-canon";
|
|
|
14
14
|
import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
|
|
15
15
|
//#region deno.json
|
|
16
16
|
var name = "@fedify/fedify";
|
|
17
|
-
var version = "2.4.0-dev.
|
|
17
|
+
var version = "2.4.0-dev.2190+be3a8be0";
|
|
18
18
|
//#endregion
|
|
19
19
|
//#region src/sig/portable-key-id.ts
|
|
20
20
|
const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
|
|
@@ -115,6 +115,50 @@ function getCanonicalPortableId(id) {
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
/**
|
|
118
|
+
* Gets the [FEP-fe34] origin of an ID for same-origin checks. The origin of
|
|
119
|
+
* an `ap:` or `ap+ef61:` URI, or of a compatible identifier on any gateway,
|
|
120
|
+
* is the DID of its canonical portable ID, as FEP-ef61 treats a compatible
|
|
121
|
+
* identifier as the portable ID it stands for; unlike `getFe34Origin()`,
|
|
122
|
+
* which gives a compatible identifier the web origin of its gateway. The
|
|
123
|
+
* origin of any other HTTP(S) URL is its web origin, and that of a DID URL
|
|
124
|
+
* is its DID.
|
|
125
|
+
*
|
|
126
|
+
* Only the parsed `URL` is validated, so dot segments that URL parsing has
|
|
127
|
+
* already resolved cannot be detected here.
|
|
128
|
+
*
|
|
129
|
+
* [FEP-fe34]: https://w3id.org/fep/fe34
|
|
130
|
+
* @returns The origin, or `null` if the ID has none that is supported, or is
|
|
131
|
+
* a malformed portable ID or compatible identifier.
|
|
132
|
+
* @internal
|
|
133
|
+
*/
|
|
134
|
+
function getAuthenticationOrigin(id) {
|
|
135
|
+
try {
|
|
136
|
+
if (isPortableId(id)) {
|
|
137
|
+
const canonical = getCanonicalPortableId(id);
|
|
138
|
+
return canonical == null ? null : getFe34Origin(canonical);
|
|
139
|
+
}
|
|
140
|
+
return getFe34Origin(id);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (error instanceof TypeError) return null;
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Checks whether two IDs identify the same object. Two portable IDs, i.e.,
|
|
148
|
+
* `ap:` or `ap+ef61:` URIs or compatible identifiers, are compared by their
|
|
149
|
+
* canonical portable IDs, so that the same object on different gateways and
|
|
150
|
+
* with different location hints matches. Other IDs have to be equal as
|
|
151
|
+
* they are.
|
|
152
|
+
* @internal
|
|
153
|
+
*/
|
|
154
|
+
function isSameObjectId(a, b) {
|
|
155
|
+
const aPortable = isPortableId(a);
|
|
156
|
+
if (aPortable !== isPortableId(b)) return false;
|
|
157
|
+
if (!aPortable) return a.href === b.href;
|
|
158
|
+
const canonical = getCanonicalPortableId(a);
|
|
159
|
+
return canonical != null && canonical === getCanonicalPortableId(b);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
118
162
|
* Gets the canonical portable ID of an `ap:` or `ap+ef61:` URI or
|
|
119
163
|
* a compatible identifier as it is written, before any URL parsing.
|
|
120
164
|
* Parsing an ID into a `URL` resolves dot segments in its path, which would
|
|
@@ -5056,16 +5100,19 @@ function parseDraftSignature(signature) {
|
|
|
5056
5100
|
* Lists the HTTP Signatures that a request claims to carry, without verifying
|
|
5057
5101
|
* any of them. Signatures whose key IDs are not URLs are left out.
|
|
5058
5102
|
* @param request The request.
|
|
5103
|
+
* @param maxSignatures The maximum number of RFC 9421 signatures to consider,
|
|
5104
|
+
* counted the same way as by {@link verifyRequest}, so
|
|
5105
|
+
* that the signatures it ignores are not listed either.
|
|
5059
5106
|
* @returns The signatures; RFC 9421 ones if the request has
|
|
5060
5107
|
* a `Signature-Input` header, or a draft-cavage one otherwise.
|
|
5061
5108
|
* @internal
|
|
5062
5109
|
*/
|
|
5063
|
-
function listRequestSignatures(request) {
|
|
5110
|
+
function listRequestSignatures(request, maxSignatures = 3) {
|
|
5064
5111
|
const signatureInput = request.headers.get("Signature-Input");
|
|
5065
5112
|
if (signatureInput != null) {
|
|
5066
5113
|
const signatures = [];
|
|
5067
|
-
const inputs = parseRfc9421SignatureInput(signatureInput);
|
|
5068
|
-
for (const [label, input] of
|
|
5114
|
+
const inputs = Object.entries(parseRfc9421SignatureInput(signatureInput));
|
|
5115
|
+
for (const [label, input] of inputs.slice(0, maxSignatures)) {
|
|
5069
5116
|
const keyId = parseKeyId(input.keyId);
|
|
5070
5117
|
if (keyId == null) continue;
|
|
5071
5118
|
signatures.push({
|
|
@@ -5193,6 +5240,18 @@ async function signRequestRfc9421(request, privateKey, keyId, span, currentTime,
|
|
|
5193
5240
|
body
|
|
5194
5241
|
});
|
|
5195
5242
|
}
|
|
5243
|
+
/**
|
|
5244
|
+
* Checks that a maximum number of signatures to verify is a positive safe
|
|
5245
|
+
* integer or `Infinity`.
|
|
5246
|
+
* @param value The value to check.
|
|
5247
|
+
* @param name The name of the option, used in the error message.
|
|
5248
|
+
* @throws {RangeError} Thrown if the value is not valid.
|
|
5249
|
+
* @internal
|
|
5250
|
+
*/
|
|
5251
|
+
function validateMaxSignatures(value, name) {
|
|
5252
|
+
if (value === Infinity || Number.isSafeInteger(value) && value >= 1) return;
|
|
5253
|
+
throw new RangeError(`${name} must be a positive integer or Infinity.`);
|
|
5254
|
+
}
|
|
5196
5255
|
const supportedHashAlgorithms = {
|
|
5197
5256
|
"sha": "SHA-1",
|
|
5198
5257
|
"sha-256": "SHA-256",
|
|
@@ -5285,6 +5344,8 @@ function recordVerificationResult(span, result) {
|
|
|
5285
5344
|
* @param options Options for verifying the request.
|
|
5286
5345
|
* @returns The public key of the verified signature, or `null` if the signature
|
|
5287
5346
|
* could not be verified.
|
|
5347
|
+
* @throws {RangeError} Thrown if {@link VerifyRequestOptions.maxSignatures}
|
|
5348
|
+
* is not a positive integer or `Infinity`.
|
|
5288
5349
|
*/
|
|
5289
5350
|
async function verifyRequest(request, options = {}) {
|
|
5290
5351
|
const result = await verifyRequestDetailed(request, options);
|
|
@@ -5297,9 +5358,12 @@ async function verifyRequest(request, options = {}) {
|
|
|
5297
5358
|
* @param request The request to verify.
|
|
5298
5359
|
* @param options Options for verifying the request.
|
|
5299
5360
|
* @returns The verified public key, or a structured verification failure.
|
|
5361
|
+
* @throws {RangeError} Thrown if {@link VerifyRequestOptions.maxSignatures}
|
|
5362
|
+
* is not a positive integer or `Infinity`.
|
|
5300
5363
|
* @since 2.1.0
|
|
5301
5364
|
*/
|
|
5302
5365
|
async function verifyRequestDetailed(request, options = {}) {
|
|
5366
|
+
if (options.maxSignatures !== void 0) validateMaxSignatures(options.maxSignatures, "maxSignatures");
|
|
5303
5367
|
return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("http_signatures.verify", async (span) => {
|
|
5304
5368
|
if (span.isRecording()) {
|
|
5305
5369
|
span.setAttribute(ATTR_HTTP_REQUEST_METHOD, request.method);
|
|
@@ -5608,7 +5672,100 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
|
|
|
5608
5672
|
}
|
|
5609
5673
|
return false;
|
|
5610
5674
|
}
|
|
5611
|
-
|
|
5675
|
+
/**
|
|
5676
|
+
* Verifies one RFC 9421 signature of a request with a key.
|
|
5677
|
+
* @param request The request, whose body may be consumed already.
|
|
5678
|
+
* @param sigInput The parsed `Signature-Input` member of the signature.
|
|
5679
|
+
* @param sigBytes The bytes of the signature.
|
|
5680
|
+
* @param key The public key named by the signature.
|
|
5681
|
+
* @param span The span to record the algorithm and the signature on.
|
|
5682
|
+
* @returns Whether the signature is verified, and the algorithm of the
|
|
5683
|
+
* signature if it is supported.
|
|
5684
|
+
*/
|
|
5685
|
+
async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span) {
|
|
5686
|
+
const logger = getLogger([
|
|
5687
|
+
"fedify",
|
|
5688
|
+
"sig",
|
|
5689
|
+
"http"
|
|
5690
|
+
]);
|
|
5691
|
+
let alg = sigInput.alg?.toLowerCase();
|
|
5692
|
+
if (alg == null) {
|
|
5693
|
+
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";
|
|
5694
|
+
else if (key.publicKey.algorithm.name === "RSA-PSS") alg = "rsa-pss-sha512";
|
|
5695
|
+
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";
|
|
5696
|
+
else if (key.publicKey.algorithm.name === "Ed25519") alg = "ed25519";
|
|
5697
|
+
}
|
|
5698
|
+
if (alg) span?.setAttribute("http_signatures.algorithm", alg);
|
|
5699
|
+
const algorithm = alg && rfc9421AlgorithmMap[alg];
|
|
5700
|
+
if (!algorithm) {
|
|
5701
|
+
logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
|
|
5702
|
+
algorithm: sigInput.alg,
|
|
5703
|
+
supported: Object.keys(rfc9421AlgorithmMap)
|
|
5704
|
+
});
|
|
5705
|
+
return {
|
|
5706
|
+
verified: false,
|
|
5707
|
+
mismatched: false
|
|
5708
|
+
};
|
|
5709
|
+
}
|
|
5710
|
+
const metricAlgorithm = alg;
|
|
5711
|
+
let signatureBase;
|
|
5712
|
+
try {
|
|
5713
|
+
signatureBase = createRfc9421SignatureBase(request, sigInput.components, sigInput.parameters);
|
|
5714
|
+
} catch (error) {
|
|
5715
|
+
logger.debug("Failed to create signature base for verification: {error}", {
|
|
5716
|
+
error,
|
|
5717
|
+
signatureInput: sigInput
|
|
5718
|
+
});
|
|
5719
|
+
return {
|
|
5720
|
+
verified: false,
|
|
5721
|
+
algorithm: metricAlgorithm,
|
|
5722
|
+
mismatched: false
|
|
5723
|
+
};
|
|
5724
|
+
}
|
|
5725
|
+
const signatureBaseBytes = new TextEncoder().encode(signatureBase);
|
|
5726
|
+
span?.setAttribute("http_signatures.signature", encodeHex(sigBytes));
|
|
5727
|
+
const algorithmName = typeof algorithm === "string" ? algorithm : algorithm.name;
|
|
5728
|
+
if (key.publicKey.algorithm.name !== algorithmName) {
|
|
5729
|
+
logger.debug("Failed to verify; key {keyId} is not a key for algorithm {algorithm}.", {
|
|
5730
|
+
keyId: sigInput.keyId,
|
|
5731
|
+
algorithm: alg
|
|
5732
|
+
});
|
|
5733
|
+
return {
|
|
5734
|
+
verified: false,
|
|
5735
|
+
algorithm: metricAlgorithm,
|
|
5736
|
+
mismatched: true
|
|
5737
|
+
};
|
|
5738
|
+
}
|
|
5739
|
+
let verified;
|
|
5740
|
+
try {
|
|
5741
|
+
verified = await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes);
|
|
5742
|
+
} catch (error) {
|
|
5743
|
+
logger.debug("Error during signature verification: {error}", {
|
|
5744
|
+
error,
|
|
5745
|
+
keyId: sigInput.keyId,
|
|
5746
|
+
algorithm: sigInput.alg
|
|
5747
|
+
});
|
|
5748
|
+
return {
|
|
5749
|
+
verified: false,
|
|
5750
|
+
algorithm: metricAlgorithm,
|
|
5751
|
+
mismatched: false
|
|
5752
|
+
};
|
|
5753
|
+
}
|
|
5754
|
+
if (verified) return {
|
|
5755
|
+
verified: true,
|
|
5756
|
+
algorithm: metricAlgorithm
|
|
5757
|
+
};
|
|
5758
|
+
logger.debug("Failed to verify signature with key {keyId}; signature invalid.", {
|
|
5759
|
+
keyId: sigInput.keyId,
|
|
5760
|
+
signatureBase
|
|
5761
|
+
});
|
|
5762
|
+
return {
|
|
5763
|
+
verified: false,
|
|
5764
|
+
algorithm: metricAlgorithm,
|
|
5765
|
+
mismatched: true
|
|
5766
|
+
};
|
|
5767
|
+
}
|
|
5768
|
+
async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, maxSignatures = 3, meterProvider, tracerProvider } = {}) {
|
|
5612
5769
|
const logger = getLogger([
|
|
5613
5770
|
"fedify",
|
|
5614
5771
|
"sig",
|
|
@@ -5621,7 +5778,6 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
|
|
|
5621
5778
|
logger.error("Failed to verify; the request body is locked.", { url: request.url });
|
|
5622
5779
|
return noSignatureResult();
|
|
5623
5780
|
}
|
|
5624
|
-
const originalRequest = request;
|
|
5625
5781
|
request = request.clone();
|
|
5626
5782
|
const signatureInputHeader = request.headers.get("Signature-Input");
|
|
5627
5783
|
if (!signatureInputHeader) {
|
|
@@ -5641,6 +5797,29 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
|
|
|
5641
5797
|
logger.debug("Failed to verify; no valid signatures found in Signature-Input header.", { header: signatureInputHeader });
|
|
5642
5798
|
return invalidSignatureResult(null);
|
|
5643
5799
|
}
|
|
5800
|
+
if (signatureNames.length > maxSignatures) {
|
|
5801
|
+
logger.debug("Verifying only the first {maxSignatures} of the {count} signatures in the Signature-Input header; the rest are ignored.", {
|
|
5802
|
+
maxSignatures,
|
|
5803
|
+
count: signatureNames.length
|
|
5804
|
+
});
|
|
5805
|
+
signatureNames.splice(maxSignatures);
|
|
5806
|
+
}
|
|
5807
|
+
const keyLookups = /* @__PURE__ */ new Map();
|
|
5808
|
+
const refreshedKeyIds = /* @__PURE__ */ new Set();
|
|
5809
|
+
const lookUpKey = (keyId, keyCache) => measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyId, CryptographicKey, {
|
|
5810
|
+
documentLoader,
|
|
5811
|
+
contextLoader,
|
|
5812
|
+
keyCache,
|
|
5813
|
+
tracerProvider,
|
|
5814
|
+
meterProvider,
|
|
5815
|
+
portableKeyResolvers: createPortableKeyResolvers({
|
|
5816
|
+
documentLoader,
|
|
5817
|
+
contextLoader,
|
|
5818
|
+
keyCache,
|
|
5819
|
+
tracerProvider,
|
|
5820
|
+
meterProvider
|
|
5821
|
+
})
|
|
5822
|
+
}));
|
|
5644
5823
|
let failure = noSignatureResult();
|
|
5645
5824
|
let failureAlgorithm;
|
|
5646
5825
|
const setFailure = (result, algorithm) => {
|
|
@@ -5714,94 +5893,40 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
|
|
|
5714
5893
|
setFailure(invalidSignatureResult(null));
|
|
5715
5894
|
continue;
|
|
5716
5895
|
}
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
tracerProvider,
|
|
5722
|
-
meterProvider,
|
|
5723
|
-
portableKeyResolvers: createPortableKeyResolvers({
|
|
5724
|
-
documentLoader,
|
|
5725
|
-
contextLoader,
|
|
5726
|
-
keyCache,
|
|
5727
|
-
tracerProvider,
|
|
5728
|
-
meterProvider
|
|
5729
|
-
})
|
|
5730
|
-
}));
|
|
5731
|
-
if (fetchError != null) {
|
|
5732
|
-
setFailure(keyFetchErrorResult(keyId, fetchError));
|
|
5733
|
-
continue;
|
|
5734
|
-
}
|
|
5735
|
-
if (!key) {
|
|
5736
|
-
logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
|
|
5737
|
-
setFailure(invalidSignatureResult(keyId));
|
|
5738
|
-
continue;
|
|
5739
|
-
}
|
|
5740
|
-
let alg = sigInput.alg?.toLowerCase();
|
|
5741
|
-
if (alg == null) {
|
|
5742
|
-
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";
|
|
5743
|
-
else if (key.publicKey.algorithm.name === "RSA-PSS") alg = "rsa-pss-sha512";
|
|
5744
|
-
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";
|
|
5745
|
-
else if (key.publicKey.algorithm.name === "Ed25519") alg = "ed25519";
|
|
5746
|
-
}
|
|
5747
|
-
if (alg) span?.setAttribute("http_signatures.algorithm", alg);
|
|
5748
|
-
const algorithm = alg && rfc9421AlgorithmMap[alg];
|
|
5749
|
-
const candidateAlgorithm = algorithm ? alg : void 0;
|
|
5750
|
-
if (!algorithm) {
|
|
5751
|
-
logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
|
|
5752
|
-
algorithm: sigInput.alg,
|
|
5753
|
-
supported: Object.keys(rfc9421AlgorithmMap)
|
|
5754
|
-
});
|
|
5755
|
-
setFailure(invalidSignatureResult(keyId));
|
|
5756
|
-
continue;
|
|
5757
|
-
}
|
|
5758
|
-
let signatureBase;
|
|
5759
|
-
try {
|
|
5760
|
-
signatureBase = createRfc9421SignatureBase(request, sigInput.components, sigInput.parameters);
|
|
5761
|
-
} catch (error) {
|
|
5762
|
-
logger.debug("Failed to create signature base for verification: {error}", {
|
|
5763
|
-
error,
|
|
5764
|
-
signatureInput: sigInput
|
|
5765
|
-
});
|
|
5766
|
-
setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
|
|
5767
|
-
continue;
|
|
5896
|
+
let lookup = keyLookups.get(keyId.href);
|
|
5897
|
+
if (lookup == null) {
|
|
5898
|
+
lookup = await lookUpKey(keyId, keyCache);
|
|
5899
|
+
keyLookups.set(keyId.href, lookup);
|
|
5768
5900
|
}
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5901
|
+
while (true) {
|
|
5902
|
+
const { key, cached, fetchError } = lookup;
|
|
5903
|
+
if (fetchError != null) {
|
|
5904
|
+
setFailure(keyFetchErrorResult(keyId, fetchError));
|
|
5905
|
+
break;
|
|
5906
|
+
}
|
|
5907
|
+
if (!key) {
|
|
5908
|
+
logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
|
|
5909
|
+
setFailure(invalidSignatureResult(keyId));
|
|
5910
|
+
break;
|
|
5911
|
+
}
|
|
5912
|
+
const result = await verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span);
|
|
5913
|
+
if (result.verified) {
|
|
5914
|
+
metricsContext.algorithm = result.algorithm;
|
|
5774
5915
|
return {
|
|
5775
5916
|
verified: true,
|
|
5776
5917
|
key,
|
|
5777
5918
|
signatureLabel: sigName
|
|
5778
5919
|
};
|
|
5779
|
-
}
|
|
5920
|
+
}
|
|
5921
|
+
if (result.mismatched && cached && !refreshedKeyIds.has(keyId.href)) {
|
|
5780
5922
|
logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
currentTime,
|
|
5786
|
-
keyCache: bypassKeyCacheReads(keyCache),
|
|
5787
|
-
spec: "rfc9421",
|
|
5788
|
-
meterProvider,
|
|
5789
|
-
tracerProvider
|
|
5790
|
-
});
|
|
5791
|
-
} else {
|
|
5792
|
-
logger.debug("Failed to verify signature with fetched key {keyId}; signature invalid.", {
|
|
5793
|
-
keyId: sigInput.keyId,
|
|
5794
|
-
signatureBase
|
|
5795
|
-
});
|
|
5796
|
-
setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
|
|
5923
|
+
refreshedKeyIds.add(keyId.href);
|
|
5924
|
+
lookup = await lookUpKey(keyId, bypassKeyCacheReads(keyCache));
|
|
5925
|
+
keyLookups.set(keyId.href, lookup);
|
|
5926
|
+
continue;
|
|
5797
5927
|
}
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
error,
|
|
5801
|
-
keyId: sigInput.keyId,
|
|
5802
|
-
algorithm: sigInput.alg
|
|
5803
|
-
});
|
|
5804
|
-
setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
|
|
5928
|
+
setFailure(invalidSignatureResult(keyId), result.algorithm);
|
|
5929
|
+
break;
|
|
5805
5930
|
}
|
|
5806
5931
|
}
|
|
5807
5932
|
metricsContext.algorithm = failureAlgorithm;
|
|
@@ -5901,16 +6026,19 @@ async function doubleKnock(request, identity, options = {}) {
|
|
|
5901
6026
|
}
|
|
5902
6027
|
async function doubleKnockInternal(request, identity, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
|
|
5903
6028
|
const { specDeterminer, log, tracerProvider, signal } = options;
|
|
6029
|
+
signal?.throwIfAborted();
|
|
5904
6030
|
const maximumRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
5905
6031
|
visited.add(request.url);
|
|
5906
6032
|
const origin = new URL(request.url).origin;
|
|
5907
6033
|
const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
|
|
6034
|
+
signal?.throwIfAborted();
|
|
5908
6035
|
const body = options.body !== void 0 ? options.body : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
|
|
5909
6036
|
let signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
5910
6037
|
spec: firstTrySpec,
|
|
5911
6038
|
tracerProvider,
|
|
5912
6039
|
body
|
|
5913
6040
|
});
|
|
6041
|
+
signal?.throwIfAborted();
|
|
5914
6042
|
log?.(signedRequest);
|
|
5915
6043
|
let response = await fetchDoubleKnockRequest(request, signedRequest, signal);
|
|
5916
6044
|
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
@@ -5935,6 +6063,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
|
|
|
5935
6063
|
let fulfilled = false;
|
|
5936
6064
|
let challengeRequest;
|
|
5937
6065
|
for (const entry of entries) {
|
|
6066
|
+
signal?.throwIfAborted();
|
|
5938
6067
|
const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
|
|
5939
6068
|
if (rfc9421 == null) continue;
|
|
5940
6069
|
logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
|
|
@@ -5957,6 +6086,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
|
|
|
5957
6086
|
}
|
|
5958
6087
|
}
|
|
5959
6088
|
if (fulfilled && challengeRequest != null) {
|
|
6089
|
+
signal?.throwIfAborted();
|
|
5960
6090
|
signedRequest = challengeRequest;
|
|
5961
6091
|
log?.(signedRequest);
|
|
5962
6092
|
response = await fetch(signedRequest, {
|
|
@@ -5981,11 +6111,13 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
|
|
|
5981
6111
|
status: response.status,
|
|
5982
6112
|
statusText: response.statusText
|
|
5983
6113
|
});
|
|
6114
|
+
signal?.throwIfAborted();
|
|
5984
6115
|
signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
5985
6116
|
spec,
|
|
5986
6117
|
tracerProvider,
|
|
5987
6118
|
body
|
|
5988
6119
|
});
|
|
6120
|
+
signal?.throwIfAborted();
|
|
5989
6121
|
log?.(signedRequest);
|
|
5990
6122
|
response = await fetchDoubleKnockRequest(request, signedRequest, signal);
|
|
5991
6123
|
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
@@ -6028,4 +6160,4 @@ function timingSafeEqual(a, b) {
|
|
|
6028
6160
|
return result === 0;
|
|
6029
6161
|
}
|
|
6030
6162
|
//#endregion
|
|
6031
|
-
export {
|
|
6163
|
+
export { instrumentDocumentLoader as $, hasSignature as A, name as At, fetchActorDocument as B, InvalidContextReferenceError as C, hasPortableActor as Ct, createSignature as D, isPortableUri as Dt, compactJsonLd as E, isPortableKeyId as Et, verifyCompactJsonLd as F, validateCryptoKey as G, fetchKeyDetailed as H, verifyJsonLd as I, getVerifiedKeyOwnerEvidence as J, verifyKeyOwnership as K, verifySignature as L, isClearlyMalformedContextReference as M, isInvalidUrlTypeError as N, detachSignature as O, isSameObjectId as Ot, signJsonLd as P, getRemoteHost as Q, wrapContextLoaderForJsonLd as R, normalizeOutgoingActivityJsonLd as S, getRawCanonicalPortableId as St, attachSignature as T, isPortableId as Tt, generateCryptoKeyPair as U, fetchKey as V, importJwk as W, getDurationMs as X, parseKeyOwnerEvidence as Y, getFederationMetrics as Z, verifyObject as _, validateAcceptSignature as _t, signRequest as a, recordCollectionTotalItems as at, verifyPortableObjectProofWithRoot as b, getGatewayKeyBase as bt, verifyRequestDetailed as c, recordInboxActivity as ct, InvalidPortableObjectIdError as d, recordWebFingerHandle as dt, isAbortError$1 as et, classifyFep2277CoreType as f, registerQueueDepthGauge as ft, verifyMapLocalProof as g, parseAcceptSignature as gt, signObject as h, fulfillAcceptSignature as ht, selectRequestSignature as i, recordCollectionRequest as it, hasSignatureLike as j, version as jt, getNormalizationContextLoader as k, isSamePublicKey as kt, fetchPortableGatewayKey as l, recordOutboxActivity as lt, hasProofLike as m, formatAcceptSignature as mt, listRequestSignatures as n, recordCollectionDispatchDuration as nt, validateMaxSignatures as o, recordDocumentCache as ot, createProof as p, preloadedOnlyDocumentLoader as pt, MAX_PORTABLE_KEY_TTL as q, parseRfc9421SignatureInput as r, recordCollectionPageItems as rt, verifyRequest as s, recordFanoutRecipients as st, doubleKnock as t, recordCircuitBreakerStateChange as tt, resolvePortableActorKey as u, recordOutboxEnqueue as ut, verifyPortableObjectProof as v, getAuthenticationOrigin as vt, assertSafeJsonLd as w, isCompatibleKeyId as wt, verifyProof as x, getPortableDid as xt, verifyPortableObjectProofPolicy as y, getCanonicalPortableId as yt, exportJwk as z };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference lib="esnext.temporal" />
|
|
2
2
|
import { Actor, CryptographicKey, Multikey } from "@fedify/vocab";
|
|
3
|
-
import { MeterProvider, TracerProvider } from "@opentelemetry/api";
|
|
4
3
|
import { DocumentLoader } from "@fedify/vocab-runtime";
|
|
4
|
+
import { MeterProvider, TracerProvider } from "@opentelemetry/api";
|
|
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>;
|