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'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
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