Helge's blog

by helge@mymath.rocks

Powered by longhorn, an ActivityPub Client based blog. This means an existing Fediverse account is used to federate blog entries. The entire thing is based on bovine and the corresponding ActivityPub server.

Atom Feed


BIN-1 Moo Authentication and Authoriation

In this first Bovine Implementation Note, I introduce Moo Authentication and Authorization for HTTP requests. This meant as an authentication and authorization scheme to replace [HTTP Signatures] when communicating with Bovine. The simplest to state advantage is that HTTP requests will be authenticable without having to look up a private key. For this Moo Auth will replace the usage of an RSA keypair with Ed25519, which has the advantage of shorter keys.

Status of this document

This document details my implementation plan for Bovine. Please consider aiming to get it into a more standardized format before wide adaptation occurs. You hereby have my permission to do so. It would be nice if you cited this document, if it inspires your approach.

Prelimineries: Cryptography

As already mentioned in the introduction, Moo-Auth-1 uses [Ed25519]. The main advantages here is that keys and signatures are relatively short, for example in [Did-Method-Key] representation the public key has the form


which is short enough to be contained in each message without creating an eyesore. Second as a Decentralized Identifier, one can use the above did:key to identify the author of the HTTP request. To the usage of the combination of multibase encoding and multicodec, the first 4 characters of this public key representation are always the same z6Mk. The corresponding 4 characters for the private key are z3u2.

We will assume that the user of Moo-Auth-1 has an Ed25519 key pair. We will refer to them as the private key and the public key. Often, we will refer to the public key as the did:key. As these are the only two keys relevant in this BIN, we hope that this will not lead to too much confusion.

Prelimineries: HTTP Requests

In this BIN, I will only talk about GET and POST requests. The only difference between these two for Moo-Auth-1 is that the POST request should contain the sha-256 digest of its body in the [Digest] header, e.g.

Digest: sha-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=
Digest: sha-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=,unixsum=30637

Furthermore, both types of requests are supposed to contain the [Date] header, e.g.

Date: Wed, 21 Oct 2015 07:28:00 GMT

and the [Host] header, e.g.

Host: developer.mozilla.org

Moo-Auth-1 Authorization Header

In this section, I introduce how the did:key is transmitted in the request. Any request using Moo-Auth-1 should contain an authorization header of one of the following two forms:

Authorization Moo-Auth-1 <key>
Authorization Moo-Auth-1 <key>,<domain>

We will refer to these as the first and second form. Examples

Authorization Moo-Auth-1 did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK

Authorization Moo-Auth-1 did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK,domain.tld

The first type of request doesn’t provide any additional data than the did:key. This means that a server is free to reject it with 401 Unauthorized if the did:key is unknown. For the second type of request, the server can enquire using webfinger on <domain> on the nature of the requester.

It should be noted here that providing a did:key in the Authorization header does not authenticate the request. We have no way of telling that the request is actually from the public key owner. This problem will be remedied by the X-Moo-Signature header that is introduced in the next section.

The X-Moo-Signature header

The X-Moo-Signature header contains a multibase encoded signature using the private key. It provides the method of authentication of the request.

The data to be signed is given by

(request-target): <method> <target>
host: <host>
date: <date>
digest: <digest>

where method is either GET or POST, target is the full path of the request, e.g. /path/to/endpoint?query#fragment, host, date, and digest correspond to their corresponding header value. For a GET request the digest value is ommitted.

In order to avoid ambiguity, the message will be utf-8 encoding before signing. As a multibase encoding of the signature is used, any encoding is possible. However, I would encourage people to use the same base58 bitcoin alphabet encoding that was used for the private and public keys. This will result in the first letter of the X-Moo-Signature being a z.

It should be clear from this how to

Furthermore, it should be noted that both signing and verifying the request does not require any information outside of the request. So it is reasonable to expect synchronous verification of the signature. This is an advantage compared to [HTTP Signatures], where the public key needs to be looked up.


In order to authenticate a request, the following steps should be taken:

  1. Verify that the signature X-Moo-Signature is correct.
  2. Check that the Host header is as expected, i.e. matches the servername of your host.
  3. Check that the (request-target) matches the expectation. These are two checks, correct method and correct path.
  4. In case of a post request, check that the digest is present in the signature, and that the content-digest matches. Furthermore, it should be checked that the sha-256 digest is present.
  5. Check that the Date header is appropriate, e.g. that it is within 3:14 minutes of the current time.

The value to be used in 5 is up to the implementation. The time should be large enough, so that errors due to processing delays are avoided, but small enough so that the data being fetched is not expected to change in a substantial way. How to make a proper choice for an acceptable interval is outside of the scope of this BIN.

Requesting information on a public key using webfinger

As stated at the end of the section introducting the Moo-Auth-1 Header, it might be necessary to look up identities associated to a public key using webfinger. Let us for simplicity consider an ActivityPub Like Activity given by

  "@context": "https://www.w3.org/ns/activitystreams",
  "actor": "https://other_domain/users/phoenix",
  "id": "https://other_domain/users/phoenix#likes/557",
  "object": "https://my_domain/activitypub/id567",
  "type": "Like"

What we need to verify now is that the did:key used to perform the authentication of the message belongs to the actor https://other_domain/users/phoenix respectively is authorized to perform activities on behalf of phoenix.

There are now three steps involved. Let us assume, the Authorization header had the form:

Authorization Moo-Auth-1 did:key:z6Mk...xxx,other_domain

We first check that the domains agree, then we can peform a webfinger lookup of the did via

GET https://other_domain/.well-known/webfinger?resource=did:key:z6Mk...xxx

This should return a JRD document containing the actor as one of the representations. Finally, we can query the actor and verify that the did:key is attached to the actor via the mechanism described in [FEP-c390].

Deprecation Notice

As explained above attached identities or a similar format will take the place of the publicKey attribute of the Actor for Bovine. Due to this the publicKey field in the Actor should be considered deprecated and new projects should only rely use it to stay compatible to legacy code.

Appendix: Test Data

In this appendix, we provide two sample signed requests. For these, we will use the sample private key in multibase, multicodec encoding


From this private key, one should be able to compute the did-key


Get request

The following is a GET request as performed using Moo-Auth-1. We note that additional headers may be present.

GET /path/to/resource
Date: Wed, 15 Mar 2023 17:28:15 GMT
Host: myhost.tld
Authorization: Moo-Auth-1 did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5
X-Moo-Signature: z5ahdHCbP9aJEsDtvG1MEZpxPzuvGKYcdXdKvMq5YL21Z2umxjs1SopCY2Ap8vZxVjTEf6dYbGuB7mtgcgUyNdBLe

For completeness sake, we note that the message to sign is:

(request-target): get /path/to/resource
host: myhost.tld
date: Wed, 15 Mar 2023 17:28:15 GMT

Post request

The following is a POST request. The new line between headers and the body is not included in the body:

POST /path/to/resource
Date: Wed, 15 Mar 2023 17:28:15 GMT
Host: myhost.tld
Digest: sha-256=MILb5lUDD6Z0pDSxhgxj+hMBEw0uTzP3g2qUJGHMp9k=
Authorization: Moo-Auth-1 did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5
X-Moo-Signature: z4vPkJaoaSVQp5DrMb8EvCajJcerW36rsyWDELTWQ3cYmaonnGfb8WHiwH54BShidCcmpoyHjanVRYNrXXXka4jAn

{"cows": "good"}

The message to sign is in this case:

(request-target): post /path/to/resource
host: myhost.tld
date: Wed, 15 Mar 2023 17:28:15 GMT
digest: sha-256=MILb5lUDD6Z0pDSxhgxj+hMBEw0uTzP3g2qUJGHMp9k=

Appendix: Proposed flow of adding an ActivityPub Client to a Server

We assume that users have a way to sign up for both an ActivityPub Server and an ActivityPub Client. This can be either by simply running the software on their own server or from a hosted source. The proposed flow is the following:

  1. User Alice signs up for a new ActivityPub Client.
  2. Alice adds the server name of the ActivityPub Server to the Client.
  3. The ActivityPub Client generates a new Ed25519 private key for Alice and then generates the corresponding did:key.
  4. ActivityPub Client displays the did:key to Alice.
  5. Alice adds the did:key to the allowed list of keys for Client access on her ActivityPub Server.
  6. The Client performs a webfinger lookup of the did:key on the ActivityPub Server. From this the Client now knows Alice’s ActivityPub Actor id.
  7. The ActivityPub Client looks up Alice’s ActivityPub actor and obtains the necessary endpoints on the ActivityPub Server.
  8. The ActivityPub Client updates Alice’s Actor with a verified identity document as descibed in [FEP-c390].

While this flow seems simple enough and easy to implement, I lack the necessary experience with an implementation to say that it will perform well in practice.


@mariusor@metalhead.club wrote at 2023-03-15 19:44 UTC (original)
@h the latest version of HTTP-Sig allows ed25519 keys. Mastodon is very close to supporting that, but somehow they don't. I don't know enough ruby (nor have the patience to set-up a dev environment) to contribute a valid patch.

helge@mymath.rocks wrote at 2023-03-16 06:51 UTC
Hi Marius.

Supporting Ed25519 keys is just one of the aspects for me. Furthermore, I want to make explicit that the "public key" used is an Identity, that's why I use the did:key format. Second, I want explicit examples! Everybody wanting to implement this, can now write unit tests with copy and paste.

Third, HTTP-Sig contains a lot of unnecessary details, it specifies how to encode dictionaries contained in HTTP headers in TWO! ways. This is not the type of specification one can "quickly" implement. It's the type of specification that I want a battle hardened library for. So let's give HTTP-Sig at least 1 year to exit draft stage and 1 year to acquire good library support. Then we can talk again about it.

@mariusor@metalhead.club wrote at 2023-03-16 07:36 UTC (original)
@h best practices surrounding asymmetric key cryptography are to rotate them periodically. I don't see how using public keys for identity makes sense. 🤷
@Natureshadow@floss.social wrote at 2023-04-28 10:47 UTC (original)
@mariusor @helge

1. You can have as many identities linked to each other as you wish.2. Identities can be transient.
@mariusor@metalhead.club wrote at 2023-04-28 10:53 UTC (original)
@Natureshadow I'm not sure if you realize that both those points are actually in contradiction to what "identity" implies.

At best I would qualify them as "compromises", and if users would be satisfied by them, well, ActivityPub doesn't have anything preventing it from having mechanisms for doing the same.

@Natureshadow@floss.social wrote at 2023-04-28 10:59 UTC (original)
@mariusor @helge I am very aware that neither point contradicts anything that identity implies.
@mariusor@metalhead.club wrote at 2023-04-28 11:05 UTC (original)
@Natureshadow to me identity implies uniqueness, but I'd be willing to revise my intuition. Could you please expand on your statement?

@Natureshadow@floss.social wrote at 2023-04-28 11:08 UTC (original)
@mariusor @helge Yes, identity implies uniqueness – but only in one direction.

An identifier must never identify more than one resource. But a single resource may have many identifiers (all of which are, for themselves, unique).
@mariusor@metalhead.club wrote at 2023-04-28 11:37 UTC (original)
@Natureshadow makes sense. I think I might be biased by previous exposure to ActivityPub (or the Internet in general) where a URI usually identifies "uniquely" a resource.

@Natureshadow@floss.social wrote at 2023-04-28 11:47 UTC (original)
@mariusor @helge Both are not true. Resources on the internet do not always have only one URI. I'd even say this is very rare.

Ever heard of HTTP redirects (301 or 302)? That's your most simple example for documents having more than one identifier, and it happens all the time.

Likewise, in Mastodon for exampe, /@foo and /users/foo map to the same actor object.

Most of the times, all lead to one *canonical* identifier, though. Maybe that's what you mixed up.
@mariusor@metalhead.club wrote at 2023-04-28 11:59 UTC (original)
@Natureshadow I don't think I agree with your full statement, but yes, an identifier which is not "canonical" is not an identifier.

301/302 signify that the old identifier should be replaced with the new identifier.

In Mastodon, the canonical actor URI is the one which is referenced from it's ID, so to me that's the "identifier".

Thank you for the replies, it helps me clarify my mental model.

@Natureshadow@floss.social wrote at 2023-04-28 12:04 UTC (original)
@mariusor @helge

> an identifier which is not "canonical" is not an identifier

Alright then. I think this is a fundamentally wrong assumption, though.
helge@mymath.rocks wrote at 2023-04-28 11:05 UTC
First do you guys realize that the mechanism that your comments show up on my blog still works. I'm truly impressed with myself.

Second, the keys involved here take a similar role as THE signing key pair in olm. This identity should be pretty much constant.

Third, the one time keys of olm are what I would call transient, but these are not used for the purposes described here. They are input into public key generation, which is much harder than what is described here.

You can reply to this post using your favorite FediVerse Application. For this look up the object
in it and just reply. Depending on how your FediVerse Application handles Articles, this post may appear different.