I’ve created a prototype for an encrypted real time (per line) collaboration feature for joplin desktop, based on websockets and sjcl. I didn’t pitch the idea here before i started as my main purpouse was to learn the basics on encryption, websockets and js in general. However i thought i might as well document my progress and bring the idea up for discussion here.
I’ll attach a demo to this post that show some of the features i’ve implemented so far in action. Feel free to comment on pros, cons and whatnot regarding design choices, assumptions, thoughts and the potential of this collaboration model as a future joplin feature.
The goal for this prototype was to see if i could get the following to work
- Connect two joplin instances to a websocket server
- Track each clients cursor moves (per line) and display it to the other clients
- Track changes in the note from each client (per line) and push changes to the other clients without exposing the contents to the server.
I recorded a short demo of how the prototype works in current state. Both clients and the server run on the same laptop from ca 2015 (i5 3GHz, 8gb ram on arch linux)
rt-collab-demo.mkv (3.8 MB)
Dependencies in current state
- ace editor (already in codebase, though i remember reading in some post it’s planned to be replaced?)
- i guess we are limited to standard keybindings
- sjcl (already in codebase)
Prototype design choices
Node.js based websocket server
As i mentioned, mostly because i was aiming to learn about websockets. However i do think this feature would be quite easy to set up for anyone with access to a server running node. As the current share feature is based on nextcloud, i guess this collab model could be an additional alternative for self hosters.
Transfering note changes between clients (lineUpdate).
I chose transfering whole lines as i figured the encryption part might cause performance issues if i were to encrypt and send updates letter by letter or word by word. Not neccesarily true, just an assumption on my part.
Per line updates also made for an easy way to build a server side model of the note in ciphertext, and keeping it synced between clients.
Client side symmetric encryption.
The encryption is based on a key that will somehow have to be shared between collaborators (in prototype it’s just a set variable). This is of course a drawback from a security standpoint.
One reason for not going asym on lineUpdates is performance, each client would have to encrypt each sent line with a separate pubkey from each of the other connected clients. With symmetric encryption and a shared key this only has to be done once per lineUpdate. independent of the number of connected clients.
Server, current state
- Stores the notes as line number and ciphertext in an array tied to a note object (stores nothing on restart right now).
- Pushes complete note to client on connect
- Recieves per line updates and cursor movements from clients
- Pushes updates to connected clients
- Pushes new clients to connected clients and vice versa
Client, current state
- Encrypts each line before sending it to server
- Decrypts each line recieved from server
- Uses a 256 bit shared key stored in variable
- Does not encrypt line numbers or cursor movements
- Is tied to a bunch of Ace methods to locally execute remote note changes. That part may or may not be a problem to solve depending on what new editor is implemented.
I’ve defined a simple API for client-server communication with messages sent over websockets in json-strings. Some of the key functions are listed below.
||Creates a marker for new collaborator
||Complete stored note from server
||Pushed from server on client connect, right now overwrites anything local.
||uid, line number
||Sends all vertical cursor movement to server (letting the server know which line each client is at)
||uid, line number
||Updates a marker object that highlights the line each collaborator is on.
||uid, line number, line ciphertext
||Sent when leaving a line. The client encrypts the contents of the line using sjcl.encrypt and a 256bit shared key, then sends line number and encrypted data to server
||uid, line number, line ciphertext
||Simply decrypts the received line and inserts it at line number, overwriting anything present on that line.
There’s a lot to be done still for this to become a usable feature. Below is a list of what my prototype currently lacks, in combination with hypothetical discussions on key sharing and gui implementations. There are many different ways a feature like could be implemented. Feel free to comment.
- Passing on the following to server and connected clients
- Deletion of lines (i’ve had a hard time catching del and backspace onkeydown, does anyone know if there’s a specific reason to that? Basically all other keys are catched and i have it working outside of joplin)
- Selections, cut, paste, deleting or replacing text by seleting and typing
- Images and other attachments
Thoughts on key sharing
The shared secret key should of course not be exposed to the server in plaintext. I guess it could be sent encrypted over the server together with salt to connecting clients, requirig only a password on the recieving end. Maybe it could also be done by a file shared by e.g email, that opens in joplin and contains an encrypted version of the shared key. A drawback for both of above alternatives is that they leave the shared key open to dictionary attacks if somehow intercepted.
Another alternative might be to encrypt the shared key asymmetrically. Each client would then need a public/private keypair in addition to the shared key.
- User management
- Note management.
- Note storage. Sqlite? Mariadb?
- Look over security
Client and thoughts on GUI-integration
- Settings, add a “Shares” tab for managing server credentials and keys
- Note, show names for online collaborators somewhere
- Note, give user markers different colors
- Notebooklist, possibly a new section for shares, first level being either user or server.
- on right click note -> share, an alternative could be added to share to a server
- I have not looked into this, but i believe that for this to become a usable feature it must also exist on mobile.