Home / GitHub Page

Sharing notes or notebooks

Interesting idea - from reading the Zerobin project page (which, incidentally, is unmaintained) they do client side encryption and the server stores the (encrpyted) message against an ID. The client then tacks the symetric AES key onto the share URL which you then send to the other recipient (and doesn’t touch the server). It’s a simple method of creating a secure channel, and relies on a single shared key known only to the client(s).

That would absolutely work in a one directional situation where a creator is sending a note to several clients. But if said clients also want to modify the note, things would get complex fast - how would you maintain sync between multiple clients who may be editing slightly different versions?

You could solve this by descoping collaborative editing (which in fairness I put into scope about a paragraph ago by inferrance). The recipients would get a read only copy at the point of sharing, but that would be it.

I supposed “sharing” by “showing” but not “modifying” notes, in that case it’s dead i think

Yes Joplin supports multiple master keys so I was thinking of having a special Share master key, which you send with the note. It’s complex both at the backend and frontend level (lots of UI to develop for each app) so not sure if it will be done. Sharing notes is complicated so I’m looking for a way to do it one step at a time, and that means the first version is unlikely to support E2EE at all.

There’s also keybase, but I’d refrain from using an external service. The whole idea of Joplin is that you can host it yourself. Using an external service for sharing will break this premise.

i agree
if we want sucha feature in the Joplin World it should be another project

On a whim, I’ve started building something that’s essentially a headless pastebin:
joplin-share-server. (no code yet, fully README driven development…)

The concept is that every Joplin client has AES-128 or 256 encryption ability built into it. Through some means (outside of the scope of what I’m doing here!), a note can be encrypted and sent to this server. When this happens, you get a note ID back from the server, which you can use to build a URL which will be sent to the recipient.

The recipient can then use that note ID to retrieve the (encrypted) contents of the note as initially shared - their client would then place a copy of it (!) into their own notebook. The recipient would be provided with the encryption key somehow (perhaps by using the clever trick that Zerobin did by tacking the key onto the end of the URL with an anchor tag) to decrypt the note.

Of course, the limitation here is that the note is only in sync when it it shared - any updates afterwards are not synchronised. If you updated the note and wanted to share the new contents, you’d need to share it again.

However, it does mean that the key is only required at the point of importing the note - therefore isn’t stored anywhere afterwards, so avoids the problem of plaintext key storage (at least for now).

It’ll run in a Docker container, and could be self hosted.

In fact, after the user has entered the key, couldn’t we store it in the js LocalStorage so that it doesn’t need to be provided every time?

Since it’s client side I guess it can be implemented in such a way that no server can access that particular key, thus keeping it E2EE.

Your repo is 404 by the way.

1 Like

It was private :slight_smile:

So yes - I was thinking you could keep the key in LocalStorage on the device you accepted the share from (I keep changing the langauge I use, I’m not sure what the right words are here!)… but the problem is what happens if you open that note on another device? If you were going to get updates to a note, you’d want any device to be able to get updates - right?

joplin-share-server now has some functionality. You can POST encrypted notes at it, and GET encrypted notes from it.

Currently, that is about it! Whilst there is a version field in the note object, it’s always going to be 1 for the moment until I work out how to send updates.

So, with this “release”, this could be implementeed in the client:

In the sender’s client, it would to encrypt a note with a key (lets say abc123), POST that note to the server - which returns a UUID v4 back to identify it (lets say b0bd7fba-c1b4-46e6-9484-5e047f05df0b).

The sender’s client could then build a URL, like https://share.joplinapp.org/note/b0bd7fba-c1b4-46e6-9484-5e047f05df0b#abc123

The sender then conveys that URL to the recipient somehow.

The recpient’s client could intercept that URL when clicked (definitely possible with React Native, probably with Electron too), then make the HTTP request it to get the encrypted note, then use the key from the anchor tag to decrypt it, then save it in the recipient’s notebook.

One way sending of notes from one person’s notebook to another.

I have an idea for how updates could work, as well. It involves encrypted operation-based CRDT update messages. That’s a bit more ambitious, but if that’s possible you can do real-time collaborative editing potentially.

1 Like

Wait, wait. The E2EE assertions don’t sound right.
Keep in mind that while I’ve read a bunch on this topic and this scheme is based on what I vaguely remember reading, I couldn’t tell you what exactly it was. Feel free to point out any errors, I might be wrong.
I think one of the schemes that would work is:

  1. generate one key as usual, let’s call it General
  2. generate another key per user. This is the one the user has a password for.
  3. encrypt the General key with the User’s.
  4. upload everything to Joplin as usual

Decrypting notes:

  1. Use the user’s password to decrypt his User key
  2. Use that to decrypt the General key
  3. Use the General key to decrypt notes, as usual

Analogously for encryption.

Sharing:

  1. Get the receiving user’s (public) key (Recipient)
  2. Get the unencrypted version of the General key
  3. Encrypt General with Recipient
  4. Upload it

After sync, the receiving user decrypts the General key with his very own private key (from the Recipient’s pair, that is) and is good to go.

So the tld;dr is, you would not directly have the password for the master key, it would instead be encrypted with your personal key. When you need to start sharing, you can encrypt it with the Recipient’s key, never giving your password away, or asking for theirs.

Thoughts? What did I miss?

Ps: if this worked, there would also be a clean migration path from the old model to the new. The current master key would become the user’s key, and a new General (master) would be generated. Yes, this would necessitate re-encrypting the entire database and would incur a substantial (if one-time) performance hit. OTOH, the user would not have to do a thing.

This is assuming that there is a public key. As far as I can tell, they are symetric keys (certainly up until now there would be no need for public / private keys)

1 Like

Come to think of it, maybe all of that will be unnecessary, depending on how the rest of the sharing works.
If I could, for instance, add a notebook that is synced from a different sync target (I’ve seen requests for this, and similar things, around the forum), then that target’s master password could be shared between parties without exposing the rest of my (unshared) notes.

As far as I’m concenred, that would be enough and would require no changes to the encryption scheme.

It would be cool if you could make a “shared notebook” which will act differently than a notebook.

Notebook will be your personal notebook which is stored on your device or in the cloud of your choice’

Shared notebooks can be stored on your device and shared with syncthing or be stored in the cloud. You could have your personal notebook stored on your nextcloud server and then have a shared notebook stored on a different nextcloud server.

That is how I can see this being well implemented.

Two thoughts. One is on backend-native sync. Don’t expect them to all do the same thing or to work well enough to support Joplin use case. I tried to sync my atom notes through dropbox and it kept duplicating files every time I had concurrent changes. No attempts to merge what-so-ever. May have improved since.

The other point is related to concurrent changes. To allow shared editing, the larger the number of people and the more off-line the editing happens, the harder it is to reconcile the different versions. It’s like doing merges in git without manual intervention or with a UI that’s accessible to non-tech people. From reading this thread (very long so I may have missed something, sorry) I am not sure people are completely aware of this challenge, since E2EE has dominated the discussion. One alternative would be shared reading, not editing, in which case this problem disappears. For editing, I would look into CRDTs and in particular http://swarmdb.net/about/ or https://github.com/yjs/ since they are in javascript. Particularly swarmdb docs talk about using any storage or transport as backend. Not used myself, not a JS developer.

I think whether there are 10 users editing the same note or just 2, conflict resolution will be the same.

Most likely conflicts will be handled like this:

  • first use diff-match-patch to merge the changes
  • if it can’t do it, build a “conflict note”, and ask the user to manually fix it.
    I think it’s not too complicated to implement and should work well in the general case.

I put my thoughts into a blog post, as they’re a bit long for a forum.

Realtime collaboration is hard :open_mouth: I’d definitely not rely on a shared backend to do conflict resolution for you.

Thanks for sharing @naxxfish, your spec might be useful if someone decides to look into it.

Another approach is to leverage the existing sync functionality and conflict resolution of Joplin along with the file sharing of Nextcloud.

This can work if both users are on Nextcloud. You can share one of your file on your Nextcloud sync folder, and the person you share with would place this file in their own sync folder. Any change from any Joplin client to this file would be applied to the client of the other user, and any conflict would be handled by the current sync algorithm.

I’ve tested this and it works fine. The only thing, as you’ve correctly identified, is that you still need a central location (in this case, a Nextcloud app) that would generate the share, and place the file in the other person’s sync folder.

It’s just an idea at this point but maybe one way to get the feature working while keeping things relatively simple.

I currently have this working via Syncthing.

  1. /sdcard/Notes is the fs sync target on my Joplin installs
  2. All devices have Syncthing installed and syncing that folder

It works quite well. Syncthing does version history and simple conflict resolution. Though I’ve never ran into conflicts with Joplin even when trying to force it. This is fine for me, I’m syncing shopping lists and stuff. In the worst case I can dive into Syncthing’s version history.

This would perfectly satisfy my use case (i.e., simple syncing between me and my partner) if the syncing was scoped to the notebook level. Currently Joplin syncs all notebooks. :disappointed_relieved:

I haven’t tried it with encryption, as that’s outside my use case. All my devices are already full disk encrypted, and if the data isn’t going to the cloud, I don’t need another layer of encryption at rest (Syncthing e2e encrypts on the wire of course).

This works today for those who want to sync all of Joplin’s contents. But then you could just use the built-in options :confused:

Any chance of per notebook syncing in the future?

Probably not, but sharing a notebook is something I’d like to support.