## Picomemo

This repository contains a compact and portable implementation of the
cryptography required for XMPP's OMEMO (E2EE).

### Features

- Portable: besides x86 also runs on WASM, ARM and embedded systems like
  the ESP32!

- Compatible with other XMPP clients that support OMEMO.

- Low amount of code with few dependencies.

- High performance by using fast crypto when available.

## `omemo.c`

`omemo.c` contains implementations of X3DH, Double Ratchet and Protobuf
with an API that is specifically tailored to OMEMO. Without dependencies
on (any) libsignal or libolm code.

Both OMEMO 0.3 (`eu.siacs.conversations.axolotl`) and OMEMO 0.9
(`urn:xmpp:omemo:2`) are supported. By default OMEMO 0.3 is enabled and
when compiled with `-DOMEMO2`, OMEMO 0.9 is enabled. That means only one
is active at a time. Be careful to compile all of your code using
`omemo.h` with `-DOMEMO2` if `omemo.c` is also compiled with it.

## `gen/omemo0.c` `gen/omemo2.c`

These files and their headers are generated by splitting `omemo.c` and its header file in two, resolving the OMEMO2 define. All identifiers are also modified to add the OMEMO version number. This is done to make dynamic linking both possible.

The CI verifies that these generated files are up-to-date.

### Crypto functions

There are two "backends" for the underlying Curve25519 and EdDSA
cryptography. By default these are provided by
[HACL\*](https://github.com/hacl-star/hacl-star) as an amalgamation in
`hacl.c`. These are both fast and formally verified.

As an alternative there is the
[c25519](https://www.dlbeer.co.nz/oss/c25519.html) library, which is
also included as amalgamation in `c25519.c`. This
library was designed for low-memory systems and is significantly slower
on modern hardware than HACL\*.

## Dependencies

One of the following two must be available at runtime:

- MbedTLS 3.0+

- OpenSSL

For compiling the shared libraries:

- C compiler (gcc)

- GNU Make 4.2+

- Lua 5.4, or 5.1+

- POSIX commands

For testing & development: docker-compose, Python, openssl,
  rlwrap, socat, xxd, ctags-exuberant

## Usage

Compile libpicomemo0 and libpicomemo2:

```bash
$ make lib
```

Run the tests:

```bash
$ make test-omemo test-omemo2
```

Take a look at the Makefile if you want to have custom build
configurations.

### API

Refer to `omemo.h` for function definitions and function-specific
documentation.

To give an understanding how the API can be integrated in a client, here
is some pseudocode for a rough overview of how the functions can be used:

<details>
<summary>API usage in pseudocode</summary>

```diff
class XmppClient {
    struct omemoStore store
    Map<(Jid, DeviceId), struct omemoSession> sessions

    Setup() {
        // Loading/Saving OMEMO store (your bundle of keys)
        storebin = ReadFile("store.bin")
        if storebin {
>           omemoDeserializeStore(storebin, len(storebin), &store)
        } else {
            // Most calls return an integer, which maps to a OMEMO_E*
            // error code (error handling is not done in this example)
>           omemoSetupStore(&store)
            file = OpenFile("store.bin")
>           file.buffer_size = omemoGetSerializedStoreSize(&store)
>           omemoSerializeStore(file.buffer, &store)
            // Extract keys/ids from the store for publishing your bundle
            PublishBundle(&store)
        }
        // Key should be rotated once every week to month, remember to
        // save the store to disk after any changes AND publish your new
        // bundle.
>       setInterval(() => omemoRotateSignedPreKey(&store), 1*Week)
    }

    SendEncryptedMessage(to_jid, body) {
        // With OMEMO 0.3 you can omit omemoGetMessagePadSize
        encrypted_buf = malloc(len(body)+omemoGetMessagePadSize(len(body)))
>       omemoEncryptMessage(encrypted_buf, out key_payload, out iv, body,
        len(body))

        // Get device list via (cached) iq stanza
        for device_id in GetDevices(to_jid) {
            session = sessions.Get(to_jid, device_id)
            if not session {
                session = {0}
                bundlexml = FetchBundle(to_id, device_id)
>               omemoInitiateSession(&session, &store, bundle.spks, bundle.spk, ...)
                sessions.Set(to_jid, device_id, session)
            }
>           omemoEncryptKey(&session, out key_msg, key_payload)
            headers.append(MakeXml(key_msg.isprekey, key_msg.p, key_msg.n))
            // Save session into some database/file just like the store
>           omemoSerializeSession(..., &session)
            // Also save the store the store (not shown)
        }
        SendMessage(MakeXml(headers, encrypted_buf, iv))
    }

    event GotMessage(msg) {
        if msg.is_omemo_encrypted? {
            // For decryption, the functions omemoLoadMessageKey and
            // omemoStoreMessageKey will be called, you must implement
            // them just like omemoRandom at the top level (shown here
            // for demonstration purposes.
            int LoadMessageKey(session, key) {
                key.mk = skipped_keys[session][key.dh, key.nr]
                return Found?(key.mk) ? 0 : 1
            }

            int StoreMessageKey(session, key, n) {
                if n > MAX_SKIP_KEYS {
                    return OMEMO_EUSER
                }
                skipped_keys[session][key.dh, key.nr] = key.mk
            }
            // This should usually be done once at the beginning of your
            // program
>           omemoSetCallbacks(LoadMessageKey, StoreMessageKey, NULL);

            // Search the XML of the message for our key and get the
            // prekey/kex attribute
            key, isprekey = FindKeyForOurDevice(msg)
>           omemoDecryptKey(&session, &store, out key_payload, isprekey, key)
            if isprekey {
                // Remove session.usedpk_id from bundle. You should
                // actually wait a bit before removing the key.
                for (k in store.prekeys)
                    bzero(k) if k.id == session.usedpk_id
>               omemoRefillPreKeys(&store)
                PublishBundle(&store)
                // Send empty message
                SendEncryptedMessage(msg.from, nil)
            } else {
                struct omemoKeyMessage keymsg = {0}
>               omemoHeartbeat(&session, &store, &keymsg)
                if msg.n > 0
                    SendEncryptedMessage(msg.from, keymsg)
            }
            plaintext = allocate(len(msg.payload))
>           omemoDecryptMessage(plaintext, key_payload, msg.iv, msg.payload, len(msg.payload))
            // Save session and store here again (not shown), also save
            // the skipped keys!
            ShowPlaintextMessage(plaintext)
        }
    }
}

```

</details>

### Security

After calling any function of the API, the stack could be zeroized for
extra security. This is *not* done by the library.

### WASM

This library can be compiled to WebAssembly using emscripten. Both
crypto backends are supported, and since HACL\* is faster you should use
that one. In the CI, the tests also run in WASM environment.

## Contributing

Open issues and pull requests at [github.com/mierenhoop/picomemo](https://github.com/mierenhoop/picomemo).

Alternatively you can send an email or XMPP message to the
addresses listed at [mnhp.nl/contact](https://mnhp.nl/contact),
optionally with a git patch.

## License

The code in this repository is licensed under ISC, all dependencies are also permissively licensed.
