@fedify/fedify 2.4.0-dev.2253 → 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-DdUWFCwL.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-CcWDD5VB.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-D9uhdIuD.mjs → deno-C8XaXmPB.mjs} +1 -1
- package/dist/{docloader-fbmPhCdS.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-B858-Ylk.cjs → federation-D6giSGUT.cjs} +524 -118
- package/dist/{http-CUyBaVsQ.mjs → http-BqSbUEiQ.mjs} +141 -98
- package/dist/{http-Bp0MKAK3.cjs → http-Cki-npuA.cjs} +456 -116
- package/dist/{http-BoGE-nk7.js → http-OMtKlbx1.js} +439 -117
- package/dist/{http-DGx0KOws.d.ts → key-C1nv7_jD.d.ts} +638 -229
- package/dist/{http-BrwKINQn.d.cts → key-DYxuyd-s.d.cts} +638 -229
- package/dist/{key-BlTaKABr.mjs → key-DecAtABm.mjs} +30 -3
- package/dist/{kv-cache-Dmqd7s2a.mjs → kv-cache-CWWlMv-s.mjs} +1 -1
- package/dist/{kv-cache-BnWemxH2.cjs → kv-cache-D73yVcE5.cjs} +1 -1
- package/dist/{kv-cache-DhMzUXEE.js → kv-cache-J0v3UbUn.js} +1 -1
- package/dist/{ld-Dx8r_qJU.mjs → ld-UsN-TIPh.mjs} +40 -5
- package/dist/{metrics-DFkZkLrO.mjs → metrics-DWCn4VXX.mjs} +2 -1
- package/dist/{middleware-DC018B4u.js → middleware-BqzfIlUO.js} +524 -118
- package/dist/{middleware-B0RRI88k.mjs → middleware-CkjwiEBd.mjs} +512 -127
- package/dist/{middleware-BNhF-Ayx.mjs → middleware-DHn2q0L1.mjs} +1 -1
- package/dist/{mod-C7_2j7VT.d.cts → mod-BojP2Bw3.d.cts} +1 -1
- package/dist/{mod-Cy9VNHVw.d.ts → mod-CavhAgLw.d.cts} +5 -253
- package/dist/{mod-DoTWIc7F.d.cts → mod-CtpF2BU5.d.ts} +5 -253
- 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-B61jjp5K.mjs → owner-CEBn81LD.mjs} +3 -3
- package/dist/{portable-collection-DhEA_nWT.cjs → portable-collection-DqTUBKY7.cjs} +1 -1
- package/dist/{portable-collection-BnktjQ04.js → portable-collection-ZkUNjtb7.js} +1 -1
- package/dist/{portable-collection-CyBJDEzs.mjs → portable-collection-qFSYw4TG.mjs} +2 -2
- package/dist/{portable-key-CPN0QZSA.mjs → portable-key-LlMkP-vz.mjs} +1 -1
- package/dist/{proof-CZrjvoJ3.mjs → proof-BHRf_jMG.mjs} +142 -22
- package/dist/{send-enxOnmZo.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.d.mts +2 -0
- package/dist/sig/mitra-receive-interop.test.mjs +260 -0
- package/dist/sig/mitra-send-interop.test.d.mts +2 -0
- package/dist/sig/mitra-send-interop.test.mjs +461 -0
- 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.d.mts +2 -0
- package/dist/sig/portable-interop.test.mjs +156 -0
- package/dist/sig/portable-key.test.mjs +6 -6
- package/dist/sig/proof.test.mjs +161 -3
- package/dist/sig/tootik-request-interop.test.d.mts +2 -0
- package/dist/sig/tootik-request-interop.test.mjs +190 -0
- package/dist/sig/tootik-send-interop.test.d.mts +2 -0
- package/dist/sig/tootik-send-interop.test.mjs +331 -0
- package/dist/sig/verification.test.d.mts +2 -0
- package/dist/sig/verification.test.mjs +310 -0
- package/dist/{temporal-CBVv0xRB.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 +6 -6
|
@@ -9,12 +9,13 @@ import { encodeHex } from "byte-encodings/hex";
|
|
|
9
9
|
import jsonld from "@fedify/vocab-runtime/jsonld";
|
|
10
10
|
import { Item, decodeDict, encodeDict, encodeItem } from "structured-field-values";
|
|
11
11
|
import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
|
|
12
|
+
import { getPortableActorGateways } from "@fedify/vocab-runtime/internal/jsonld-cache";
|
|
12
13
|
import { isPlainJsonTree, retainSignedRepresentation } from "@fedify/vocab-runtime/internal/signed-representation";
|
|
13
14
|
import serialize from "json-canon";
|
|
14
15
|
import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
|
|
15
16
|
//#region deno.json
|
|
16
17
|
var name = "@fedify/fedify";
|
|
17
|
-
var version = "2.4.0-dev.
|
|
18
|
+
var version = "2.4.0-dev.2259+d9c843fc";
|
|
18
19
|
//#endregion
|
|
19
20
|
//#region src/sig/portable-key-id.ts
|
|
20
21
|
const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
|
|
@@ -257,6 +258,106 @@ async function isSamePublicKey(a, b) {
|
|
|
257
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;
|
|
258
259
|
}
|
|
259
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
|
|
260
361
|
//#region src/sig/accept.ts
|
|
261
362
|
/**
|
|
262
363
|
* `Accept-Signature` header parsing, serialization, and validation utilities
|
|
@@ -713,6 +814,7 @@ var FederationMetrics = class {
|
|
|
713
814
|
};
|
|
714
815
|
if (extra.algorithm != null) attributes["http_signatures.algorithm"] = extra.algorithm;
|
|
715
816
|
if (extra.failureReason != null) attributes["http_signatures.failure_reason"] = extra.failureReason;
|
|
817
|
+
if (extra.verificationFailureReason != null) attributes["activitypub.verification.failure_reason"] = extra.verificationFailureReason;
|
|
716
818
|
if (extra.ldType != null) attributes["ld_signatures.type"] = extra.ldType;
|
|
717
819
|
if (extra.cryptosuite != null) attributes["object_integrity_proofs.cryptosuite"] = extra.cryptosuite;
|
|
718
820
|
this.signatureVerificationDuration.record(durationMs, attributes);
|
|
@@ -2216,12 +2318,38 @@ async function fetchPortableActorKey(cacheKey, cls, options, onCachedUnavailable
|
|
|
2216
2318
|
};
|
|
2217
2319
|
}
|
|
2218
2320
|
async function fetchKeyInternal(keyId, cls, options = {}) {
|
|
2219
|
-
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 {}
|
|
2220
2331
|
return {
|
|
2221
2332
|
key: null,
|
|
2222
2333
|
cached: true
|
|
2223
2334
|
};
|
|
2224
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
|
+
}
|
|
2225
2353
|
logger.debug("Failed to fetch key {keyId}.", {
|
|
2226
2354
|
keyId,
|
|
2227
2355
|
error
|
|
@@ -2678,6 +2806,13 @@ function detachSignature(jsonLd) {
|
|
|
2678
2806
|
*/
|
|
2679
2807
|
async function verifySignature(jsonLd, options = {}) {
|
|
2680
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
|
+
}
|
|
2681
2816
|
const sig = jsonLd.signature;
|
|
2682
2817
|
let signature;
|
|
2683
2818
|
try {
|
|
@@ -2723,6 +2858,7 @@ async function verifySignature(jsonLd, options = {}) {
|
|
|
2723
2858
|
const encoder = new TextEncoder();
|
|
2724
2859
|
const message = sigOptsHash + docHash;
|
|
2725
2860
|
const messageBytes = encoder.encode(message);
|
|
2861
|
+
triedKey(options, key);
|
|
2726
2862
|
if (await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, signature.slice(), messageBytes)) return key;
|
|
2727
2863
|
if (cached) {
|
|
2728
2864
|
logger$4.debug("Failed to verify with the cached key {keyId}; signature {signatureValue} is invalid. Retrying with the freshly fetched key...", {
|
|
@@ -2734,6 +2870,7 @@ async function verifySignature(jsonLd, options = {}) {
|
|
|
2734
2870
|
keyCache: bypassKeyCacheReads(options.keyCache)
|
|
2735
2871
|
}));
|
|
2736
2872
|
if (key == null) return null;
|
|
2873
|
+
triedKey(options, key);
|
|
2737
2874
|
return await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, signature.slice(), messageBytes) ? key : null;
|
|
2738
2875
|
}
|
|
2739
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}", {
|
|
@@ -2787,6 +2924,14 @@ async function verifyCompactJsonLd(jsonLd, options = {}) {
|
|
|
2787
2924
|
return await verifyJsonLdInternal(jsonLd, options, false);
|
|
2788
2925
|
}
|
|
2789
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];
|
|
2790
2935
|
return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("ld_signatures.verify", async (span) => {
|
|
2791
2936
|
const start = performance.now();
|
|
2792
2937
|
let verified = false;
|
|
@@ -2799,6 +2944,8 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
|
2799
2944
|
} : options;
|
|
2800
2945
|
const compacted = compact ? hasSignature(jsonLd) ? await compactJsonLd(jsonLd, options.contextLoader) : jsonLd : jsonLd;
|
|
2801
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);
|
|
2802
2949
|
if (object.id != null) span.setAttribute("activitypub.object.id", object.id.href);
|
|
2803
2950
|
span.setAttribute("activitypub.object.type", getTypeId(object).href);
|
|
2804
2951
|
const sig = getLdSignatureObject(jsonLd);
|
|
@@ -2813,13 +2960,21 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
|
2813
2960
|
const attributions = new Set(object.attributionIds.map((uri) => uri.href));
|
|
2814
2961
|
if (object instanceof Activity) for (const uri of object.actorIds) attributions.add(uri.href);
|
|
2815
2962
|
const key = await verifySignature(compacted, verificationOptions);
|
|
2816
|
-
if (key == null)
|
|
2963
|
+
if (key == null) {
|
|
2964
|
+
if (observation?.attempt != null && !hasLdSignatureProperty(jsonLd)) observation.attempt.reason = { type: "noSignature" };
|
|
2965
|
+
return false;
|
|
2966
|
+
}
|
|
2817
2967
|
if (key.ownerId == null) {
|
|
2968
|
+
if (observation?.attempt != null) observation.attempt.reason = { type: "missingOwner" };
|
|
2818
2969
|
logger$4.debug("Key {keyId} has no owner.", { keyId: key.id?.href });
|
|
2819
2970
|
return false;
|
|
2820
2971
|
}
|
|
2821
2972
|
attributions.delete(key.ownerId.href);
|
|
2822
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
|
+
};
|
|
2823
2978
|
logger$4.debug("Some attributions are not authenticated by the Linked Data Signatures: {attributions}.", { attributions: [...attributions] });
|
|
2824
2979
|
return false;
|
|
2825
2980
|
}
|
|
@@ -2833,8 +2988,15 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
|
2833
2988
|
});
|
|
2834
2989
|
throw error;
|
|
2835
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
|
+
}
|
|
2836
2995
|
const classified = threw ? "error" : verified ? "verified" : hasLdSignatureProperty(jsonLd) ? "rejected" : "missing";
|
|
2837
|
-
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
|
+
});
|
|
2838
3000
|
span.end();
|
|
2839
3001
|
}
|
|
2840
3002
|
});
|
|
@@ -3462,19 +3624,50 @@ async function verifyProof(jsonLd, proof, options = {}) {
|
|
|
3462
3624
|
*/
|
|
3463
3625
|
async function verifyMapLocalProof(jsonLd, options = {}) {
|
|
3464
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));
|
|
3465
3634
|
const proofContextLoader = getNormalizationContextLoader(preloadedOnlyDocumentLoader);
|
|
3466
3635
|
try {
|
|
3467
3636
|
const [candidate] = await parseRawProofCandidates(jsonLd, [jsonLd.proof], options, proofContextLoader);
|
|
3468
|
-
if (candidate.proof == null) return
|
|
3637
|
+
if (candidate.proof == null) return await malformed();
|
|
3469
3638
|
return await verifyProofWithMessageDigestCache(jsonLd, candidate.proof, options, {
|
|
3470
3639
|
proofContextLoader,
|
|
3471
3640
|
proofPropertyMode: "literal"
|
|
3472
3641
|
}, candidate);
|
|
3473
3642
|
} catch {
|
|
3643
|
+
if (previousChecks != null && options[verificationObservation]?.attempt?.checks.length === previousChecks) return await malformed();
|
|
3474
3644
|
return null;
|
|
3475
3645
|
}
|
|
3476
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
|
+
}
|
|
3477
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));
|
|
3478
3671
|
return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("object_integrity_proofs.verify", async (span) => {
|
|
3479
3672
|
const start = performance.now();
|
|
3480
3673
|
let verified = false;
|
|
@@ -3487,8 +3680,10 @@ async function verifyProofWithMessageDigestCache(jsonLd, proof, options, message
|
|
|
3487
3680
|
}
|
|
3488
3681
|
try {
|
|
3489
3682
|
const key = await verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
|
|
3490
|
-
if (key == null)
|
|
3491
|
-
|
|
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;
|
|
3492
3687
|
return key;
|
|
3493
3688
|
} catch (error) {
|
|
3494
3689
|
threw = true;
|
|
@@ -3499,7 +3694,10 @@ async function verifyProofWithMessageDigestCache(jsonLd, proof, options, message
|
|
|
3499
3694
|
throw error;
|
|
3500
3695
|
} finally {
|
|
3501
3696
|
const classified = threw ? "error" : verified ? "verified" : "rejected";
|
|
3502
|
-
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
|
+
});
|
|
3503
3701
|
span.end();
|
|
3504
3702
|
}
|
|
3505
3703
|
});
|
|
@@ -3516,14 +3714,15 @@ function expandContextPropertyIri(activeContext, key) {
|
|
|
3516
3714
|
if (typeof mapping === "object" && mapping != null && mapping._prefix === true && typeof mapping["@id"] === "string") return mapping["@id"] + suffix;
|
|
3517
3715
|
return key;
|
|
3518
3716
|
}
|
|
3519
|
-
async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoader = preloadedOnlyDocumentLoader, inheritedContext, rejectOnContextError = false) {
|
|
3717
|
+
async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoader = preloadedOnlyDocumentLoader, inheritedContext, rejectOnContextError = false, contextState) {
|
|
3520
3718
|
const names = new Set(defaults);
|
|
3521
3719
|
const context = jsonLd["@context"] ?? inheritedContext;
|
|
3522
|
-
if (context == null) return names;
|
|
3720
|
+
if (context == null && contextState?.initial == null) return names;
|
|
3523
3721
|
try {
|
|
3524
3722
|
const options = { documentLoader };
|
|
3525
|
-
let activeContext = await jsonld.processContext(null, null, options);
|
|
3526
|
-
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);
|
|
3527
3726
|
const typeScopedContext = activeContext;
|
|
3528
3727
|
for (const key of globalThis.Object.keys(jsonLd).sort()) {
|
|
3529
3728
|
if (key !== "@type" && expandContextPropertyIri(activeContext, key) !== "@type") continue;
|
|
@@ -3535,6 +3734,7 @@ async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoa
|
|
|
3535
3734
|
if (scopedContext != null) activeContext = await jsonld.processContext(activeContext, scopedContext, options);
|
|
3536
3735
|
}
|
|
3537
3736
|
}
|
|
3737
|
+
if (contextState != null) contextState.active = activeContext;
|
|
3538
3738
|
for (const key of globalThis.Object.keys(jsonLd)) if (expandContextPropertyIri(activeContext, key) === propertyIri) names.add(key);
|
|
3539
3739
|
} catch {
|
|
3540
3740
|
if (rejectOnContextError) return null;
|
|
@@ -3601,6 +3801,36 @@ function sameBytes(left, right) {
|
|
|
3601
3801
|
function sameProof(left, right) {
|
|
3602
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();
|
|
3603
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
|
+
}
|
|
3604
3834
|
function normalizeDocumentUrl(url) {
|
|
3605
3835
|
try {
|
|
3606
3836
|
return formatIri(parseIri(url));
|
|
@@ -3619,6 +3849,7 @@ async function parseRawProofCandidates(jsonLd, values, options, documentLoader)
|
|
|
3619
3849
|
const candidates = [];
|
|
3620
3850
|
for (const value of values) {
|
|
3621
3851
|
let parsed = null;
|
|
3852
|
+
const contexts = options[verificationObservation] != null && options[verificationObservation]?.captureRawKeyIds !== false ? recordProofContexts(documentLoader) : void 0;
|
|
3622
3853
|
if (isJsonLdNode(value)) {
|
|
3623
3854
|
const proofJsonLd = value["@context"] == null && jsonLd["@context"] != null ? {
|
|
3624
3855
|
"@context": jsonLd["@context"],
|
|
@@ -3627,14 +3858,15 @@ async function parseRawProofCandidates(jsonLd, values, options, documentLoader)
|
|
|
3627
3858
|
try {
|
|
3628
3859
|
parsed = await DataIntegrityProof.fromJsonLd(proofJsonLd, {
|
|
3629
3860
|
...options,
|
|
3630
|
-
contextLoader: documentLoader
|
|
3861
|
+
contextLoader: contexts?.loader ?? documentLoader
|
|
3631
3862
|
});
|
|
3632
3863
|
} catch {}
|
|
3633
3864
|
}
|
|
3634
3865
|
candidates.push({
|
|
3635
3866
|
value,
|
|
3636
3867
|
proof: parsed,
|
|
3637
|
-
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)
|
|
3638
3870
|
});
|
|
3639
3871
|
}
|
|
3640
3872
|
return candidates;
|
|
@@ -3858,8 +4090,13 @@ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, r
|
|
|
3858
4090
|
const digest = new Uint8Array(proofDigest.byteLength + 32);
|
|
3859
4091
|
digest.set(new Uint8Array(proofDigest), 0);
|
|
3860
4092
|
const proofValue = proof.proofValue;
|
|
4093
|
+
let keyTried = false;
|
|
3861
4094
|
const verifyCandidate = async (msgDigest) => {
|
|
3862
4095
|
digest.set(new Uint8Array(msgDigest), proofDigest.byteLength);
|
|
4096
|
+
if (!keyTried) {
|
|
4097
|
+
triedKey(options, publicKey);
|
|
4098
|
+
keyTried = true;
|
|
4099
|
+
}
|
|
3863
4100
|
return await crypto.subtle.verify("Ed25519", publicKey.publicKey, proofValue.slice(), digest);
|
|
3864
4101
|
};
|
|
3865
4102
|
const messageDigestKey = serialize(proofConfiguration.context ?? null);
|
|
@@ -3904,7 +4141,6 @@ const FEP_2277_COLLECTION_PROPERTIES = [
|
|
|
3904
4141
|
"prev",
|
|
3905
4142
|
"current"
|
|
3906
4143
|
].map((property) => AS_NAMESPACE + property);
|
|
3907
|
-
const FEP_EF61_GATEWAYS = "https://w3id.org/fep/ef61/gateways";
|
|
3908
4144
|
const PORTABLE_OBJECT_ID_PATTERN = /^ap(?:\+ef61)?:\/\//i;
|
|
3909
4145
|
const FUNCTIONAL_PROOF_PROPERTIES = [
|
|
3910
4146
|
`${SECURITY_NAMESPACE}cryptosuite`,
|
|
@@ -3935,14 +4171,15 @@ function hasValidPortableProofShape(proofValue) {
|
|
|
3935
4171
|
* FEP-ef61 requires: a non-empty list whose items are all HTTP(S) URIs with
|
|
3936
4172
|
* an empty path, query, and fragment. Like the generated vocabulary decoder,
|
|
3937
4173
|
* this also tolerates a plain set of values instead of a `@list`, and gateway
|
|
3938
|
-
* strings given as `@value`s instead of `@id`s.
|
|
4174
|
+
* strings given as `@value`s instead of `@id`s. JCS-authenticated actors may
|
|
4175
|
+
* also supply an unmapped literal `gateways` list.
|
|
3939
4176
|
*/
|
|
3940
|
-
function hasValidPortableActorGateways(node) {
|
|
3941
|
-
const values = node
|
|
4177
|
+
function hasValidPortableActorGateways(node, allowUnmapped) {
|
|
4178
|
+
const values = getPortableActorGateways(node, allowUnmapped);
|
|
3942
4179
|
if (!Array.isArray(values)) return false;
|
|
3943
4180
|
let items = values;
|
|
3944
4181
|
if (values.some((value) => isJsonLdNode(value) && "@list" in value)) {
|
|
3945
|
-
if (values.length !== 1) return false;
|
|
4182
|
+
if (values.length !== 1 || !isJsonLdNode(values[0])) return false;
|
|
3946
4183
|
items = values[0]["@list"];
|
|
3947
4184
|
}
|
|
3948
4185
|
if (!Array.isArray(items) || items.length < 1) return false;
|
|
@@ -4163,7 +4400,7 @@ async function preparePortableObjectProof(jsonLd, options) {
|
|
|
4163
4400
|
}
|
|
4164
4401
|
};
|
|
4165
4402
|
}
|
|
4166
|
-
if (objectType === "actor" && !hasValidPortableActorGateways(root)) return {
|
|
4403
|
+
if (objectType === "actor" && !hasValidPortableActorGateways(root, proofs.every((proof) => proof.cryptosuite === "eddsa-jcs-2022"))) return {
|
|
4167
4404
|
prepared: false,
|
|
4168
4405
|
root,
|
|
4169
4406
|
objectType,
|
|
@@ -4230,6 +4467,8 @@ async function verifyPortableObjectProofPolicy(jsonLd, key, options = {}) {
|
|
|
4230
4467
|
* and fragment; otherwise, it is rejected with the `invalidGateways` reason
|
|
4231
4468
|
* before its proofs are verified. The proofs are verified over the document as
|
|
4232
4469
|
* given; a compatible identifier is never rewritten into a portable ID.
|
|
4470
|
+
* An unmapped literal `gateways` property is also accepted when every proof
|
|
4471
|
+
* uses `eddsa-jcs-2022`, which authenticates the original JSON properties.
|
|
4233
4472
|
*
|
|
4234
4473
|
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
4235
4474
|
*
|
|
@@ -4315,12 +4554,47 @@ async function verifyPreparedPortableObjectProof(jsonLd, prepared, options, mess
|
|
|
4315
4554
|
* @since 0.10.0
|
|
4316
4555
|
*/
|
|
4317
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];
|
|
4318
4590
|
const logger = getLogger([
|
|
4319
4591
|
"fedify",
|
|
4320
4592
|
"sig",
|
|
4321
4593
|
"proof"
|
|
4322
4594
|
]);
|
|
4323
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);
|
|
4324
4598
|
const defaultDocumentLoader = getDocumentLoader();
|
|
4325
4599
|
const proofContextLoader = options.contextLoader ?? defaultDocumentLoader;
|
|
4326
4600
|
const attributions = new Set(object.attributionIds.map((uri) => uri.href));
|
|
@@ -4347,10 +4621,11 @@ async function verifyObject(cls, jsonLd, options = {}) {
|
|
|
4347
4621
|
if (candidateIndex >= 0) {
|
|
4348
4622
|
hydratedCandidates.add(candidateIndex);
|
|
4349
4623
|
let parsed = null;
|
|
4624
|
+
const contexts = options[verificationObservation] != null && options[verificationObservation]?.captureRawKeyIds !== false ? recordProofContexts(proofContextLoader) : void 0;
|
|
4350
4625
|
try {
|
|
4351
4626
|
parsed = await DataIntegrityProof.fromJsonLd(remoteDocument.document, {
|
|
4352
4627
|
documentLoader: baseDocumentLoader,
|
|
4353
|
-
contextLoader: proofContextLoader,
|
|
4628
|
+
contextLoader: contexts?.loader ?? proofContextLoader,
|
|
4354
4629
|
tracerProvider: options.tracerProvider,
|
|
4355
4630
|
baseUrl: parseIri(remoteDocument.documentUrl)
|
|
4356
4631
|
});
|
|
@@ -4358,7 +4633,8 @@ async function verifyObject(cls, jsonLd, options = {}) {
|
|
|
4358
4633
|
rawProofCandidates[candidateIndex] = {
|
|
4359
4634
|
value: structuredClone(remoteDocument.document),
|
|
4360
4635
|
proof: parsed,
|
|
4361
|
-
reference
|
|
4636
|
+
reference,
|
|
4637
|
+
declaredKeyId: contexts == null ? void 0 : await getAliasedDeclaredProofKeyId(remoteDocument.document, void 0, contexts.replay)
|
|
4362
4638
|
};
|
|
4363
4639
|
}
|
|
4364
4640
|
return remoteDocument;
|
|
@@ -4386,6 +4662,10 @@ async function verifyObject(cls, jsonLd, options = {}) {
|
|
|
4386
4662
|
return null;
|
|
4387
4663
|
}
|
|
4388
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
|
+
};
|
|
4389
4669
|
logger.debug("Some attributions are not authenticated by the proofs: {attributions}.", { attributions: [...attributions] });
|
|
4390
4670
|
return null;
|
|
4391
4671
|
}
|
|
@@ -5405,6 +5685,14 @@ async function verifyRequest(request, options = {}) {
|
|
|
5405
5685
|
* @since 2.1.0
|
|
5406
5686
|
*/
|
|
5407
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);
|
|
5408
5696
|
if (options.maxSignatures !== void 0) validateMaxSignatures(options.maxSignatures, "maxSignatures");
|
|
5409
5697
|
return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("http_signatures.verify", async (span) => {
|
|
5410
5698
|
if (span.isRecording()) {
|
|
@@ -5443,7 +5731,27 @@ async function verifyRequestDetailed(request, options = {}) {
|
|
|
5443
5731
|
}
|
|
5444
5732
|
});
|
|
5445
5733
|
}
|
|
5446
|
-
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 } = {}) {
|
|
5447
5755
|
const logger = getLogger([
|
|
5448
5756
|
"fedify",
|
|
5449
5757
|
"sig",
|
|
@@ -5624,6 +5932,7 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
|
|
|
5624
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");
|
|
5625
5933
|
const sig = decodeBase64(signature);
|
|
5626
5934
|
span?.setAttribute("http_signatures.signature", encodeHex(sig));
|
|
5935
|
+
triedKey({ [verificationObservation]: observation }, key);
|
|
5627
5936
|
if (!await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, sig, new TextEncoder().encode(message))) {
|
|
5628
5937
|
if (cached) {
|
|
5629
5938
|
logger.debug("Failed to verify with the cached key {keyId}; signature {signature} is invalid. Retrying with the freshly fetched key...", {
|
|
@@ -5638,7 +5947,8 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
|
|
|
5638
5947
|
currentTime,
|
|
5639
5948
|
keyCache: bypassKeyCacheReads(keyCache),
|
|
5640
5949
|
meterProvider,
|
|
5641
|
-
tracerProvider
|
|
5950
|
+
tracerProvider,
|
|
5951
|
+
[verificationObservation]: observation
|
|
5642
5952
|
});
|
|
5643
5953
|
}
|
|
5644
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}", {
|
|
@@ -5724,7 +6034,7 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
|
|
|
5724
6034
|
* @returns Whether the signature is verified, and the algorithm of the
|
|
5725
6035
|
* signature if it is supported.
|
|
5726
6036
|
*/
|
|
5727
|
-
async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span) {
|
|
6037
|
+
async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span, observationOptions = {}) {
|
|
5728
6038
|
const logger = getLogger([
|
|
5729
6039
|
"fedify",
|
|
5730
6040
|
"sig",
|
|
@@ -5780,6 +6090,7 @@ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, s
|
|
|
5780
6090
|
}
|
|
5781
6091
|
let verified;
|
|
5782
6092
|
try {
|
|
6093
|
+
triedKey(observationOptions, key);
|
|
5783
6094
|
verified = await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes);
|
|
5784
6095
|
} catch (error) {
|
|
5785
6096
|
logger.debug("Error during signature verification: {error}", {
|
|
@@ -5807,7 +6118,7 @@ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, s
|
|
|
5807
6118
|
mismatched: true
|
|
5808
6119
|
};
|
|
5809
6120
|
}
|
|
5810
|
-
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 } = {}) {
|
|
5811
6122
|
const logger = getLogger([
|
|
5812
6123
|
"fedify",
|
|
5813
6124
|
"sig",
|
|
@@ -5871,105 +6182,116 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
|
|
|
5871
6182
|
let body = null;
|
|
5872
6183
|
let digestValid = null;
|
|
5873
6184
|
for (const sigName of signatureNames) {
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
signatureName: sigName,
|
|
5884
|
-
signatureInput: signatureInputHeader
|
|
5885
|
-
});
|
|
5886
|
-
setFailure(invalidSignatureResult(null));
|
|
5887
|
-
continue;
|
|
5888
|
-
}
|
|
5889
|
-
if (!sigInput.created) {
|
|
5890
|
-
logger.debug("Failed to verify; missing created timestamp in signature {signatureName}.", {
|
|
5891
|
-
signatureName: sigName,
|
|
5892
|
-
signatureInput: signatureInputHeader
|
|
5893
|
-
});
|
|
5894
|
-
setFailure(invalidSignatureResult(keyId));
|
|
5895
|
-
continue;
|
|
5896
|
-
}
|
|
5897
|
-
const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
|
|
5898
|
-
const now = currentTime ?? Temporal.Now.instant();
|
|
5899
|
-
if (timeWindow !== false) {
|
|
5900
|
-
const tw = timeWindow ?? { hours: 1 };
|
|
5901
|
-
if (Temporal.Instant.compare(signatureCreated, now.add(tw)) > 0) {
|
|
5902
|
-
logger.debug("Failed to verify; signature created time is too far in the future.", {
|
|
5903
|
-
created: signatureCreated.toString(),
|
|
5904
|
-
now: now.toString()
|
|
5905
|
-
});
|
|
5906
|
-
setFailure(invalidSignatureResult(keyId));
|
|
5907
|
-
continue;
|
|
5908
|
-
} else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
|
|
5909
|
-
logger.debug("Failed to verify; signature created time is too far in the past.", {
|
|
5910
|
-
created: signatureCreated.toString(),
|
|
5911
|
-
now: now.toString()
|
|
5912
|
-
});
|
|
5913
|
-
setFailure(invalidSignatureResult(keyId));
|
|
5914
|
-
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;
|
|
5915
6194
|
}
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
const
|
|
5919
|
-
if (!
|
|
5920
|
-
logger.debug("Failed to verify;
|
|
5921
|
-
|
|
5922
|
-
|
|
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;
|
|
5923
6205
|
}
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
6206
|
+
if (!sigInput.created) {
|
|
6207
|
+
logger.debug("Failed to verify; missing created timestamp in signature {signatureName}.", {
|
|
6208
|
+
signatureName: sigName,
|
|
6209
|
+
signatureInput: signatureInputHeader
|
|
6210
|
+
});
|
|
5928
6211
|
setFailure(invalidSignatureResult(keyId));
|
|
5929
|
-
|
|
6212
|
+
return null;
|
|
5930
6213
|
}
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
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
|
+
}
|
|
5948
6233
|
}
|
|
5949
|
-
if (
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
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
|
+
}
|
|
5953
6248
|
}
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
key,
|
|
5960
|
-
signatureLabel: sigName
|
|
5961
|
-
};
|
|
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;
|
|
5962
6254
|
}
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
lookup = await lookUpKey(keyId, bypassKeyCacheReads(keyCache));
|
|
6255
|
+
let lookup = keyLookups.get(keyId.href);
|
|
6256
|
+
if (lookup == null) {
|
|
6257
|
+
lookup = await lookUpKey(keyId, keyCache);
|
|
5967
6258
|
keyLookups.set(keyId.href, lookup);
|
|
5968
|
-
continue;
|
|
5969
6259
|
}
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
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;
|
|
5973
6295
|
}
|
|
5974
6296
|
metricsContext.algorithm = failureAlgorithm;
|
|
5975
6297
|
return failure;
|
|
@@ -6202,4 +6524,4 @@ function timingSafeEqual(a, b) {
|
|
|
6202
6524
|
return result === 0;
|
|
6203
6525
|
}
|
|
6204
6526
|
//#endregion
|
|
6205
|
-
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 };
|