@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
|
@@ -10,13 +10,14 @@ let _fedify_vocab_runtime_jsonld = require("@fedify/vocab-runtime/jsonld");
|
|
|
10
10
|
_fedify_vocab_runtime_jsonld = require_rolldown_runtime.__toESM(_fedify_vocab_runtime_jsonld, 1);
|
|
11
11
|
let structured_field_values = require("structured-field-values");
|
|
12
12
|
let byte_encodings_base64 = require("byte-encodings/base64");
|
|
13
|
+
let _fedify_vocab_runtime_internal_jsonld_cache = require("@fedify/vocab-runtime/internal/jsonld-cache");
|
|
13
14
|
let _fedify_vocab_runtime_internal_signed_representation = require("@fedify/vocab-runtime/internal/signed-representation");
|
|
14
15
|
let json_canon = require("json-canon");
|
|
15
16
|
json_canon = require_rolldown_runtime.__toESM(json_canon, 1);
|
|
16
17
|
let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
|
|
17
18
|
//#region deno.json
|
|
18
19
|
var name = "@fedify/fedify";
|
|
19
|
-
var version = "2.4.0-dev.
|
|
20
|
+
var version = "2.4.0-dev.2259+d9c843fc";
|
|
20
21
|
//#endregion
|
|
21
22
|
//#region src/sig/portable-key-id.ts
|
|
22
23
|
const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
|
|
@@ -3823,6 +3824,106 @@ const Temporal$5 = /*@__PURE__*/ Object.defineProperties({}, {
|
|
|
3823
3824
|
const Temporal$4 = NativeTemporal || Temporal$5;
|
|
3824
3825
|
NativeTemporal && Date.prototype.toTemporalInstant;
|
|
3825
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
|
|
3826
3927
|
//#region src/sig/accept.ts
|
|
3827
3928
|
/**
|
|
3828
3929
|
* `Accept-Signature` header parsing, serialization, and validation utilities
|
|
@@ -4279,6 +4380,7 @@ var FederationMetrics = class {
|
|
|
4279
4380
|
};
|
|
4280
4381
|
if (extra.algorithm != null) attributes["http_signatures.algorithm"] = extra.algorithm;
|
|
4281
4382
|
if (extra.failureReason != null) attributes["http_signatures.failure_reason"] = extra.failureReason;
|
|
4383
|
+
if (extra.verificationFailureReason != null) attributes["activitypub.verification.failure_reason"] = extra.verificationFailureReason;
|
|
4282
4384
|
if (extra.ldType != null) attributes["ld_signatures.type"] = extra.ldType;
|
|
4283
4385
|
if (extra.cryptosuite != null) attributes["object_integrity_proofs.cryptosuite"] = extra.cryptosuite;
|
|
4284
4386
|
this.signatureVerificationDuration.record(durationMs, attributes);
|
|
@@ -5784,12 +5886,38 @@ async function fetchPortableActorKey(cacheKey, cls, options, onCachedUnavailable
|
|
|
5784
5886
|
};
|
|
5785
5887
|
}
|
|
5786
5888
|
async function fetchKeyInternal(keyId, cls, options = {}) {
|
|
5787
|
-
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 {}
|
|
5788
5899
|
return {
|
|
5789
5900
|
key: null,
|
|
5790
5901
|
cached: true
|
|
5791
5902
|
};
|
|
5792
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
|
+
}
|
|
5793
5921
|
logger.debug("Failed to fetch key {keyId}.", {
|
|
5794
5922
|
keyId,
|
|
5795
5923
|
error
|
|
@@ -6246,6 +6374,13 @@ function detachSignature(jsonLd) {
|
|
|
6246
6374
|
*/
|
|
6247
6375
|
async function verifySignature(jsonLd, options = {}) {
|
|
6248
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
|
+
}
|
|
6249
6384
|
const sig = jsonLd.signature;
|
|
6250
6385
|
let signature;
|
|
6251
6386
|
try {
|
|
@@ -6291,6 +6426,7 @@ async function verifySignature(jsonLd, options = {}) {
|
|
|
6291
6426
|
const encoder = new TextEncoder();
|
|
6292
6427
|
const message = sigOptsHash + docHash;
|
|
6293
6428
|
const messageBytes = encoder.encode(message);
|
|
6429
|
+
triedKey(options, key);
|
|
6294
6430
|
if (await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, signature.slice(), messageBytes)) return key;
|
|
6295
6431
|
if (cached) {
|
|
6296
6432
|
logger$4.debug("Failed to verify with the cached key {keyId}; signature {signatureValue} is invalid. Retrying with the freshly fetched key...", {
|
|
@@ -6302,6 +6438,7 @@ async function verifySignature(jsonLd, options = {}) {
|
|
|
6302
6438
|
keyCache: bypassKeyCacheReads(options.keyCache)
|
|
6303
6439
|
}));
|
|
6304
6440
|
if (key == null) return null;
|
|
6441
|
+
triedKey(options, key);
|
|
6305
6442
|
return await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, signature.slice(), messageBytes) ? key : null;
|
|
6306
6443
|
}
|
|
6307
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}", {
|
|
@@ -6355,6 +6492,14 @@ async function verifyCompactJsonLd(jsonLd, options = {}) {
|
|
|
6355
6492
|
return await verifyJsonLdInternal(jsonLd, options, false);
|
|
6356
6493
|
}
|
|
6357
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];
|
|
6358
6503
|
return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("ld_signatures.verify", async (span) => {
|
|
6359
6504
|
const start = performance.now();
|
|
6360
6505
|
let verified = false;
|
|
@@ -6367,6 +6512,8 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
|
6367
6512
|
} : options;
|
|
6368
6513
|
const compacted = compact ? hasSignature(jsonLd) ? await compactJsonLd(jsonLd, options.contextLoader) : jsonLd : jsonLd;
|
|
6369
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);
|
|
6370
6517
|
if (object.id != null) span.setAttribute("activitypub.object.id", object.id.href);
|
|
6371
6518
|
span.setAttribute("activitypub.object.type", (0, _fedify_vocab.getTypeId)(object).href);
|
|
6372
6519
|
const sig = getLdSignatureObject(jsonLd);
|
|
@@ -6381,13 +6528,21 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
|
6381
6528
|
const attributions = new Set(object.attributionIds.map((uri) => uri.href));
|
|
6382
6529
|
if (object instanceof _fedify_vocab.Activity) for (const uri of object.actorIds) attributions.add(uri.href);
|
|
6383
6530
|
const key = await verifySignature(compacted, verificationOptions);
|
|
6384
|
-
if (key == null)
|
|
6531
|
+
if (key == null) {
|
|
6532
|
+
if (observation?.attempt != null && !hasLdSignatureProperty(jsonLd)) observation.attempt.reason = { type: "noSignature" };
|
|
6533
|
+
return false;
|
|
6534
|
+
}
|
|
6385
6535
|
if (key.ownerId == null) {
|
|
6536
|
+
if (observation?.attempt != null) observation.attempt.reason = { type: "missingOwner" };
|
|
6386
6537
|
logger$4.debug("Key {keyId} has no owner.", { keyId: key.id?.href });
|
|
6387
6538
|
return false;
|
|
6388
6539
|
}
|
|
6389
6540
|
attributions.delete(key.ownerId.href);
|
|
6390
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
|
+
};
|
|
6391
6546
|
logger$4.debug("Some attributions are not authenticated by the Linked Data Signatures: {attributions}.", { attributions: [...attributions] });
|
|
6392
6547
|
return false;
|
|
6393
6548
|
}
|
|
@@ -6401,8 +6556,15 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
|
6401
6556
|
});
|
|
6402
6557
|
throw error;
|
|
6403
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
|
+
}
|
|
6404
6563
|
const classified = threw ? "error" : verified ? "verified" : hasLdSignatureProperty(jsonLd) ? "rejected" : "missing";
|
|
6405
|
-
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
|
+
});
|
|
6406
6568
|
span.end();
|
|
6407
6569
|
}
|
|
6408
6570
|
});
|
|
@@ -7031,19 +7193,50 @@ async function verifyProof(jsonLd, proof, options = {}) {
|
|
|
7031
7193
|
*/
|
|
7032
7194
|
async function verifyMapLocalProof(jsonLd, options = {}) {
|
|
7033
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));
|
|
7034
7203
|
const proofContextLoader = getNormalizationContextLoader(preloadedOnlyDocumentLoader);
|
|
7035
7204
|
try {
|
|
7036
7205
|
const [candidate] = await parseRawProofCandidates(jsonLd, [jsonLd.proof], options, proofContextLoader);
|
|
7037
|
-
if (candidate.proof == null) return
|
|
7206
|
+
if (candidate.proof == null) return await malformed();
|
|
7038
7207
|
return await verifyProofWithMessageDigestCache(jsonLd, candidate.proof, options, {
|
|
7039
7208
|
proofContextLoader,
|
|
7040
7209
|
proofPropertyMode: "literal"
|
|
7041
7210
|
}, candidate);
|
|
7042
7211
|
} catch {
|
|
7212
|
+
if (previousChecks != null && options[verificationObservation]?.attempt?.checks.length === previousChecks) return await malformed();
|
|
7043
7213
|
return null;
|
|
7044
7214
|
}
|
|
7045
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
|
+
}
|
|
7046
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));
|
|
7047
7240
|
return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("object_integrity_proofs.verify", async (span) => {
|
|
7048
7241
|
const start = performance.now();
|
|
7049
7242
|
let verified = false;
|
|
@@ -7056,8 +7249,10 @@ async function verifyProofWithMessageDigestCache(jsonLd, proof, options, message
|
|
|
7056
7249
|
}
|
|
7057
7250
|
try {
|
|
7058
7251
|
const key = await verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
|
|
7059
|
-
if (key == null)
|
|
7060
|
-
|
|
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;
|
|
7061
7256
|
return key;
|
|
7062
7257
|
} catch (error) {
|
|
7063
7258
|
threw = true;
|
|
@@ -7068,7 +7263,10 @@ async function verifyProofWithMessageDigestCache(jsonLd, proof, options, message
|
|
|
7068
7263
|
throw error;
|
|
7069
7264
|
} finally {
|
|
7070
7265
|
const classified = threw ? "error" : verified ? "verified" : "rejected";
|
|
7071
|
-
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
|
+
});
|
|
7072
7270
|
span.end();
|
|
7073
7271
|
}
|
|
7074
7272
|
});
|
|
@@ -7085,14 +7283,15 @@ function expandContextPropertyIri(activeContext, key) {
|
|
|
7085
7283
|
if (typeof mapping === "object" && mapping != null && mapping._prefix === true && typeof mapping["@id"] === "string") return mapping["@id"] + suffix;
|
|
7086
7284
|
return key;
|
|
7087
7285
|
}
|
|
7088
|
-
async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoader = preloadedOnlyDocumentLoader, inheritedContext, rejectOnContextError = false) {
|
|
7286
|
+
async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoader = preloadedOnlyDocumentLoader, inheritedContext, rejectOnContextError = false, contextState) {
|
|
7089
7287
|
const names = new Set(defaults);
|
|
7090
7288
|
const context = jsonLd["@context"] ?? inheritedContext;
|
|
7091
|
-
if (context == null) return names;
|
|
7289
|
+
if (context == null && contextState?.initial == null) return names;
|
|
7092
7290
|
try {
|
|
7093
7291
|
const options = { documentLoader };
|
|
7094
|
-
let activeContext = await _fedify_vocab_runtime_jsonld.default.processContext(null, null, options);
|
|
7095
|
-
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);
|
|
7096
7295
|
const typeScopedContext = activeContext;
|
|
7097
7296
|
for (const key of globalThis.Object.keys(jsonLd).sort()) {
|
|
7098
7297
|
if (key !== "@type" && expandContextPropertyIri(activeContext, key) !== "@type") continue;
|
|
@@ -7104,6 +7303,7 @@ async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoa
|
|
|
7104
7303
|
if (scopedContext != null) activeContext = await _fedify_vocab_runtime_jsonld.default.processContext(activeContext, scopedContext, options);
|
|
7105
7304
|
}
|
|
7106
7305
|
}
|
|
7306
|
+
if (contextState != null) contextState.active = activeContext;
|
|
7107
7307
|
for (const key of globalThis.Object.keys(jsonLd)) if (expandContextPropertyIri(activeContext, key) === propertyIri) names.add(key);
|
|
7108
7308
|
} catch {
|
|
7109
7309
|
if (rejectOnContextError) return null;
|
|
@@ -7170,6 +7370,36 @@ function sameBytes(left, right) {
|
|
|
7170
7370
|
function sameProof(left, right) {
|
|
7171
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();
|
|
7172
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
|
+
}
|
|
7173
7403
|
function normalizeDocumentUrl(url) {
|
|
7174
7404
|
try {
|
|
7175
7405
|
return (0, _fedify_vocab_runtime.formatIri)((0, _fedify_vocab_runtime.parseIri)(url));
|
|
@@ -7188,6 +7418,7 @@ async function parseRawProofCandidates(jsonLd, values, options, documentLoader)
|
|
|
7188
7418
|
const candidates = [];
|
|
7189
7419
|
for (const value of values) {
|
|
7190
7420
|
let parsed = null;
|
|
7421
|
+
const contexts = options[verificationObservation] != null && options[verificationObservation]?.captureRawKeyIds !== false ? recordProofContexts(documentLoader) : void 0;
|
|
7191
7422
|
if (isJsonLdNode(value)) {
|
|
7192
7423
|
const proofJsonLd = value["@context"] == null && jsonLd["@context"] != null ? {
|
|
7193
7424
|
"@context": jsonLd["@context"],
|
|
@@ -7196,14 +7427,15 @@ async function parseRawProofCandidates(jsonLd, values, options, documentLoader)
|
|
|
7196
7427
|
try {
|
|
7197
7428
|
parsed = await _fedify_vocab.DataIntegrityProof.fromJsonLd(proofJsonLd, {
|
|
7198
7429
|
...options,
|
|
7199
|
-
contextLoader: documentLoader
|
|
7430
|
+
contextLoader: contexts?.loader ?? documentLoader
|
|
7200
7431
|
});
|
|
7201
7432
|
} catch {}
|
|
7202
7433
|
}
|
|
7203
7434
|
candidates.push({
|
|
7204
7435
|
value,
|
|
7205
7436
|
proof: parsed,
|
|
7206
|
-
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)
|
|
7207
7439
|
});
|
|
7208
7440
|
}
|
|
7209
7441
|
return candidates;
|
|
@@ -7427,8 +7659,13 @@ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, r
|
|
|
7427
7659
|
const digest = new Uint8Array(proofDigest.byteLength + 32);
|
|
7428
7660
|
digest.set(new Uint8Array(proofDigest), 0);
|
|
7429
7661
|
const proofValue = proof.proofValue;
|
|
7662
|
+
let keyTried = false;
|
|
7430
7663
|
const verifyCandidate = async (msgDigest) => {
|
|
7431
7664
|
digest.set(new Uint8Array(msgDigest), proofDigest.byteLength);
|
|
7665
|
+
if (!keyTried) {
|
|
7666
|
+
triedKey(options, publicKey);
|
|
7667
|
+
keyTried = true;
|
|
7668
|
+
}
|
|
7432
7669
|
return await crypto.subtle.verify("Ed25519", publicKey.publicKey, proofValue.slice(), digest);
|
|
7433
7670
|
};
|
|
7434
7671
|
const messageDigestKey = (0, json_canon.default)(proofConfiguration.context ?? null);
|
|
@@ -7473,7 +7710,6 @@ const FEP_2277_COLLECTION_PROPERTIES = [
|
|
|
7473
7710
|
"prev",
|
|
7474
7711
|
"current"
|
|
7475
7712
|
].map((property) => AS_NAMESPACE + property);
|
|
7476
|
-
const FEP_EF61_GATEWAYS = "https://w3id.org/fep/ef61/gateways";
|
|
7477
7713
|
const PORTABLE_OBJECT_ID_PATTERN = /^ap(?:\+ef61)?:\/\//i;
|
|
7478
7714
|
const FUNCTIONAL_PROOF_PROPERTIES = [
|
|
7479
7715
|
`${SECURITY_NAMESPACE}cryptosuite`,
|
|
@@ -7504,14 +7740,15 @@ function hasValidPortableProofShape(proofValue) {
|
|
|
7504
7740
|
* FEP-ef61 requires: a non-empty list whose items are all HTTP(S) URIs with
|
|
7505
7741
|
* an empty path, query, and fragment. Like the generated vocabulary decoder,
|
|
7506
7742
|
* this also tolerates a plain set of values instead of a `@list`, and gateway
|
|
7507
|
-
* strings given as `@value`s instead of `@id`s.
|
|
7743
|
+
* strings given as `@value`s instead of `@id`s. JCS-authenticated actors may
|
|
7744
|
+
* also supply an unmapped literal `gateways` list.
|
|
7508
7745
|
*/
|
|
7509
|
-
function hasValidPortableActorGateways(node) {
|
|
7510
|
-
const values = node
|
|
7746
|
+
function hasValidPortableActorGateways(node, allowUnmapped) {
|
|
7747
|
+
const values = (0, _fedify_vocab_runtime_internal_jsonld_cache.getPortableActorGateways)(node, allowUnmapped);
|
|
7511
7748
|
if (!Array.isArray(values)) return false;
|
|
7512
7749
|
let items = values;
|
|
7513
7750
|
if (values.some((value) => isJsonLdNode(value) && "@list" in value)) {
|
|
7514
|
-
if (values.length !== 1) return false;
|
|
7751
|
+
if (values.length !== 1 || !isJsonLdNode(values[0])) return false;
|
|
7515
7752
|
items = values[0]["@list"];
|
|
7516
7753
|
}
|
|
7517
7754
|
if (!Array.isArray(items) || items.length < 1) return false;
|
|
@@ -7732,7 +7969,7 @@ async function preparePortableObjectProof(jsonLd, options) {
|
|
|
7732
7969
|
}
|
|
7733
7970
|
};
|
|
7734
7971
|
}
|
|
7735
|
-
if (objectType === "actor" && !hasValidPortableActorGateways(root)) return {
|
|
7972
|
+
if (objectType === "actor" && !hasValidPortableActorGateways(root, proofs.every((proof) => proof.cryptosuite === "eddsa-jcs-2022"))) return {
|
|
7736
7973
|
prepared: false,
|
|
7737
7974
|
root,
|
|
7738
7975
|
objectType,
|
|
@@ -7799,6 +8036,8 @@ async function verifyPortableObjectProofPolicy(jsonLd, key, options = {}) {
|
|
|
7799
8036
|
* and fragment; otherwise, it is rejected with the `invalidGateways` reason
|
|
7800
8037
|
* before its proofs are verified. The proofs are verified over the document as
|
|
7801
8038
|
* given; a compatible identifier is never rewritten into a portable ID.
|
|
8039
|
+
* An unmapped literal `gateways` property is also accepted when every proof
|
|
8040
|
+
* uses `eddsa-jcs-2022`, which authenticates the original JSON properties.
|
|
7802
8041
|
*
|
|
7803
8042
|
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
7804
8043
|
*
|
|
@@ -7884,12 +8123,47 @@ async function verifyPreparedPortableObjectProof(jsonLd, prepared, options, mess
|
|
|
7884
8123
|
* @since 0.10.0
|
|
7885
8124
|
*/
|
|
7886
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];
|
|
7887
8159
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
7888
8160
|
"fedify",
|
|
7889
8161
|
"sig",
|
|
7890
8162
|
"proof"
|
|
7891
8163
|
]);
|
|
7892
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);
|
|
7893
8167
|
const defaultDocumentLoader = (0, _fedify_vocab_runtime.getDocumentLoader)();
|
|
7894
8168
|
const proofContextLoader = options.contextLoader ?? defaultDocumentLoader;
|
|
7895
8169
|
const attributions = new Set(object.attributionIds.map((uri) => uri.href));
|
|
@@ -7916,10 +8190,11 @@ async function verifyObject(cls, jsonLd, options = {}) {
|
|
|
7916
8190
|
if (candidateIndex >= 0) {
|
|
7917
8191
|
hydratedCandidates.add(candidateIndex);
|
|
7918
8192
|
let parsed = null;
|
|
8193
|
+
const contexts = options[verificationObservation] != null && options[verificationObservation]?.captureRawKeyIds !== false ? recordProofContexts(proofContextLoader) : void 0;
|
|
7919
8194
|
try {
|
|
7920
8195
|
parsed = await _fedify_vocab.DataIntegrityProof.fromJsonLd(remoteDocument.document, {
|
|
7921
8196
|
documentLoader: baseDocumentLoader,
|
|
7922
|
-
contextLoader: proofContextLoader,
|
|
8197
|
+
contextLoader: contexts?.loader ?? proofContextLoader,
|
|
7923
8198
|
tracerProvider: options.tracerProvider,
|
|
7924
8199
|
baseUrl: (0, _fedify_vocab_runtime.parseIri)(remoteDocument.documentUrl)
|
|
7925
8200
|
});
|
|
@@ -7927,7 +8202,8 @@ async function verifyObject(cls, jsonLd, options = {}) {
|
|
|
7927
8202
|
rawProofCandidates[candidateIndex] = {
|
|
7928
8203
|
value: structuredClone(remoteDocument.document),
|
|
7929
8204
|
proof: parsed,
|
|
7930
|
-
reference
|
|
8205
|
+
reference,
|
|
8206
|
+
declaredKeyId: contexts == null ? void 0 : await getAliasedDeclaredProofKeyId(remoteDocument.document, void 0, contexts.replay)
|
|
7931
8207
|
};
|
|
7932
8208
|
}
|
|
7933
8209
|
return remoteDocument;
|
|
@@ -7955,6 +8231,10 @@ async function verifyObject(cls, jsonLd, options = {}) {
|
|
|
7955
8231
|
return null;
|
|
7956
8232
|
}
|
|
7957
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
|
+
};
|
|
7958
8238
|
logger.debug("Some attributions are not authenticated by the proofs: {attributions}.", { attributions: [...attributions] });
|
|
7959
8239
|
return null;
|
|
7960
8240
|
}
|
|
@@ -8975,6 +9255,14 @@ async function verifyRequest(request, options = {}) {
|
|
|
8975
9255
|
* @since 2.1.0
|
|
8976
9256
|
*/
|
|
8977
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);
|
|
8978
9266
|
if (options.maxSignatures !== void 0) validateMaxSignatures(options.maxSignatures, "maxSignatures");
|
|
8979
9267
|
return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("http_signatures.verify", async (span) => {
|
|
8980
9268
|
if (span.isRecording()) {
|
|
@@ -9013,7 +9301,27 @@ async function verifyRequestDetailed(request, options = {}) {
|
|
|
9013
9301
|
}
|
|
9014
9302
|
});
|
|
9015
9303
|
}
|
|
9016
|
-
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 } = {}) {
|
|
9017
9325
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
9018
9326
|
"fedify",
|
|
9019
9327
|
"sig",
|
|
@@ -9194,6 +9502,7 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
|
|
|
9194
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");
|
|
9195
9503
|
const sig = (0, byte_encodings_base64.decodeBase64)(signature);
|
|
9196
9504
|
span?.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(sig));
|
|
9505
|
+
triedKey({ [verificationObservation]: observation }, key);
|
|
9197
9506
|
if (!await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, sig, new TextEncoder().encode(message))) {
|
|
9198
9507
|
if (cached) {
|
|
9199
9508
|
logger.debug("Failed to verify with the cached key {keyId}; signature {signature} is invalid. Retrying with the freshly fetched key...", {
|
|
@@ -9208,7 +9517,8 @@ async function verifyRequestDraft(request, span, metricsContext, { documentLoade
|
|
|
9208
9517
|
currentTime,
|
|
9209
9518
|
keyCache: bypassKeyCacheReads(keyCache),
|
|
9210
9519
|
meterProvider,
|
|
9211
|
-
tracerProvider
|
|
9520
|
+
tracerProvider,
|
|
9521
|
+
[verificationObservation]: observation
|
|
9212
9522
|
});
|
|
9213
9523
|
}
|
|
9214
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}", {
|
|
@@ -9294,7 +9604,7 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
|
|
|
9294
9604
|
* @returns Whether the signature is verified, and the algorithm of the
|
|
9295
9605
|
* signature if it is supported.
|
|
9296
9606
|
*/
|
|
9297
|
-
async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span) {
|
|
9607
|
+
async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, span, observationOptions = {}) {
|
|
9298
9608
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
9299
9609
|
"fedify",
|
|
9300
9610
|
"sig",
|
|
@@ -9350,6 +9660,7 @@ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, s
|
|
|
9350
9660
|
}
|
|
9351
9661
|
let verified;
|
|
9352
9662
|
try {
|
|
9663
|
+
triedKey(observationOptions, key);
|
|
9353
9664
|
verified = await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes);
|
|
9354
9665
|
} catch (error) {
|
|
9355
9666
|
logger.debug("Error during signature verification: {error}", {
|
|
@@ -9377,7 +9688,7 @@ async function verifyRfc9421SignatureWithKey(request, sigInput, sigBytes, key, s
|
|
|
9377
9688
|
mismatched: true
|
|
9378
9689
|
};
|
|
9379
9690
|
}
|
|
9380
|
-
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 } = {}) {
|
|
9381
9692
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
9382
9693
|
"fedify",
|
|
9383
9694
|
"sig",
|
|
@@ -9441,105 +9752,116 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
|
|
|
9441
9752
|
let body = null;
|
|
9442
9753
|
let digestValid = null;
|
|
9443
9754
|
for (const sigName of signatureNames) {
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
signatureName: sigName,
|
|
9454
|
-
signatureInput: signatureInputHeader
|
|
9455
|
-
});
|
|
9456
|
-
setFailure(invalidSignatureResult(null));
|
|
9457
|
-
continue;
|
|
9458
|
-
}
|
|
9459
|
-
if (!sigInput.created) {
|
|
9460
|
-
logger.debug("Failed to verify; missing created timestamp in signature {signatureName}.", {
|
|
9461
|
-
signatureName: sigName,
|
|
9462
|
-
signatureInput: signatureInputHeader
|
|
9463
|
-
});
|
|
9464
|
-
setFailure(invalidSignatureResult(keyId));
|
|
9465
|
-
continue;
|
|
9466
|
-
}
|
|
9467
|
-
const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
|
|
9468
|
-
const now = currentTime ?? Temporal.Now.instant();
|
|
9469
|
-
if (timeWindow !== false) {
|
|
9470
|
-
const tw = timeWindow ?? { hours: 1 };
|
|
9471
|
-
if (Temporal.Instant.compare(signatureCreated, now.add(tw)) > 0) {
|
|
9472
|
-
logger.debug("Failed to verify; signature created time is too far in the future.", {
|
|
9473
|
-
created: signatureCreated.toString(),
|
|
9474
|
-
now: now.toString()
|
|
9475
|
-
});
|
|
9476
|
-
setFailure(invalidSignatureResult(keyId));
|
|
9477
|
-
continue;
|
|
9478
|
-
} else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
|
|
9479
|
-
logger.debug("Failed to verify; signature created time is too far in the past.", {
|
|
9480
|
-
created: signatureCreated.toString(),
|
|
9481
|
-
now: now.toString()
|
|
9482
|
-
});
|
|
9483
|
-
setFailure(invalidSignatureResult(keyId));
|
|
9484
|
-
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;
|
|
9485
9764
|
}
|
|
9486
|
-
|
|
9487
|
-
|
|
9488
|
-
const
|
|
9489
|
-
if (!
|
|
9490
|
-
logger.debug("Failed to verify;
|
|
9491
|
-
|
|
9492
|
-
|
|
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;
|
|
9493
9775
|
}
|
|
9494
|
-
|
|
9495
|
-
|
|
9496
|
-
|
|
9497
|
-
|
|
9776
|
+
if (!sigInput.created) {
|
|
9777
|
+
logger.debug("Failed to verify; missing created timestamp in signature {signatureName}.", {
|
|
9778
|
+
signatureName: sigName,
|
|
9779
|
+
signatureInput: signatureInputHeader
|
|
9780
|
+
});
|
|
9498
9781
|
setFailure(invalidSignatureResult(keyId));
|
|
9499
|
-
|
|
9782
|
+
return null;
|
|
9500
9783
|
}
|
|
9501
|
-
|
|
9502
|
-
|
|
9503
|
-
|
|
9504
|
-
|
|
9505
|
-
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
|
|
9509
|
-
|
|
9510
|
-
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
9516
|
-
|
|
9517
|
-
|
|
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
|
+
}
|
|
9518
9803
|
}
|
|
9519
|
-
if (
|
|
9520
|
-
|
|
9521
|
-
|
|
9522
|
-
|
|
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
|
+
}
|
|
9523
9818
|
}
|
|
9524
|
-
|
|
9525
|
-
|
|
9526
|
-
|
|
9527
|
-
|
|
9528
|
-
|
|
9529
|
-
key,
|
|
9530
|
-
signatureLabel: sigName
|
|
9531
|
-
};
|
|
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;
|
|
9532
9824
|
}
|
|
9533
|
-
|
|
9534
|
-
|
|
9535
|
-
|
|
9536
|
-
lookup = await lookUpKey(keyId, bypassKeyCacheReads(keyCache));
|
|
9825
|
+
let lookup = keyLookups.get(keyId.href);
|
|
9826
|
+
if (lookup == null) {
|
|
9827
|
+
lookup = await lookUpKey(keyId, keyCache);
|
|
9537
9828
|
keyLookups.set(keyId.href, lookup);
|
|
9538
|
-
continue;
|
|
9539
9829
|
}
|
|
9540
|
-
|
|
9541
|
-
|
|
9542
|
-
|
|
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;
|
|
9543
9865
|
}
|
|
9544
9866
|
metricsContext.algorithm = failureAlgorithm;
|
|
9545
9867
|
return failure;
|
|
@@ -10060,6 +10382,12 @@ Object.defineProperty(exports, "normalizeOutgoingActivityJsonLd", {
|
|
|
10060
10382
|
return normalizeOutgoingActivityJsonLd;
|
|
10061
10383
|
}
|
|
10062
10384
|
});
|
|
10385
|
+
Object.defineProperty(exports, "observeAttempt", {
|
|
10386
|
+
enumerable: true,
|
|
10387
|
+
get: function() {
|
|
10388
|
+
return observeAttempt;
|
|
10389
|
+
}
|
|
10390
|
+
});
|
|
10063
10391
|
Object.defineProperty(exports, "parseAcceptSignature", {
|
|
10064
10392
|
enumerable: true,
|
|
10065
10393
|
get: function() {
|
|
@@ -10186,6 +10514,12 @@ Object.defineProperty(exports, "signRequest", {
|
|
|
10186
10514
|
return signRequest;
|
|
10187
10515
|
}
|
|
10188
10516
|
});
|
|
10517
|
+
Object.defineProperty(exports, "snapshotKey", {
|
|
10518
|
+
enumerable: true,
|
|
10519
|
+
get: function() {
|
|
10520
|
+
return snapshotKey;
|
|
10521
|
+
}
|
|
10522
|
+
});
|
|
10189
10523
|
Object.defineProperty(exports, "validateAcceptSignature", {
|
|
10190
10524
|
enumerable: true,
|
|
10191
10525
|
get: function() {
|
|
@@ -10204,6 +10538,12 @@ Object.defineProperty(exports, "validateMaxSignatures", {
|
|
|
10204
10538
|
return validateMaxSignatures;
|
|
10205
10539
|
}
|
|
10206
10540
|
});
|
|
10541
|
+
Object.defineProperty(exports, "verificationObservation", {
|
|
10542
|
+
enumerable: true,
|
|
10543
|
+
get: function() {
|
|
10544
|
+
return verificationObservation;
|
|
10545
|
+
}
|
|
10546
|
+
});
|
|
10207
10547
|
Object.defineProperty(exports, "verifyCompactJsonLd", {
|
|
10208
10548
|
enumerable: true,
|
|
10209
10549
|
get: function() {
|