@fedify/fedify 2.4.0-dev.2256 → 2.4.0-dev.2259
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-DvSQqk2l.mjs → builder-CBNa1Uw2.mjs} +8 -2
- 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-5bg_j_ye.mjs → compound-proof-CMPv4UK0.mjs} +20 -3
- package/dist/{context-CtI307_A.d.ts → context-7NyiqIcg.d.ts} +111 -2
- package/dist/{context-B6X-t8gI.d.cts → context-tsYPJ2Ew.d.cts} +111 -2
- package/dist/{deno-AmcRMq5z.mjs → deno-C8XaXmPB.mjs} +1 -1
- package/dist/{docloader-CIP_SG39.mjs → docloader-CpdFLwAg.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -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 +76 -5
- package/dist/federation/hashlink-media.test.mjs +2 -2
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/inbox-report.test.d.mts +2 -0
- package/dist/federation/inbox-report.test.mjs +475 -0
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +7 -7
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +4 -3
- package/dist/federation/mod.d.ts +4 -3
- 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-delivery.test.mjs +2 -2
- package/dist/federation/portable-gateway-keys.test.mjs +4 -4
- package/dist/federation/portable-inbox.test.mjs +67 -6
- package/dist/federation/portable-parse-uri.test.mjs +1 -1
- package/dist/federation/portable-route-activity.test.mjs +2 -2
- 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-BDzIgqOd.cjs → federation-D6giSGUT.cjs} +524 -118
- package/dist/{http-DkqWu-sm.mjs → http-BqSbUEiQ.mjs} +141 -98
- package/dist/{http-1fZ83jj7.cjs → http-Cki-npuA.cjs} +447 -110
- package/dist/{http-BGb3QioZ.js → http-OMtKlbx1.js} +430 -111
- package/dist/{http-BrwKINQn.d.cts → key-C1nv7_jD.d.ts} +638 -229
- package/dist/{http-DGx0KOws.d.ts → key-DYxuyd-s.d.cts} +638 -229
- package/dist/{key-CEsu7oaD.mjs → key-DecAtABm.mjs} +30 -3
- package/dist/{kv-cache-CJS4mImS.mjs → kv-cache-CWWlMv-s.mjs} +1 -1
- package/dist/{kv-cache-BEWuZSB4.cjs → kv-cache-D73yVcE5.cjs} +1 -1
- package/dist/{kv-cache-CGhEgt98.js → kv-cache-J0v3UbUn.js} +1 -1
- package/dist/{ld-Cf4CPWN_.mjs → ld-UsN-TIPh.mjs} +40 -5
- package/dist/{metrics-CocsZP0E.mjs → metrics-DWCn4VXX.mjs} +2 -1
- package/dist/{middleware-BfYke3YQ.js → middleware-BqzfIlUO.js} +524 -118
- package/dist/{middleware-Dxn0E9ES.mjs → middleware-CkjwiEBd.mjs} +512 -127
- package/dist/{middleware-Cne_jMZo.mjs → middleware-DHn2q0L1.mjs} +1 -1
- package/dist/{mod-C7_2j7VT.d.cts → mod-BojP2Bw3.d.cts} +1 -1
- package/dist/{mod-BlLwewYe.d.ts → mod-CavhAgLw.d.cts} +5 -255
- package/dist/{mod-VHYh1lTK.d.cts → mod-CtpF2BU5.d.ts} +5 -255
- package/dist/{mod-B8MI1mpi.d.ts → mod-DEZXwV6J.d.ts} +1 -1
- package/dist/{mod-DFL6IxYQ.d.cts → mod-Dw-nPxQh.d.cts} +1 -1
- package/dist/{mod-BhTaDR5H.d.ts → mod-Xj5P4s4d.d.ts} +1 -1
- package/dist/mod.cjs +4 -4
- package/dist/mod.d.cts +7 -7
- package/dist/mod.d.ts +7 -7
- package/dist/mod.js +4 -4
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/{owner-DSJ7nFgy.mjs → owner-CEBn81LD.mjs} +3 -3
- package/dist/{portable-collection-r-mHg1PV.cjs → portable-collection-DqTUBKY7.cjs} +1 -1
- package/dist/{portable-collection-BuBV6xTo.js → portable-collection-ZkUNjtb7.js} +1 -1
- package/dist/{portable-collection-qskqXJrH.mjs → portable-collection-qFSYw4TG.mjs} +2 -2
- package/dist/{portable-key-woGH_1nn.mjs → portable-key-LlMkP-vz.mjs} +1 -1
- package/dist/{proof-8WiH_-Gq.mjs → proof-BHRf_jMG.mjs} +133 -16
- package/dist/{send-JKzZBgPU.mjs → send-DjG8DDU_.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 +19 -2
- package/dist/sig/compound-proof.test.mjs +2 -2
- package/dist/sig/http.test.mjs +2 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +2 -2
- package/dist/sig/mitra-receive-interop.test.mjs +4 -4
- package/dist/sig/mitra-send-interop.test.mjs +3 -3
- 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-interop.test.mjs +3 -3
- package/dist/sig/portable-key.test.mjs +6 -6
- package/dist/sig/proof.test.mjs +76 -3
- package/dist/sig/tootik-request-interop.test.mjs +4 -4
- package/dist/sig/tootik-send-interop.test.mjs +3 -3
- package/dist/sig/verification.test.d.mts +2 -0
- package/dist/sig/verification.test.mjs +310 -0
- package/dist/{temporal-CT3fRaMH.mjs → temporal-BRFH_dte.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +315 -28
- package/dist/utils/docloader.test.mjs +2 -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/dist/verification-C3954hQh.mjs +5 -0
- package/dist/verification-Dpfcw8ml.mjs +105 -0
- package/package.json +7 -7
|
@@ -15,7 +15,7 @@ import serialize from "json-canon";
|
|
|
15
15
|
import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
|
|
16
16
|
//#region deno.json
|
|
17
17
|
var name = "@fedify/fedify";
|
|
18
|
-
var version = "2.4.0-dev.
|
|
18
|
+
var version = "2.4.0-dev.2259+d9c843fc";
|
|
19
19
|
//#endregion
|
|
20
20
|
//#region src/sig/portable-key-id.ts
|
|
21
21
|
const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
|
|
@@ -258,6 +258,106 @@ async function isSamePublicKey(a, b) {
|
|
|
258
258
|
return x.kty === y.kty && x.crv === y.crv && x.n === y.n && x.e === y.e && x.x === y.x && x.y === y.y;
|
|
259
259
|
}
|
|
260
260
|
//#endregion
|
|
261
|
+
//#region src/sig/verification.ts
|
|
262
|
+
const verificationObservation = Symbol("verificationObservation");
|
|
263
|
+
function snapshotKey(key) {
|
|
264
|
+
if (key.publicKey == null) throw new TypeError("Verification key has no public key.");
|
|
265
|
+
const id = key.id == null ? null : new URL(key.id.href);
|
|
266
|
+
return key instanceof CryptographicKey ? {
|
|
267
|
+
type: "cryptographicKey",
|
|
268
|
+
id,
|
|
269
|
+
ownerId: key.ownerId == null ? null : new URL(key.ownerId.href),
|
|
270
|
+
publicKey: key.publicKey
|
|
271
|
+
} : {
|
|
272
|
+
type: "multikey",
|
|
273
|
+
id,
|
|
274
|
+
controllerId: key.controllerId == null ? null : new URL(key.controllerId.href),
|
|
275
|
+
publicKey: key.publicKey
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function triedKey(options, key) {
|
|
279
|
+
const check = options[verificationObservation]?.check;
|
|
280
|
+
if (check != null) check.triedKeys.push(snapshotKey(key));
|
|
281
|
+
}
|
|
282
|
+
async function observeCheck(options, identity, declaredKeyId, operation) {
|
|
283
|
+
const observation = options[verificationObservation];
|
|
284
|
+
if (observation?.attempt == null) return await operation(observation ?? { attempts: [] });
|
|
285
|
+
const check = {
|
|
286
|
+
identity,
|
|
287
|
+
declaredKeyId,
|
|
288
|
+
triedKeys: []
|
|
289
|
+
};
|
|
290
|
+
const base = {
|
|
291
|
+
...identity,
|
|
292
|
+
declaredKeyId,
|
|
293
|
+
triedKeys: check.triedKeys
|
|
294
|
+
};
|
|
295
|
+
try {
|
|
296
|
+
const key = await operation({
|
|
297
|
+
...observation,
|
|
298
|
+
check
|
|
299
|
+
});
|
|
300
|
+
observation.attempt.checks.push(key == null ? {
|
|
301
|
+
...base,
|
|
302
|
+
status: "rejected",
|
|
303
|
+
reason: check.reason ?? { type: "invalidSignature" }
|
|
304
|
+
} : {
|
|
305
|
+
...base,
|
|
306
|
+
status: "verified",
|
|
307
|
+
key: check.triedKeys.at(-1) ?? snapshotKey(key)
|
|
308
|
+
});
|
|
309
|
+
return key;
|
|
310
|
+
} catch (error) {
|
|
311
|
+
observation.attempt.checks.push({
|
|
312
|
+
...base,
|
|
313
|
+
status: "error",
|
|
314
|
+
error
|
|
315
|
+
});
|
|
316
|
+
throw error;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async function observeAttempt(options, mechanism, operation, isVerified) {
|
|
320
|
+
const observation = options[verificationObservation];
|
|
321
|
+
if (observation == null) return await operation({ attempts: [] });
|
|
322
|
+
const attempt = { checks: [] };
|
|
323
|
+
const base = {
|
|
324
|
+
mechanism,
|
|
325
|
+
subject: observation.subject == null ? {
|
|
326
|
+
id: null,
|
|
327
|
+
pointer: ""
|
|
328
|
+
} : {
|
|
329
|
+
id: observation.subject.id == null ? null : new URL(observation.subject.id.href),
|
|
330
|
+
pointer: observation.subject.pointer
|
|
331
|
+
},
|
|
332
|
+
checks: attempt.checks
|
|
333
|
+
};
|
|
334
|
+
try {
|
|
335
|
+
const value = await operation({
|
|
336
|
+
...observation,
|
|
337
|
+
subject: base.subject,
|
|
338
|
+
attempt,
|
|
339
|
+
check: void 0
|
|
340
|
+
});
|
|
341
|
+
observation.attempts.push(isVerified(value) ? {
|
|
342
|
+
...base,
|
|
343
|
+
status: "verified",
|
|
344
|
+
signatures: attempt.checks.filter((check) => check.status === "verified")
|
|
345
|
+
} : {
|
|
346
|
+
...base,
|
|
347
|
+
status: "rejected",
|
|
348
|
+
reason: attempt.reason ?? { type: "signatureVerificationFailed" }
|
|
349
|
+
});
|
|
350
|
+
return value;
|
|
351
|
+
} catch (error) {
|
|
352
|
+
observation.attempts.push({
|
|
353
|
+
...base,
|
|
354
|
+
status: "error",
|
|
355
|
+
error
|
|
356
|
+
});
|
|
357
|
+
throw error;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
//#endregion
|
|
261
361
|
//#region src/sig/accept.ts
|
|
262
362
|
/**
|
|
263
363
|
* `Accept-Signature` header parsing, serialization, and validation utilities
|
|
@@ -714,6 +814,7 @@ var FederationMetrics = class {
|
|
|
714
814
|
};
|
|
715
815
|
if (extra.algorithm != null) attributes["http_signatures.algorithm"] = extra.algorithm;
|
|
716
816
|
if (extra.failureReason != null) attributes["http_signatures.failure_reason"] = extra.failureReason;
|
|
817
|
+
if (extra.verificationFailureReason != null) attributes["activitypub.verification.failure_reason"] = extra.verificationFailureReason;
|
|
717
818
|
if (extra.ldType != null) attributes["ld_signatures.type"] = extra.ldType;
|
|
718
819
|
if (extra.cryptosuite != null) attributes["object_integrity_proofs.cryptosuite"] = extra.cryptosuite;
|
|
719
820
|
this.signatureVerificationDuration.record(durationMs, attributes);
|
|
@@ -2217,12 +2318,38 @@ async function fetchPortableActorKey(cacheKey, cls, options, onCachedUnavailable
|
|
|
2217
2318
|
};
|
|
2218
2319
|
}
|
|
2219
2320
|
async function fetchKeyInternal(keyId, cls, options = {}) {
|
|
2220
|
-
return await fetchKeyWithResult(typeof keyId === "string" ? parseKeyIdOrThrow(keyId) : keyId, cls, options, (_cacheKey, _keyId, _keyCache, _logger) => {
|
|
2321
|
+
return await fetchKeyWithResult(typeof keyId === "string" ? parseKeyIdOrThrow(keyId) : keyId, cls, options, async (_cacheKey, _keyId, _keyCache, _logger) => {
|
|
2322
|
+
const check = options[verificationObservation]?.check;
|
|
2323
|
+
if (check != null) try {
|
|
2324
|
+
const result = await _keyCache?.getFetchError?.(_cacheKey);
|
|
2325
|
+
if (result != null) check.reason = {
|
|
2326
|
+
type: "keyFetchError",
|
|
2327
|
+
keyId: new URL(_cacheKey.href),
|
|
2328
|
+
result
|
|
2329
|
+
};
|
|
2330
|
+
} catch {}
|
|
2221
2331
|
return {
|
|
2222
2332
|
key: null,
|
|
2223
2333
|
cached: true
|
|
2224
2334
|
};
|
|
2225
2335
|
}, async (error, cacheKey, keyId, keyCache, logger) => {
|
|
2336
|
+
const check = options[verificationObservation]?.check;
|
|
2337
|
+
if (check != null) {
|
|
2338
|
+
let result;
|
|
2339
|
+
try {
|
|
2340
|
+
result = error instanceof FetchError && error.response != null ? {
|
|
2341
|
+
status: error.response.status,
|
|
2342
|
+
response: error.response.clone()
|
|
2343
|
+
} : { error: error instanceof Error ? error : new Error(String(error)) };
|
|
2344
|
+
} catch {
|
|
2345
|
+
result = { error: error instanceof Error ? error : /* @__PURE__ */ new Error() };
|
|
2346
|
+
}
|
|
2347
|
+
check.reason = {
|
|
2348
|
+
type: "keyFetchError",
|
|
2349
|
+
keyId: new URL(cacheKey.href),
|
|
2350
|
+
result
|
|
2351
|
+
};
|
|
2352
|
+
}
|
|
2226
2353
|
logger.debug("Failed to fetch key {keyId}.", {
|
|
2227
2354
|
keyId,
|
|
2228
2355
|
error
|
|
@@ -2679,6 +2806,13 @@ function detachSignature(jsonLd) {
|
|
|
2679
2806
|
*/
|
|
2680
2807
|
async function verifySignature(jsonLd, options = {}) {
|
|
2681
2808
|
if (!hasSignature(jsonLd)) return null;
|
|
2809
|
+
if (options[verificationObservation]?.attempt != null && options[verificationObservation]?.check == null) {
|
|
2810
|
+
const declared = getLdSignatureObject(jsonLd)?.creator;
|
|
2811
|
+
return await observeCheck(options, { mechanism: "linkedData" }, typeof declared === "string" ? declared : null, (observation) => verifySignature(jsonLd, {
|
|
2812
|
+
...options,
|
|
2813
|
+
[verificationObservation]: observation
|
|
2814
|
+
}));
|
|
2815
|
+
}
|
|
2682
2816
|
const sig = jsonLd.signature;
|
|
2683
2817
|
let signature;
|
|
2684
2818
|
try {
|
|
@@ -2724,6 +2858,7 @@ async function verifySignature(jsonLd, options = {}) {
|
|
|
2724
2858
|
const encoder = new TextEncoder();
|
|
2725
2859
|
const message = sigOptsHash + docHash;
|
|
2726
2860
|
const messageBytes = encoder.encode(message);
|
|
2861
|
+
triedKey(options, key);
|
|
2727
2862
|
if (await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, signature.slice(), messageBytes)) return key;
|
|
2728
2863
|
if (cached) {
|
|
2729
2864
|
logger$4.debug("Failed to verify with the cached key {keyId}; signature {signatureValue} is invalid. Retrying with the freshly fetched key...", {
|
|
@@ -2735,6 +2870,7 @@ async function verifySignature(jsonLd, options = {}) {
|
|
|
2735
2870
|
keyCache: bypassKeyCacheReads(options.keyCache)
|
|
2736
2871
|
}));
|
|
2737
2872
|
if (key == null) return null;
|
|
2873
|
+
triedKey(options, key);
|
|
2738
2874
|
return await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, signature.slice(), messageBytes) ? key : null;
|
|
2739
2875
|
}
|
|
2740
2876
|
logger$4.debug("Failed to verify with the fetched key {keyId}; signature {signatureValue} is invalid. Check if the key is correct or if the signed message is correct. The message to sign is:\n{message}", {
|
|
@@ -2788,6 +2924,14 @@ async function verifyCompactJsonLd(jsonLd, options = {}) {
|
|
|
2788
2924
|
return await verifyJsonLdInternal(jsonLd, options, false);
|
|
2789
2925
|
}
|
|
2790
2926
|
async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
2927
|
+
if (options[verificationObservation]?.attempt == null) return await observeAttempt({
|
|
2928
|
+
...options,
|
|
2929
|
+
[verificationObservation]: options[verificationObservation] ?? { attempts: [] }
|
|
2930
|
+
}, "linkedData", (observation) => verifyJsonLdInternal(jsonLd, {
|
|
2931
|
+
...options,
|
|
2932
|
+
[verificationObservation]: observation
|
|
2933
|
+
}, compact), (value) => value);
|
|
2934
|
+
const observation = options[verificationObservation];
|
|
2791
2935
|
return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("ld_signatures.verify", async (span) => {
|
|
2792
2936
|
const start = performance.now();
|
|
2793
2937
|
let verified = false;
|
|
@@ -2800,6 +2944,8 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
|
2800
2944
|
} : options;
|
|
2801
2945
|
const compacted = compact ? hasSignature(jsonLd) ? await compactJsonLd(jsonLd, options.contextLoader) : jsonLd : jsonLd;
|
|
2802
2946
|
const object = await Object$1.fromJsonLd(compacted, verificationOptions);
|
|
2947
|
+
observation?.parsedObject?.(object);
|
|
2948
|
+
if (observation?.subject != null) observation.subject.id = object.id == null ? null : new URL(object.id.href);
|
|
2803
2949
|
if (object.id != null) span.setAttribute("activitypub.object.id", object.id.href);
|
|
2804
2950
|
span.setAttribute("activitypub.object.type", getTypeId(object).href);
|
|
2805
2951
|
const sig = getLdSignatureObject(jsonLd);
|
|
@@ -2814,13 +2960,21 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
|
2814
2960
|
const attributions = new Set(object.attributionIds.map((uri) => uri.href));
|
|
2815
2961
|
if (object instanceof Activity) for (const uri of object.actorIds) attributions.add(uri.href);
|
|
2816
2962
|
const key = await verifySignature(compacted, verificationOptions);
|
|
2817
|
-
if (key == null)
|
|
2963
|
+
if (key == null) {
|
|
2964
|
+
if (observation?.attempt != null && !hasLdSignatureProperty(jsonLd)) observation.attempt.reason = { type: "noSignature" };
|
|
2965
|
+
return false;
|
|
2966
|
+
}
|
|
2818
2967
|
if (key.ownerId == null) {
|
|
2968
|
+
if (observation?.attempt != null) observation.attempt.reason = { type: "missingOwner" };
|
|
2819
2969
|
logger$4.debug("Key {keyId} has no owner.", { keyId: key.id?.href });
|
|
2820
2970
|
return false;
|
|
2821
2971
|
}
|
|
2822
2972
|
attributions.delete(key.ownerId.href);
|
|
2823
2973
|
if (attributions.size > 0) {
|
|
2974
|
+
if (observation?.attempt != null) observation.attempt.reason = {
|
|
2975
|
+
type: "uncoveredAttribution",
|
|
2976
|
+
attributionIds: [...attributions].map((id) => new URL(id))
|
|
2977
|
+
};
|
|
2824
2978
|
logger$4.debug("Some attributions are not authenticated by the Linked Data Signatures: {attributions}.", { attributions: [...attributions] });
|
|
2825
2979
|
return false;
|
|
2826
2980
|
}
|
|
@@ -2834,8 +2988,15 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
|
2834
2988
|
});
|
|
2835
2989
|
throw error;
|
|
2836
2990
|
} finally {
|
|
2991
|
+
if (observation?.attempt?.reason != null) {
|
|
2992
|
+
span.setAttribute("activitypub.verification.failure_reason", observation.attempt.reason.type);
|
|
2993
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
2994
|
+
}
|
|
2837
2995
|
const classified = threw ? "error" : verified ? "verified" : hasLdSignatureProperty(jsonLd) ? "rejected" : "missing";
|
|
2838
|
-
getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "linked_data", classified, {
|
|
2996
|
+
getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "linked_data", classified, {
|
|
2997
|
+
ldType: signatureType,
|
|
2998
|
+
verificationFailureReason: verified || threw ? void 0 : observation?.attempt?.reason?.type ?? (hasLdSignatureProperty(jsonLd) ? "signatureVerificationFailed" : "noSignature")
|
|
2999
|
+
});
|
|
2839
3000
|
span.end();
|
|
2840
3001
|
}
|
|
2841
3002
|
});
|
|
@@ -3463,19 +3624,50 @@ async function verifyProof(jsonLd, proof, options = {}) {
|
|
|
3463
3624
|
*/
|
|
3464
3625
|
async function verifyMapLocalProof(jsonLd, options = {}) {
|
|
3465
3626
|
if (!isJsonLdNode(jsonLd) || !globalThis.Object.hasOwn(jsonLd, "proof") || !isJsonLdNode(jsonLd.proof)) return null;
|
|
3627
|
+
const rawProof = jsonLd.proof;
|
|
3628
|
+
const previousChecks = options[verificationObservation]?.attempt?.checks.length;
|
|
3629
|
+
const malformed = () => observeCheck(options, {
|
|
3630
|
+
mechanism: "objectIntegrity",
|
|
3631
|
+
proofId: typeof rawProof.id === "string" ? rawProof.id : typeof rawProof["@id"] === "string" ? rawProof["@id"] : null,
|
|
3632
|
+
proofIndex: previousChecks ?? null
|
|
3633
|
+
}, getDeclaredProofKeyId(rawProof), () => Promise.resolve(null));
|
|
3466
3634
|
const proofContextLoader = getNormalizationContextLoader(preloadedOnlyDocumentLoader);
|
|
3467
3635
|
try {
|
|
3468
3636
|
const [candidate] = await parseRawProofCandidates(jsonLd, [jsonLd.proof], options, proofContextLoader);
|
|
3469
|
-
if (candidate.proof == null) return
|
|
3637
|
+
if (candidate.proof == null) return await malformed();
|
|
3470
3638
|
return await verifyProofWithMessageDigestCache(jsonLd, candidate.proof, options, {
|
|
3471
3639
|
proofContextLoader,
|
|
3472
3640
|
proofPropertyMode: "literal"
|
|
3473
3641
|
}, candidate);
|
|
3474
3642
|
} catch {
|
|
3643
|
+
if (previousChecks != null && options[verificationObservation]?.attempt?.checks.length === previousChecks) return await malformed();
|
|
3475
3644
|
return null;
|
|
3476
3645
|
}
|
|
3477
3646
|
}
|
|
3647
|
+
function getDeclaredProofKeyId(value, propertyNames = ["verificationMethod", "https://w3id.org/security#verificationMethod"]) {
|
|
3648
|
+
if (!isJsonLdNode(value)) return null;
|
|
3649
|
+
const method = Array.from(propertyNames, (name) => value[name]).find((v) => v != null);
|
|
3650
|
+
const candidate = Array.isArray(method) ? method[0] : method;
|
|
3651
|
+
if (typeof candidate === "string") return candidate;
|
|
3652
|
+
return isJsonLdNode(candidate) && typeof candidate["@id"] === "string" ? candidate["@id"] : null;
|
|
3653
|
+
}
|
|
3478
3654
|
async function verifyProofWithMessageDigestCache(jsonLd, proof, options, messageDigestCache = {}, rawProofCandidate, keyIdBoundByCaller = false) {
|
|
3655
|
+
if (options[verificationObservation] == null) return await verifyProofWithMessageDigestCache(jsonLd, proof, {
|
|
3656
|
+
...options,
|
|
3657
|
+
[verificationObservation]: {
|
|
3658
|
+
attempts: [],
|
|
3659
|
+
attempt: { checks: [] },
|
|
3660
|
+
captureRawKeyIds: false
|
|
3661
|
+
}
|
|
3662
|
+
}, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
|
|
3663
|
+
if (options[verificationObservation]?.attempt != null && options[verificationObservation]?.check == null) return await observeCheck(options, {
|
|
3664
|
+
mechanism: "objectIntegrity",
|
|
3665
|
+
proofId: proof.id?.href ?? null,
|
|
3666
|
+
proofIndex: options[verificationObservation].attempt.checks.length
|
|
3667
|
+
}, rawProofCandidate?.declaredKeyId ?? getDeclaredProofKeyId(rawProofCandidate?.value) ?? proof.verificationMethodId?.href ?? null, (observation) => verifyProofWithMessageDigestCache(jsonLd, proof, {
|
|
3668
|
+
...options,
|
|
3669
|
+
[verificationObservation]: observation
|
|
3670
|
+
}, messageDigestCache, rawProofCandidate, keyIdBoundByCaller));
|
|
3479
3671
|
return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("object_integrity_proofs.verify", async (span) => {
|
|
3480
3672
|
const start = performance.now();
|
|
3481
3673
|
let verified = false;
|
|
@@ -3488,8 +3680,10 @@ async function verifyProofWithMessageDigestCache(jsonLd, proof, options, message
|
|
|
3488
3680
|
}
|
|
3489
3681
|
try {
|
|
3490
3682
|
const key = await verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
|
|
3491
|
-
if (key == null)
|
|
3492
|
-
|
|
3683
|
+
if (key == null) {
|
|
3684
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
3685
|
+
span.setAttribute("activitypub.verification.failure_reason", options[verificationObservation]?.check?.reason?.type ?? "invalidSignature");
|
|
3686
|
+
} else verified = true;
|
|
3493
3687
|
return key;
|
|
3494
3688
|
} catch (error) {
|
|
3495
3689
|
threw = true;
|
|
@@ -3500,7 +3694,10 @@ async function verifyProofWithMessageDigestCache(jsonLd, proof, options, message
|
|
|
3500
3694
|
throw error;
|
|
3501
3695
|
} finally {
|
|
3502
3696
|
const classified = threw ? "error" : verified ? "verified" : "rejected";
|
|
3503
|
-
getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "object_integrity", classified, {
|
|
3697
|
+
getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "object_integrity", classified, {
|
|
3698
|
+
cryptosuite,
|
|
3699
|
+
verificationFailureReason: verified || threw ? void 0 : options[verificationObservation]?.check?.reason?.type ?? "invalidSignature"
|
|
3700
|
+
});
|
|
3504
3701
|
span.end();
|
|
3505
3702
|
}
|
|
3506
3703
|
});
|
|
@@ -3517,14 +3714,15 @@ function expandContextPropertyIri(activeContext, key) {
|
|
|
3517
3714
|
if (typeof mapping === "object" && mapping != null && mapping._prefix === true && typeof mapping["@id"] === "string") return mapping["@id"] + suffix;
|
|
3518
3715
|
return key;
|
|
3519
3716
|
}
|
|
3520
|
-
async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoader = preloadedOnlyDocumentLoader, inheritedContext, rejectOnContextError = false) {
|
|
3717
|
+
async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoader = preloadedOnlyDocumentLoader, inheritedContext, rejectOnContextError = false, contextState) {
|
|
3521
3718
|
const names = new Set(defaults);
|
|
3522
3719
|
const context = jsonLd["@context"] ?? inheritedContext;
|
|
3523
|
-
if (context == null) return names;
|
|
3720
|
+
if (context == null && contextState?.initial == null) return names;
|
|
3524
3721
|
try {
|
|
3525
3722
|
const options = { documentLoader };
|
|
3526
|
-
let activeContext = await jsonld.processContext(null, null, options);
|
|
3527
|
-
activeContext = await jsonld.processContext(activeContext,
|
|
3723
|
+
let activeContext = contextState?.initial ?? await jsonld.processContext(null, null, options);
|
|
3724
|
+
if (contextState?.scoped != null) activeContext = await jsonld.processContext(activeContext, contextState.scoped, options);
|
|
3725
|
+
if (context != null) activeContext = await jsonld.processContext(activeContext, context, options);
|
|
3528
3726
|
const typeScopedContext = activeContext;
|
|
3529
3727
|
for (const key of globalThis.Object.keys(jsonLd).sort()) {
|
|
3530
3728
|
if (key !== "@type" && expandContextPropertyIri(activeContext, key) !== "@type") continue;
|
|
@@ -3536,6 +3734,7 @@ async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoa
|
|
|
3536
3734
|
if (scopedContext != null) activeContext = await jsonld.processContext(activeContext, scopedContext, options);
|
|
3537
3735
|
}
|
|
3538
3736
|
}
|
|
3737
|
+
if (contextState != null) contextState.active = activeContext;
|
|
3539
3738
|
for (const key of globalThis.Object.keys(jsonLd)) if (expandContextPropertyIri(activeContext, key) === propertyIri) names.add(key);
|
|
3540
3739
|
} catch {
|
|
3541
3740
|
if (rejectOnContextError) return null;
|
|
@@ -3602,6 +3801,36 @@ function sameBytes(left, right) {
|
|
|
3602
3801
|
function sameProof(left, right) {
|
|
3603
3802
|
return left.cryptosuite === right.cryptosuite && left.verificationMethodId?.href === right.verificationMethodId?.href && left.proofPurpose === right.proofPurpose && sameBytes(left.proofValue, right.proofValue) && left.created?.toString() === right.created?.toString();
|
|
3604
3803
|
}
|
|
3804
|
+
function recordProofContexts(documentLoader) {
|
|
3805
|
+
const documents = /* @__PURE__ */ new Map();
|
|
3806
|
+
return {
|
|
3807
|
+
loader: async (url, options) => {
|
|
3808
|
+
const document = await documentLoader(url, options);
|
|
3809
|
+
documents.set(url, document);
|
|
3810
|
+
return document;
|
|
3811
|
+
},
|
|
3812
|
+
replay: (url, options) => {
|
|
3813
|
+
const document = documents.get(url);
|
|
3814
|
+
return document == null ? preloadedOnlyDocumentLoader(url, options) : Promise.resolve(document);
|
|
3815
|
+
}
|
|
3816
|
+
};
|
|
3817
|
+
}
|
|
3818
|
+
async function getAliasedDeclaredProofKeyId(value, inheritedContext, contextLoader) {
|
|
3819
|
+
if (!isJsonLdNode(value)) return null;
|
|
3820
|
+
const contextState = {};
|
|
3821
|
+
const names = await getJsonLdPropertyNames(value, "https://w3id.org/security#verificationMethod", ["verificationMethod", "https://w3id.org/security#verificationMethod"], contextLoader, inheritedContext, false, contextState);
|
|
3822
|
+
const methodName = Array.from(names ?? []).find((name) => value[name] != null);
|
|
3823
|
+
const method = methodName == null ? void 0 : value[methodName];
|
|
3824
|
+
const candidate = Array.isArray(method) ? method[0] : method;
|
|
3825
|
+
if (typeof candidate === "string") return candidate;
|
|
3826
|
+
if (!isJsonLdNode(candidate)) return null;
|
|
3827
|
+
const idNames = await getJsonLdPropertyNames(candidate, "@id", ["@id"], contextLoader, void 0, false, {
|
|
3828
|
+
initial: contextState.active,
|
|
3829
|
+
scoped: contextState.active == null || methodName == null ? void 0 : jsonld.getContextValue(contextState.active, methodName, "@context")
|
|
3830
|
+
});
|
|
3831
|
+
for (const name of idNames ?? []) if (typeof candidate[name] === "string") return candidate[name];
|
|
3832
|
+
return null;
|
|
3833
|
+
}
|
|
3605
3834
|
function normalizeDocumentUrl(url) {
|
|
3606
3835
|
try {
|
|
3607
3836
|
return formatIri(parseIri(url));
|
|
@@ -3620,6 +3849,7 @@ async function parseRawProofCandidates(jsonLd, values, options, documentLoader)
|
|
|
3620
3849
|
const candidates = [];
|
|
3621
3850
|
for (const value of values) {
|
|
3622
3851
|
let parsed = null;
|
|
3852
|
+
const contexts = options[verificationObservation] != null && options[verificationObservation]?.captureRawKeyIds !== false ? recordProofContexts(documentLoader) : void 0;
|
|
3623
3853
|
if (isJsonLdNode(value)) {
|
|
3624
3854
|
const proofJsonLd = value["@context"] == null && jsonLd["@context"] != null ? {
|
|
3625
3855
|
"@context": jsonLd["@context"],
|
|
@@ -3628,14 +3858,15 @@ async function parseRawProofCandidates(jsonLd, values, options, documentLoader)
|
|
|
3628
3858
|
try {
|
|
3629
3859
|
parsed = await DataIntegrityProof.fromJsonLd(proofJsonLd, {
|
|
3630
3860
|
...options,
|
|
3631
|
-
contextLoader: documentLoader
|
|
3861
|
+
contextLoader: contexts?.loader ?? documentLoader
|
|
3632
3862
|
});
|
|
3633
3863
|
} catch {}
|
|
3634
3864
|
}
|
|
3635
3865
|
candidates.push({
|
|
3636
3866
|
value,
|
|
3637
3867
|
proof: parsed,
|
|
3638
|
-
reference: getRawProofReference(value) ?? parsed?.id?.href
|
|
3868
|
+
reference: getRawProofReference(value) ?? parsed?.id?.href,
|
|
3869
|
+
declaredKeyId: contexts == null ? void 0 : await getAliasedDeclaredProofKeyId(value, jsonLd["@context"], contexts.replay)
|
|
3639
3870
|
});
|
|
3640
3871
|
}
|
|
3641
3872
|
return candidates;
|
|
@@ -3859,8 +4090,13 @@ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, r
|
|
|
3859
4090
|
const digest = new Uint8Array(proofDigest.byteLength + 32);
|
|
3860
4091
|
digest.set(new Uint8Array(proofDigest), 0);
|
|
3861
4092
|
const proofValue = proof.proofValue;
|
|
4093
|
+
let keyTried = false;
|
|
3862
4094
|
const verifyCandidate = async (msgDigest) => {
|
|
3863
4095
|
digest.set(new Uint8Array(msgDigest), proofDigest.byteLength);
|
|
4096
|
+
if (!keyTried) {
|
|
4097
|
+
triedKey(options, publicKey);
|
|
4098
|
+
keyTried = true;
|
|
4099
|
+
}
|
|
3864
4100
|
return await crypto.subtle.verify("Ed25519", publicKey.publicKey, proofValue.slice(), digest);
|
|
3865
4101
|
};
|
|
3866
4102
|
const messageDigestKey = serialize(proofConfiguration.context ?? null);
|
|
@@ -4318,12 +4554,47 @@ async function verifyPreparedPortableObjectProof(jsonLd, prepared, options, mess
|
|
|
4318
4554
|
* @since 0.10.0
|
|
4319
4555
|
*/
|
|
4320
4556
|
async function verifyObject(cls, jsonLd, options = {}) {
|
|
4557
|
+
if (options[verificationObservation]?.attempt == null) {
|
|
4558
|
+
const observedOptions = {
|
|
4559
|
+
...options,
|
|
4560
|
+
[verificationObservation]: options[verificationObservation] ?? {
|
|
4561
|
+
attempts: [],
|
|
4562
|
+
captureRawKeyIds: false
|
|
4563
|
+
}
|
|
4564
|
+
};
|
|
4565
|
+
return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("object_integrity_proofs.verify_object", async (span) => {
|
|
4566
|
+
try {
|
|
4567
|
+
return await observeAttempt(observedOptions, "objectIntegrity", async (observation) => {
|
|
4568
|
+
const object = await verifyObject(cls, jsonLd, {
|
|
4569
|
+
...options,
|
|
4570
|
+
[verificationObservation]: observation
|
|
4571
|
+
});
|
|
4572
|
+
if (object == null) {
|
|
4573
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
4574
|
+
span.setAttribute("activitypub.verification.failure_reason", observation.attempt?.reason?.type ?? "signatureVerificationFailed");
|
|
4575
|
+
}
|
|
4576
|
+
return object;
|
|
4577
|
+
}, (value) => value != null);
|
|
4578
|
+
} catch (error) {
|
|
4579
|
+
span.setStatus({
|
|
4580
|
+
code: SpanStatusCode.ERROR,
|
|
4581
|
+
message: String(error)
|
|
4582
|
+
});
|
|
4583
|
+
throw error;
|
|
4584
|
+
} finally {
|
|
4585
|
+
span.end();
|
|
4586
|
+
}
|
|
4587
|
+
});
|
|
4588
|
+
}
|
|
4589
|
+
const observation = options[verificationObservation];
|
|
4321
4590
|
const logger = getLogger([
|
|
4322
4591
|
"fedify",
|
|
4323
4592
|
"sig",
|
|
4324
4593
|
"proof"
|
|
4325
4594
|
]);
|
|
4326
4595
|
const object = await cls.fromJsonLd(jsonLd, options);
|
|
4596
|
+
observation?.parsedObject?.(object);
|
|
4597
|
+
if (observation?.subject != null) observation.subject.id = object.id == null ? null : new URL(object.id.href);
|
|
4327
4598
|
const defaultDocumentLoader = getDocumentLoader();
|
|
4328
4599
|
const proofContextLoader = options.contextLoader ?? defaultDocumentLoader;
|
|
4329
4600
|
const attributions = new Set(object.attributionIds.map((uri) => uri.href));
|
|
@@ -4350,10 +4621,11 @@ async function verifyObject(cls, jsonLd, options = {}) {
|
|
|
4350
4621
|
if (candidateIndex >= 0) {
|
|
4351
4622
|
hydratedCandidates.add(candidateIndex);
|
|
4352
4623
|
let parsed = null;
|
|
4624
|
+
const contexts = options[verificationObservation] != null && options[verificationObservation]?.captureRawKeyIds !== false ? recordProofContexts(proofContextLoader) : void 0;
|
|
4353
4625
|
try {
|
|
4354
4626
|
parsed = await DataIntegrityProof.fromJsonLd(remoteDocument.document, {
|
|
4355
4627
|
documentLoader: baseDocumentLoader,
|
|
4356
|
-
contextLoader: proofContextLoader,
|
|
4628
|
+
contextLoader: contexts?.loader ?? proofContextLoader,
|
|
4357
4629
|
tracerProvider: options.tracerProvider,
|
|
4358
4630
|
baseUrl: parseIri(remoteDocument.documentUrl)
|
|
4359
4631
|
});
|
|
@@ -4361,7 +4633,8 @@ async function verifyObject(cls, jsonLd, options = {}) {
|
|
|
4361
4633
|
rawProofCandidates[candidateIndex] = {
|
|
4362
4634
|
value: structuredClone(remoteDocument.document),
|
|
4363
4635
|
proof: parsed,
|
|
4364
|
-
reference
|
|
4636
|
+
reference,
|
|
4637
|
+
declaredKeyId: contexts == null ? void 0 : await getAliasedDeclaredProofKeyId(remoteDocument.document, void 0, contexts.replay)
|
|
4365
4638
|
};
|
|
4366
4639
|
}
|
|
4367
4640
|
return remoteDocument;
|
|
@@ -4389,6 +4662,10 @@ async function verifyObject(cls, jsonLd, options = {}) {
|
|
|
4389
4662
|
return null;
|
|
4390
4663
|
}
|
|
4391
4664
|
if (attributions.size > 0) {
|
|
4665
|
+
if (observation?.attempt != null) observation.attempt.reason = observation.attempt.checks.length === 0 ? { type: "noSignature" } : {
|
|
4666
|
+
type: "uncoveredAttribution",
|
|
4667
|
+
attributionIds: [...attributions].map((id) => new URL(id))
|
|
4668
|
+
};
|
|
4392
4669
|
logger.debug("Some attributions are not authenticated by the proofs: {attributions}.", { attributions: [...attributions] });
|
|
4393
4670
|
return null;
|
|
4394
4671
|
}
|
|
@@ -5408,6 +5685,14 @@ async function verifyRequest(request, options = {}) {
|
|
|
5408
5685
|
* @since 2.1.0
|
|
5409
5686
|
*/
|
|
5410
5687
|
async function verifyRequestDetailed(request, options = {}) {
|
|
5688
|
+
if (options[verificationObservation] != null && options[verificationObservation]?.attempt == null) return await observeAttempt(options, "http", async (observation) => {
|
|
5689
|
+
const result = await verifyRequestDetailed(request, {
|
|
5690
|
+
...options,
|
|
5691
|
+
[verificationObservation]: observation
|
|
5692
|
+
});
|
|
5693
|
+
if (!result.verified) observation.attempt.reason = result.reason.type === "noSignature" ? { type: "noSignature" } : { type: "signatureVerificationFailed" };
|
|
5694
|
+
return result;
|
|
5695
|
+
}, (result) => result.verified);
|
|
5411
5696
|
if (options.maxSignatures !== void 0) validateMaxSignatures(options.maxSignatures, "maxSignatures");
|
|
5412
5697
|
return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("http_signatures.verify", async (span) => {
|
|
5413
5698
|
if (span.isRecording()) {
|
|
@@ -5446,7 +5731,27 @@ async function verifyRequestDetailed(request, options = {}) {
|
|
|
5446
5731
|
}
|
|
5447
5732
|
});
|
|
5448
5733
|
}
|
|
5449
|
-
async function verifyRequestDraft(request, span, metricsContext,
|
|
5734
|
+
async function verifyRequestDraft(request, span, metricsContext, options = {}) {
|
|
5735
|
+
if (options[verificationObservation]?.check != null) return await verifyRequestDraftInternal(request, span, metricsContext, options);
|
|
5736
|
+
let result = noSignatureResult();
|
|
5737
|
+
const raw = request.headers.get("Signature");
|
|
5738
|
+
const declared = raw == null ? null : parseDraftSignature(raw)?.keyId ?? null;
|
|
5739
|
+
if (raw == null) return result;
|
|
5740
|
+
await observeCheck(options, {
|
|
5741
|
+
mechanism: "http",
|
|
5742
|
+
spec: "draft-cavage-http-signatures-12",
|
|
5743
|
+
label: null
|
|
5744
|
+
}, declared, async (observation) => {
|
|
5745
|
+
result = await verifyRequestDraftInternal(request, span, metricsContext, {
|
|
5746
|
+
...options,
|
|
5747
|
+
[verificationObservation]: observation
|
|
5748
|
+
});
|
|
5749
|
+
if (!result.verified && result.reason.type === "keyFetchError" && observation.check != null) observation.check.reason = result.reason;
|
|
5750
|
+
return result.verified ? result.key : null;
|
|
5751
|
+
});
|
|
5752
|
+
return result;
|
|
5753
|
+
}
|
|
5754
|
+
async function verifyRequestDraftInternal(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider, [verificationObservation]: observation } = {}) {
|
|
5450
5755
|
const logger = getLogger([
|
|
5451
5756
|
"fedify",
|
|
5452
5757
|
"sig",
|
|
@@ -5627,6 +5932,7 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
|
|
|
5627
5932
|
const message = headerNames.map((name) => `${name}: ` + (name === "(request-target)" ? `${request.method.toLowerCase()} ${new URL(request.url).pathname}` : name === "(created)" ? sigValues.created ?? "" : name === "(expires)" ? sigValues.expires ?? "" : name === "host" ? request.headers.get("host") ?? new URL(request.url).host : request.headers.get(name))).join("\n");
|
|
5628
5933
|
const sig = decodeBase64(signature);
|
|
5629
5934
|
span?.setAttribute("http_signatures.signature", encodeHex(sig));
|
|
5935
|
+
triedKey({ [verificationObservation]: observation }, key);
|
|
5630
5936
|
if (!await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, sig, new TextEncoder().encode(message))) {
|
|
5631
5937
|
if (cached) {
|
|
5632
5938
|
logger.debug("Failed to verify with the cached key {keyId}; signature {signature} is invalid. Retrying with the freshly fetched key...", {
|
|
@@ -5641,7 +5947,8 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
|
|
|
5641
5947
|
currentTime,
|
|
5642
5948
|
keyCache: bypassKeyCacheReads(keyCache),
|
|
5643
5949
|
meterProvider,
|
|
5644
|
-
tracerProvider
|
|
5950
|
+
tracerProvider,
|
|
5951
|
+
[verificationObservation]: observation
|
|
5645
5952
|
});
|
|
5646
5953
|
}
|
|
5647
5954
|
logger.debug("Failed to verify with the fetched key {keyId}; signature {signature} is invalid. Check if the key is correct or if the signed message is correct. The message to sign is:\n{message}", {
|
|
@@ -5727,7 +6034,7 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
|
|
|
5727
6034
|
* @returns Whether the signature is verified, and the algorithm of the
|
|
5728
6035
|
* signature if it is supported.
|
|
5729
6036
|
*/
|
|
5730
|
-
async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span) {
|
|
6037
|
+
async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span, observationOptions = {}) {
|
|
5731
6038
|
const logger = getLogger([
|
|
5732
6039
|
"fedify",
|
|
5733
6040
|
"sig",
|
|
@@ -5783,6 +6090,7 @@ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, s
|
|
|
5783
6090
|
}
|
|
5784
6091
|
let verified;
|
|
5785
6092
|
try {
|
|
6093
|
+
triedKey(observationOptions, key);
|
|
5786
6094
|
verified = await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes);
|
|
5787
6095
|
} catch (error) {
|
|
5788
6096
|
logger.debug("Error during signature verification: {error}", {
|
|
@@ -5810,7 +6118,7 @@ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, s
|
|
|
5810
6118
|
mismatched: true
|
|
5811
6119
|
};
|
|
5812
6120
|
}
|
|
5813
|
-
async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, maxSignatures = 3, meterProvider, tracerProvider } = {}) {
|
|
6121
|
+
async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, maxSignatures = 3, [verificationObservation]: observation, meterProvider, tracerProvider } = {}) {
|
|
5814
6122
|
const logger = getLogger([
|
|
5815
6123
|
"fedify",
|
|
5816
6124
|
"sig",
|
|
@@ -5874,105 +6182,116 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
|
|
|
5874
6182
|
let body = null;
|
|
5875
6183
|
let digestValid = null;
|
|
5876
6184
|
for (const sigName of signatureNames) {
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
signatureName: sigName,
|
|
5887
|
-
signatureInput: signatureInputHeader
|
|
5888
|
-
});
|
|
5889
|
-
setFailure(invalidSignatureResult(null));
|
|
5890
|
-
continue;
|
|
5891
|
-
}
|
|
5892
|
-
if (!sigInput.created) {
|
|
5893
|
-
logger.debug("Failed to verify; missing created timestamp in signature {signatureName}.", {
|
|
5894
|
-
signatureName: sigName,
|
|
5895
|
-
signatureInput: signatureInputHeader
|
|
5896
|
-
});
|
|
5897
|
-
setFailure(invalidSignatureResult(keyId));
|
|
5898
|
-
continue;
|
|
5899
|
-
}
|
|
5900
|
-
const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
|
|
5901
|
-
const now = currentTime ?? Temporal.Now.instant();
|
|
5902
|
-
if (timeWindow !== false) {
|
|
5903
|
-
const tw = timeWindow ?? { hours: 1 };
|
|
5904
|
-
if (Temporal.Instant.compare(signatureCreated, now.add(tw)) > 0) {
|
|
5905
|
-
logger.debug("Failed to verify; signature created time is too far in the future.", {
|
|
5906
|
-
created: signatureCreated.toString(),
|
|
5907
|
-
now: now.toString()
|
|
5908
|
-
});
|
|
5909
|
-
setFailure(invalidSignatureResult(keyId));
|
|
5910
|
-
continue;
|
|
5911
|
-
} else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
|
|
5912
|
-
logger.debug("Failed to verify; signature created time is too far in the past.", {
|
|
5913
|
-
created: signatureCreated.toString(),
|
|
5914
|
-
now: now.toString()
|
|
5915
|
-
});
|
|
5916
|
-
setFailure(invalidSignatureResult(keyId));
|
|
5917
|
-
continue;
|
|
6185
|
+
let winning;
|
|
6186
|
+
await observeCheck({ [verificationObservation]: observation }, {
|
|
6187
|
+
mechanism: "http",
|
|
6188
|
+
spec: "rfc9421",
|
|
6189
|
+
label: sigName
|
|
6190
|
+
}, signatureInputs[sigName]?.keyId ?? null, async (candidateObservation) => {
|
|
6191
|
+
if (!signatures[sigName]) {
|
|
6192
|
+
setFailure(invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId)));
|
|
6193
|
+
return null;
|
|
5918
6194
|
}
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
const
|
|
5922
|
-
if (!
|
|
5923
|
-
logger.debug("Failed to verify;
|
|
5924
|
-
|
|
5925
|
-
|
|
6195
|
+
const sigInput = signatureInputs[sigName];
|
|
6196
|
+
const sigBytes = signatures[sigName];
|
|
6197
|
+
const keyId = parseKeyId(sigInput.keyId);
|
|
6198
|
+
if (!sigInput.keyId) {
|
|
6199
|
+
logger.debug("Failed to verify; missing keyId in signature {signatureName}.", {
|
|
6200
|
+
signatureName: sigName,
|
|
6201
|
+
signatureInput: signatureInputHeader
|
|
6202
|
+
});
|
|
6203
|
+
setFailure(invalidSignatureResult(null));
|
|
6204
|
+
return null;
|
|
5926
6205
|
}
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
6206
|
+
if (!sigInput.created) {
|
|
6207
|
+
logger.debug("Failed to verify; missing created timestamp in signature {signatureName}.", {
|
|
6208
|
+
signatureName: sigName,
|
|
6209
|
+
signatureInput: signatureInputHeader
|
|
6210
|
+
});
|
|
5931
6211
|
setFailure(invalidSignatureResult(keyId));
|
|
5932
|
-
|
|
6212
|
+
return null;
|
|
5933
6213
|
}
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
6214
|
+
const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
|
|
6215
|
+
const now = currentTime ?? Temporal.Now.instant();
|
|
6216
|
+
if (timeWindow !== false) {
|
|
6217
|
+
const tw = timeWindow ?? { hours: 1 };
|
|
6218
|
+
if (Temporal.Instant.compare(signatureCreated, now.add(tw)) > 0) {
|
|
6219
|
+
logger.debug("Failed to verify; signature created time is too far in the future.", {
|
|
6220
|
+
created: signatureCreated.toString(),
|
|
6221
|
+
now: now.toString()
|
|
6222
|
+
});
|
|
6223
|
+
setFailure(invalidSignatureResult(keyId));
|
|
6224
|
+
return null;
|
|
6225
|
+
} else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
|
|
6226
|
+
logger.debug("Failed to verify; signature created time is too far in the past.", {
|
|
6227
|
+
created: signatureCreated.toString(),
|
|
6228
|
+
now: now.toString()
|
|
6229
|
+
});
|
|
6230
|
+
setFailure(invalidSignatureResult(keyId));
|
|
6231
|
+
return null;
|
|
6232
|
+
}
|
|
5951
6233
|
}
|
|
5952
|
-
if (
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
6234
|
+
if (request.method !== "GET" && request.method !== "HEAD" && sigInput.components.some((c) => c.value === "content-digest")) {
|
|
6235
|
+
const contentDigestHeader = request.headers.get("Content-Digest");
|
|
6236
|
+
if (!contentDigestHeader) {
|
|
6237
|
+
logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
|
|
6238
|
+
setFailure(invalidSignatureResult(keyId));
|
|
6239
|
+
return null;
|
|
6240
|
+
}
|
|
6241
|
+
body ??= await request.arrayBuffer();
|
|
6242
|
+
digestValid ??= await verifyRfc9421ContentDigest(contentDigestHeader, body);
|
|
6243
|
+
if (!digestValid) {
|
|
6244
|
+
logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
|
|
6245
|
+
setFailure(invalidSignatureResult(keyId));
|
|
6246
|
+
return null;
|
|
6247
|
+
}
|
|
5956
6248
|
}
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
key,
|
|
5963
|
-
signatureLabel: sigName
|
|
5964
|
-
};
|
|
6249
|
+
span?.setAttribute("http_signatures.key_id", sigInput.keyId);
|
|
6250
|
+
span?.setAttribute("http_signatures.created", sigInput.created.toString());
|
|
6251
|
+
if (keyId == null) {
|
|
6252
|
+
setFailure(invalidSignatureResult(null));
|
|
6253
|
+
return null;
|
|
5965
6254
|
}
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
lookup = await lookUpKey(keyId, bypassKeyCacheReads(keyCache));
|
|
6255
|
+
let lookup = keyLookups.get(keyId.href);
|
|
6256
|
+
if (lookup == null) {
|
|
6257
|
+
lookup = await lookUpKey(keyId, keyCache);
|
|
5970
6258
|
keyLookups.set(keyId.href, lookup);
|
|
5971
|
-
continue;
|
|
5972
6259
|
}
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
6260
|
+
while (true) {
|
|
6261
|
+
const { key, cached, fetchError } = lookup;
|
|
6262
|
+
if (fetchError != null) {
|
|
6263
|
+
setFailure(keyFetchErrorResult(keyId, fetchError));
|
|
6264
|
+
break;
|
|
6265
|
+
}
|
|
6266
|
+
if (!key) {
|
|
6267
|
+
logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
|
|
6268
|
+
setFailure(invalidSignatureResult(keyId));
|
|
6269
|
+
break;
|
|
6270
|
+
}
|
|
6271
|
+
const result = await verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span, { [verificationObservation]: candidateObservation });
|
|
6272
|
+
if (result.verified) {
|
|
6273
|
+
metricsContext.algorithm = result.algorithm;
|
|
6274
|
+
winning = {
|
|
6275
|
+
verified: true,
|
|
6276
|
+
key,
|
|
6277
|
+
signatureLabel: sigName
|
|
6278
|
+
};
|
|
6279
|
+
return key;
|
|
6280
|
+
}
|
|
6281
|
+
if (result.mismatched && cached && !refreshedKeyIds.has(keyId.href)) {
|
|
6282
|
+
logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
|
|
6283
|
+
refreshedKeyIds.add(keyId.href);
|
|
6284
|
+
lookup = await lookUpKey(keyId, bypassKeyCacheReads(keyCache));
|
|
6285
|
+
keyLookups.set(keyId.href, lookup);
|
|
6286
|
+
continue;
|
|
6287
|
+
}
|
|
6288
|
+
setFailure(invalidSignatureResult(keyId), result.algorithm);
|
|
6289
|
+
break;
|
|
6290
|
+
}
|
|
6291
|
+
if (!failure.verified && failure.reason.type === "keyFetchError" && candidateObservation.check != null) candidateObservation.check.reason = failure.reason;
|
|
6292
|
+
return null;
|
|
6293
|
+
});
|
|
6294
|
+
if (winning != null) return winning;
|
|
5976
6295
|
}
|
|
5977
6296
|
metricsContext.algorithm = failureAlgorithm;
|
|
5978
6297
|
return failure;
|
|
@@ -6205,4 +6524,4 @@ function timingSafeEqual(a, b) {
|
|
|
6205
6524
|
return result === 0;
|
|
6206
6525
|
}
|
|
6207
6526
|
//#endregion
|
|
6208
|
-
export { instrumentDocumentLoader as $, hasSignature as A,
|
|
6527
|
+
export { instrumentDocumentLoader as $, hasSignature as A, isPortableUri as At, fetchActorDocument as B, InvalidContextReferenceError as C, getGatewayKeyBase as Ct, createSignature as D, isCompatibleKeyId as Dt, compactJsonLd as E, hasPortableActor 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, isSamePublicKey as Mt, isInvalidUrlTypeError as N, name as Nt, detachSignature as O, isPortableId as Ot, signJsonLd as P, version as Pt, getRemoteHost as Q, wrapContextLoaderForJsonLd as R, normalizeOutgoingActivityJsonLd as S, getCanonicalPortableId as St, attachSignature as T, getRawCanonicalPortableId 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, verificationObservation 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, isSameObjectId as jt, getNormalizationContextLoader as k, isPortableKeyId 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, observeAttempt as vt, assertSafeJsonLd as w, getPortableDid as wt, verifyProof as x, getAuthenticationOrigin as xt, verifyPortableObjectProofPolicy as y, snapshotKey as yt, exportJwk as z };
|