HTTP Signature Infinite Loop?
I'm trying to get my head round HTTP Signatures as they're used extensively in the Fediverse.
Conceptually, they're relatively straightforward.
You send me a normal HTTP request. For example, you want to POST something to https://example.com/data
You send me these headers:
POST /data
Host: example.com
Date: Sat, 24 Feb 2024 14:43:48 GMT
Accept-Encoding: gzip
Digest: SHA-256=aaC57TDzM0Wq+50We2TkCsdMDvdqON92edg7KI+Hk8M=
Content-Type: application/activity+json
Signature: keyId="https://your_website.biz/publicKey",algorithm="rsa-sha256",headers="(request-target) host date digest content-type",signature="JGQ53kEoIiMWRp9By9jajVGCOCu4n7XBeiA1uY5xLcnAxL2Y1GIgU/...=="
Connection: Keep-Alive
Content-Length: 751
In order to verify the contents of the message, I need to do three things:
- Check the SHA-256 hash of the message matches the content of the "Digest" header.
- Check the timestamp is somewhat fresh.
- Check the signature matches.
The first is simple: base64_encode( hash( "sha256", $request_body, true ) )
. The second is a matter of opinion. I might be happy to receive messages from the distant past or far in the future. For the sake of a little clock drift, let's allow 60 seconds either way. The third gets complicated.
First, I need to get the public key published at keyId="https://your_website.biz/publicKey"
.
Next, I need to know which algorithm is being used to sign the headers: algorithm="rsa-sha256"
Then, I need to know which headers - and in what order - are being signed: headers="(request-target) host date digest content-type"
So I create a string using the received details which matches those headers in that specific order:
(request-target) POST /data
Host: example.com
Date: Sat, 24 Feb 2024 14:43:48 GMT
Digest: SHA-256=aaC57TDzM0Wq+50We2TkCsdMDvdqON92edg7KI+Hk8M=
Content-Type: application/activity+json
I can verify if the signature - signature="JGQ53kEoIiMWRp9By9jajVGCOCu4n7XBeiA1uY5xLcnAxL2Y1GIgU/...=="
matches by:
PHPopenssl_verify(
$headersString,
$signature,
$publicKey,
$algorithm
);
If that's TRUE
then all is well.
But can you spot the implicit problem?
How do I get your server's public key?
I just GET https://your_website.biz/publicKey - but if your server uses something like Authorised Fetch then I have to sign my request to you.
Which means your server will need to validate my signature by obtaining my public key. Which it will get by signing a request and sending it to me. Which, before I return my public key, I will need to validate your signature by obtaining your public key. Which I will get by signing a request... and so on.
This deadlock loop is documented. The usual way around it is either for the sending server to use an instance-specific signature which can be retrieved by an unsigned request, or to allow any unsigned request to access a user's public key.
I get why things happen this way - I just wish it were easier to implement!
Mike P says:
@blog This blog post is well worth a read: https://seb.jambor.dev/posts/understanding-activitypub-part-4-threads/
Evan Prodromou said on cosocial.ca:
@Edent actor and key objects need to be available without authentication
Steve Bate says:
@blog Yes, it’s a shame that Fedi public keys for HTTP signatures are not publicly accessible in authorized fetch scenarios. That doesn’t make much sense to me. The reason for it (fragment-based key IDs in Mastodon) is obscure and seems arbitrary to me. The fix is relatively easy, but I don’t see much motivation to fix it.
arcanicanis says:
I think it's worth just replacing/upgrading the present state of HTTP Signatures, such as working towards a FEP that instead utilizes RFC9421 (instead of it's earlier incompatible drafts), enabling the ability to have a server-wide key (especially to lock it down to an HSM or other secured storage) rather than this present joke of private keys generated for each user, typically stored unwrapped in a database, that the user can't export for risk of other users on the same instance.
The first step however is defining some mechanism for announcing support for "upgraded HTTP Signatures", as I don't think both could coexist without some discovery/upgrade mechanism: https://socialhub.activitypub.rocks/t/extension-support-discovery/3925
Yes, it won't solve anything with trying to resolve your implementation struggles in the current present, however there needs to be momentum started with fixing this, and garnering support for building a 'better HTTP Signatures', so that people don't have to fight with this absurdity hopefully in the future.
silverpill says:
@arcanicanis @steve @blog What are the benefits of RFC9421 in comparison to the Cavage draft?
arcanicanis says:
Cleaner syntax (
@method
and@path
are separate, versus rolling it together as(request-target)
), clearly lays out the expectations of which implementation decisions must be made when rolling it into a larger protocol/system (section 1.4), more standardized signature methods (includinged25519
), and can be possible to build and make use of well-tested reusable libraries than having to need some niche implementation that only applies to ActivityPub.In cursory glance, it's just a few syntax changes to 'upgrade' existing implementations. But part of it isn't just RFC9421 itself, but an opportunity to fix the state of HTTP Signatures in ActivityPub within the same effort.
silverpill says:
@arcanicanis @steve @blog Current usage can be documented in a FEP, eddsa extension can also be proposed that way. Reusable libraries already exist. I still don't see how some random RFC could fix anything. You can't just tell developers to implement it, Fediverse needs a very specific variant of HTTP signatures, which should be described in a FEP or in W3C spec. This document can include everything we need, RFC is really redundant and it would only make everything more complicated
arcanicanis says:
It's not just the RFC that just 'magically fixes everything'. I cited that it should be broader effort of working on a FEP, with inclusion of the RFC as a target rather than the earlier draft, including what people want changed of HTTP Signatures, as an initiative that should happen. But also that can't be done yet if we don't have feature/support discovery.
Instead of just shoving a FEP on everyone of "this is how HTTP Signatures needs to be done, everybody needs to do it my way", I'd like to see discussion first (such as on SocialHub) of other potential pitfalls or needs (e.g. what about users on polyglot platforms that don't have a "server" concept, where each user is a sovereign identity, etc, if there's to be a 'server-wide key'?) to be considered.
I don't know if everyone's just afraid of voicing their opinions, if SocialHub is measurably 'dead', if there's just general unseen friction between projects, or if most folks just aren't actionable personality types. If it's legitimately down to someone pushing in a direction, writing up a stack of specifications, and pestering people, I can do that. But I'm sure others likely have more constructive input/insight than what I could do solo.
silverpill says:
@arcanicanis @steve @blog
This is "If it ain't broke, don't fix it" kind of situation. A network-scale change won't happen without a very good reason, and I think most developers don't really care about key type or message syntax.
They simply want to connect their software to Mastodon. The real problem is a deluge of questions about HTTP signatures - I see new posts about failed verification on SocialHub and in fedi every week or so. This is what needs to be fixed, and the solution is twofold:
Better testing tools: https://verify.funfedi.dev/ and others
- Documentation (like FEP-e2ce but ideally without proposing anything new)
Khleedril says:
@blog I don't understand the reason for needing a signed request to get the public key; these things are public aren't they?
More comments on Mastodon.