Nomadic Identities on the Fediverse
Table of contents
- Edit 28/06/2023
- Introduction
- Nomadic identities?
- Identity proofs
- SSH? Wait, how is that related?
- SSH authentication protocol
- Putting it all together
- How does this actually make you nomadic?
- Another by-product: Logins on other platforms
- Closing thoughts
Edit 28/06/2023
Thank you to silverpill for pointing out that a good alternative to the did:key
method with functionality, such as updating or revoking keys, is the did:web
method.
I forgot to mention them in the main part of the post itself, so I thought I’d add the mention right here in the beginning.
Introduction
So, Kitsune has been my main project for a while and it’s been coming along pretty well. One of the many things I want to look into before its first “proper” release is the idea of nomadic identities and, more recently, nomadic logins.
Nomadic identities?
For everyone confused by the term “nomadic identity”, it sounds more complex than it actually is. It’s basically the idea of nomads taken to the realm of digital identities.
no·mad ˈnō-ˌmad. : a member of a people that has no fixed home but wanders from place to place.
Instead of being fixed to, let’s say, hannya.nexus
, you can log in on dev.joinkitsune.org
as well and all these different logins are connected to your single identity that you are in control of.
Identity proofs
A big step into the direction are “identity proofs”, the proof that your identity is actually the one you claim it to be.
There is already a FEP for this in the works, currently in “Draft” status: FEP-c390
The workings behind these proofs is actually pretty simple, if you are somewhat cryptographically inclined you should give it a look. It’s basically just an adaptation of the “Verifiable Credential Data Integrity 1.0” draft by the W3C.
This FEP helps us establish the following facts:
- User with a private key they have demonstrable control over
- A source of the key which is demonstrably legit
- The key is embedded into a DID
- The DID is part of the signature
- The DID is placed next to the signature inside the signed actor
With those two points out of the way, some might already have an idea where this is going.
SSH? Wait, how is that related?
Yes! SSH! This might sound like I lost my mind but think about it. What does SSH have to have?
- A user with a private key they have demonstrable control over
- A source of the key which is demonstrably legit
SSH achieves this via various methods. The most traditional one is manually transferring a public key over, but there are also different approaches such as Tailscale SSH (not sponsored btw, I just gush about them way too much).
That way the SSH server knows that the keys are legit (they are literally on the hard drive), so the user can sign something with their private key and authentication is ensured, and all lived happily after.
SSH authentication protocol
The authentication protocol SSH is defined in RFC-4252, but a lot of the things are actually unrelated to what I wanna talk about. I actually want to talk about Section 7 of that very RFC.
- Public Key Authentication Method: “publickey”
After doing some back-and-forth of whether the public key authentication is actually acceptable for that particular host and blah blah, we get to the juicy parts.
To perform actual authentication, the client MAY then send a signature generated using the private key. The client MAY send the signature directly without first verifying whether the key is acceptable. The signature is sent using the following packet:
byte SSH_MSG_USERAUTH_REQUEST string user name string service name string "publickey" boolean TRUE string public key algorithm name string public key to be used for authentication string signature
The value of ‘signature’ is a signature by the corresponding private key over the following data, in the following order:
string session identifier byte SSH_MSG_USERAUTH_REQUEST string user name string service name string "publickey" boolean TRUE string public key algorithm name string public key to be used for authentication
Basically what SSH does can be expressed in short like so:
Where
To bring this back to ActivityPub, the signature function is defined inside actor which originally gave us the public key.
The server then does the following:
Where
Then the instance we try to log-in to can either give us access to the existing account this identity is connected to or create a new account for us on their instance that is then internally linked to that signature.
Obviously we can’t (and won’t) reuse the entirely same mechanism as SSH, but it is a great example because many are familiar with it
This is effectively a decentralised ActivityPub-powered SSO solution.
If you’re not entirely sure how, I will elaborate on that right now!
Putting it all together
Let’s assume we have a normal actor
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1"
],
"type": "Person",
"id": "https://nightcity.bar/users/9fr74qe71z",
"inbox": "https://nightcity.bar/users/9fr74qe71z/inbox",
"outbox": "https://nightcity.bar/users/9fr74qe71z/outbox",
"followers": "https://nightcity.bar/users/9fr74qe71z/followers",
"following": "https://nightcity.bar/users/9fr74qe71z/following",
"url": "https://nightcity.bar/@macropunk",
"preferredUsername": "macropunk",
"name": null,
"summary": "Hello world!",
"manuallyApprovesFollowers": false,
"discoverable": true,
"publicKey": {
"id": "https://nightcity.bar/users/9fr74qe71z#main-key",
"type": "Key",
"owner": "https://nightcity.bar/users/9fr74qe71z",
"publicKeyPem": "[Public key PEM]"
},
"attachment": [
{
"type": "PropertyValue",
"name": "Pronouns",
"value": "they/them"
}
]
}
Let’s say I want to make this “ActivityPub SSO” enabled, for that we do the following:
- We generate an Ed25519 keypair
- We generate an unsigned document with the “authentication” proof type
- We sign that document
- We attach the proof
All these things are done according to FEP-C390. The actor then looks like so:
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1"
],
"type": "Person",
"id": "https://nightcity.bar/users/9fr74qe71z",
"inbox": "https://nightcity.bar/users/9fr74qe71z/inbox",
"outbox": "https://nightcity.bar/users/9fr74qe71z/outbox",
"followers": "https://nightcity.bar/users/9fr74qe71z/followers",
"following": "https://nightcity.bar/users/9fr74qe71z/following",
"url": "https://nightcity.bar/@macropunk",
"preferredUsername": "macropunk",
"name": null,
"summary": "Hello world!",
"manuallyApprovesFollowers": false,
"discoverable": true,
"publicKey": {
"id": "https://nightcity.bar/users/9fr74qe71z#main-key",
"type": "Key",
"owner": "https://nightcity.bar/users/9fr74qe71z",
"publicKeyPem": "[Public key PEM]"
},
"attachment": [
{
"type": "PropertyValue",
"name": "Pronouns",
"value": "they/them"
},
{
"type": "VerifiableIdentityStatement",
"subject": "did:key:[multibase encoded key]",
"alsoKnownAs": "https://nightcity.bar/users/9fr74qe71z",
"proof": {
"type": "JcsEd25519Signature2022",
"created": "2023-06-20T00:00:00Z",
"verificationMethod": "did:key:[multibase encoded key]",
"proofPurpose": "assertionMethod",
"proofValue": "<proof-value>"
}
}
]
}
Therefore we are establishing the fact that this is indeed us.
On the other server
Let’s say we then immediately say “fuck it” and want to log into another service, you choose the, whatever, “Sign up with ActivityPub” method.
There we type in the URL to our actor. The remote server then fetches the actor, loads it into their database and then asks you for a signature.
How you create that signature is up to you, you can do it yourself locally or there is the possibility for hosted key handlers that could create a signature for you.
Afterwards you get to set the rest of your profile up (or copy the data from the other server).
How does this actually make you nomadic?
To some extent it doesn’t. At least not automatically.
In the beginning your identity is still bound to the first server you signed up at, the origin or “genesis” server.
The word “genesis” might make this server seem really important but in the grand scheme of things it isn’t!
Genesis is just supposed to imply that it is the first server that ever saw your identity, nothing else.This server can go down before you logged into any other servers, all you need to do to is upload another proof. Servers can then establish a link between the two accounts by correlating the public keys
But what you can do is log into another service with your private key. On the new service you can attach an identity proof, just like on your main account.
This doesn’t sound like an immediate win, I know, but now you can log into other services via that elusive “ActivityPub SSO” using two different servers. Doing the same again makes it three, four, five, etc etc.
At some point you could have 100 “accounts” that can all be linked back to the same private key, making it all part of your identity.
Another by-product: Logins on other platforms
If this indeed becomes main-stream, other platforms (such as GitHub, Vercel, DeviantArt, etc.) could have a “Log in with ActivityPub” option.
They could make use of the same identity proof infrastructure. The only difference is that they have to store the subject
field instead of the concrete instance URL.
Then they can go through the following flow:
- Request the actor
- Verify the attached identity proof
- Request a signature
- Verify the signature
- Authenticate/create the user as the user that has the DID attached to their account
--- Just me rambling to myself the next few lines ---
“But we can do that with the OAuth endpoint properties already!”, you might say and you’d be correct. But here I ask you, how many people implement those?
As of the time of writing the only implementations I’m aware of are Pleroma and Akkoma.
Better question, what do implementations do that do not support OAuth, such as Lemmy? Just implement OAuth? Yeah, if it were that easy.
(aside from that, the identity would then still be bound to a single instance instead of your cryptographic identity)
Closing thoughts
At the moment the identifiers use the DID key
method. This method is great for getting something off the ground but has severe problems.
They can’t get rotated, updated, nor deactivated (the inofficial draft admits this and discourages long-term use), making a key leak absolutely fatal.
So these things in particular still need thought put into. Luckily the FEP is still in a draft state, making these changes still very much possible.
Bluesky has the exact same problem with their DIDs. They came up with their own
did:plc
method which just uses a central authority server.
Central servers are exactly what we can’t and, more importantly, don’t want to rely on.
This was a quick idea, outlined as a blog post. I didn’t even go in-depth on how this login proof should look like or how to embed external identity providers.
The idea is based on a quick thought I had when discussing global SSO platforms for the fediverse.
If you have thoughts on this, I’m open to chat. I’m on the fediverse and my accounts are linked on my GitHub.
Find my GitHub to find me.