yujiri.xyz

The Sufec protocol

Sufec: Simple User Friendly Encrypted Chat.

Rationale

Secure messengers have been popping up like crazy in recent years. Matrix. Signal. Threema. Wire. Session. Briar. Tox. Jami. I can't even list all the ones I investigated in my search before deciding to make another. Sufec is necessary because all of these have fallen short in one or more ways:

Et cetera.

Use cases

One thing Sufec tries to do that most secure messengers don't is replace email. You can message a Sufec user without having to "send a contact request" or "invite them to a DM"; "conversations" or "rooms" are not even first-class things.

Besides the general impetus to minimize the number of applications necessary for digital life, a pressing reason to want to replace email is that it's ubiquitously used for sensitive things such as password reset codes and communication from banks and governments. That our society's standard is to send such things unencrypted is a travesty.

Sufec has no provisions for unencrypted messaging; we think that belongs in a different protocol.

What Sufec is not

Security model

Sufec should provide:

Protocol

Sufec is formally a federated protocol, similar to email. A user has a *homeserver* through which they receive messages, and a long-term public/private key pair, whose public part is their ID. There are no human-readable usernames as there are in email; your Sufec address is `<id>@<server>`, not `<name>@<server>`.

Unless otherwise stated, all numbers are serialized in big-endian format.

Handshake

Each Sufec message is encrypted with a key derived from the long-term key pair and an ephemeral key pair from each party (this is based on the Signal handshake and is supposed to have the same properties):

Explanation of the Signal handshake

(This is where the handshake differs from Signal, by the simplistic use of XOR instead of a special key derivation function. I conjecture that XOR is as secure as any other way of combining them, since all inputs are secret, it's irreversible, and produces no statistical anomalies such as non-uniform probability distribution.)

Connecting to a homeserver

Every interaction with a homeserver starts by connecting to it over TCP port 49002. The server sends its public key for transport encryption, which the client handles with TOFU (trust on first use) policy: remember the public key when connecting to a server for the first time, and alert the user on subsequent connections to the same server if the key ever changes.

While reading the sections for each type of conversation you can have with a homeserver after receiving its public key, bear in mind:

Sending a message

1. Send a byte with value 1 to indicate you are connecting to send.

2. Randomly generate a symmetric session key, anonymously encrypt it to the server's public key, and send. This doesn't authenticate the client because sending is meant to be anonymous. From this point on, all transmissions are encrypted with the session key.

3. Send the ID of the recipient.

4. The server sends the 1-byte number of devices the recipient has linked, then sends all of their ephemeral keys concatenated. If the recipient ID is not found, the server indicates 0 linked devices, and the client should abort and show an error message.

5. For each of those keys, do 6-7.

6. Encrypt the message with the key (as described in the Handshake section), then create the following concentation:

7. Anonymously encrypt this concatenation to the recipient's ID (so their homeserver can't read the metadata), then send the result. Only prepend the length for the first copy, since they must all be the same length.

8. Once the server receives such a payload for each key it gave you, it sends an arbitrary 1-byte receipt to indicate delivery was successful.

Logging in

This process is used in all of the conversations flows that require authenticating the client.

1. Encrypt your ID anonymously to the server's key and send that.

2. Do a handshake with your ID and the server's key to arrive at a symmetric session key. From this point on, all transmissions are encrypted with the session key.

Receiving messages

1. Send a byte with value 0 to indicate you are connecting to receive.

2. Log in.

3. Generate a new ephemeral keypair for receiving, and send your device ID followed by your new ephemeral key.

4. The server will send you any messages that were stored for you. Each message is sent as the anonymously encrypted concatenation as received by the server in Sending a message step 6 (preceded by its length).

5. After each message, you send back an arbitrary 1-byte receipt to indicate the receipt was successful.

The connection stays open and the server will send any new messages that arrive.

When an unrecognized client connects to receive, the server should treat this as a registration and create the mailbox.

Adding a device

To add a new device to your account, simply copy over the long-term private key and have the new device generate its own device ID. The new device can then connect to the homeserver in receiving mode and on seeing the unrecognized device ID, it will treat this as a newly linked device. From then on incoming messages will be stored until both devices have downloaded them.

Naming a device

The homeserver stores names of each device on your account visible only to you (see "Listing devices"). When a new device is added as described above, its name is empty. Alternatively, client programs may have a default name they set when you link them to your account, or may require users to set one before connecting. Any device can change its name with the following process:

1. Send a byte with value 2 to indicate you are connecting to name a device.

2. Log in.

3. Send your device ID.

4. Encrypt your desired device name anonymously to your ID (so the server can't read it).

5. Send your encrypted device name (preceded by its 1-byte length).

6. The server will send an arbitrary 1-byte confirmation to confirm the update was successful.

Listing devices

1. Send a byte with value 3 to indicate you are connecting to list devices.

2. Log in.

3. The server will send back the 1-byte number of devices on your account.

4. For each device, the server will send back its ID, and its encrypted name (preceded by its 1-byte length).

Removing a device

You can use any device that has your private key to tell your homeserver to forget about another.

1. Send a byte with value 4 to indicate you are connecting to forget a device.

2. Log in.

3. Send the device ID you want to forget.

4. The server sends back an arbitrary 1-byte receipt to indicate the device was forgotten.

This is *not* a secure response to a lost or stolen device. Such a device, since it has your private key, would be able to re-add itself to your account, and even if it couldn't, it would still be able to send on your behalf, which your homeserver has no control over. If a device with your long-term private key is lost, you should generate a new identity.

The intended use of this feature is to tell your homeserver to stop storing messages for a device you don't or won't possess anymore, for example if you have a hard drive failure, if you reinstall your operating system and forgot to back up the relevant files, or if you are wiping a device preparing to give or sell it.

Message format

A Message is serialized as, in sequence:

If the type is 0, the message is plain text.

If the type is 1, the message is a file, and there are these additional fields before the file content:

If the type is 2, the message is not part of a conversation but a contact sync message, sent to your other linked devices when you add or rename a contact. The message content is the 1-byte length of the contact's address, then the address, then the name. In a contact sync message, the other recipients field is meaningless, and it should be disregarded if it comes from someone other than yourself.

In a group chat, each participant should, for each received message, include a hash of it with their next outgoing message, confirming that they received it. This is necessary to prevent a group member from spoofing the recipient list, secretly sending different messages to different people. For example, if A, B, and C are in a group chat and A sends a message to B that includes C in the recipient list but doesn't actually send the message to C, or sends a different message to C, then B will notice that they never receive a hash of that message from C, and C will notice that they never receive one from B.

The exact input to hash is the serialized sender's address followed by the type indicator byte and message content.

Address format

An address is serialized as the ID followed by the homeserver name. The homeserver name can be a domain name, a dotted-decimal IPv4 address, or a bracketed colon-separated IPv6 address.

Cryptographic primitives

Sufec is based on libsodium, so:

Group chats

Group chats are implemented similarly to email: you just send your message to each recipient, and in the Message to each one, you include the list of *other* recipients. Client-side, messages with the same set of recipients can be sorted into "rooms", providing a UX not too different from what we expect from chat apps.

There is no moderation, no invite/leave/kick/ban commands, no questions about how to agree on what members are in a group.

Homeserver independence

One downside of most forms of federation is that you depend on your homeserver to interact with the network at all, and switching to a new one can be quite difficult since you need to message all your contacts and explain that you have a new address. One of the benefits of being `<id>@<server>` instead of `<name>@<server>` is that your ID is unspoofable, so when a Sufec client receives a messages from a recognized ID at a different server, it should automatically update its knowledge of that user's address (whereas in email, anybody could've made an account on a different server with the same username as your contact and tried to impersonate them). There is no need for out-of-band verification of your identity.

Peer to peer usage

Although Sufec is a federated protocol, we intend it to be usable in a peer to peer way by having a client act as its own homeserver, and having `<id>@<ip address>` as an address. This is part of the reason for the emphasis on homeserver-independence: someone can use it both ways, even in the same conversation, with minimal friction.

Linking a smartphone

Of course, one of the most convenient ways to link a phone is to have a desktop client that can show a QR code, and for every mobile client to be able to do this with every desktop client, we need a standard format for the data in the QR code. The payload should be:

This whole payload is encoded to base64 and used with the "binary" QR encoding scheme. Base64 is necessary because many popular QR scanners, including zxing and zbar, don't support true binary data.

Omitted features

Let's be straight up: Sufec is not going to have all the features getting cargo-culted into every modern chat app. Matrix tried, and look how that turned out (there's only one client that actually implements all the features and it's extremely buggy and contains anti-features). Features I intend to avoid:

Features I have left out for now, but might specify in the future:

Omitted security and privacy properties

Note some properties one might expect that are absent:

Roadmap

Currently there is:

A terminal client (most mature)

A GTK client

An Android client (least mature)

A server

Todo list for the project

FAQ (these have not actually been asked)

Why not TLS for transport encryption?

Why not encrypt the intent indicator byte?

The conversation flows are sufficiently different that it would be trivial for a network eavesdropper to tell the difference anyway.

Why not use randomly generated nonces on the transport layer?

That would allow one nonce to be used twice. A network attacker, such as an evil ISP, could record certain messages, such as the 1-byte receipt you send after downloading a message successfully, and then hijack the connection and send the same receipt after each subsequent message, emptying your mailbox without letting you download the others.

Another solution would be to send not a 1-byte receipt but a hash of the downloaded message, but that would be more expensive (computation and data size) and have no relative benefit.

Proxied content from gemini://yujiri.xyz/sufec.gmi

Gemini request details:

Original URL
gemini://yujiri.xyz/sufec.gmi
Status code
Success
Meta
text/gemini; lang=en
Proxied by
kineto

Be advised that no attempt was made to verify the remote SSL certificate.

What is Gemini?