@fedify/fedify 2.4.0-dev.2256 → 2.4.0-dev.2260
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-BK8HfSUm.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-D--HeIbV.mjs} +41 -4
- 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-DVP7DGJC.mjs} +1 -1
- package/dist/{docloader-CIP_SG39.mjs → docloader-Dv1JJl37.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 +54 -10
- 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 +66 -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-Bo5z395X.cjs} +552 -122
- package/dist/{http-1fZ83jj7.cjs → http-BIhWdDf5.cjs} +447 -110
- package/dist/{http-BGb3QioZ.js → http-DRMu1C2T.js} +430 -111
- package/dist/{http-DkqWu-sm.mjs → http-DvpuyDJB.mjs} +141 -98
- package/dist/{key-CEsu7oaD.mjs → key-B7N8mNoY.mjs} +30 -3
- 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/{kv-cache-CJS4mImS.mjs → kv-cache-D0kA0rml.mjs} +1 -1
- package/dist/{kv-cache-CGhEgt98.js → kv-cache-DZLpqhW9.js} +1 -1
- package/dist/{kv-cache-BEWuZSB4.cjs → kv-cache-mF8Zmjix.cjs} +1 -1
- package/dist/{ld-Cf4CPWN_.mjs → ld-BDMNyM2M.mjs} +40 -5
- package/dist/{metrics-CocsZP0E.mjs → metrics-BbBj-VZN.mjs} +2 -1
- package/dist/{middleware-Cne_jMZo.mjs → middleware-CE1X7-UM.mjs} +1 -1
- package/dist/{middleware-Dxn0E9ES.mjs → middleware-DAA7wDdS.mjs} +520 -131
- package/dist/{middleware-BfYke3YQ.js → middleware-DfUWPAa-.js} +552 -122
- 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-CzUycuhd.mjs} +3 -3
- package/dist/{portable-collection-BuBV6xTo.js → portable-collection-COcJxZg_.js} +1 -1
- package/dist/{portable-collection-qskqXJrH.mjs → portable-collection-CTcNJiAa.mjs} +2 -2
- package/dist/{portable-collection-r-mHg1PV.cjs → portable-collection-cPMffcHJ.cjs} +1 -1
- package/dist/{portable-key-woGH_1nn.mjs → portable-key-B4QN3OzR.mjs} +1 -1
- package/dist/{proof-8WiH_-Gq.mjs → proof-nce4pf5S.mjs} +133 -16
- package/dist/{send-JKzZBgPU.mjs → send-4Idpe2z0.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-BbjmPHYn.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
|
@@ -17,7 +17,7 @@ json_canon = require_rolldown_runtime.__toESM(json_canon, 1);
|
|
|
17
17
|
let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
|
|
18
18
|
//#region deno.json
|
|
19
19
|
var name = "@fedify/fedify";
|
|
20
|
-
var version = "2.4.0-dev.
|
|
20
|
+
var version = "2.4.0-dev.2260+42356e7d";
|
|
21
21
|
//#endregion
|
|
22
22
|
//#region src/sig/portable-key-id.ts
|
|
23
23
|
const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
|
|
@@ -3824,6 +3824,106 @@ const Temporal$5 = /*@__PURE__*/ Object.defineProperties({}, {
|
|
|
3824
3824
|
const Temporal$4 = NativeTemporal || Temporal$5;
|
|
3825
3825
|
NativeTemporal && Date.prototype.toTemporalInstant;
|
|
3826
3826
|
//#endregion
|
|
3827
|
+
//#region src/sig/verification.ts
|
|
3828
|
+
const verificationObservation = Symbol("verificationObservation");
|
|
3829
|
+
function snapshotKey(key) {
|
|
3830
|
+
if (key.publicKey == null) throw new TypeError("Verification key has no public key.");
|
|
3831
|
+
const id = key.id == null ? null : new URL(key.id.href);
|
|
3832
|
+
return key instanceof _fedify_vocab.CryptographicKey ? {
|
|
3833
|
+
type: "cryptographicKey",
|
|
3834
|
+
id,
|
|
3835
|
+
ownerId: key.ownerId == null ? null : new URL(key.ownerId.href),
|
|
3836
|
+
publicKey: key.publicKey
|
|
3837
|
+
} : {
|
|
3838
|
+
type: "multikey",
|
|
3839
|
+
id,
|
|
3840
|
+
controllerId: key.controllerId == null ? null : new URL(key.controllerId.href),
|
|
3841
|
+
publicKey: key.publicKey
|
|
3842
|
+
};
|
|
3843
|
+
}
|
|
3844
|
+
function triedKey(options, key) {
|
|
3845
|
+
const check = options[verificationObservation]?.check;
|
|
3846
|
+
if (check != null) check.triedKeys.push(snapshotKey(key));
|
|
3847
|
+
}
|
|
3848
|
+
async function observeCheck(options, identity, declaredKeyId, operation) {
|
|
3849
|
+
const observation = options[verificationObservation];
|
|
3850
|
+
if (observation?.attempt == null) return await operation(observation ?? { attempts: [] });
|
|
3851
|
+
const check = {
|
|
3852
|
+
identity,
|
|
3853
|
+
declaredKeyId,
|
|
3854
|
+
triedKeys: []
|
|
3855
|
+
};
|
|
3856
|
+
const base = {
|
|
3857
|
+
...identity,
|
|
3858
|
+
declaredKeyId,
|
|
3859
|
+
triedKeys: check.triedKeys
|
|
3860
|
+
};
|
|
3861
|
+
try {
|
|
3862
|
+
const key = await operation({
|
|
3863
|
+
...observation,
|
|
3864
|
+
check
|
|
3865
|
+
});
|
|
3866
|
+
observation.attempt.checks.push(key == null ? {
|
|
3867
|
+
...base,
|
|
3868
|
+
status: "rejected",
|
|
3869
|
+
reason: check.reason ?? { type: "invalidSignature" }
|
|
3870
|
+
} : {
|
|
3871
|
+
...base,
|
|
3872
|
+
status: "verified",
|
|
3873
|
+
key: check.triedKeys.at(-1) ?? snapshotKey(key)
|
|
3874
|
+
});
|
|
3875
|
+
return key;
|
|
3876
|
+
} catch (error) {
|
|
3877
|
+
observation.attempt.checks.push({
|
|
3878
|
+
...base,
|
|
3879
|
+
status: "error",
|
|
3880
|
+
error
|
|
3881
|
+
});
|
|
3882
|
+
throw error;
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
async function observeAttempt(options, mechanism, operation, isVerified) {
|
|
3886
|
+
const observation = options[verificationObservation];
|
|
3887
|
+
if (observation == null) return await operation({ attempts: [] });
|
|
3888
|
+
const attempt = { checks: [] };
|
|
3889
|
+
const base = {
|
|
3890
|
+
mechanism,
|
|
3891
|
+
subject: observation.subject == null ? {
|
|
3892
|
+
id: null,
|
|
3893
|
+
pointer: ""
|
|
3894
|
+
} : {
|
|
3895
|
+
id: observation.subject.id == null ? null : new URL(observation.subject.id.href),
|
|
3896
|
+
pointer: observation.subject.pointer
|
|
3897
|
+
},
|
|
3898
|
+
checks: attempt.checks
|
|
3899
|
+
};
|
|
3900
|
+
try {
|
|
3901
|
+
const value = await operation({
|
|
3902
|
+
...observation,
|
|
3903
|
+
subject: base.subject,
|
|
3904
|
+
attempt,
|
|
3905
|
+
check: void 0
|
|
3906
|
+
});
|
|
3907
|
+
observation.attempts.push(isVerified(value) ? {
|
|
3908
|
+
...base,
|
|
3909
|
+
status: "verified",
|
|
3910
|
+
signatures: attempt.checks.filter((check) => check.status === "verified")
|
|
3911
|
+
} : {
|
|
3912
|
+
...base,
|
|
3913
|
+
status: "rejected",
|
|
3914
|
+
reason: attempt.reason ?? { type: "signatureVerificationFailed" }
|
|
3915
|
+
});
|
|
3916
|
+
return value;
|
|
3917
|
+
} catch (error) {
|
|
3918
|
+
observation.attempts.push({
|
|
3919
|
+
...base,
|
|
3920
|
+
status: "error",
|
|
3921
|
+
error
|
|
3922
|
+
});
|
|
3923
|
+
throw error;
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
//#endregion
|
|
3827
3927
|
//#region src/sig/accept.ts
|
|
3828
3928
|
/**
|
|
3829
3929
|
* `Accept-Signature` header parsing, serialization, and validation utilities
|
|
@@ -4280,6 +4380,7 @@ var FederationMetrics = class {
|
|
|
4280
4380
|
};
|
|
4281
4381
|
if (extra.algorithm != null) attributes["http_signatures.algorithm"] = extra.algorithm;
|
|
4282
4382
|
if (extra.failureReason != null) attributes["http_signatures.failure_reason"] = extra.failureReason;
|
|
4383
|
+
if (extra.verificationFailureReason != null) attributes["activitypub.verification.failure_reason"] = extra.verificationFailureReason;
|
|
4283
4384
|
if (extra.ldType != null) attributes["ld_signatures.type"] = extra.ldType;
|
|
4284
4385
|
if (extra.cryptosuite != null) attributes["object_integrity_proofs.cryptosuite"] = extra.cryptosuite;
|
|
4285
4386
|
this.signatureVerificationDuration.record(durationMs, attributes);
|
|
@@ -5785,12 +5886,38 @@ async function fetchPortableActorKey(cacheKey, cls, options, onCachedUnavailable
|
|
|
5785
5886
|
};
|
|
5786
5887
|
}
|
|
5787
5888
|
async function fetchKeyInternal(keyId, cls, options = {}) {
|
|
5788
|
-
return await fetchKeyWithResult(typeof keyId === "string" ? parseKeyIdOrThrow(keyId) : keyId, cls, options, (_cacheKey, _keyId, _keyCache, _logger) => {
|
|
5889
|
+
return await fetchKeyWithResult(typeof keyId === "string" ? parseKeyIdOrThrow(keyId) : keyId, cls, options, async (_cacheKey, _keyId, _keyCache, _logger) => {
|
|
5890
|
+
const check = options[verificationObservation]?.check;
|
|
5891
|
+
if (check != null) try {
|
|
5892
|
+
const result = await _keyCache?.getFetchError?.(_cacheKey);
|
|
5893
|
+
if (result != null) check.reason = {
|
|
5894
|
+
type: "keyFetchError",
|
|
5895
|
+
keyId: new URL(_cacheKey.href),
|
|
5896
|
+
result
|
|
5897
|
+
};
|
|
5898
|
+
} catch {}
|
|
5789
5899
|
return {
|
|
5790
5900
|
key: null,
|
|
5791
5901
|
cached: true
|
|
5792
5902
|
};
|
|
5793
5903
|
}, async (error, cacheKey, keyId, keyCache, logger) => {
|
|
5904
|
+
const check = options[verificationObservation]?.check;
|
|
5905
|
+
if (check != null) {
|
|
5906
|
+
let result;
|
|
5907
|
+
try {
|
|
5908
|
+
result = error instanceof _fedify_vocab_runtime.FetchError && error.response != null ? {
|
|
5909
|
+
status: error.response.status,
|
|
5910
|
+
response: error.response.clone()
|
|
5911
|
+
} : { error: error instanceof Error ? error : new Error(String(error)) };
|
|
5912
|
+
} catch {
|
|
5913
|
+
result = { error: error instanceof Error ? error : /* @__PURE__ */ new Error() };
|
|
5914
|
+
}
|
|
5915
|
+
check.reason = {
|
|
5916
|
+
type: "keyFetchError",
|
|
5917
|
+
keyId: new URL(cacheKey.href),
|
|
5918
|
+
result
|
|
5919
|
+
};
|
|
5920
|
+
}
|
|
5794
5921
|
logger.debug("Failed to fetch key {keyId}.", {
|
|
5795
5922
|
keyId,
|
|
5796
5923
|
error
|
|
@@ -6247,6 +6374,13 @@ function detachSignature(jsonLd) {
|
|
|
6247
6374
|
*/
|
|
6248
6375
|
async function verifySignature(jsonLd, options = {}) {
|
|
6249
6376
|
if (!hasSignature(jsonLd)) return null;
|
|
6377
|
+
if (options[verificationObservation]?.attempt != null && options[verificationObservation]?.check == null) {
|
|
6378
|
+
const declared = getLdSignatureObject(jsonLd)?.creator;
|
|
6379
|
+
return await observeCheck(options, { mechanism: "linkedData" }, typeof declared === "string" ? declared : null, (observation) => verifySignature(jsonLd, {
|
|
6380
|
+
...options,
|
|
6381
|
+
[verificationObservation]: observation
|
|
6382
|
+
}));
|
|
6383
|
+
}
|
|
6250
6384
|
const sig = jsonLd.signature;
|
|
6251
6385
|
let signature;
|
|
6252
6386
|
try {
|
|
@@ -6292,6 +6426,7 @@ async function verifySignature(jsonLd, options = {}) {
|
|
|
6292
6426
|
const encoder = new TextEncoder();
|
|
6293
6427
|
const message = sigOptsHash + docHash;
|
|
6294
6428
|
const messageBytes = encoder.encode(message);
|
|
6429
|
+
triedKey(options, key);
|
|
6295
6430
|
if (await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, signature.slice(), messageBytes)) return key;
|
|
6296
6431
|
if (cached) {
|
|
6297
6432
|
logger$4.debug("Failed to verify with the cached key {keyId}; signature {signatureValue} is invalid. Retrying with the freshly fetched key...", {
|
|
@@ -6303,6 +6438,7 @@ async function verifySignature(jsonLd, options = {}) {
|
|
|
6303
6438
|
keyCache: bypassKeyCacheReads(options.keyCache)
|
|
6304
6439
|
}));
|
|
6305
6440
|
if (key == null) return null;
|
|
6441
|
+
triedKey(options, key);
|
|
6306
6442
|
return await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, signature.slice(), messageBytes) ? key : null;
|
|
6307
6443
|
}
|
|
6308
6444
|
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}", {
|
|
@@ -6356,6 +6492,14 @@ async function verifyCompactJsonLd(jsonLd, options = {}) {
|
|
|
6356
6492
|
return await verifyJsonLdInternal(jsonLd, options, false);
|
|
6357
6493
|
}
|
|
6358
6494
|
async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
6495
|
+
if (options[verificationObservation]?.attempt == null) return await observeAttempt({
|
|
6496
|
+
...options,
|
|
6497
|
+
[verificationObservation]: options[verificationObservation] ?? { attempts: [] }
|
|
6498
|
+
}, "linkedData", (observation) => verifyJsonLdInternal(jsonLd, {
|
|
6499
|
+
...options,
|
|
6500
|
+
[verificationObservation]: observation
|
|
6501
|
+
}, compact), (value) => value);
|
|
6502
|
+
const observation = options[verificationObservation];
|
|
6359
6503
|
return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("ld_signatures.verify", async (span) => {
|
|
6360
6504
|
const start = performance.now();
|
|
6361
6505
|
let verified = false;
|
|
@@ -6368,6 +6512,8 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
|
6368
6512
|
} : options;
|
|
6369
6513
|
const compacted = compact ? hasSignature(jsonLd) ? await compactJsonLd(jsonLd, options.contextLoader) : jsonLd : jsonLd;
|
|
6370
6514
|
const object = await _fedify_vocab.Object.fromJsonLd(compacted, verificationOptions);
|
|
6515
|
+
observation?.parsedObject?.(object);
|
|
6516
|
+
if (observation?.subject != null) observation.subject.id = object.id == null ? null : new URL(object.id.href);
|
|
6371
6517
|
if (object.id != null) span.setAttribute("activitypub.object.id", object.id.href);
|
|
6372
6518
|
span.setAttribute("activitypub.object.type", (0, _fedify_vocab.getTypeId)(object).href);
|
|
6373
6519
|
const sig = getLdSignatureObject(jsonLd);
|
|
@@ -6382,13 +6528,21 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
|
6382
6528
|
const attributions = new Set(object.attributionIds.map((uri) => uri.href));
|
|
6383
6529
|
if (object instanceof _fedify_vocab.Activity) for (const uri of object.actorIds) attributions.add(uri.href);
|
|
6384
6530
|
const key = await verifySignature(compacted, verificationOptions);
|
|
6385
|
-
if (key == null)
|
|
6531
|
+
if (key == null) {
|
|
6532
|
+
if (observation?.attempt != null && !hasLdSignatureProperty(jsonLd)) observation.attempt.reason = { type: "noSignature" };
|
|
6533
|
+
return false;
|
|
6534
|
+
}
|
|
6386
6535
|
if (key.ownerId == null) {
|
|
6536
|
+
if (observation?.attempt != null) observation.attempt.reason = { type: "missingOwner" };
|
|
6387
6537
|
logger$4.debug("Key {keyId} has no owner.", { keyId: key.id?.href });
|
|
6388
6538
|
return false;
|
|
6389
6539
|
}
|
|
6390
6540
|
attributions.delete(key.ownerId.href);
|
|
6391
6541
|
if (attributions.size > 0) {
|
|
6542
|
+
if (observation?.attempt != null) observation.attempt.reason = {
|
|
6543
|
+
type: "uncoveredAttribution",
|
|
6544
|
+
attributionIds: [...attributions].map((id) => new URL(id))
|
|
6545
|
+
};
|
|
6392
6546
|
logger$4.debug("Some attributions are not authenticated by the Linked Data Signatures: {attributions}.", { attributions: [...attributions] });
|
|
6393
6547
|
return false;
|
|
6394
6548
|
}
|
|
@@ -6402,8 +6556,15 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
|
6402
6556
|
});
|
|
6403
6557
|
throw error;
|
|
6404
6558
|
} finally {
|
|
6559
|
+
if (observation?.attempt?.reason != null) {
|
|
6560
|
+
span.setAttribute("activitypub.verification.failure_reason", observation.attempt.reason.type);
|
|
6561
|
+
span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
|
|
6562
|
+
}
|
|
6405
6563
|
const classified = threw ? "error" : verified ? "verified" : hasLdSignatureProperty(jsonLd) ? "rejected" : "missing";
|
|
6406
|
-
getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "linked_data", classified, {
|
|
6564
|
+
getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "linked_data", classified, {
|
|
6565
|
+
ldType: signatureType,
|
|
6566
|
+
verificationFailureReason: verified || threw ? void 0 : observation?.attempt?.reason?.type ?? (hasLdSignatureProperty(jsonLd) ? "signatureVerificationFailed" : "noSignature")
|
|
6567
|
+
});
|
|
6407
6568
|
span.end();
|
|
6408
6569
|
}
|
|
6409
6570
|
});
|
|
@@ -7032,19 +7193,50 @@ async function verifyProof(jsonLd, proof, options = {}) {
|
|
|
7032
7193
|
*/
|
|
7033
7194
|
async function verifyMapLocalProof(jsonLd, options = {}) {
|
|
7034
7195
|
if (!isJsonLdNode(jsonLd) || !globalThis.Object.hasOwn(jsonLd, "proof") || !isJsonLdNode(jsonLd.proof)) return null;
|
|
7196
|
+
const rawProof = jsonLd.proof;
|
|
7197
|
+
const previousChecks = options[verificationObservation]?.attempt?.checks.length;
|
|
7198
|
+
const malformed = () => observeCheck(options, {
|
|
7199
|
+
mechanism: "objectIntegrity",
|
|
7200
|
+
proofId: typeof rawProof.id === "string" ? rawProof.id : typeof rawProof["@id"] === "string" ? rawProof["@id"] : null,
|
|
7201
|
+
proofIndex: previousChecks ?? null
|
|
7202
|
+
}, getDeclaredProofKeyId(rawProof), () => Promise.resolve(null));
|
|
7035
7203
|
const proofContextLoader = getNormalizationContextLoader(preloadedOnlyDocumentLoader);
|
|
7036
7204
|
try {
|
|
7037
7205
|
const [candidate] = await parseRawProofCandidates(jsonLd, [jsonLd.proof], options, proofContextLoader);
|
|
7038
|
-
if (candidate.proof == null) return
|
|
7206
|
+
if (candidate.proof == null) return await malformed();
|
|
7039
7207
|
return await verifyProofWithMessageDigestCache(jsonLd, candidate.proof, options, {
|
|
7040
7208
|
proofContextLoader,
|
|
7041
7209
|
proofPropertyMode: "literal"
|
|
7042
7210
|
}, candidate);
|
|
7043
7211
|
} catch {
|
|
7212
|
+
if (previousChecks != null && options[verificationObservation]?.attempt?.checks.length === previousChecks) return await malformed();
|
|
7044
7213
|
return null;
|
|
7045
7214
|
}
|
|
7046
7215
|
}
|
|
7216
|
+
function getDeclaredProofKeyId(value, propertyNames = ["verificationMethod", "https://w3id.org/security#verificationMethod"]) {
|
|
7217
|
+
if (!isJsonLdNode(value)) return null;
|
|
7218
|
+
const method = Array.from(propertyNames, (name) => value[name]).find((v) => v != null);
|
|
7219
|
+
const candidate = Array.isArray(method) ? method[0] : method;
|
|
7220
|
+
if (typeof candidate === "string") return candidate;
|
|
7221
|
+
return isJsonLdNode(candidate) && typeof candidate["@id"] === "string" ? candidate["@id"] : null;
|
|
7222
|
+
}
|
|
7047
7223
|
async function verifyProofWithMessageDigestCache(jsonLd, proof, options, messageDigestCache = {}, rawProofCandidate, keyIdBoundByCaller = false) {
|
|
7224
|
+
if (options[verificationObservation] == null) return await verifyProofWithMessageDigestCache(jsonLd, proof, {
|
|
7225
|
+
...options,
|
|
7226
|
+
[verificationObservation]: {
|
|
7227
|
+
attempts: [],
|
|
7228
|
+
attempt: { checks: [] },
|
|
7229
|
+
captureRawKeyIds: false
|
|
7230
|
+
}
|
|
7231
|
+
}, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
|
|
7232
|
+
if (options[verificationObservation]?.attempt != null && options[verificationObservation]?.check == null) return await observeCheck(options, {
|
|
7233
|
+
mechanism: "objectIntegrity",
|
|
7234
|
+
proofId: proof.id?.href ?? null,
|
|
7235
|
+
proofIndex: options[verificationObservation].attempt.checks.length
|
|
7236
|
+
}, rawProofCandidate?.declaredKeyId ?? getDeclaredProofKeyId(rawProofCandidate?.value) ?? proof.verificationMethodId?.href ?? null, (observation) => verifyProofWithMessageDigestCache(jsonLd, proof, {
|
|
7237
|
+
...options,
|
|
7238
|
+
[verificationObservation]: observation
|
|
7239
|
+
}, messageDigestCache, rawProofCandidate, keyIdBoundByCaller));
|
|
7048
7240
|
return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("object_integrity_proofs.verify", async (span) => {
|
|
7049
7241
|
const start = performance.now();
|
|
7050
7242
|
let verified = false;
|
|
@@ -7057,8 +7249,10 @@ async function verifyProofWithMessageDigestCache(jsonLd, proof, options, message
|
|
|
7057
7249
|
}
|
|
7058
7250
|
try {
|
|
7059
7251
|
const key = await verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
|
|
7060
|
-
if (key == null)
|
|
7061
|
-
|
|
7252
|
+
if (key == null) {
|
|
7253
|
+
span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
|
|
7254
|
+
span.setAttribute("activitypub.verification.failure_reason", options[verificationObservation]?.check?.reason?.type ?? "invalidSignature");
|
|
7255
|
+
} else verified = true;
|
|
7062
7256
|
return key;
|
|
7063
7257
|
} catch (error) {
|
|
7064
7258
|
threw = true;
|
|
@@ -7069,7 +7263,10 @@ async function verifyProofWithMessageDigestCache(jsonLd, proof, options, message
|
|
|
7069
7263
|
throw error;
|
|
7070
7264
|
} finally {
|
|
7071
7265
|
const classified = threw ? "error" : verified ? "verified" : "rejected";
|
|
7072
|
-
getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "object_integrity", classified, {
|
|
7266
|
+
getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "object_integrity", classified, {
|
|
7267
|
+
cryptosuite,
|
|
7268
|
+
verificationFailureReason: verified || threw ? void 0 : options[verificationObservation]?.check?.reason?.type ?? "invalidSignature"
|
|
7269
|
+
});
|
|
7073
7270
|
span.end();
|
|
7074
7271
|
}
|
|
7075
7272
|
});
|
|
@@ -7086,14 +7283,15 @@ function expandContextPropertyIri(activeContext, key) {
|
|
|
7086
7283
|
if (typeof mapping === "object" && mapping != null && mapping._prefix === true && typeof mapping["@id"] === "string") return mapping["@id"] + suffix;
|
|
7087
7284
|
return key;
|
|
7088
7285
|
}
|
|
7089
|
-
async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoader = preloadedOnlyDocumentLoader, inheritedContext, rejectOnContextError = false) {
|
|
7286
|
+
async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoader = preloadedOnlyDocumentLoader, inheritedContext, rejectOnContextError = false, contextState) {
|
|
7090
7287
|
const names = new Set(defaults);
|
|
7091
7288
|
const context = jsonLd["@context"] ?? inheritedContext;
|
|
7092
|
-
if (context == null) return names;
|
|
7289
|
+
if (context == null && contextState?.initial == null) return names;
|
|
7093
7290
|
try {
|
|
7094
7291
|
const options = { documentLoader };
|
|
7095
|
-
let activeContext = await _fedify_vocab_runtime_jsonld.default.processContext(null, null, options);
|
|
7096
|
-
activeContext = await _fedify_vocab_runtime_jsonld.default.processContext(activeContext,
|
|
7292
|
+
let activeContext = contextState?.initial ?? await _fedify_vocab_runtime_jsonld.default.processContext(null, null, options);
|
|
7293
|
+
if (contextState?.scoped != null) activeContext = await _fedify_vocab_runtime_jsonld.default.processContext(activeContext, contextState.scoped, options);
|
|
7294
|
+
if (context != null) activeContext = await _fedify_vocab_runtime_jsonld.default.processContext(activeContext, context, options);
|
|
7097
7295
|
const typeScopedContext = activeContext;
|
|
7098
7296
|
for (const key of globalThis.Object.keys(jsonLd).sort()) {
|
|
7099
7297
|
if (key !== "@type" && expandContextPropertyIri(activeContext, key) !== "@type") continue;
|
|
@@ -7105,6 +7303,7 @@ async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoa
|
|
|
7105
7303
|
if (scopedContext != null) activeContext = await _fedify_vocab_runtime_jsonld.default.processContext(activeContext, scopedContext, options);
|
|
7106
7304
|
}
|
|
7107
7305
|
}
|
|
7306
|
+
if (contextState != null) contextState.active = activeContext;
|
|
7108
7307
|
for (const key of globalThis.Object.keys(jsonLd)) if (expandContextPropertyIri(activeContext, key) === propertyIri) names.add(key);
|
|
7109
7308
|
} catch {
|
|
7110
7309
|
if (rejectOnContextError) return null;
|
|
@@ -7171,6 +7370,36 @@ function sameBytes(left, right) {
|
|
|
7171
7370
|
function sameProof(left, right) {
|
|
7172
7371
|
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();
|
|
7173
7372
|
}
|
|
7373
|
+
function recordProofContexts(documentLoader) {
|
|
7374
|
+
const documents = /* @__PURE__ */ new Map();
|
|
7375
|
+
return {
|
|
7376
|
+
loader: async (url, options) => {
|
|
7377
|
+
const document = await documentLoader(url, options);
|
|
7378
|
+
documents.set(url, document);
|
|
7379
|
+
return document;
|
|
7380
|
+
},
|
|
7381
|
+
replay: (url, options) => {
|
|
7382
|
+
const document = documents.get(url);
|
|
7383
|
+
return document == null ? preloadedOnlyDocumentLoader(url, options) : Promise.resolve(document);
|
|
7384
|
+
}
|
|
7385
|
+
};
|
|
7386
|
+
}
|
|
7387
|
+
async function getAliasedDeclaredProofKeyId(value, inheritedContext, contextLoader) {
|
|
7388
|
+
if (!isJsonLdNode(value)) return null;
|
|
7389
|
+
const contextState = {};
|
|
7390
|
+
const names = await getJsonLdPropertyNames(value, "https://w3id.org/security#verificationMethod", ["verificationMethod", "https://w3id.org/security#verificationMethod"], contextLoader, inheritedContext, false, contextState);
|
|
7391
|
+
const methodName = Array.from(names ?? []).find((name) => value[name] != null);
|
|
7392
|
+
const method = methodName == null ? void 0 : value[methodName];
|
|
7393
|
+
const candidate = Array.isArray(method) ? method[0] : method;
|
|
7394
|
+
if (typeof candidate === "string") return candidate;
|
|
7395
|
+
if (!isJsonLdNode(candidate)) return null;
|
|
7396
|
+
const idNames = await getJsonLdPropertyNames(candidate, "@id", ["@id"], contextLoader, void 0, false, {
|
|
7397
|
+
initial: contextState.active,
|
|
7398
|
+
scoped: contextState.active == null || methodName == null ? void 0 : _fedify_vocab_runtime_jsonld.default.getContextValue(contextState.active, methodName, "@context")
|
|
7399
|
+
});
|
|
7400
|
+
for (const name of idNames ?? []) if (typeof candidate[name] === "string") return candidate[name];
|
|
7401
|
+
return null;
|
|
7402
|
+
}
|
|
7174
7403
|
function normalizeDocumentUrl(url) {
|
|
7175
7404
|
try {
|
|
7176
7405
|
return (0, _fedify_vocab_runtime.formatIri)((0, _fedify_vocab_runtime.parseIri)(url));
|
|
@@ -7189,6 +7418,7 @@ async function parseRawProofCandidates(jsonLd, values, options, documentLoader)
|
|
|
7189
7418
|
const candidates = [];
|
|
7190
7419
|
for (const value of values) {
|
|
7191
7420
|
let parsed = null;
|
|
7421
|
+
const contexts = options[verificationObservation] != null && options[verificationObservation]?.captureRawKeyIds !== false ? recordProofContexts(documentLoader) : void 0;
|
|
7192
7422
|
if (isJsonLdNode(value)) {
|
|
7193
7423
|
const proofJsonLd = value["@context"] == null && jsonLd["@context"] != null ? {
|
|
7194
7424
|
"@context": jsonLd["@context"],
|
|
@@ -7197,14 +7427,15 @@ async function parseRawProofCandidates(jsonLd, values, options, documentLoader)
|
|
|
7197
7427
|
try {
|
|
7198
7428
|
parsed = await _fedify_vocab.DataIntegrityProof.fromJsonLd(proofJsonLd, {
|
|
7199
7429
|
...options,
|
|
7200
|
-
contextLoader: documentLoader
|
|
7430
|
+
contextLoader: contexts?.loader ?? documentLoader
|
|
7201
7431
|
});
|
|
7202
7432
|
} catch {}
|
|
7203
7433
|
}
|
|
7204
7434
|
candidates.push({
|
|
7205
7435
|
value,
|
|
7206
7436
|
proof: parsed,
|
|
7207
|
-
reference: getRawProofReference(value) ?? parsed?.id?.href
|
|
7437
|
+
reference: getRawProofReference(value) ?? parsed?.id?.href,
|
|
7438
|
+
declaredKeyId: contexts == null ? void 0 : await getAliasedDeclaredProofKeyId(value, jsonLd["@context"], contexts.replay)
|
|
7208
7439
|
});
|
|
7209
7440
|
}
|
|
7210
7441
|
return candidates;
|
|
@@ -7428,8 +7659,13 @@ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, r
|
|
|
7428
7659
|
const digest = new Uint8Array(proofDigest.byteLength + 32);
|
|
7429
7660
|
digest.set(new Uint8Array(proofDigest), 0);
|
|
7430
7661
|
const proofValue = proof.proofValue;
|
|
7662
|
+
let keyTried = false;
|
|
7431
7663
|
const verifyCandidate = async (msgDigest) => {
|
|
7432
7664
|
digest.set(new Uint8Array(msgDigest), proofDigest.byteLength);
|
|
7665
|
+
if (!keyTried) {
|
|
7666
|
+
triedKey(options, publicKey);
|
|
7667
|
+
keyTried = true;
|
|
7668
|
+
}
|
|
7433
7669
|
return await crypto.subtle.verify("Ed25519", publicKey.publicKey, proofValue.slice(), digest);
|
|
7434
7670
|
};
|
|
7435
7671
|
const messageDigestKey = (0, json_canon.default)(proofConfiguration.context ?? null);
|
|
@@ -7887,12 +8123,47 @@ async function verifyPreparedPortableObjectProof(jsonLd, prepared, options, mess
|
|
|
7887
8123
|
* @since 0.10.0
|
|
7888
8124
|
*/
|
|
7889
8125
|
async function verifyObject(cls, jsonLd, options = {}) {
|
|
8126
|
+
if (options[verificationObservation]?.attempt == null) {
|
|
8127
|
+
const observedOptions = {
|
|
8128
|
+
...options,
|
|
8129
|
+
[verificationObservation]: options[verificationObservation] ?? {
|
|
8130
|
+
attempts: [],
|
|
8131
|
+
captureRawKeyIds: false
|
|
8132
|
+
}
|
|
8133
|
+
};
|
|
8134
|
+
return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("object_integrity_proofs.verify_object", async (span) => {
|
|
8135
|
+
try {
|
|
8136
|
+
return await observeAttempt(observedOptions, "objectIntegrity", async (observation) => {
|
|
8137
|
+
const object = await verifyObject(cls, jsonLd, {
|
|
8138
|
+
...options,
|
|
8139
|
+
[verificationObservation]: observation
|
|
8140
|
+
});
|
|
8141
|
+
if (object == null) {
|
|
8142
|
+
span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
|
|
8143
|
+
span.setAttribute("activitypub.verification.failure_reason", observation.attempt?.reason?.type ?? "signatureVerificationFailed");
|
|
8144
|
+
}
|
|
8145
|
+
return object;
|
|
8146
|
+
}, (value) => value != null);
|
|
8147
|
+
} catch (error) {
|
|
8148
|
+
span.setStatus({
|
|
8149
|
+
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
8150
|
+
message: String(error)
|
|
8151
|
+
});
|
|
8152
|
+
throw error;
|
|
8153
|
+
} finally {
|
|
8154
|
+
span.end();
|
|
8155
|
+
}
|
|
8156
|
+
});
|
|
8157
|
+
}
|
|
8158
|
+
const observation = options[verificationObservation];
|
|
7890
8159
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
7891
8160
|
"fedify",
|
|
7892
8161
|
"sig",
|
|
7893
8162
|
"proof"
|
|
7894
8163
|
]);
|
|
7895
8164
|
const object = await cls.fromJsonLd(jsonLd, options);
|
|
8165
|
+
observation?.parsedObject?.(object);
|
|
8166
|
+
if (observation?.subject != null) observation.subject.id = object.id == null ? null : new URL(object.id.href);
|
|
7896
8167
|
const defaultDocumentLoader = (0, _fedify_vocab_runtime.getDocumentLoader)();
|
|
7897
8168
|
const proofContextLoader = options.contextLoader ?? defaultDocumentLoader;
|
|
7898
8169
|
const attributions = new Set(object.attributionIds.map((uri) => uri.href));
|
|
@@ -7919,10 +8190,11 @@ async function verifyObject(cls, jsonLd, options = {}) {
|
|
|
7919
8190
|
if (candidateIndex >= 0) {
|
|
7920
8191
|
hydratedCandidates.add(candidateIndex);
|
|
7921
8192
|
let parsed = null;
|
|
8193
|
+
const contexts = options[verificationObservation] != null && options[verificationObservation]?.captureRawKeyIds !== false ? recordProofContexts(proofContextLoader) : void 0;
|
|
7922
8194
|
try {
|
|
7923
8195
|
parsed = await _fedify_vocab.DataIntegrityProof.fromJsonLd(remoteDocument.document, {
|
|
7924
8196
|
documentLoader: baseDocumentLoader,
|
|
7925
|
-
contextLoader: proofContextLoader,
|
|
8197
|
+
contextLoader: contexts?.loader ?? proofContextLoader,
|
|
7926
8198
|
tracerProvider: options.tracerProvider,
|
|
7927
8199
|
baseUrl: (0, _fedify_vocab_runtime.parseIri)(remoteDocument.documentUrl)
|
|
7928
8200
|
});
|
|
@@ -7930,7 +8202,8 @@ async function verifyObject(cls, jsonLd, options = {}) {
|
|
|
7930
8202
|
rawProofCandidates[candidateIndex] = {
|
|
7931
8203
|
value: structuredClone(remoteDocument.document),
|
|
7932
8204
|
proof: parsed,
|
|
7933
|
-
reference
|
|
8205
|
+
reference,
|
|
8206
|
+
declaredKeyId: contexts == null ? void 0 : await getAliasedDeclaredProofKeyId(remoteDocument.document, void 0, contexts.replay)
|
|
7934
8207
|
};
|
|
7935
8208
|
}
|
|
7936
8209
|
return remoteDocument;
|
|
@@ -7958,6 +8231,10 @@ async function verifyObject(cls, jsonLd, options = {}) {
|
|
|
7958
8231
|
return null;
|
|
7959
8232
|
}
|
|
7960
8233
|
if (attributions.size > 0) {
|
|
8234
|
+
if (observation?.attempt != null) observation.attempt.reason = observation.attempt.checks.length === 0 ? { type: "noSignature" } : {
|
|
8235
|
+
type: "uncoveredAttribution",
|
|
8236
|
+
attributionIds: [...attributions].map((id) => new URL(id))
|
|
8237
|
+
};
|
|
7961
8238
|
logger.debug("Some attributions are not authenticated by the proofs: {attributions}.", { attributions: [...attributions] });
|
|
7962
8239
|
return null;
|
|
7963
8240
|
}
|
|
@@ -8978,6 +9255,14 @@ async function verifyRequest(request, options = {}) {
|
|
|
8978
9255
|
* @since 2.1.0
|
|
8979
9256
|
*/
|
|
8980
9257
|
async function verifyRequestDetailed(request, options = {}) {
|
|
9258
|
+
if (options[verificationObservation] != null && options[verificationObservation]?.attempt == null) return await observeAttempt(options, "http", async (observation) => {
|
|
9259
|
+
const result = await verifyRequestDetailed(request, {
|
|
9260
|
+
...options,
|
|
9261
|
+
[verificationObservation]: observation
|
|
9262
|
+
});
|
|
9263
|
+
if (!result.verified) observation.attempt.reason = result.reason.type === "noSignature" ? { type: "noSignature" } : { type: "signatureVerificationFailed" };
|
|
9264
|
+
return result;
|
|
9265
|
+
}, (result) => result.verified);
|
|
8981
9266
|
if (options.maxSignatures !== void 0) validateMaxSignatures(options.maxSignatures, "maxSignatures");
|
|
8982
9267
|
return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("http_signatures.verify", async (span) => {
|
|
8983
9268
|
if (span.isRecording()) {
|
|
@@ -9016,7 +9301,27 @@ async function verifyRequestDetailed(request, options = {}) {
|
|
|
9016
9301
|
}
|
|
9017
9302
|
});
|
|
9018
9303
|
}
|
|
9019
|
-
async function verifyRequestDraft(request, span, metricsContext,
|
|
9304
|
+
async function verifyRequestDraft(request, span, metricsContext, options = {}) {
|
|
9305
|
+
if (options[verificationObservation]?.check != null) return await verifyRequestDraftInternal(request, span, metricsContext, options);
|
|
9306
|
+
let result = noSignatureResult();
|
|
9307
|
+
const raw = request.headers.get("Signature");
|
|
9308
|
+
const declared = raw == null ? null : parseDraftSignature(raw)?.keyId ?? null;
|
|
9309
|
+
if (raw == null) return result;
|
|
9310
|
+
await observeCheck(options, {
|
|
9311
|
+
mechanism: "http",
|
|
9312
|
+
spec: "draft-cavage-http-signatures-12",
|
|
9313
|
+
label: null
|
|
9314
|
+
}, declared, async (observation) => {
|
|
9315
|
+
result = await verifyRequestDraftInternal(request, span, metricsContext, {
|
|
9316
|
+
...options,
|
|
9317
|
+
[verificationObservation]: observation
|
|
9318
|
+
});
|
|
9319
|
+
if (!result.verified && result.reason.type === "keyFetchError" && observation.check != null) observation.check.reason = result.reason;
|
|
9320
|
+
return result.verified ? result.key : null;
|
|
9321
|
+
});
|
|
9322
|
+
return result;
|
|
9323
|
+
}
|
|
9324
|
+
async function verifyRequestDraftInternal(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider, [verificationObservation]: observation } = {}) {
|
|
9020
9325
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
9021
9326
|
"fedify",
|
|
9022
9327
|
"sig",
|
|
@@ -9197,6 +9502,7 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
|
|
|
9197
9502
|
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");
|
|
9198
9503
|
const sig = (0, byte_encodings_base64.decodeBase64)(signature);
|
|
9199
9504
|
span?.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(sig));
|
|
9505
|
+
triedKey({ [verificationObservation]: observation }, key);
|
|
9200
9506
|
if (!await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, sig, new TextEncoder().encode(message))) {
|
|
9201
9507
|
if (cached) {
|
|
9202
9508
|
logger.debug("Failed to verify with the cached key {keyId}; signature {signature} is invalid. Retrying with the freshly fetched key...", {
|
|
@@ -9211,7 +9517,8 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
|
|
|
9211
9517
|
currentTime,
|
|
9212
9518
|
keyCache: bypassKeyCacheReads(keyCache),
|
|
9213
9519
|
meterProvider,
|
|
9214
|
-
tracerProvider
|
|
9520
|
+
tracerProvider,
|
|
9521
|
+
[verificationObservation]: observation
|
|
9215
9522
|
});
|
|
9216
9523
|
}
|
|
9217
9524
|
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}", {
|
|
@@ -9297,7 +9604,7 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
|
|
|
9297
9604
|
* @returns Whether the signature is verified, and the algorithm of the
|
|
9298
9605
|
* signature if it is supported.
|
|
9299
9606
|
*/
|
|
9300
|
-
async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span) {
|
|
9607
|
+
async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span, observationOptions = {}) {
|
|
9301
9608
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
9302
9609
|
"fedify",
|
|
9303
9610
|
"sig",
|
|
@@ -9353,6 +9660,7 @@ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, s
|
|
|
9353
9660
|
}
|
|
9354
9661
|
let verified;
|
|
9355
9662
|
try {
|
|
9663
|
+
triedKey(observationOptions, key);
|
|
9356
9664
|
verified = await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes);
|
|
9357
9665
|
} catch (error) {
|
|
9358
9666
|
logger.debug("Error during signature verification: {error}", {
|
|
@@ -9380,7 +9688,7 @@ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, s
|
|
|
9380
9688
|
mismatched: true
|
|
9381
9689
|
};
|
|
9382
9690
|
}
|
|
9383
|
-
async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, maxSignatures = 3, meterProvider, tracerProvider } = {}) {
|
|
9691
|
+
async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, maxSignatures = 3, [verificationObservation]: observation, meterProvider, tracerProvider } = {}) {
|
|
9384
9692
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
9385
9693
|
"fedify",
|
|
9386
9694
|
"sig",
|
|
@@ -9444,105 +9752,116 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
|
|
|
9444
9752
|
let body = null;
|
|
9445
9753
|
let digestValid = null;
|
|
9446
9754
|
for (const sigName of signatureNames) {
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
signatureName: sigName,
|
|
9457
|
-
signatureInput: signatureInputHeader
|
|
9458
|
-
});
|
|
9459
|
-
setFailure(invalidSignatureResult(null));
|
|
9460
|
-
continue;
|
|
9461
|
-
}
|
|
9462
|
-
if (!sigInput.created) {
|
|
9463
|
-
logger.debug("Failed to verify; missing created timestamp in signature {signatureName}.", {
|
|
9464
|
-
signatureName: sigName,
|
|
9465
|
-
signatureInput: signatureInputHeader
|
|
9466
|
-
});
|
|
9467
|
-
setFailure(invalidSignatureResult(keyId));
|
|
9468
|
-
continue;
|
|
9469
|
-
}
|
|
9470
|
-
const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
|
|
9471
|
-
const now = currentTime ?? Temporal.Now.instant();
|
|
9472
|
-
if (timeWindow !== false) {
|
|
9473
|
-
const tw = timeWindow ?? { hours: 1 };
|
|
9474
|
-
if (Temporal.Instant.compare(signatureCreated, now.add(tw)) > 0) {
|
|
9475
|
-
logger.debug("Failed to verify; signature created time is too far in the future.", {
|
|
9476
|
-
created: signatureCreated.toString(),
|
|
9477
|
-
now: now.toString()
|
|
9478
|
-
});
|
|
9479
|
-
setFailure(invalidSignatureResult(keyId));
|
|
9480
|
-
continue;
|
|
9481
|
-
} else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
|
|
9482
|
-
logger.debug("Failed to verify; signature created time is too far in the past.", {
|
|
9483
|
-
created: signatureCreated.toString(),
|
|
9484
|
-
now: now.toString()
|
|
9485
|
-
});
|
|
9486
|
-
setFailure(invalidSignatureResult(keyId));
|
|
9487
|
-
continue;
|
|
9755
|
+
let winning;
|
|
9756
|
+
await observeCheck({ [verificationObservation]: observation }, {
|
|
9757
|
+
mechanism: "http",
|
|
9758
|
+
spec: "rfc9421",
|
|
9759
|
+
label: sigName
|
|
9760
|
+
}, signatureInputs[sigName]?.keyId ?? null, async (candidateObservation) => {
|
|
9761
|
+
if (!signatures[sigName]) {
|
|
9762
|
+
setFailure(invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId)));
|
|
9763
|
+
return null;
|
|
9488
9764
|
}
|
|
9489
|
-
|
|
9490
|
-
|
|
9491
|
-
const
|
|
9492
|
-
if (!
|
|
9493
|
-
logger.debug("Failed to verify;
|
|
9494
|
-
|
|
9495
|
-
|
|
9765
|
+
const sigInput = signatureInputs[sigName];
|
|
9766
|
+
const sigBytes = signatures[sigName];
|
|
9767
|
+
const keyId = parseKeyId(sigInput.keyId);
|
|
9768
|
+
if (!sigInput.keyId) {
|
|
9769
|
+
logger.debug("Failed to verify; missing keyId in signature {signatureName}.", {
|
|
9770
|
+
signatureName: sigName,
|
|
9771
|
+
signatureInput: signatureInputHeader
|
|
9772
|
+
});
|
|
9773
|
+
setFailure(invalidSignatureResult(null));
|
|
9774
|
+
return null;
|
|
9496
9775
|
}
|
|
9497
|
-
|
|
9498
|
-
|
|
9499
|
-
|
|
9500
|
-
|
|
9776
|
+
if (!sigInput.created) {
|
|
9777
|
+
logger.debug("Failed to verify; missing created timestamp in signature {signatureName}.", {
|
|
9778
|
+
signatureName: sigName,
|
|
9779
|
+
signatureInput: signatureInputHeader
|
|
9780
|
+
});
|
|
9501
9781
|
setFailure(invalidSignatureResult(keyId));
|
|
9502
|
-
|
|
9782
|
+
return null;
|
|
9503
9783
|
}
|
|
9504
|
-
|
|
9505
|
-
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
|
|
9509
|
-
|
|
9510
|
-
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
9516
|
-
|
|
9517
|
-
|
|
9518
|
-
|
|
9519
|
-
|
|
9520
|
-
|
|
9784
|
+
const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
|
|
9785
|
+
const now = currentTime ?? Temporal.Now.instant();
|
|
9786
|
+
if (timeWindow !== false) {
|
|
9787
|
+
const tw = timeWindow ?? { hours: 1 };
|
|
9788
|
+
if (Temporal.Instant.compare(signatureCreated, now.add(tw)) > 0) {
|
|
9789
|
+
logger.debug("Failed to verify; signature created time is too far in the future.", {
|
|
9790
|
+
created: signatureCreated.toString(),
|
|
9791
|
+
now: now.toString()
|
|
9792
|
+
});
|
|
9793
|
+
setFailure(invalidSignatureResult(keyId));
|
|
9794
|
+
return null;
|
|
9795
|
+
} else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
|
|
9796
|
+
logger.debug("Failed to verify; signature created time is too far in the past.", {
|
|
9797
|
+
created: signatureCreated.toString(),
|
|
9798
|
+
now: now.toString()
|
|
9799
|
+
});
|
|
9800
|
+
setFailure(invalidSignatureResult(keyId));
|
|
9801
|
+
return null;
|
|
9802
|
+
}
|
|
9521
9803
|
}
|
|
9522
|
-
if (
|
|
9523
|
-
|
|
9524
|
-
|
|
9525
|
-
|
|
9804
|
+
if (request.method !== "GET" && request.method !== "HEAD" && sigInput.components.some((c) => c.value === "content-digest")) {
|
|
9805
|
+
const contentDigestHeader = request.headers.get("Content-Digest");
|
|
9806
|
+
if (!contentDigestHeader) {
|
|
9807
|
+
logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
|
|
9808
|
+
setFailure(invalidSignatureResult(keyId));
|
|
9809
|
+
return null;
|
|
9810
|
+
}
|
|
9811
|
+
body ??= await request.arrayBuffer();
|
|
9812
|
+
digestValid ??= await verifyRfc9421ContentDigest(contentDigestHeader, body);
|
|
9813
|
+
if (!digestValid) {
|
|
9814
|
+
logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
|
|
9815
|
+
setFailure(invalidSignatureResult(keyId));
|
|
9816
|
+
return null;
|
|
9817
|
+
}
|
|
9526
9818
|
}
|
|
9527
|
-
|
|
9528
|
-
|
|
9529
|
-
|
|
9530
|
-
|
|
9531
|
-
|
|
9532
|
-
key,
|
|
9533
|
-
signatureLabel: sigName
|
|
9534
|
-
};
|
|
9819
|
+
span?.setAttribute("http_signatures.key_id", sigInput.keyId);
|
|
9820
|
+
span?.setAttribute("http_signatures.created", sigInput.created.toString());
|
|
9821
|
+
if (keyId == null) {
|
|
9822
|
+
setFailure(invalidSignatureResult(null));
|
|
9823
|
+
return null;
|
|
9535
9824
|
}
|
|
9536
|
-
|
|
9537
|
-
|
|
9538
|
-
|
|
9539
|
-
lookup = await lookUpKey(keyId, bypassKeyCacheReads(keyCache));
|
|
9825
|
+
let lookup = keyLookups.get(keyId.href);
|
|
9826
|
+
if (lookup == null) {
|
|
9827
|
+
lookup = await lookUpKey(keyId, keyCache);
|
|
9540
9828
|
keyLookups.set(keyId.href, lookup);
|
|
9541
|
-
continue;
|
|
9542
9829
|
}
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
9830
|
+
while (true) {
|
|
9831
|
+
const { key, cached, fetchError } = lookup;
|
|
9832
|
+
if (fetchError != null) {
|
|
9833
|
+
setFailure(keyFetchErrorResult(keyId, fetchError));
|
|
9834
|
+
break;
|
|
9835
|
+
}
|
|
9836
|
+
if (!key) {
|
|
9837
|
+
logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
|
|
9838
|
+
setFailure(invalidSignatureResult(keyId));
|
|
9839
|
+
break;
|
|
9840
|
+
}
|
|
9841
|
+
const result = await verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span, { [verificationObservation]: candidateObservation });
|
|
9842
|
+
if (result.verified) {
|
|
9843
|
+
metricsContext.algorithm = result.algorithm;
|
|
9844
|
+
winning = {
|
|
9845
|
+
verified: true,
|
|
9846
|
+
key,
|
|
9847
|
+
signatureLabel: sigName
|
|
9848
|
+
};
|
|
9849
|
+
return key;
|
|
9850
|
+
}
|
|
9851
|
+
if (result.mismatched && cached && !refreshedKeyIds.has(keyId.href)) {
|
|
9852
|
+
logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
|
|
9853
|
+
refreshedKeyIds.add(keyId.href);
|
|
9854
|
+
lookup = await lookUpKey(keyId, bypassKeyCacheReads(keyCache));
|
|
9855
|
+
keyLookups.set(keyId.href, lookup);
|
|
9856
|
+
continue;
|
|
9857
|
+
}
|
|
9858
|
+
setFailure(invalidSignatureResult(keyId), result.algorithm);
|
|
9859
|
+
break;
|
|
9860
|
+
}
|
|
9861
|
+
if (!failure.verified && failure.reason.type === "keyFetchError" && candidateObservation.check != null) candidateObservation.check.reason = failure.reason;
|
|
9862
|
+
return null;
|
|
9863
|
+
});
|
|
9864
|
+
if (winning != null) return winning;
|
|
9546
9865
|
}
|
|
9547
9866
|
metricsContext.algorithm = failureAlgorithm;
|
|
9548
9867
|
return failure;
|
|
@@ -10063,6 +10382,12 @@ Object.defineProperty(exports, "normalizeOutgoingActivityJsonLd", {
|
|
|
10063
10382
|
return normalizeOutgoingActivityJsonLd;
|
|
10064
10383
|
}
|
|
10065
10384
|
});
|
|
10385
|
+
Object.defineProperty(exports, "observeAttempt", {
|
|
10386
|
+
enumerable: true,
|
|
10387
|
+
get: function() {
|
|
10388
|
+
return observeAttempt;
|
|
10389
|
+
}
|
|
10390
|
+
});
|
|
10066
10391
|
Object.defineProperty(exports, "parseAcceptSignature", {
|
|
10067
10392
|
enumerable: true,
|
|
10068
10393
|
get: function() {
|
|
@@ -10189,6 +10514,12 @@ Object.defineProperty(exports, "signRequest", {
|
|
|
10189
10514
|
return signRequest;
|
|
10190
10515
|
}
|
|
10191
10516
|
});
|
|
10517
|
+
Object.defineProperty(exports, "snapshotKey", {
|
|
10518
|
+
enumerable: true,
|
|
10519
|
+
get: function() {
|
|
10520
|
+
return snapshotKey;
|
|
10521
|
+
}
|
|
10522
|
+
});
|
|
10192
10523
|
Object.defineProperty(exports, "validateAcceptSignature", {
|
|
10193
10524
|
enumerable: true,
|
|
10194
10525
|
get: function() {
|
|
@@ -10207,6 +10538,12 @@ Object.defineProperty(exports, "validateMaxSignatures", {
|
|
|
10207
10538
|
return validateMaxSignatures;
|
|
10208
10539
|
}
|
|
10209
10540
|
});
|
|
10541
|
+
Object.defineProperty(exports, "verificationObservation", {
|
|
10542
|
+
enumerable: true,
|
|
10543
|
+
get: function() {
|
|
10544
|
+
return verificationObservation;
|
|
10545
|
+
}
|
|
10546
|
+
});
|
|
10210
10547
|
Object.defineProperty(exports, "verifyCompactJsonLd", {
|
|
10211
10548
|
enumerable: true,
|
|
10212
10549
|
get: function() {
|