We would like to commission a plugin that enables private, encrypted messaging between end-users.
As it stands there are very few venues online for secure, private, long form communication. The existing tooling is usually quite arcane and requires extreme technical skills to use. As a result majority of “private” communication is done “short form” in Telegram, Skype or WhatsApp. Additionally, this mode of mass communication is not properly auditable as it builds on closed source tooling and often closed source protocols.
We would like to build a highly usable and auditable solution for encrypted messaging in a dedicated Discourse plugin.
To achieve this we would like to build on a few principle
Build the solution on top of Web Crypto API
Have the server store an encrypted private key and public key per user
Have the server store an encrypted conversation key per participant in private messages (encrypted using end users private key)
Have all markdown and titles stored in the raw encrypted form per message (encrypted using conversation key)
Accept the limitation that the server knows who talked to who and when (we are only protecting the “what”). Accept the limitation that search will not work for this content.
Proposed end user experience
Jane heads to her user page and clicks the “enable encrypted messaging” button. Once clicked she is prompted to enter a secret, the UI clearly explains that if she forgets this secret she will NEVER have access to her encrypted messages anymore.
Once encrypted messaging is enabled a new
[ ] will appear in the create message UI with the text
[ ] encrypt message. This will only be clickable if all the recipients have “enable encrypted messaging” turned on, if some do not have public keys, when attempting to click it we will say: “Sorry but some of the participants do not have encrypted messaging enabled”.
Jane decides she wants to talk privately to Pete, she adds him to the participant list and clicks
encrypt message and sends a message to Pete.
Pete receives a notification saying that Jane sent him an encrypted message. There is no title, just information that an encrypted message exists and links to the message (both in email and web notification)
If Pete is on the original device where he enabled encrypted messaging his secret passphrase was already entered and the credential already stored in index db. If Pete is on a new device he will be prompted to enter his secret passphrase to turn on encrypted messages.
The UI clearly displays an overlay or visual hint to show that the message is encrypted.
Once communication starts between Jane and Pete, either of them can invite new people to the message provided they have public keys.
Pete or Jane may change the secret passphrase whenever they wish, if they do so the private key will be re-encrypted using the new passphrase and shipped to the server. (For V1 we will not allow changes to the actual private key, just passphrase)
Private and public key pairs will be generated 100% on the client side and then the private key will be encrypted using AES symmetric encryption prior to handing it to the server for safekeeping, we will stretch the passphrase using randomly generated salt stored on the server. Key stretching will be done on the client using PBKDF2 (available in web crypto api). The private/public key pair will be stored on the client in IndexDB using a non exportable
CryptoKey object. Exportable CryptoKey data will be removed from memory as soon as possible.
Encrypted private key and public key will be stored in a user custom fields. Only
current_user will be allowed to read/write the encrypted private key, all logged on users will be allowed to read public keys for all users.
Conversation keys will be generated client side on conversation initiation or invite and encrypted using all the public keys involved in the conversation. This data will be stored in a dedicated table
(user_id, topic_id, encrypted_conversation_key), this row will be creatable by any user in the conversation but only readable by
current_user == user_id.
Special care will be taken never to leak out unencrypted data to the server, this includes ensuring drafts do not get stored unencrypted.
To avoid a large class of errors new DOM elements (with new ids) will be used for the composer edit and preview. Current
.d-editor-input will only ever contain encrypted blobs and traditional preview will be disabled. (meaning 2 textareas will be at play, encrypted and unencrypted) Encryption will be heavily debounced to avoid a class of performance issues.
For V1 (and the scope of this work item) we are happy to eliminate certain features, including upload support, oneboxing of encrypted content and other complex edge cases.
We are fine to decrypt / bake and pass HTML through whitelister on-the-fly when rendering this private content.
Prior to implementation a very detailed spec will be created including web mockups and most importantly detailed security overview.
This plugin must include a comprehensive set of client and server based unit/integration tests. If you take this project on, write tests early, do not wait until the project is finished to retrofit tests.
WebCrypto API is still relatively young but quite widely supported, the plugin should work across all our supported browsers.
Simple rough mockups
* when starting a message user can opt to encrypt the conversation, a warning will be displayed if any of the participants are missing public keys.
* users can enable encrypted messages by clicking that button and working through a UI that generates the key pair.
If encrypted messages are enabled it should simply display “You have encrypted messaging enabled”.
* Some sort of icon, or overlay will denote that a message is encrypted.
This is a very complex piece to build that required very deep integration and careful review. We expect it is extremely well tested (using both client side and server side tests). Our current budget for an MVP is $10K USD.