Personas prove what goes to relays; the server says what it reads
FEP-521a and FEP-8b32. Every persona has an Ed25519 key of its own (Avatar.SigningKey; migration 014 gives the earlier ones theirs), named in its actor's assertionMethod as a Multikey, the terms defined in the actor's own context. A persona's activity going to a relay carries an eddsa-jcs-2022 proof (JSON canonicalised by RFC 8785, Jcs), so what Activity-Relay forwards reaches Mastodon, which verifies it with its own code. Nothing else carries one: Mitra takes a proof over the HTTP signature and refuses one by a key it has not read, without reading the actor again. Received: an actor's own Multikeys are kept, and a forwarded activity whose proof one of them verifies is taken as it came instead of being read again from its origin. Discovery: WebFinger for the server's origin links its instance actor (FEP-d556), NodeInfo links it as the application actor (FEP-2677), and actors name RFC 9421 under implements (FEP-844e). Checked live: relay 16 (Activity-Relay's forward of alice's post reaches Mastodon), Mitra, GoToSocial and Mastodon unchanged (165 in all). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
1 parent
c47b6e5533
commit
9ab87b2779
22 files changed
+702
-30
No files matched your search
@@ -47,9 +47,26 @@ namespace PrivaPub.Federation.Outbox
|
||||
{
|
||||
var body = activity.ToJsonString();
|
||||
var activityId = activity["id"] is JsonValue id && id.TryGetValue<string>(out var text) ? text : default;
|
||||
var jobs = inboxes
|
||||
var targets = inboxes
|
||||
.Where(i => !string.IsNullOrEmpty(i) && !i.StartsWith(signer.BaseAddress + "/", StringComparison.OrdinalIgnoreCase))
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.ToList();
|
||||
// what goes to a relay carries the signer's proof (FEP-8b32): the relay passes it on as it came, signed with its own
|
||||
// key, and Mastodon takes it on the proof's strength. Nothing else does: a server that knew the persona before it
|
||||
// had its key (Mitra) refuses a proof by a key it does not hold, and does not read the actor again
|
||||
var relayed = new HashSet<string>(StringComparer.Ordinal);
|
||||
if (!string.IsNullOrEmpty(signer.SigningKey) && activity["actor"] is JsonValue actor && actor.TryGetValue<string>(out var actorUri) && actorUri == signer.Uri)
|
||||
relayed = (await DB.Default.Find<RelaySubscription, string>().Match(s => targets.Contains(s.InboxURL)).Project(s => s.InboxURL).ExecuteAsync(token))
|
||||
.ToHashSet(StringComparer.Ordinal);
|
||||
var provenBody = default(string);
|
||||
if (relayed.Count > 0)
|
||||
{
|
||||
var proven = activity.DeepClone().AsObject();
|
||||
proven.Remove("proof");
|
||||
proven["proof"] = IntegrityProofs.Create(proven, signer.AssertionKeyId, signer.SigningKey, DateTime.UtcNow);
|
||||
provenBody = proven.ToJsonString();
|
||||
}
|
||||
var jobs = targets
|
||||
.Select(inbox => Uri.TryCreate(inbox, UriKind.Absolute, out var uri) ? (inbox, uri) : default)
|
||||
.Where(target => target.uri != default)
|
||||
.Select(target => new Job
|
||||
@@ -57,7 +74,7 @@ namespace PrivaPub.Federation.Outbox
|
||||
Kind = JobKind.Deliver,
|
||||
Host = target.uri.Host.ToLowerInvariant(),
|
||||
DedupeKey = activityId == default ? default : again == default ? $"{activityId}|{target.inbox}" : $"{activityId}|{target.inbox}|{again}",
|
||||
Payload = JsonSerializer.Serialize(new DeliveryPayload(signer.Id, signer.Kind, target.inbox, body))
|
||||
Payload = JsonSerializer.Serialize(new DeliveryPayload(signer.Id, signer.Kind, target.inbox, relayed.Contains(target.inbox) ? provenBody : body))
|
||||
})
|
||||
.ToList();
|
||||
if (jobs.Count > 0)
|
||||
|
||||
Reference in new issue
Block a user