Skip to content

Linked accounts (proposal #1)

Joshua Thijssen edited this page Mar 24, 2021 · 8 revisions

To minimize the friction when creating organization accounts, we can enforce that the organization accounts are actually linked accounts to a personal one. This way, it will be easier to set them up, and the organization will have the ability to remove the access to it or move it at any time. They will also allow linking personal accounts if you don't use an old account anymore or because you want to.

What is a linked account?

A linked account will be basically an account that is associated with another account. When we link john@acme! to john! all messages sent to john@acme! will actually be sent to john!. Also, john! could send messages as if they were coming from john@acme!. Basically, a new redirect field will be added to the resolver.

But not only organizations can link accounts, but also regular users could link their accounts.

How to link accounts?

From a user perspective, a way to link personal accounts could be this:

john@dev$ ./bm-client account link --account src! --to dst!
john@dev$ ./bm-client account unlink --account src!

You need to have access to the private key of src!.. but you don't need access to the private key of dst!. This means you can link to EVERY address possible if you like.

or this one, for organizations

john@dev$ ./bm-client organisation account create -a john@acme! -t john!
john@dev$ ./bm-client organisation account link -a john@acme! -t jane!
john@dev$ ./bm-client organisation account delete -a john@acme!

Please note that organization accounts will always be linked accounts, so routing_id will be empty.

When you link an account you basically set a new redirect field to the resolver pointing to the recipient hash.

Receiving messages

john@dev$ ./bm-client message compose -f someone! -t john@acme! -s "Testing linked accounts" -b "default,Hello!"
  • When someone sends a message to john@acme! their client will query for that address on the resolver.
  • The new redirect field will be pointing to the hash of john!.
  • Your client will query the new hash
  • It will use the public_key to encrypt the message and send it

This way the message is E2EE using the key of the actual recipient. The message will appear as destined to john@acme! instead of john! so your client can sort the messages appropriately.

Sending messages

john@dev$ ./bm-client message compose -f john! --as john@acme! -t someone! -s "Replying to someone" -b "default,Hi there!"

If john! wants to send a message as john@acme! then the client needs to set appropriate headers so the message will appear as coming from john@acme!.

  • The client will use the private key of john! to encrypt the message
  • It will check that the --as parameter is given so it will query the resolver for that address If the address is linked to john!, it will set the header as from john@acme! Should we set a new SignedByType?
  • When someone! receives the message, it will see that it's coming from a linked account so it will use the linked account public key to decrypt it

Questions that arise

Pros

  • no complex organization stuff anymore on servers (invite tokens, whitelists of organizations, etc)
  • no key checks for organizations when trying to delete keys
  • a good cleanup of the key resolver with the organizational parts (I'm not sure if we need to send both parts anymore)
  • we can easily remove a user from an organization

Cons

  • extra complexity in setting up the symlinking (we need to have extra fields like redirect = <sha> and do an extra lookup in the key resolver.. but that transparent for the user

  • what about symlink to symlink?

    Solved: This should not be a problem, but we need to consider the following few issues below:

  • what about the symlink loop (a to b to c to a)

    Solved: This should be detected when creating a symlink by the resolver. If a loop is found, the symlink may not be created.

  • We should follow redirects up to a maximum of nested redirects (5?) and check if there is a loop

    Solved: This should be done transparently on the key-resolver as well. It will redirect up to a certain level.

  • what happens when the organization links the account to other users? old messages sent by the original user won't be able to be decrypted by the recipients since they can't access the old public key

    Solved: Old keys must be available to verify the client signature that is sent with a message. To authenticate a message, these old public key must be available. To do this, we can add the public key of the sender to the message catalog (we already do this). This, however, does not help authentication, because we still do not know for sure if the key is from the actual user that has sent the email (anyone could generate a keypair and send the message through their own server system). To verify authenticity, the recipient can check with the key resolver if the message's key belongs to the user (see below).

Resolving old keys

The key resolver does not store old keys but only the current key used for receiving mail. To check authenticity, the key resolver is used as OOB information for checking the client signature in messages. When a user rotates their key, the old key is not found anymore on the key resolver, making verification of the client signature impossible.

We solve this by sending the public key with the message. This already happens. To authenticate that it really is the sending user that has this key, we can ask the key resolver if the key is known for that user:

  https://resolver.bitmaelum.com/account/<address>/<sha256 fingerprint>

This will return either a exist (204) or not found (404) response. This way, we can detect if a user has held a given public key, but we cannot query any keys as they are not available (anymore) on the resolver.

Any key rotation on the key resolver will add the hash-fingerprint tuple to the key history.

We store the history of old keys onto the key-resolver. So when you query a fingerprint on the resolver for a certain account, it can tell you if that was actually a key in the past.

This way, we can make sure that messages can be read even when the key is not valid anymore (because the symlink has changed, or the key has just rotated or any other situation).

We send the fingerprint of the CURRENT key inside a message. This is the key we need to check authenticity against.

Clone this wiki locally