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
did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK
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
- Create a signature
- Verify the signature
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.
Authentication
In order to authenticate a request, the following steps should be taken:
- Verify that the signature
X-Moo-Signature
is correct. - Check that the
Host
header is as expected, i.e. matches the servername of your host. - Check that the
(request-target)
matches the expectation. These are two checks, correct method and correct path. - 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. - Check that the
Date
header is appropriate, e.g. that it is within3: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
z3u2Yxcowsarethebestcowsarethebestcowsarethebest
From this private key, one should be able to compute the did-key
did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5
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:
- User Alice signs up for a new ActivityPub Client.
- Alice adds the server name of the ActivityPub Server to the Client.
- The ActivityPub Client generates a new Ed25519 private key for Alice and then generates the corresponding did:key.
- ActivityPub Client displays the did:key to Alice.
- Alice adds the did:key to the allowed list of keys for Client access on her ActivityPub Server.
- 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.
- The ActivityPub Client looks up Alice’s ActivityPub actor and obtains the necessary endpoints on the ActivityPub Server.
- 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.
References
- [Date] MDN https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Date
- [Did-Method-Key] Dave Longley, Manu Sporny, Dmitri Zagidulin https://w3c-ccg.github.io/did-method-key/
- [Digest] MDN https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Digest
- [Ed25519] Daniel J. Bernstein, Niels Duif, Tanja Lange, Peter Schwabe, Bo-Yin Yang https://ed25519.cr.yp.to/
- [FEP-c390] silverpill, FEP-c390: Identity Proofs
- [Host] MDN https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host
- [HTTP Signatures] Mastodon Contributors Security: HTTP Signatures
https://github.com/mastodon/mastodon/issues/21429
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.
1. You can have as many identities linked to each other as you wish.2. Identities can be transient.
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.
@helge
@helge
An identifier must never identify more than one resource. But a single resource may have many identifiers (all of which are, for themselves, unique).
@helge
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.
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.
@helge
> an identifier which is not "canonical" is not an identifier
Alright then. I think this is a fundamentally wrong assumption, though.
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.