If you’ve worked with Mastodon (or possibly other ActivityPub implementations too) HTTP Signatures might sound familiar. When notifying another server of an event, the request can be signed thus proving its authenticity, meaning that the receiving server doesn’t need to go and fetch the authoritative version from the origin. This reduces load on both the receiver of the event (less requests to send) and the origin (less requests to serve).

black audio mixer

The Mastodon draft

Mastodon got pretty big by 2018, and in the beginning at least it supported a bunch of open source protocols like Salmon and OStatus. At that point the HTTP Signature spec was at the 6th draft version. That version thus got pretty much baked in to every implementation that was willing and able to be compatible with Mastodon.

The idea is that you take a bunch of the HTTP headers, serialize them in a certain format, and sign that value with the encryption key of the ActivityPub event’s Actor (in the case of a new microblog post, the author of the post). In the 6th draft version the resulting Signature header looked something like below.

Signature: keyId="https://example.com/users/foo#main",algorithm="rsa-sha256",headers="(request-target) digest date content-type",signature="TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQgZXJvcy4="

I too made a library to handle this stuff in Clojure, though the past few years I’ve been just updating the dependencies when there was a vulnerability report and nothing much else. However this year while doing that I searched for the spec and I was surprised to learn that it’s now way, way different.

HTTP Signatures are now RFC9421!

The RFC version

The basic idea (take a bunch of headers, serialize them in a standard format and sign that) hasn’t changed, but the details are completely different. I think the single biggest difference is that the HTTP Signature spec now “depends on” the HTTP Structured Fields spec (RFC8941) which describes a standard way to deal with complex headers. For example, the Signature header above has a bunch of keys and values encoded in it (like the “algorithm” is “rsa-256”). Implementing RFC8941 from scratch is quite an endeavor. You have to write a parser and a serializer implementation strictly following the spec. That’s quite a lot of work.

In my case, luckily there’s a Java implementation which I can then use from Clojure. With the library doing the header parsing and serialization for me, I could focus on the signing logic itself. Another big difference is that the Signature header now contains only the HTTP signature(s), while the metadata details about them are in the Signature-Input header. (Yeah, multiple signatures are supported too.)

In Mastodon’s case (at least back when I last looked at it), the users’ keys (and thus the signatures) were hardcoded to RSA with SHA256. Technically the 6th draft allowed RSA+SHA1, HMAC+SHA256 and ECDSA+SHA256 as well. In the RFC version, the list of supported algorithms (at the time of writing) is RSASSA-PSS with SHA512, RSASSA-PKCS1-v1.5 with SHA256 (this is kinda what Mastodon is using), HMAC with SHA256, ECDSA curves P256 and P384 with SHA256 and SHA384 respectively, Ed25519, and JSON Web Signatures.

In my (work in progress) compliant implementation I decided to support all except HMAC (since AP is fundamentally public key based) and JWS (which looked like a complete mess). The whole reason I even noticed the RFC was that I wanted to support some quantum safe signature scheme and looked if the spec allowed that. Some quantum safe signature algos have been standardized by the NIST so I hoped… but not yet. Maybe someone adds them when they are better established. They’ve been a standard only a few months now.

Compatibility with the existing AP infrastructure is a concern, but since both the RFC and the 6th draft use the Signature header (in wildly different ways) it’s tricky to support both transparently. On the one hand, it’s possible to use the old way with servers that only support that (like Mastodon) and use the RFC way with compliant servers. Detecting what supports it is an exercise left to the reader. On the other hand, it’s not really the end of the world either if Mastodon can’t verify your signatures. It’ll just go and fetch the resource. Sure that’ll put some extra load on your server, but that’s the price to pay. Up to the admin (and/or author of the server code) to decide which way to go.