@fedify/fedify 2.4.0-dev.1962 → 2.4.0-dev.1988
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-CWYQXC2W.mjs → builder-Cts78G9N.mjs} +2 -2
- package/dist/{client-CtxvmazV.mjs → client-DpnpgjyX.mjs} +78 -4
- 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/{context-COLwHogJ.d.cts → context-BeDnDuF3.d.cts} +4 -3
- package/dist/{context-DiiCt4S7.d.ts → context-uyRUWoZQ.d.ts} +4 -3
- package/dist/{deno-CfLMX4HF.mjs → deno-BgMEefEr.mjs} +1 -1
- package/dist/{docloader-CDmmYxYb.mjs → docloader-DEYs99FZ.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/handler.test.mjs +122 -4
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/keycache.test.mjs +52 -9
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +111 -15
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +2 -2
- package/dist/federation/mod.d.ts +2 -2
- package/dist/federation/mod.js +1 -1
- package/dist/federation/send.test.mjs +139 -4
- 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-CIeDMHxZ.cjs → federation-DzPUG_AP.cjs} +93 -17
- package/dist/{http-6TI0tI03.d.cts → http-65PYi9jY.d.ts} +34 -2
- package/dist/{http-CdrSn2ip.d.ts → http-CP-7OdSq.d.cts} +34 -2
- package/dist/{http-DWZZicb2.js → http-CgwqtxAy.js} +169 -7
- package/dist/{http-BR190IDB.mjs → http-CtidZS_t.mjs} +3 -3
- package/dist/{http-C3kkrvAE.cjs → http-Z3anvCd-.cjs} +180 -6
- package/dist/{key-BcA-AzPp.mjs → key-Cwbg5ggV.mjs} +169 -7
- package/dist/{keycache-ZLqIfrkL.mjs → keycache-BUDHB5rY.mjs} +12 -4
- package/dist/{kv-cache-DCItgZUM.cjs → kv-cache-BM2-w-Wq.cjs} +2 -2
- package/dist/{kv-cache-5d2HgB_-.js → kv-cache-CykuL0ya.js} +2 -2
- package/dist/{kv-cache-m962_cok.mjs → kv-cache-DHiR8yTf.mjs} +1 -1
- package/dist/{ld-QpPkmUjy.mjs → ld-CzhdWyTt.mjs} +3 -3
- package/dist/{metrics-Dkyzq7ml.mjs → metrics-CrqsHP0l.mjs} +1 -1
- package/dist/{middleware-BdKtWJK1.mjs → middleware-CZpYxSKD.mjs} +1 -1
- package/dist/{middleware-BNktRUnj.js → middleware-CeCj5caM.js} +101 -25
- package/dist/{middleware-BaZ32nCi.mjs → middleware-DCpiAsZi.mjs} +34 -23
- package/dist/{mod-DypxPS9H.d.ts → mod-C0BYc0OU.d.ts} +1 -1
- package/dist/{mod-CnGLNoVw.d.cts → mod-CbKDOnCt.d.cts} +1 -1
- package/dist/{mod-CLHkCwLm.d.cts → mod-CyiSjRkk.d.cts} +1 -1
- package/dist/{mod-CSH1Ee-I.d.ts → mod-D882_Xsg.d.ts} +1 -1
- package/dist/{mod-DzRXACM5.d.ts → mod-UDwMjuHM.d.ts} +1 -1
- package/dist/{mod-BZldCEU0.d.cts → mod-x9W0d0Pk.d.cts} +1 -1
- package/dist/mod.cjs +5 -5
- package/dist/mod.d.cts +6 -6
- package/dist/mod.d.ts +6 -6
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/client.test.mjs +28 -1
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/{owner-r_waTiQj.d.cts → owner-BF0L9cMt.d.cts} +11 -0
- package/dist/{owner-DGSfntI-.d.ts → owner-BSzr5-I8.d.ts} +11 -0
- package/dist/owner-BqVXJLW8.mjs +175 -0
- package/dist/{proof-DrWB_vel.js → proof-CUghqhAf.js} +122 -65
- package/dist/{proof-CYdLcTFg.cjs → proof-DHu5zI0Z.cjs} +122 -65
- package/dist/{proof-Mov6-gSw.mjs → proof-DL8Iid3a.mjs} +14 -11
- package/dist/{send-DB1mkFKO.mjs → send-6jS4xFsX.mjs} +68 -11
- package/dist/sig/http.test.mjs +2 -2
- package/dist/sig/key.test.mjs +169 -2
- package/dist/sig/ld.test.mjs +55 -2
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +3 -3
- package/dist/sig/mod.d.ts +3 -3
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +103 -4
- package/dist/sig/proof.test.mjs +143 -2
- package/dist/{temporal-Yi0JAV9z.mjs → temporal-DcyzeytP.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +2 -1
- package/dist/{types-RXm5YxpC.cjs → types-Be2zbyeG.cjs} +94 -2
- package/dist/{types-Dtyf8lVj.js → types-C_cNlNVK.js} +78 -4
- package/dist/utils/docloader.test.mjs +27 -3
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +7 -7
- package/dist/owner-B795xXwU.mjs +0 -120
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
2
2
|
const require_rolldown_runtime = require("./rolldown-runtime-Dmm3t92W.cjs");
|
|
3
|
-
const require_http = require("./http-
|
|
3
|
+
const require_http = require("./http-Z3anvCd-.cjs");
|
|
4
4
|
let _logtape_logtape = require("@logtape/logtape");
|
|
5
5
|
let _fedify_vocab = require("@fedify/vocab");
|
|
6
6
|
let _opentelemetry_api = require("@opentelemetry/api");
|
|
@@ -12,7 +12,7 @@ _fedify_vocab_runtime_jsonld = require_rolldown_runtime.__toESM(_fedify_vocab_ru
|
|
|
12
12
|
let json_canon = require("json-canon");
|
|
13
13
|
json_canon = require_rolldown_runtime.__toESM(json_canon, 1);
|
|
14
14
|
//#region src/sig/ld.ts
|
|
15
|
-
const logger$
|
|
15
|
+
const logger$4 = (0, _logtape_logtape.getLogger)([
|
|
16
16
|
"fedify",
|
|
17
17
|
"sig",
|
|
18
18
|
"ld"
|
|
@@ -455,7 +455,7 @@ async function verifySignature(jsonLd, options = {}) {
|
|
|
455
455
|
try {
|
|
456
456
|
signature = (0, byte_encodings_base64.decodeBase64)(sig.signatureValue);
|
|
457
457
|
} catch (error) {
|
|
458
|
-
logger$
|
|
458
|
+
logger$4.debug("Failed to verify; invalid base64 signatureValue: {signatureValue}", {
|
|
459
459
|
...sig,
|
|
460
460
|
error
|
|
461
461
|
});
|
|
@@ -474,7 +474,7 @@ async function verifySignature(jsonLd, options = {}) {
|
|
|
474
474
|
try {
|
|
475
475
|
sigOptsHash = await hashJsonLd(sigOpts, options.contextLoader);
|
|
476
476
|
} catch (error) {
|
|
477
|
-
logger$
|
|
477
|
+
logger$4.warn("Failed to verify; failed to hash the signature options: {signatureOptions}\n{error}", {
|
|
478
478
|
signatureOptions: sigOpts,
|
|
479
479
|
error
|
|
480
480
|
});
|
|
@@ -486,7 +486,7 @@ async function verifySignature(jsonLd, options = {}) {
|
|
|
486
486
|
try {
|
|
487
487
|
docHash = await hashJsonLd(document, options.contextLoader);
|
|
488
488
|
} catch (error) {
|
|
489
|
-
logger$
|
|
489
|
+
logger$4.warn("Failed to verify; failed to hash the document: {document}\n{error}", {
|
|
490
490
|
document,
|
|
491
491
|
error
|
|
492
492
|
});
|
|
@@ -497,7 +497,7 @@ async function verifySignature(jsonLd, options = {}) {
|
|
|
497
497
|
const messageBytes = encoder.encode(message);
|
|
498
498
|
if (await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, signature.slice(), messageBytes)) return key;
|
|
499
499
|
if (cached) {
|
|
500
|
-
logger$
|
|
500
|
+
logger$4.debug("Failed to verify with the cached key {keyId}; signature {signatureValue} is invalid. Retrying with the freshly fetched key...", {
|
|
501
501
|
keyId: sig.creator,
|
|
502
502
|
...sig
|
|
503
503
|
});
|
|
@@ -511,7 +511,7 @@ async function verifySignature(jsonLd, options = {}) {
|
|
|
511
511
|
if (key == null) return null;
|
|
512
512
|
return await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key.publicKey, signature.slice(), messageBytes) ? key : null;
|
|
513
513
|
}
|
|
514
|
-
logger$
|
|
514
|
+
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}", {
|
|
515
515
|
keyId: sig.creator,
|
|
516
516
|
...sig,
|
|
517
517
|
message
|
|
@@ -590,12 +590,12 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
|
590
590
|
const key = await verifySignature(compacted, verificationOptions);
|
|
591
591
|
if (key == null) return false;
|
|
592
592
|
if (key.ownerId == null) {
|
|
593
|
-
logger$
|
|
593
|
+
logger$4.debug("Key {keyId} has no owner.", { keyId: key.id?.href });
|
|
594
594
|
return false;
|
|
595
595
|
}
|
|
596
596
|
attributions.delete(key.ownerId.href);
|
|
597
597
|
if (attributions.size > 0) {
|
|
598
|
-
logger$
|
|
598
|
+
logger$4.debug("Some attributions are not authenticated by the Linked Data Signatures: {attributions}.", { attributions: [...attributions] });
|
|
599
599
|
return false;
|
|
600
600
|
}
|
|
601
601
|
verified = true;
|
|
@@ -624,8 +624,20 @@ async function hashJsonLd(jsonLd, contextLoader) {
|
|
|
624
624
|
}
|
|
625
625
|
//#endregion
|
|
626
626
|
//#region src/sig/owner.ts
|
|
627
|
+
const logger$3 = (0, _logtape_logtape.getLogger)([
|
|
628
|
+
"fedify",
|
|
629
|
+
"sig",
|
|
630
|
+
"owner"
|
|
631
|
+
]);
|
|
627
632
|
/**
|
|
628
633
|
* Checks if the actor of the given activity owns the specified key.
|
|
634
|
+
*
|
|
635
|
+
* Ownership is never taken from what the activity or the key say about
|
|
636
|
+
* themselves, as both are written by whoever sent the activity. It is
|
|
637
|
+
* established from a document that the owner itself serves: either the key's
|
|
638
|
+
* claimed owner links back to the key, or the actor the activity claims lists
|
|
639
|
+
* the key as its own.
|
|
640
|
+
*
|
|
629
641
|
* @param activity The activity to check.
|
|
630
642
|
* @param key The public key to check.
|
|
631
643
|
* @param options Options for checking the key ownership.
|
|
@@ -640,23 +652,30 @@ async function doesActorOwnKey(activity, key, options) {
|
|
|
640
652
|
}
|
|
641
653
|
}, async (span) => {
|
|
642
654
|
try {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
span.setAttribute("activitypub.key_ownership.verified", owns);
|
|
646
|
-
span.setAttribute("activitypub.key_ownership.method", "owner_id");
|
|
647
|
-
return owns;
|
|
648
|
-
}
|
|
649
|
-
const actor = await activity.getActor(options);
|
|
650
|
-
if (actor == null || !(0, _fedify_vocab.isActor)(actor)) {
|
|
655
|
+
const actorId = activity.actorId;
|
|
656
|
+
if (actorId == null) {
|
|
651
657
|
span.setAttribute("activitypub.key_ownership.verified", false);
|
|
652
|
-
span.setAttribute("activitypub.key_ownership.method", "
|
|
658
|
+
span.setAttribute("activitypub.key_ownership.method", "none");
|
|
653
659
|
return false;
|
|
654
660
|
}
|
|
655
|
-
|
|
661
|
+
if (key.ownerId != null) {
|
|
662
|
+
const owner = await require_http.verifyKeyOwnership(key, options);
|
|
663
|
+
if (owner?.id != null && owner.id.href === actorId.href) {
|
|
664
|
+
span.setAttribute("activitypub.key_ownership.verified", true);
|
|
665
|
+
span.setAttribute("activitypub.key_ownership.method", "key_owner");
|
|
666
|
+
return true;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
const actor = await require_http.fetchActorDocument(actorId, options);
|
|
670
|
+
if (actor != null && await doesActorListKey(actor, key, options)) {
|
|
656
671
|
span.setAttribute("activitypub.key_ownership.verified", true);
|
|
657
672
|
span.setAttribute("activitypub.key_ownership.method", "actor_fetch");
|
|
658
673
|
return true;
|
|
659
674
|
}
|
|
675
|
+
logger$3.debug("The actor {actorId} does not own key {keyId}: neither the owner the key claims nor the actor's own document links the two to each other.", {
|
|
676
|
+
actorId: actorId.href,
|
|
677
|
+
keyId: key.id?.href
|
|
678
|
+
});
|
|
660
679
|
span.setAttribute("activitypub.key_ownership.verified", false);
|
|
661
680
|
span.setAttribute("activitypub.key_ownership.method", "actor_fetch");
|
|
662
681
|
return false;
|
|
@@ -673,9 +692,46 @@ async function doesActorOwnKey(activity, key, options) {
|
|
|
673
692
|
});
|
|
674
693
|
}
|
|
675
694
|
/**
|
|
695
|
+
* Determines whether the given actor's own document lists the given key among
|
|
696
|
+
* its public keys.
|
|
697
|
+
*
|
|
698
|
+
* Keys are matched by id, which is what an actor document links to. A key
|
|
699
|
+
* document may leave its id implicit, though, and then there is no id for the
|
|
700
|
+
* actor to link to; the key material itself is the only thing the two can
|
|
701
|
+
* have in common, so that is what gets compared.
|
|
702
|
+
*/
|
|
703
|
+
async function doesActorListKey(actor, key, options) {
|
|
704
|
+
if (key.id != null) {
|
|
705
|
+
for (const publicKeyId of actor.publicKeyIds) if (publicKeyId.href === key.id.href) return true;
|
|
706
|
+
return false;
|
|
707
|
+
}
|
|
708
|
+
if (key.publicKey == null) return false;
|
|
709
|
+
const publicKeys = actor.getPublicKeys({
|
|
710
|
+
...options,
|
|
711
|
+
suppressError: true
|
|
712
|
+
});
|
|
713
|
+
for await (const publicKey of publicKeys) {
|
|
714
|
+
if (publicKey.publicKey == null) continue;
|
|
715
|
+
if (await isSamePublicKey(publicKey.publicKey, key.publicKey)) return true;
|
|
716
|
+
}
|
|
717
|
+
return false;
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Compares two public keys by their key material, ignoring the metadata that
|
|
721
|
+
* surrounds it.
|
|
722
|
+
*/
|
|
723
|
+
async function isSamePublicKey(a, b) {
|
|
724
|
+
const [x, y] = await Promise.all([require_http.exportJwk(a), require_http.exportJwk(b)]);
|
|
725
|
+
return x.kty === y.kty && x.crv === y.crv && x.n === y.n && x.e === y.e && x.x === y.x && x.y === y.y;
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
676
728
|
* Gets the actor that owns the specified key. Returns `null` if the key has no
|
|
677
729
|
* known owner.
|
|
678
730
|
*
|
|
731
|
+
* The owner is only returned when the key and the owner link to each other:
|
|
732
|
+
* a document that merely claims to own a key is not enough, as the claim and
|
|
733
|
+
* the key can come from the same host.
|
|
734
|
+
*
|
|
679
735
|
* @param keyId The ID of the key to check, or the key itself.
|
|
680
736
|
* @param options Options for getting the key owner.
|
|
681
737
|
* @returns The actor that owns the key, or `null` if the key has no known
|
|
@@ -685,52 +741,50 @@ async function doesActorOwnKey(activity, key, options) {
|
|
|
685
741
|
async function getKeyOwner(keyId, options) {
|
|
686
742
|
const tracerProvider = options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider();
|
|
687
743
|
const documentLoader = options.documentLoader ?? (0, _fedify_vocab_runtime.getDocumentLoader)();
|
|
688
|
-
const
|
|
744
|
+
const fetchOptions = {
|
|
745
|
+
documentLoader,
|
|
746
|
+
contextLoader: options.contextLoader ?? (0, _fedify_vocab_runtime.getDocumentLoader)(),
|
|
747
|
+
tracerProvider
|
|
748
|
+
};
|
|
749
|
+
if (keyId instanceof _fedify_vocab.CryptographicKey) return await require_http.verifyKeyOwnership(keyId, fetchOptions);
|
|
750
|
+
let keyDoc;
|
|
751
|
+
let documentUrl = keyId;
|
|
752
|
+
try {
|
|
753
|
+
const remoteDocument = await documentLoader(keyId.href);
|
|
754
|
+
keyDoc = remoteDocument.document;
|
|
755
|
+
documentUrl = new URL(remoteDocument.documentUrl ?? "", keyId);
|
|
756
|
+
} catch (_) {
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
689
759
|
let object;
|
|
690
|
-
|
|
691
|
-
object =
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
const { document } = await documentLoader(keyId.href);
|
|
698
|
-
keyDoc = document;
|
|
699
|
-
} catch (_) {
|
|
700
|
-
return null;
|
|
701
|
-
}
|
|
760
|
+
try {
|
|
761
|
+
object = await _fedify_vocab.Object.fromJsonLd(keyDoc, {
|
|
762
|
+
...fetchOptions,
|
|
763
|
+
baseUrl: documentUrl
|
|
764
|
+
});
|
|
765
|
+
} catch (e) {
|
|
766
|
+
if (!(e instanceof TypeError)) throw e;
|
|
702
767
|
try {
|
|
703
|
-
object = await _fedify_vocab.
|
|
704
|
-
documentLoader,
|
|
705
|
-
contextLoader,
|
|
706
|
-
tracerProvider
|
|
707
|
-
});
|
|
768
|
+
object = await _fedify_vocab.CryptographicKey.fromJsonLd(keyDoc, fetchOptions);
|
|
708
769
|
} catch (e) {
|
|
709
|
-
if (
|
|
710
|
-
|
|
711
|
-
object = await _fedify_vocab.CryptographicKey.fromJsonLd(keyDoc, {
|
|
712
|
-
documentLoader,
|
|
713
|
-
contextLoader,
|
|
714
|
-
tracerProvider
|
|
715
|
-
});
|
|
716
|
-
} catch (e) {
|
|
717
|
-
if (e instanceof TypeError) return null;
|
|
718
|
-
throw e;
|
|
719
|
-
}
|
|
770
|
+
if (e instanceof TypeError) return null;
|
|
771
|
+
throw e;
|
|
720
772
|
}
|
|
721
773
|
}
|
|
722
|
-
let owner = null;
|
|
723
774
|
if (object instanceof _fedify_vocab.CryptographicKey) {
|
|
724
|
-
if (object.
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
775
|
+
if (object.id != null && object.id.href !== keyId.href) return null;
|
|
776
|
+
return await require_http.verifyKeyOwnership(object.id == null ? object.clone({ id: keyId }) : object, fetchOptions);
|
|
777
|
+
}
|
|
778
|
+
if (!(0, _fedify_vocab.isActor)(object)) return null;
|
|
779
|
+
if (object.id == null || object.id.origin !== documentUrl.origin) {
|
|
780
|
+
logger$3.debug("The document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin; refusing to treat it as the owner of key {keyId}.", {
|
|
781
|
+
documentUrl: documentUrl.href,
|
|
782
|
+
actorId: object.id?.href,
|
|
783
|
+
keyId: keyId.href
|
|
729
784
|
});
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
if (
|
|
733
|
-
for (const kid of owner.publicKeyIds) if (kid.href === keyId.href) return owner;
|
|
785
|
+
return null;
|
|
786
|
+
}
|
|
787
|
+
for (const kid of object.publicKeyIds) if (kid.href === keyId.href) return object;
|
|
734
788
|
return null;
|
|
735
789
|
}
|
|
736
790
|
//#endregion
|
|
@@ -1265,7 +1319,7 @@ async function signObject(object, privateKey, keyId, options = {}) {
|
|
|
1265
1319
|
async function verifyProof(jsonLd, proof, options = {}) {
|
|
1266
1320
|
return await verifyProofWithMessageDigestCache(jsonLd, proof, options);
|
|
1267
1321
|
}
|
|
1268
|
-
async function verifyProofWithMessageDigestCache(jsonLd, proof, options, messageDigestCache = {}, rawProofCandidate) {
|
|
1322
|
+
async function verifyProofWithMessageDigestCache(jsonLd, proof, options, messageDigestCache = {}, rawProofCandidate, keyIdBoundByCaller = false) {
|
|
1269
1323
|
return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(require_http.name, require_http.version).startActiveSpan("object_integrity_proofs.verify", async (span) => {
|
|
1270
1324
|
const start = performance.now();
|
|
1271
1325
|
let verified = false;
|
|
@@ -1277,7 +1331,7 @@ async function verifyProofWithMessageDigestCache(jsonLd, proof, options, message
|
|
|
1277
1331
|
if (proof.proofValue != null) span.setAttribute("object_integrity_proofs.signature", (0, byte_encodings_hex.encodeHex)(proof.proofValue));
|
|
1278
1332
|
}
|
|
1279
1333
|
try {
|
|
1280
|
-
const key = await verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate);
|
|
1334
|
+
const key = await verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
|
|
1281
1335
|
if (key == null) span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
|
|
1282
1336
|
else verified = true;
|
|
1283
1337
|
return key;
|
|
@@ -1594,12 +1648,15 @@ async function hasValidProofOptions(proofConfig, options, documentLoader) {
|
|
|
1594
1648
|
if (previousProof == null || previousProof.present && typeof previousProof.value !== "string" && (!Array.isArray(previousProof.value) || previousProof.value.some((item) => typeof item !== "string"))) return false;
|
|
1595
1649
|
return true;
|
|
1596
1650
|
}
|
|
1597
|
-
async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate) {
|
|
1651
|
+
async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate, keyIdBoundByCaller = false) {
|
|
1598
1652
|
if (!isJsonLdNode(jsonLd) || proof.cryptosuite !== "eddsa-jcs-2022" || proof.verificationMethodId == null || proof.proofPurpose !== "assertionMethod" || proof.proofValue == null || proof.created == null) return null;
|
|
1599
1653
|
const proofContextLoader = messageDigestCache.proofContextLoader ?? preloadedOnlyDocumentLoader;
|
|
1600
1654
|
const proofConfiguration = await createProofConfiguration(jsonLd, proof, options, proofContextLoader, rawProofCandidate);
|
|
1601
1655
|
if (proofConfiguration == null || !await hasValidProofOptions(proofConfiguration.value, options, proofContextLoader)) return null;
|
|
1602
|
-
const publicKeyPromise = require_http.measureSignatureKeyFetch(options.meterProvider, "object_integrity", () => require_http.fetchKey(proof.verificationMethodId, _fedify_vocab.Multikey,
|
|
1656
|
+
const publicKeyPromise = require_http.measureSignatureKeyFetch(options.meterProvider, "object_integrity", () => require_http.fetchKey(proof.verificationMethodId, _fedify_vocab.Multikey, {
|
|
1657
|
+
...options,
|
|
1658
|
+
keyIdBoundByCaller
|
|
1659
|
+
}));
|
|
1603
1660
|
const proofBytes = new TextEncoder().encode((0, json_canon.default)(proofConfiguration.value));
|
|
1604
1661
|
const proofDigest = await crypto.subtle.digest("SHA-256", proofBytes);
|
|
1605
1662
|
let fetchedKey;
|
|
@@ -1633,7 +1690,7 @@ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, r
|
|
|
1633
1690
|
get: () => Promise.resolve(void 0),
|
|
1634
1691
|
set: async (keyId, key) => await options.keyCache?.set(keyId, key)
|
|
1635
1692
|
}
|
|
1636
|
-
}, messageDigestCache, rawProofCandidate);
|
|
1693
|
+
}, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
|
|
1637
1694
|
}
|
|
1638
1695
|
logger.debug("The fetched key (verificationMethod) for the proof is not a valid Ed25519 key:\n{keyId}", {
|
|
1639
1696
|
proof,
|
|
@@ -1670,7 +1727,7 @@ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, r
|
|
|
1670
1727
|
get: () => Promise.resolve(void 0),
|
|
1671
1728
|
set: async (keyId, key) => await options.keyCache?.set(keyId, key)
|
|
1672
1729
|
}
|
|
1673
|
-
}, messageDigestCache, rawProofCandidate);
|
|
1730
|
+
}, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
|
|
1674
1731
|
}
|
|
1675
1732
|
logger.debug("Failed to verify the proof with the fetched key {keyId}:\n{proof}", {
|
|
1676
1733
|
keyId: proof.verificationMethodId.href,
|
|
@@ -1885,7 +1942,7 @@ async function verifyPortableObjectProof(jsonLd, options = {}) {
|
|
|
1885
1942
|
proofIndex
|
|
1886
1943
|
}
|
|
1887
1944
|
};
|
|
1888
|
-
const key = await verifyProofWithMessageDigestCache(jsonLd, proofs[proofIndex], options, messageDigestCache, rawProofCandidate);
|
|
1945
|
+
const key = await verifyProofWithMessageDigestCache(jsonLd, proofs[proofIndex], options, messageDigestCache, rawProofCandidate, true);
|
|
1889
1946
|
if (key == null) return {
|
|
1890
1947
|
verified: false,
|
|
1891
1948
|
reason: {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Temporal } from "temporal-polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-
|
|
6
|
-
import {
|
|
4
|
+
import { n as version, t as name } from "./deno-BgMEefEr.mjs";
|
|
5
|
+
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-CrqsHP0l.mjs";
|
|
6
|
+
import { r as fetchKey, s as validateCryptoKey } from "./key-Cwbg5ggV.mjs";
|
|
7
7
|
import { n as preloadedOnlyDocumentLoader } from "./public-audience-Cc2YJ3ip.mjs";
|
|
8
8
|
import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-B5Ne7JoL.mjs";
|
|
9
|
-
import { c as getNormalizationContextLoader } from "./ld-
|
|
9
|
+
import { c as getNormalizationContextLoader } from "./ld-CzhdWyTt.mjs";
|
|
10
10
|
import { getLogger } from "@logtape/logtape";
|
|
11
11
|
import { Activity, DataIntegrityProof, Multikey, getTypeId } from "@fedify/vocab";
|
|
12
12
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
@@ -164,7 +164,7 @@ async function signObject(object, privateKey, keyId, options = {}) {
|
|
|
164
164
|
async function verifyProof(jsonLd, proof, options = {}) {
|
|
165
165
|
return await verifyProofWithMessageDigestCache(jsonLd, proof, options);
|
|
166
166
|
}
|
|
167
|
-
async function verifyProofWithMessageDigestCache(jsonLd, proof, options, messageDigestCache = {}, rawProofCandidate) {
|
|
167
|
+
async function verifyProofWithMessageDigestCache(jsonLd, proof, options, messageDigestCache = {}, rawProofCandidate, keyIdBoundByCaller = false) {
|
|
168
168
|
return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("object_integrity_proofs.verify", async (span) => {
|
|
169
169
|
const start = performance.now();
|
|
170
170
|
let verified = false;
|
|
@@ -176,7 +176,7 @@ async function verifyProofWithMessageDigestCache(jsonLd, proof, options, message
|
|
|
176
176
|
if (proof.proofValue != null) span.setAttribute("object_integrity_proofs.signature", encodeHex(proof.proofValue));
|
|
177
177
|
}
|
|
178
178
|
try {
|
|
179
|
-
const key = await verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate);
|
|
179
|
+
const key = await verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
|
|
180
180
|
if (key == null) span.setStatus({ code: SpanStatusCode.ERROR });
|
|
181
181
|
else verified = true;
|
|
182
182
|
return key;
|
|
@@ -493,12 +493,15 @@ async function hasValidProofOptions(proofConfig, options, documentLoader) {
|
|
|
493
493
|
if (previousProof == null || previousProof.present && typeof previousProof.value !== "string" && (!Array.isArray(previousProof.value) || previousProof.value.some((item) => typeof item !== "string"))) return false;
|
|
494
494
|
return true;
|
|
495
495
|
}
|
|
496
|
-
async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate) {
|
|
496
|
+
async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate, keyIdBoundByCaller = false) {
|
|
497
497
|
if (!isJsonLdNode(jsonLd) || proof.cryptosuite !== "eddsa-jcs-2022" || proof.verificationMethodId == null || proof.proofPurpose !== "assertionMethod" || proof.proofValue == null || proof.created == null) return null;
|
|
498
498
|
const proofContextLoader = messageDigestCache.proofContextLoader ?? preloadedOnlyDocumentLoader;
|
|
499
499
|
const proofConfiguration = await createProofConfiguration(jsonLd, proof, options, proofContextLoader, rawProofCandidate);
|
|
500
500
|
if (proofConfiguration == null || !await hasValidProofOptions(proofConfiguration.value, options, proofContextLoader)) return null;
|
|
501
|
-
const publicKeyPromise = measureSignatureKeyFetch(options.meterProvider, "object_integrity", () => fetchKey(proof.verificationMethodId, Multikey,
|
|
501
|
+
const publicKeyPromise = measureSignatureKeyFetch(options.meterProvider, "object_integrity", () => fetchKey(proof.verificationMethodId, Multikey, {
|
|
502
|
+
...options,
|
|
503
|
+
keyIdBoundByCaller
|
|
504
|
+
}));
|
|
502
505
|
const proofBytes = new TextEncoder().encode(serialize(proofConfiguration.value));
|
|
503
506
|
const proofDigest = await crypto.subtle.digest("SHA-256", proofBytes);
|
|
504
507
|
let fetchedKey;
|
|
@@ -532,7 +535,7 @@ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, r
|
|
|
532
535
|
get: () => Promise.resolve(void 0),
|
|
533
536
|
set: async (keyId, key) => await options.keyCache?.set(keyId, key)
|
|
534
537
|
}
|
|
535
|
-
}, messageDigestCache, rawProofCandidate);
|
|
538
|
+
}, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
|
|
536
539
|
}
|
|
537
540
|
logger.debug("The fetched key (verificationMethod) for the proof is not a valid Ed25519 key:\n{keyId}", {
|
|
538
541
|
proof,
|
|
@@ -569,7 +572,7 @@ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, r
|
|
|
569
572
|
get: () => Promise.resolve(void 0),
|
|
570
573
|
set: async (keyId, key) => await options.keyCache?.set(keyId, key)
|
|
571
574
|
}
|
|
572
|
-
}, messageDigestCache, rawProofCandidate);
|
|
575
|
+
}, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
|
|
573
576
|
}
|
|
574
577
|
logger.debug("Failed to verify the proof with the fetched key {keyId}:\n{proof}", {
|
|
575
578
|
keyId: proof.verificationMethodId.href,
|
|
@@ -784,7 +787,7 @@ async function verifyPortableObjectProof(jsonLd, options = {}) {
|
|
|
784
787
|
proofIndex
|
|
785
788
|
}
|
|
786
789
|
};
|
|
787
|
-
const key = await verifyProofWithMessageDigestCache(jsonLd, proofs[proofIndex], options, messageDigestCache, rawProofCandidate);
|
|
790
|
+
const key = await verifyProofWithMessageDigestCache(jsonLd, proofs[proofIndex], options, messageDigestCache, rawProofCandidate, true);
|
|
788
791
|
if (key == null) return {
|
|
789
792
|
verified: false,
|
|
790
793
|
reason: {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import "temporal-polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import { n as getDurationMs, r as getFederationMetrics } from "./metrics-
|
|
6
|
-
import { n as doubleKnock } from "./http-
|
|
4
|
+
import { n as version, t as name } from "./deno-BgMEefEr.mjs";
|
|
5
|
+
import { n as getDurationMs, r as getFederationMetrics } from "./metrics-CrqsHP0l.mjs";
|
|
6
|
+
import { n as doubleKnock } from "./http-CtidZS_t.mjs";
|
|
7
7
|
import { getLogger } from "@logtape/logtape";
|
|
8
8
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
9
|
-
import { FetchError } from "@fedify/vocab-runtime";
|
|
9
|
+
import { FetchError, UrlError, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
10
10
|
//#region src/federation/send.ts
|
|
11
11
|
/**
|
|
12
12
|
* Extracts the inbox URLs from recipients.
|
|
@@ -112,7 +112,7 @@ async function readLimitedResponseBody(response, maxBytes) {
|
|
|
112
112
|
if (truncated) result += "… (truncated)";
|
|
113
113
|
return result;
|
|
114
114
|
}
|
|
115
|
-
async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, headers, specDeterminer, meterProvider, tracerProvider }, span) {
|
|
115
|
+
async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, allowPrivateAddress, headers, specDeterminer, meterProvider, tracerProvider }, span) {
|
|
116
116
|
const logger = getLogger([
|
|
117
117
|
"fedify",
|
|
118
118
|
"federation",
|
|
@@ -121,12 +121,25 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
121
121
|
const federationMetrics = getFederationMetrics(meterProvider);
|
|
122
122
|
const started = performance.now();
|
|
123
123
|
let deliverySuccess = false;
|
|
124
|
+
async function validateUrl(url) {
|
|
125
|
+
if (!allowPrivateAddress) try {
|
|
126
|
+
await validatePublicUrl(url);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (error instanceof UrlError) logger.error("Disallowed private URL: {url}", {
|
|
129
|
+
url,
|
|
130
|
+
error
|
|
131
|
+
});
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
await validateUrl(inbox.href);
|
|
124
136
|
headers = new Headers(headers);
|
|
125
137
|
headers.set("Content-Type", "application/activity+json");
|
|
126
138
|
const request = new Request(inbox, {
|
|
127
139
|
method: "POST",
|
|
128
140
|
headers,
|
|
129
|
-
body: JSON.stringify(activity)
|
|
141
|
+
body: JSON.stringify(activity),
|
|
142
|
+
redirect: "manual"
|
|
130
143
|
});
|
|
131
144
|
let rsaKey = null;
|
|
132
145
|
for (const key of keys) if (key.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
|
|
@@ -142,19 +155,20 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
142
155
|
});
|
|
143
156
|
let response;
|
|
144
157
|
try {
|
|
145
|
-
response = rsaKey == null ? await
|
|
158
|
+
response = rsaKey == null ? await fetchWithValidatedRedirects(request, validateUrl) : await doubleKnock(request, rsaKey, {
|
|
146
159
|
tracerProvider,
|
|
147
|
-
specDeterminer
|
|
160
|
+
specDeterminer,
|
|
161
|
+
validateRedirect: validateUrl
|
|
148
162
|
});
|
|
149
163
|
} catch (error) {
|
|
150
|
-
const
|
|
164
|
+
const failure = error instanceof FetchError || error instanceof UrlError ? error : createFetchError(inbox.href, error);
|
|
151
165
|
logger.error("Failed to send activity {activityId} to {inbox}:\n{error}", {
|
|
152
166
|
activityId,
|
|
153
167
|
inbox: inbox.href,
|
|
154
|
-
error:
|
|
168
|
+
error: failure
|
|
155
169
|
});
|
|
156
170
|
federationMetrics.recordDelivery(inbox, getDurationMs(started), false, activityType);
|
|
157
|
-
throw
|
|
171
|
+
throw failure;
|
|
158
172
|
}
|
|
159
173
|
try {
|
|
160
174
|
if (!response.ok) {
|
|
@@ -191,6 +205,49 @@ function createFetchError(url, cause) {
|
|
|
191
205
|
error.cause = cause;
|
|
192
206
|
return error;
|
|
193
207
|
}
|
|
208
|
+
async function fetchWithValidatedRedirects(request, validateUrl) {
|
|
209
|
+
let body = await request.clone().text();
|
|
210
|
+
for (let redirects = 0;; redirects++) {
|
|
211
|
+
const response = await fetch(request, { redirect: "manual" });
|
|
212
|
+
const location = response.headers.get("Location");
|
|
213
|
+
if (![
|
|
214
|
+
301,
|
|
215
|
+
302,
|
|
216
|
+
303,
|
|
217
|
+
307,
|
|
218
|
+
308
|
|
219
|
+
].includes(response.status) || location == null) return response;
|
|
220
|
+
await response.body?.cancel();
|
|
221
|
+
if (redirects >= 20) throw new TypeError("Too many redirects");
|
|
222
|
+
const url = new URL(location, request.url);
|
|
223
|
+
await validateUrl(url.href);
|
|
224
|
+
const headers = new Headers(request.headers);
|
|
225
|
+
if (url.origin !== new URL(request.url).origin) {
|
|
226
|
+
headers.delete("Authorization");
|
|
227
|
+
headers.delete("Proxy-Authorization");
|
|
228
|
+
headers.delete("Cookie");
|
|
229
|
+
headers.delete("Host");
|
|
230
|
+
}
|
|
231
|
+
let method = request.method;
|
|
232
|
+
if ((response.status === 301 || response.status === 302) && method === "POST" || response.status === 303 && method !== "GET" && method !== "HEAD") {
|
|
233
|
+
method = "GET";
|
|
234
|
+
body = void 0;
|
|
235
|
+
for (const name of [
|
|
236
|
+
"Content-Encoding",
|
|
237
|
+
"Content-Language",
|
|
238
|
+
"Content-Length",
|
|
239
|
+
"Content-Location",
|
|
240
|
+
"Content-Type"
|
|
241
|
+
]) headers.delete(name);
|
|
242
|
+
}
|
|
243
|
+
request = new Request(url, {
|
|
244
|
+
method,
|
|
245
|
+
headers,
|
|
246
|
+
body,
|
|
247
|
+
redirect: "manual"
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
194
251
|
/**
|
|
195
252
|
* An error that is thrown when an activity fails to send to a remote inbox.
|
|
196
253
|
* It contains structured information about the failure, including the HTTP
|
package/dist/sig/http.test.mjs
CHANGED
|
@@ -10,8 +10,8 @@ import { t as assertThrows } from "../assert_throws-BJ5wuvR2.mjs";
|
|
|
10
10
|
import "../std__assert-CxFT0MNt.mjs";
|
|
11
11
|
import { t as assertRejects } from "../assert_rejects-wFbSPn1O.mjs";
|
|
12
12
|
import { t as assert } from "../assert-CebeUf0C.mjs";
|
|
13
|
-
import { t as exportJwk } from "../key-
|
|
14
|
-
import { a as parseRfc9421Signature, c as timingSafeEqual, i as formatRfc9421SignatureParameters, l as verifyRequest, n as doubleKnock, o as parseRfc9421SignatureInput, r as formatRfc9421Signature, s as signRequest, t as createRfc9421SignatureBase, u as verifyRequestDetailed } from "../http-
|
|
13
|
+
import { t as exportJwk } from "../key-Cwbg5ggV.mjs";
|
|
14
|
+
import { a as parseRfc9421Signature, c as timingSafeEqual, i as formatRfc9421SignatureParameters, l as verifyRequest, n as doubleKnock, o as parseRfc9421SignatureInput, r as formatRfc9421Signature, s as signRequest, t as createRfc9421SignatureBase, u as verifyRequestDetailed } from "../http-CtidZS_t.mjs";
|
|
15
15
|
import { i as rsaPrivateKey2, l as rsaPublicKey5, o as rsaPublicKey1, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
|
|
16
16
|
import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
|
|
17
17
|
import { FetchError, exportSpki } from "@fedify/vocab-runtime";
|