RFC 9421 HTTP signatures in 2026

Now that RFC 9421 has been published and is no longer a draft, I think it would be a good idea to write a FEP (or other document) with implementation recommendations, to ensure interoperability between AP servers. The RFC describes how to create and verify signatures, but it’s still up to us to define things like the required fields to be signed, which algorithms are likely to work, and how to discover servers that support it.

I believe HTTP signatures are still useful even with FEP-8b32 object signing, because they prove the authenticity of the origin server. That can be used to implement federation policies on private networks (not connected to the wider “fediverse”), or as a basis of trust before even parsing the AP object body. FEP-8b32 proofs validate the activity object itself and remain with the object as it traverses the network; HTTP signatures validate each link at the transport layer.

Also, I think it’s fine & good for the popular servers (mastodon, misskey, gotosocial, …) to wait for smaller servers to shake out interoperability first. It’s easier for the small servers to iterate and debug. Once we have something working, the more popular servers can implement our consensus requirements with a higher confidence it will “just work”.

Silverpill, in a separate thread, pointed me to a list of tootik’s HTTP signature requirements (here: tootik/FEDERATION.md at d6fecfefd80a445b27f589250bb19ebcd95acee2 · dimkr/tootik · GitHub) and I think they make a good starting point, so I’ll kick off discussion with a lightly modified version:

  • require ed25519, recommend rsa-v1_5_sha256 also
  • required signed fields: @method, @target-uri
    • if a query is present, require: @query
    • for POST, also require: content-type, content-digest
  • advertise support using FEP-844e on the server actor
  • signatures must use public keys from FEP-521a (“assertionMethod”)
  • signatures must have a “recent” (one hour?) “created=” time, since this is a transport signature
  • signatures may use the server actor key if a FEP-8b32 object proof is present

I’ve implemented a first draft of this in squidcity, and I’m excited to try it out with other small servers to see what works.

1 Like

@robey there's a task force for HTTP Signature in the SocialCG:

https://swicg.github.io/activitypub-http-signature/

It would be cool to do a revised report, or a new report, for supporting the published version of HTTP Signature and especially for smooth transition from draft-cavage-11.

Yeah, I saw that yesterday, but it appears to be about documenting the current “standard” (the old cavage draft)… which is definitely worth doing!

I’d like to start a conversation about how we migrate to the RFC, and what it looks like when we’re done. I think we have a pretty good start from other threads here but want to consolidate them here.

I’ve recently started working through this in my own project. Here are some quick thoughts:

I think it would be a good idea to write a FEP (or other document)

I’d suggest an update to the report Evan linked to. Having just one document seems less prone to confusion.

I believe HTTP signatures are still useful even with FEP-8b32 object signing

Agreed, because FEP-8b32 is only useful for POSTs. It can’t be used to sign GET requests.

advertise support using FEP-844e on the server actor

RFC9421 defines its own mechanism for this using the Accept-Signature header field. For Fediverse purposes this would appear in the response to a POST request, and apply to subsequent POSTs to the same inbox. I haven’t seen anyone talk about using this on the Fediverse; perhaps this is because it’s only applicable to HTTP signatures and doesn’t cover any other features that people might want to advertise.

required signed fields: @method, @target-uri
if a query is present, require: @query

Isn’t the @query part already covered by @target-uri?

for POST, also require: content-type, content-digest

+1 for requiring the Content-Type. I did this at first, and was a bit puzzled to find that there are projects out there that don’t sign this header.

I also sign Accept if it’s present (eg in a GET request). I don’t know if it should be required, but I’d lean towards “better safe than sorry”.

signatures must use public keys from FEP-521a (“assertionMethod”)

Right now I allow the publicKey to be used if the algorithm is rsa-v1_5_sha256 - maybe this will offer an easier migration path for projects that don’t want to implement ED25519 yet?

You can also do HEAD on the inbox and discover links whose relation is http://www.w3.org/ns/ldp#constrainedBy per Linked Data Notifications – one of those might give you more information about what particular constraints an inbox is assuming.

The Accept-Signature header can also be exposed by doing OPTIONS on the inbox. LDN uses a similar Accept-Post header to indicate the Content-Type that a POST must have. You don’t have to try a POST first then potentially have it fail.

Does the value of the Content-Type have any security implications? Are there perceived attacks where the Content-Type might be varied after signing the other headers? I am guessing the concern would be something like “The signature was on application/ld+json content but the Content-Type was later set to application/vc”, which doesn’t really make sense for fedi’s current usage. Signing the Content-Digest of a POST makes sense and is what Mastodon currently enforces.

These should be separate concerns. Using publicKey implies nothing about which specific purposes the key may be used for. It is equivalent to having a verificationMethod only in the newer VC work. You still have no information about whether a specific key is allowed to be used for authenticating or asserting or other purposes. Right now in fedi, everyone assumes keys used to sign HTTP messages are implicitly fulfilling some purpose – perhaps they take a Signature to mean that the sec:owner or sec:controller of the keyId is either authenticating the delivery, or asserting the statements made in the content, or both, or something else.

This is an ambiguity that is generally bad for security, and things like git forges have moved to differentiate “authentication keys” for SSH access from “signing keys” for GPG commits. Using the same key for both means that if someone compromises your SSH authentication key, they will also be able to sign commits using your GPG identity. Using a different key means that they will be able to access repos as you, but commits will fail GPG validation.

In the case of fedi, an HTTP message being signed by a key for the purpose of sec:authentication means that “This is who is sending the HTTP message”. But an HTTP message being signed by a key for the purpose of sec:assertionMethod means that “This is who is making the claims in the content”. These might not be the same entity. An HTTP POST might be delivered by a proxy actor for every user on the domain, and the content might be asserted by a specific user’s actor.

+1 for writing a FEP, I would contribute to that effort

require ed25519, recommend rsa-v1_5_sha256

I think it should be the other way around: require rsa-v1_5_sha256, recommend ed25519. Since RSA is already used, implementers can start with RFC-9421 and add support for ed25519 later.

As far as I know, Mastodon and WordPress implemented RFC-9421 but not ed25519.

tootik

It's developed by @dimkr (he's on a tootik instance, you can test your implementation there)

1 Like

Hello! If you read tootik's FEDERATION.md, make sure you look in main branch HEAD

(I hope mitra.social forwards replies)

@dimkr It only forwards replies from its own threads, but this thread is from SocialHub

cc @proto-s2s

It looks like it could be in the response to both 202 Accepted and 401 Unauthorized, so I like this idea as another way to find out that a server supports RFC signatures.

Oh hey, you’re right… I even have a comment to that effect in my code, I just didn’t remember that when writing my notes. Okay, so @query doesn’t need to be required, just @method & @target-uri.

I think using RSA at all was a mistake, and this is a good opportunity to push people into migrating. In my experience it will be difficult or impossible to enforce better algorithms later if we have bad ones in our “required” list.

In the languages I use (ts/python/rust), it’s been easier to find ed25519 support than RSA, but I don’t use ruby – is it hard to find a good ed25519 implementation there, and is that the reason mastodon & friends used it at first?

1 Like

is that the reason mastodon & friends used it at first?

Ed25519 is a newer crypto, EdDSA was first mentioned in draft-cavage-http-signatures-11, 2019. As far as I know, ActivityPub servers were already using RSA at that time.