HTTP Signature Infinite Loop?
https://shkspr.mobi/blog/2024/02/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 /dataHost: example.comDate: Sat, 24 Feb 2024 14:43:48 GMTAccept-Encoding: gzipDigest: SHA-256=aaC57TDzM0Wq+50We2TkCsdMDvdqON92edg7KI+Hk8M=Content-Type: application/activity+jsonSignature: keyId="https://your_website.biz/publicKey",algorithm="rsa-sha256",headers="(request-target) host date digest content-type",signature="JGQ53kEoIiMWRp9By9jajVGCOCu4n7XBeiA1uY5xLcnAxL2Y1GIgU/...=="Connection: Keep-AliveContent-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 /dataHost: example.comDate: Sat, 24 Feb 2024 14:43:48 GMTDigest: SHA-256=aaC57TDzM0Wq+50We2TkCsdMDvdqON92edg7KI+Hk8M=Content-Type: application/activity+json
I can verify if the signature -
signature="JGQ53kEoIiMWRp9By9jajVGCOCu4n7XBeiA1uY5xLcnAxL2Y1GIgU/...=="
matches by:openssl_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!
https://shkspr.mobi/blog/2024/02/http-signature-infinite-loop/
#ActivityPub #CyberSecurity #encryption #fediverse #http
HTTP Signature Infinite Loop?
https://shkspr.mobi/blog/2024/02/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 /dataHost: example.comDate: Sat, 24 Feb 2024 14:43:48 GMTAccept-Encoding: gzipDigest: SHA-256=aaC57TDzM0Wq+50We2TkCsdMDvdqON92edg7KI+Hk8M=Content-Type: application/activity+jsonSignature: keyId="https://your_website.biz/publicKey",algorithm="rsa-sha256",headers="(request-target) host date digest content-type",signature="JGQ53kEoIiMWRp9By9jajVGCOCu4n7XBeiA1uY5xLcnAxL2Y1GIgU/...=="Connection: Keep-AliveContent-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 /dataHost: example.comDate: Sat, 24 Feb 2024 14:43:48 GMTDigest: SHA-256=aaC57TDzM0Wq+50We2TkCsdMDvdqON92edg7KI+Hk8M=Content-Type: application/activity+json
I can verify if the signature -signature="JGQ53kEoIiMWRp9By9jajVGCOCu4n7XBeiA1uY5xLcnAxL2Y1GIgU/...=="
matches by:openssl_verify( $headersString, $signature, $publicKey, $algorithm);
If that'sTRUE
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!
https://shkspr.mobi/blog/2024/02/http-signature-infinite-loop/
#ActivityPub #CyberSecurity #encryption #fediverse #http
Authorized Fetch and the Instance Actor
Using an instance actor for mitigating the authorized fetch key retrieval bug seems strange to me so I dug into the historical record to try to understand how this all evolved.SocialHub
Steve Bate
•arcanicanis
•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.
Extension Support Discovery
SocialHub