Retrieving the Public Key from an ActivityPub Actor
This technical note describes the mechanism to resolve the public key from
the actor object. The fediverse_python
library and its tests can be consulted
to inspect implementation details.
Caution
The strategy outlined here is somewhat permissive. Choices in a production application can be different.
Outline of the strategy by example
Taking the first example from How to implement a basic ActivityPub server as
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1"
],
"id": "https://domain.example/actor",
"type": "Person",
"preferredUsername": "alice",
"inbox": "https://domain.example/inbox",
"publicKey": {
"id": "https://domain.example/actor#main-key",
"owner": "https://domain.example/actor",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----"
}
}
one can determine the basic strategy:
Multiple Keys
The ActivityPub Actor object is a JSON-LD object. This means that publicKey
is
not actually single valued, but could be a set. So something such as the following
is possible
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1"
],
"id": "https://domain.example/actor",
"type": "Person",
"preferredUsername": "alice",
"inbox": "https://domain.example/inbox",
"publicKey": [
{
"id": "https://domain.example/actor#main-key",
"owner": "https://domain.example/actor",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----"
},
{
"id": "https://domain.example/actor#other-key",
"owner": "https://domain.example/actor",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----"
}
]
}
If publicKey
is a set, the elements will be inspected and the key with
id
matching keyId
from the HTTP Signature will be chosen.
JSON-LD @context ambiguity
The @context
property can verify between implementations.
Using the JSON-LD Playground, one can see
that the following JSON-LD is equivalent to our original one. Unfortunately,
our strategy to retrieve the public key does not work.
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://domain.example/actor",
"type": "Person",
"inbox": "https://domain.example/inbox",
"https://w3id.org/security#publicKey": {
"id": "https://domain.example/actor#main-key",
"https://w3id.org/security#owner": {
"id": "https://domain.example/actor"
},
"https://w3id.org/security#publicKeyPem": "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----"
},
"preferredUsername": "alice"
}
In order to get around this, we will compact the JSON documents against
the @context
given by
Incorrect JSON-LD terms
Finally, we wish to allow for incorrect usage of JSON-LD. This means that the following example should also lead to the public key.
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://domain.example/actor",
"type": "Person",
"preferredUsername": "alice",
"inbox": "https://domain.example/inbox",
"publicKey": {
"id": "https://domain.example/actor#main-key",
"owner": "https://domain.example/actor",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----"
}
}
References
- How to implement a basic ActivityPub server on the Mastodon Blog by Eugen Rochko.