light: secure messaging
Here’s a secure messaging system that you can’t hack remotely. You can use this to talk to your friends in a way that even mercenary spyware can’t access your messages. This is guaranteed by hardware-level primitives. Let’s call it light.
Our threat model is remote-only attacks, with ability of performing zero-click remote-code execution (mercenary spyware).
The basic idea #
light is an endpoint architecture: three independent nodes connected by unidirectional links. On top of this endpoint architecture, we implement public-key cryptography to encrypt+authenticate messages between peers. The nuance here is how each “endpoint” is constructed.
A conventional secure messenger does everything in one machine: parse untrusted network input, run crypto, handle keys, render + store plaintext, accept keyboard input.
light doesn’t do this. Instead, light carefully decomposes this monolithic networking + cryptographic stack into 3 different hardware-isolated nodes, connected via unidirectional links (data diodes).
System architecture #
Each endpoint is partitioned into three discrete nodes:
-
a TX computer (“the source”): takes plaintext you want to send, encrypts/authenticates it, outputs ciphertext and sends it to the GW. This computer isn’t connected to the internet. Even though it can send messages to GW, it cannot receive any bit of information from GW.
-
a RX computer (“the sink”): receives messages from GW, decrypts and displays plaintext on a dedicated display. RX can only receive messages to GW, it cannot send any messages to GW.
-
a GW computer (an untrusted “proxy”): this computer is connected to the internet and is connected to RX and from TX. Its task is to talk to other peers’ GWs and send/receive encrypted messages. This GW can use any transport layer: Signal messaging, XMPP, or other more sophisticated systems to provide better anonymity (tor, etc).
INTERNET
│
│ (bidirectional)
│
┌────▼────┐
│ GW │ (untrusted proxy)
│ │
└─┬───────┘───────┬
│ ▲
│ │
encrypted │ │ encrypted
(to RX) │ │ (from TX)
▼ │
┌──────────┐ ┌──────────┐
│ RX │ │ TX │
│ (sink) │ │ (source) │
└────┬─────┘ └─────▲────┘
│ │
│ │ plaintext
│ │ (input)
▼ │
┌──────────┐ ┌──────────┐
│ Screen │ │ Keyboard │
│ │ │ │
└──────────┘ └──────────┘
▲
│
┌─────────────┐│
│ Human ││
│ Operator │┘
└─────────────┘
There’s an additional unidirectional link from TX to RX. This is used to send direct keystrokes and have a smooth UI.
Note that the set of 3 computers work together, and this is abstracted away to the user. The user experience is identical to that of a regular computer (one keyboard, one display).
System security properties #
The trick is that the links are unidirectional. Observe that:
-
TX never sees untrusted input. This means this computer will always execute the original code, and it’s impossible to remotely exploit it. Egress messages will always be encrypted to the intended recipient.
-
RX does not have an egress path to the internet. This means that if this computer gets compromised, it’s impossible to exfiltrate keys or plaintext from this computer. The only “output” from this computer is a screen.
An attack: human-in-the-loop social engineering #
Note that there is an exfiltration path to the internet: via the human operating the node. A compromised RX node can’t send bits to the GW, but it can send bits to the user’s brain (by lying on the screen). For example, a compromised RX could display “Sync error. Please type the following ‘session recovery code’ into your TX terminal to reconnect: [hex-encoded private key]”.
There are two ways to mitigate this:
- Prevent that from happening with operator discipline. Train them in possible ways to trick them.
- Even if it happens, make exfiltration astronomically slow by using big-key cryptography
Big-key cryptography means using very long private keys. Instead of 32-byte keys, use (say) a 10MB private key. Exfiltrating a 10MB key with a human in the loop is so expensive and slow that exfiltration isn’t practical.
Cryptographic constructions #
The baseline construction is just public-key authenticated encryption (for example, using ECIES). This works well. Your peer’s public key can be hardcoded in the firmware of your TX (predistributed). Or alternatively you can introduce new peers by punching / typing the public key on the keyboard (which is connected to your TX). Note that TX has a unidirectional link to RX, so you can see your own messages you sent.
The full protocol goes like this:
- Alice TX generates $s:= (s_1, s_2)$ at random, uses it to key a symmetric big-key cipher and encrypt message $m$.
- Alice TX sends $c := (\text{pk-enc}(\text{pk-bob}, s_1), \text{big-pk-enc}(\text{pk-bob}, s_2))$ where pk-enc is regular ECIES and big-pk-enc is a big-key public key encryption scheme.
- Bob RX receives and decrypts $s$. Uses $s$ to decrypt $m$.
- When Alice wants to send subsequent messages, she sets $s \leftarrow h(s)$ and uses $s$ to encrypt a new message.
(Gateways omitted in the description above.)
A wrinkle is that you can’t do a full Diffie-Hellman exchange, since there is no bidirectional channel by design. This prevents us from getting the traditional “perfect forward secrecy” properties. We can get slighter weaker post-compromise security by hashing the key material on each message round. Like this, a compromise of endpoint at time N can’t reveal anything about N-1.
(You can still provide some security for future messages if you send on each message material to be added to the shared secret. That only works if the adversary doesn’t get a copy of every message. This assumption is pretty strong.)
Big-key public key encryption. For a concrete scheme, see the accompanying post.
Slow/high domains #
In addition to the segregation into RX/TX/GW explained above, there’s a hardware-enforced bandwidth limited channel between keyboard and TX, and between RX and display. The goal here is to restrict to a few bits per second the information that can flow in those links.
We do this to provide exfiltration resistance. We do not speed-limit the unidirectional links TX -> GW, TX -> RX nor GW -> RX. These channels can carry a considerable bandwidth (even though they are unidirectional) since big-key cryptography is expensive (can result in large ciphertexts too).
Unidirectional links consideration #
You can’t use ARQ or selective retransmission. We resort to the simplest forward error correcting code: just send the same thing a bunch of times, and hope for the best.
A more elegant solution could be using fountain codes. Keep sending constantly the most recent messages, in case the data link goes temporarily down.
Hardware #
I’m prototyping each node with 3 raspberry pis linked with unidirectional serial links. I’m using two UARTs in the raspberry: the hardware one and a soft uart via the kernel module https://github.com/oreparaz/soft_uart.
You can also implement all 3 nodes within a single FPGA. This is left for future work.