support zero-knowledge encryption for toots/DMs
Pitch
The UI now warns us that:
Posts on Mastodon are not end-to-end encrypted. Do not share any sensitive information over Mastodon.
Would it be possible to use zero-knowledge encryption such that neither ISPs nor Mastodon servers could read a user's posts (whether toots or DMs), if not made public?
Disclaimer: I'm unsure what challenges federation adds here, including if this is even possible.
Related:
- #13820
- #1093
- #12128
Motivation
Now that Twitter has changed hands, interest in Mastodon seems on the rise. It would be nice if we were to support encryption to the extent that users would feel safer on Mastodon.
My understanding is they WANT to use crypto, but don't have anybody with the skills.. Do you know a good cryptographer who knows Ruby and mobile appdev?
That was planned and we did make the server-side work (or at least some of it) as you've seen in #13820. But the client-side work is missing, not primarily because of the cryptographic aspect (although as always, implementing cryptography has to be done carefully), but because we haven't defined a protocol or a format for the contents of the encrypted messages: the server-to-server protocol is ActivityPub, and much could be reused for the encrypted messages as well, but this is not necessarily the best choice, and client apps typically do not handle ActivityPub itself but other representations simplified and produced by the server software. Of course, with encrypted messages, the server cannot do this processing, and any way we chose, the apps will have more work to do.
i see, thanks for clarifying! if the design (protocol/format) is the first challenge, could it help to split the cryptographic design from the implementation? not that i'm in cryptographer circles, but perhaps if such a need could be boosted online by someone who is from such a circle...
The cryptographic design side of things is done (though not implemented in any app, and both the design and eventual implementation could surely benefit from a review). What's missing is deciding and agreeing upon a format for the cleartext itself.
I don't see encryption as really fundamental for toots. However, it would be nice to have E2EE (End-to-end Encryption) for DM's.
This seems inherently difficult as mastodon is used as a website, or with many different apps. Where are the users supposed to store the keys for their encrypted DMs? Of course one could use the user's password to decrypt keys stored on the server inside the browser but since admins are capable of resetting users passwords that wouldn't bring any real additional security.
Not in the crypto circle. Could we use something like pgp with a public key and a personal private key?
If E2EE is the only thing that's the goal, then simply publishing your public key with your fingerprint on your Mastodon profile and then letting people use it to encrypt messages to you is something that could be accomplished today -- without any further implementation in the Mastodon code base. It just works.
However, as stated earlier, the logistics of managing the private key is the problem. Is it stored outside of Mastodon -- forcing you to "manually" decrypting each message -- or is it stored on a Mastodon instance which kinda defeats the purpose of being "zero knowledge" or at least making it much more difficult, based on how the key is decrypted using a password (and by sending it to the instance).
By using public/private keys as an optional overlay makes this much simpler. Albeit, not necessarily what people is asking for.
Edit: Of course, the decryption could be done client side using something like a javascript program, but this raises other security concerns instead.
Most of these issues have been solved by zero-knowledge encryption email providers like Proton Mail.
You use the user's password to encrypt their private key. You store all a user's DMs encrypted at rest, and decrypt on the client side after login. If the user has to reset their password, they lose access to their encrypted DMs unless they can remember their old password (just the history of DMs. They still have access to the account, so they can send new DMs with a new public/private key). If a user changes their password you encrypt the private key with the new password.
The only real challenge is third party clients, those will have to implement support themselves.
The only issue that isn't resolved using client side decryption in a web browser (using something like a javascript program) -- and this is perhaps a discussion of a greater issue -- is the trust in proprietary third party browser plugins. Without insight into how these plugins work you will never be sure if they collect either the encryption key, the password or the message. Sure, you could block them from accessing specific sites depending on you browser. And given how common it is for users to install third party plugins in their web browser this can be a big issue for security. Of course the same can be said about proprietary operating systems in general, so it all comes down to the level of security you are after and the risks you are prepared to take (and hopefully aware of).
Maybe, I'm just overly pedantic.
@z0noxz The pubkey could be part of each account's metadata, which is used by other instances for follows anyway.
@virtadpt That sounds like a good idea for an implemented solution. The only risk I see, as stated earlier, is with the private key that has to be stored somewhere as well.
How it is implemented in the Matrix protocol as it has also web clients?
@bhack I'm not that well read on how Element is implementing decryption, and there are non-web based clients for the Matrix protocol as well (see https://matrix.org/clients-matrix/). But, to cite an answer from stack overflow (https://stackoverflow.com/a/4524652):
[...] yes, if the password [referring to the password of the private key] is strong enough this looks to be very secure (unless someone messes with the user's browser or computer, think keylogger or malicious firefox plugin or the site itself undermining the script).
The big question is whether you can trust the browser or not, when it comes to web clients.
I think that the core Matrix library to implement the E2E encryption has a we bassembly version:
https://gitlab.matrix.org/matrix-org/olm/-/tree/master/javascript
There is also an MLS standardization effort in IETF with some reference implementations in different languages:
https://messaginglayersecurity.rocks/
Can't we use key providers for a start? OpenKeychain for android, gnupg for desktop? Then account for the missing user friendliness by not enforcing but using mutual encryption? We'd combine that with nudging (annoying open lock icon in DM's where not both parties provide public keys) and a comprehensive step by step guide for those we successfully nudged into it.
I think implementing E2EE as Matrix did is the best solution. It's secure but not too much of a pain as you can also use QR signing in/verifying to unlock encrypted DMs.
Apparently the first problem is where it should keep the private keys. There are at least two methods.
- One is to encrypt them with the hash of user's password and keep them in the server, as @CraftyCorvid suggested.
- The other is to keep the private keys in the client side, which is more suitable for more security sensitive accounts like government accounts, etc. if they know how to protect them better.
I suggest to support both of them.
API
As far as I can see, on the server side we only need to support signing of the public keys by the domain's certificate and create the chain.pem file. The rests are client sides or API modifications. For instance, we have to add some fields in the Account, here, to wrap the public keys, and potentially symmetric-keys. It would be something like:
Account entity:
{
"id": "23634",
"username": "noiob",
"acct": "[email protected]",
"display_name": "ikea shark fan account",
"created_at": "2017-02-08T02:00:53.274Z",
....
"public_keys": [
{
"id": "privkey.pem hash",
"expire_date": "the expire date that already exists in the cert.pem file",
"public_key_pem": "cert.pem content",
"chain_pem": "chain.pem content, which is basically the public key signed by the root certificate of the server's domain certificate"
},
.....
],
"encrypted_private_keys": [
{
"id": "privkey.pem hash",
"expire_date": "the expire date that already exists in the related cert.pem file",
"private_key_pem": "Base64 of the symmetrically encrypted of the content of privkey.pem file with hash of the password",
},
....
],
"symmetric_keys": [
{
"id": "key hash",
"expire_date": "the expiration date",
"content": "Base64 of the encrypted JSON. The JSON sample could be found below."
},
.....
]
...
}
where encrypted_private_keys list can be empty if user could keep and protect the private keys by themselves. The public_keys can be used to verify signature of users on their contents, but the reason we added it to this thread is to provide a hand shake mechanism to exchange some symmetric-keys, or initiate any other form of end to end communication.
JSON sample for symmetric-key content:
{
"id": "key hash",
"key": "The key for symmetric cryptography",
"shared_with": [
"[email protected]",
"[email protected]",
]
}
The shared_with is the users that we shared this key with them to be able to only share content among ourselves, in a group, etc. Notice everything in the symmetric_keys list can potentially be recovered by using private keys in encrypted_private_keys list.
The hard part is the client side, because most of the encryption and decryption and security concerns should be taken in the client side. For instance, we need a standard for starting an encrypted conversation and its type.
encrypted message:
{
"encryption_type": "The type of encryption that the receiver client may or may not support it!"
"content": "the encrypted message"
}
Also we need a standard for backing up the private keys, so all the clients could restore the private keys, if the user decided to keep the keys by himself/herself. Something like:
content of backup file:
{
"acct": "[email protected]",
"encrypted_private_keys": [
{
"id": "privkey.pem hash",
"expire_date": "the expire date that already exists in the related cert.pem file",
"private_key_pem": "privkey.pem content",
"chain_pem": "chain.pem content, which is basically the public key signed by the root certificate of the server's domain certificate"
},
....
],
"symmetric_keys": [
{
"id": "key hash",
"expire_date": "the expiration date",
"content": "Base64 of the encrypted JSON. The JSON sample could be found above."
},
....
],
}
I forgot to address the issue with keeping private keys in client, when we have multiple clients. For instance, a user can sign in with web app in Firefox, "Toot in Android", and "Toot in iOS". In that case Firefox client cannot read a message that's sent, or received, to the "Toot in Android", simply because it doesn't have the private key, or symmetric-key, to decrypt the message.
To solve it, one solution could be improving the account addresses to include the client app's name in it. For instance, address of client apps in the previous example would be "@firefox-1@[email protected]", "@toot-android-1@[email protected]", and "@toot-ios-1@[email protected]", where "firefox-1" is the web app name that is running in the Firefox. The same for other client app names. They even could be defined by the user himself/herself. So as soon as a client app has an address in the network, the user can have a secure communication from one client app to the other one. The first simple application of it is being able to securely share the private keys from one client app to the other client app to decrypt the messages. Notice, we even have a standard for sharing the keys, which is the backup file protocol.
I forgot to address the issue with keeping private keys in client, when we have multiple clients. For instance, a user can sign in with web app in Firefox, "Toot in Android", and "Toot in iOS". In that case Firefox client cannot read a message that's sent, or received, to the "Toot in Android", simply because it doesn't have the private key, or symmetric-key, to decrypt the message.
To solve it, one solution could be improving the account addresses to include the client app's name in it. For instance, address of client apps in the previous example would be "@FireFox-1@[email protected]", "@toot-android-1@[email protected]", and "@toot-ios-1@[email protected]", where "firefox-1" is the web app name that is running in the Firefox. The same for other client app names. They even could be defined by the user himself/herself. So as soon as a client app has an address in the network, the user can have a secure communication from one client app to the other one. The first simple application of it is being able to securely share the private keys from one client app to the other client app to decrypt the messages. Notice, we even have a standard for sharing the keys, which is the backup file protocol.
How does one handle key revocation? (This seems like you're gradually re-implementing PGP subkeys...)
@kc9jud Very good question. Let me answer the re-implementation of PGP subkeys first. Here, we're talking about interfaces(API), not implementations. If it fits, people could use any implementation, including PGP, to handle them.
Regarding the revocation, I didn't consider to list all the fields in above suggestions. We also need to have a field like public_key_self_signature, along the public_key_pem, to handle the validity of the public key itself, which contains its self-signature. To revoke it we just need to replace the old signature with a new self-signature with signature type 0x20, based on this answer. So it'll be something like:
"public_keys": [
{
"id": "privkey.pem hash",
"expire_date": "the expire date that already exists in the cert.pem file",
"public_key_pem": "cert.pem content",
"chain_pem": "chain.pem content, which is basically the public key signed by the root certificate of the server's domain certificate",
"public_key_self_signature": "Base64 of the signature"
},
.....
],
https://github.com/mastodon/mastodon/issues/14576 seems like a better way to fix this.
To follow!
Excelente discusión.

You can subscibe to any issue/pr by clicking Subscibe button.
No need to comment like +1 or Good discussion @GatoOscuro
#14576 seems like a better way to fix this.
That could work, but Matrix is heavy.
Want to draw some attention to the matter since this https://www.neowin.net/news/admin-of-an-anarchist-mastodon-server-raided-by-fbi-insecure-user-data-gets-seized/