Jump to content

Rosenpass

From NixOS Wiki

Rosenpass implements a post-quantum-secure key exchange for use with e.g. WireGuard.

Setting up Rosenpass

This wiki page guides through the set up of an encrypted Wireguard with Rosenpass connection between two network hosts.

For this guide, we assume that there are 2 NixOS hosts, both reachable via the network hostnames server and client.

After successful setup, the hosts will be able to reach each other via these IPs:

  • server: 1:c:bad:c0de::1
  • client: 1:c:bad:c0de::2

Generate and distribute keypairs

Each peer needs to have a public-private keypair, for both WireGuard and Rosenpass. The keys can be generated on any machine that already has WireGuard and Rosenpass installed using the wg and the rosenpass utilities. Install these tools either temporarily with nix-shell -p rosenpass wireguard-tools or add these tools to your system profile.

We will first need to generate and then distribute the keypairs.

Creating the key pairs is simple, but to do this securely, it should happen on the respective hosts. This way it becomes a bit elaborate to distribute the public keys to the other respective peer:

ssh root@server "rm -rf /var/secrets/{rp,wg} && mkdir -m 755 -p /var/secrets/{rp,wg} && chown systemd-network:systemd-network /var/secrets/wg"
ssh root@client "rm -rf /var/secrets/{rp,wg} && mkdir -m 755 -p /var/secrets/{rp,wg} && chown systemd-network:systemd-network /var/secrets/wg"

ssh root@server "cd /var/secrets/rp && rosenpass gen-keys --secret-key pqsk --public-key pqpk"
ssh root@client "cd /var/secrets/rp && rosenpass gen-keys --secret-key pqsk --public-key pqpk"

ssh root@server "cd /var/secrets/wg && wg genkey | tee wgsk | wg pubkey > wgpk"
ssh root@client "cd /var/secrets/wg && wg genkey | tee wgsk | wg pubkey > wgpk"

rsync root@server:/var/secrets/rp/pqpk server.pqpk
rsync root@client:/var/secrets/rp/pqpk client.pqpk
rsync --perms --chmod=644 server.pqpk root@client:/var/secrets/rp/server.pqpk
rsync --perms --chmod=644 client.pqpk root@server:/var/secrets/rp/client.pqpk

ssh root@server "echo server wg pubkey is \$(cat /var/secrets/wg/wgpk)"
ssh root@client "echo client wg pubkey is \$(cat /var/secrets/wg/wgpk)"

Note down the results of the last two printed lines as these are the public keys that need to be entered in the following NixOS configuration snippets.

You can create as many keypairs as you like for different connections or roles; it is also possible to reuse the same keypair for every connection.

Server setup

Enable WireGuard on the server via /etc/nixos/configuration.nix:

{
  # ... rest of the server config ...

  systemd.network = {
    networks."rosenpass" = {
      matchConfig.Name = "rosenpass0";
      networkConfig.IPv4Forwarding = true;
      networkConfig.IPv6Forwarding = true;
      address = [ "1:c:bad:c0de::1/64" ];
    };
    netdevs."10-rosenpass0" = {
      netdevConfig = {
        Kind = "wireguard";
        Name = "rosenpass0";
      };
      wireguardConfig.PrivateKeyFile = "/var/secrets/wg/wgsk";
      wireguardConfig.ListenPort = 10000;
      wireguardPeers = [
        {
          # It is possible to restrict this to the actual IP here.
          AllowedIPs = [ "::/0" ];
          PublicKey = "<copy content of client.wgpk here>";
        }
      ];
    };
  };

  services.rosenpass = {
    enable = true;
    defaultDevice = "rosenpass0";
    settings = {
      verbosity = "Verbose";
      public_key = "/var/secrets/rp/pqpk";
      secret_key = "/var/secrets/rp/pqsk";
      listen = [ "[::]:9999" ];
      peers = [
        {
          public_key = "/var/secrets/rp/client.pqpk";
          peer = "<copy content of client.wgpk here>";
        }
      ];
    };
  };

  networking.firewall.allowedUDPPorts = [
    9999 # rosenpass
    10000 # WireGuard
  ];
}

Client setup

{
  # ...

  systemd.network = {
    networks."rosenpass" = {
      matchConfig.Name = "rosenpass0";
      networkConfig.IPv4Forwarding = true;
      networkConfig.IPv6Forwarding = true;
      address = [ "1:c:bad:c0de::2/64" ];
    };
    netdevs."10-rosenpass0" = {
      netdevConfig = {
        Kind = "wireguard";
        Name = "rosenpass0";
      };
      wireguardConfig.PrivateKeyFile = "/var/secrets/wg/wgsk";
      wireguardPeers = [
        {
          # It is possible to restrict this to the actual IP here.
          AllowedIPs = [ "::/0" ];
          PublicKey = "<copy content of server.wgpk here>";
          Endpoint = "<IP of Server>:10000";
          PersistentKeepalive = 25;
        }
      ];
    };
  };

  services.rosenpass = {
    enable = true;
    defaultDevice = "rosenpass0";
    settings = {
      verbosity = "Verbose";
      public_key = "/var/secrets/rp/pqpk";
      secret_key = "/var/secrets/rp/pqsk";
      peers = [
        {
          public_key = "/var/secrets/rp/server.pqpk";
          endpoint = "<IP of Server>:9999";
          peer = "<copy content of server.wgpk here>";
        }
      ];
    };
  };
}

As the keypairs are already in place, running nixos-rebuild switch (or any of the deployment tools you prefer) should result in a working setup.

If you need more connections, then create keypairs for more hosts, reuse the *client* config for them and extend the peer lists (for both WireGuard and Rosenpass) in the server configuration.

Testing the connection

Log in (preferably as root) on both servers and check if the network setup was successful:

# ip a
...
3: rosenpass0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
    link/none
    inet6 1:c:bad:c0de::1/64 scope global
       valid_lft forever preferred_lft forever

# wg show all
interface: rosenpass0
  public key: nF7sPZ6pfsyR2yVg+TBen2awq00iU54aMiQ1DPLxGWA=
  private key: (hidden)
  listening port: 10000

peer: sl23uUw0pdWO7vS/O6a7+ZBALLEPkf9jFEl8sviJsnY=
  preshared key: (hidden)
  endpoint: [2a01:4f8:c013:afbc::]:52845
  allowed ips: ::/0
  latest handshake: 59 seconds ago
  transfer: 2.00 KiB received, 1.43 KiB sent

# wg show all preshared-keys
rosenpass0	sl23uUw0pdWO7vS/O6a7+ZBALLEPkf9jFEl8sviJsnY=	2WlrMm+4c56xazoeewsEUmY9rnRhn1kKu9iAepoGCrg=

It is important that rosenpass0 exists at all, has the right configured addresses on both hosts, and that the other peer is correctly shown in the WireGuard output.

For Rosenpass, it is important that the last column in the output of wg show all preshared-keys is the same on both hosts, as this is the key that is exchanged via Rosenpass and then inserted as preshared key into the WireGuard connection.

Finally, both machines must be able to reach each other:

$ ssh server "ping -c3 1:c:bad:c0de::2"
PING 1:c:bad:c0de::2 (1:c:bad:c0de::2) 56 data bytes
64 bytes from 1:c:bad:c0de::2: icmp_seq=1 ttl=64 time=1.12 ms
64 bytes from 1:c:bad:c0de::2: icmp_seq=2 ttl=64 time=1.33 ms
64 bytes from 1:c:bad:c0de::2: icmp_seq=3 ttl=64 time=1.13 ms

--- 1:c:bad:c0de::2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 1.116/1.190/1.329/0.098 ms

$ ssh client "ping -c3 1:c:bad:c0de::1"
PING 1:c:bad:c0de::1 (1:c:bad:c0de::1) 56 data bytes
64 bytes from 1:c:bad:c0de::1: icmp_seq=1 ttl=64 time=1.03 ms
64 bytes from 1:c:bad:c0de::1: icmp_seq=2 ttl=64 time=1.40 ms
64 bytes from 1:c:bad:c0de::1: icmp_seq=3 ttl=64 time=1.97 ms

--- 1:c:bad:c0de::1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 1.027/1.466/1.969/0.387 ms

Troubleshooting

The rosenpass0 device does not pop up

This is most likely an issue in the non-Rosenpass part of the configuration. Please have a look at the output of the command systemctl status sytemd-network. If for example file permissions of the keys are too restrictive, then this will manifest as error messages here.

Rosenpass is not able to exchange keys

Even if the rosenpass0 device pops up, it still might be possible that Rosenpass observes errors during setup or connect. Please have a look at the output of the command systemctl status rosenpass. If for example file permissions of the keys are too restrictive, then this will manifest as error messages here.

See also