Clevis & Tang on NixOS


February 4, 2024

Motivation: full-disk encryption on remote servers

  • Servers data should be protected by full-disk encryption
  • Problem: full-disk encryption requires physical intervention at boot
  • Possible solution: spawn an openssh server in initrd. Even with this, synchroneous intervention is still required.

clevis, the automated encryption framework

The clevis project is a pluggable framework for automated decryption.

The encryption-decryption is left to jose, a C implementation of the JavaScript Object Signing and Encryption standard.


In clevis terminology, a pin is a plugin which implements automated decryption.

To encrypt some data with clevis, one can use this command:

clevis encrypt $PIN $CONFIG < $PLAINTEXT > $CIPHERTEXT.jwe

The TPM 2.0 pin

The clevis command provides support to encrypt a key in a Trusted Platform Module 2.0 (TPM 2.0) chip.

First, a key \(K\) is generated to encrypt the message. Then \(K\) is encrypted using the TPM 2.0 chip, and will be decrypted the same way when clevis needs \(K\) to decrypt the message stored in the JWE.

Figure 1: Components of a TPM.

Example 1  

echo -n $SECRET | sudo clevis encrypt tpm2 '{}' > secret.jwe

The tang pin

The tang project is a server implementation which provides cryptographic binding services without the need for an escrow. The clevis command has full support for tang.

Example 2  

echo -n $SECRET | sudo clevis encrypt tang '{"url": ""}' > secret.jwe

Shamir Secret Sharing (SSS)

Adi Shamir (1979)

The clevis command provides a way to mix pins together to provide sophisticated unlocking policies. This is accomplished by using an algorithm called Shamir Secret Sharing (SSS).

Example 3  

echo -n $SECRET | sudo clevis encrypt sss \
  '{"t": 2, "pins": {"tpm2": {}, "tang": [{"url": ""}, {"url": ""}] }}' \
  > secret.jwe

In depth view of the tang protocol

The Diffie-Hellman key exchange

  • \(g\) is a public parameter.

  • Hypothesis: Given \(C = A*B\), is is computationally infeasible to retrieve \(A\) and \(B\).

  • After the protocol execution, both parties have agreed on a common symmetric encryption key \(K\), while an eavesdropper has not aquired any information.

  participant Server
  participant Client
  Note over Server: Generates S
  Note over Client: Generates C
  Server->>Client: S∗g
  Client->>Server: C∗g
  Note over Server: Computes K = C∗g∗S
  Note over Client: Computes K = S∗g∗C
Figure 2: Key exchange.

The McCallum-Relyea protocol

  participant Server
  participant Client
  Note over Server: Generates S
  Note over Client: Generates C
  Server->>Client: g∗S
  Note over Client: Computes K = g∗S∗C
  Note over Client: Encodes the message with K
  Note over Client: Discards K
Figure 3: Key provisioning, message encoding.
  participant Server
  participant Client
  Note over Client: Generates E
  Client->>Server: x = g∗E + g∗C
  Server->>Client: y = x∗S
  Note over Client: Computes K' = y - g∗S∗E = K
  Note over Client: Decodes the message with K
Figure 4: Message decoding.

\[\begin{aligned} K' &= y - gSE = xS - gSE \\ &= (gE + gC)S - gSE = gES + gCS - gSE \\ &= gCS = K \end{aligned}\]

Security of the McCallum-Relyea protocol

  • An attacker gaining access to the client is not able to recover the secret if the machine is not able to contact the tang server
  • An attacker gaining access to the tang server is not able to recover the secret if it does not have access to the client
  • An MITM attacker can not infer any information on the secret by the same hypothesis as Diffie-Hellman

The NixOS implementation

The tang module

Added in #247037 by jfroche, thanks!

  • Enable it with:

    services.tang.enable = true;
  • Specify the range of IPs allowed to communicate with the tang server:

    services.tang.ipAddressAllow = "";

    (This has to be a trusted subnet that you fully control)

  • Don’t forget to open the TCP port in the firewall:

    networking.firewall.allowedTCPPorts = [ 7654 ];

    You can customize it with:

    services.tang.listenStream = [ "80" ];

The clevis module

  • Runs in initrd just before root disk decryption

  • Tries to run

    clevis decrypt < secret.jwe

    with provided secret

  • If success, uses the value to decrypt root partition

  • If failure, fallbacks on interactive unlocking


  1. First, create a secret using the clevis CLI:

    echo -n $SECRET | clevis encrypt tpm2 '{}' > secret.jwe
  2. Declare you encrypted devices in your NixOS configuration:

    boot.initrd.clevis.devices."rpool/root".secretFile = ./secret.jwe;

    Supported encrypted systems:

    • zfs
    • bcachefs
    • luks
  3. Profit!


  • One virtual machine with bcachefs as root partition
  • Two different tang servers (because why not)
  • One TPM 2.0 device
  • We require at least the TPM and 1 of the 2 tang servers to be available at boot time
Figure 5: How to use Clevis.

Future work

  • Package more pins (YubiKey for example)
  • Support more encryption solutions