Rosenpass Documentation

repository·main·Indexed 23 days ago

https://github.com/rosenpass/rosenpass

A protocol and toolset providing hybrid post-quantum security to WireGuard by establishing and refreshing symmetric keys. It includes the rosenpass reference implementation, the rp VPN frontend, and the rosenpass-protocol. The project also provides security analysis tools using proverif, cryptographic primitive benchmarks, and the rosenpass-to Rust crate for handling destination parameters.

Tokens
51.1K
Snippets
122
Records
216
Agent score
80%

What's inside Rosenpass

  1. Overview of Rosenpass components

    main

    Rosenpass consists of three primary components:

    1. rosenpass tool: The reference implementation of the Rosenpass protocol. It establishes a symmetric key and provides it to WireGuard via the PSK feature, providing "hybrid security" (cryptographically no less secure than WireGuard alone). It refreshes the symmetric key every two minutes.
    2. rp frontend: A Rust-based tool that integrates Rosenpass and WireGuard to create a VPN. It is designed for ease of use but runs as root and manages a single interface.
    3. rosenpass-protocol: The underlying protocol described in the Rosenpass whitepaper.

    Developers can use the rosenpass tool in stand-alone mode to write keys to files instead of supplying them directly to WireGuard. This allows for integration with other tools or running Rosenpass in more secure environments like containers, jails, or VMs.

  2. Configure Rosenpass networking modes (Client vs Server)

    main

    Rosenpass does not enforce a strict separation between clients and servers. The behavior is determined by the listen and endpoint options:

    • Client Mode: Occurs if you do not specify the listen option. Rosenpass and WireGuard will choose random ports.
    • Server Mode: Occurs if you do not specify the endpoint option. Rosenpass will wait for connections from peers instead of attempting to connect to one.

    You can specify both options if needed.

    Port Allocation: If you specify port N for Rosenpass, the rp tool will automatically allocate port N+1 for WireGuard.

  3. Understand Rosenpass protocol roles and state management

    main

    The Rosenpass protocol defines two roles: initiator and responder.

    • Initiator: The party that starts the handshake. The initiator is stateful and directs the process.
    • Responder: The party that reacts to the initiator. The responder is stateless for most of the protocol to prevent state disruption (DoS) attacks. Any necessary responder state is stored in an encrypted cookie called a "biscuit".

    Role Switching and Timing

    There is no negotiation for roles. Instead, roles are determined by timers:

    • At startup or during a timer-triggered rekey, a Rosenpass instance will act as an initiator.
    • When another peer starts a handshake, the local instance acts as a responder.

    To ensure participants take turns, the reference implementation uses different rekey intervals:

    • Initiator rekey interval: 130s
    • Responder rekey interval: 120s

    Implementation Requirements

    • Implementations must support one ongoing initiator-role handshake and many concurrent responder-role handshakes.
    • If a responder successfully completes a handshake, it should abort any ongoing initiator-role handshakes.
    • Implementations should use different back-off periods for initiator vs. responder roles.
  4. Use the `To` trait for destination parameters

    main

    The rosenpass-to crate provides a pattern for handling destination parameters in Rust functions. Instead of forcing a specific order for source and destination arguments, it allows callers to choose between two styles:

    1. Chained method style: copy(source).to(dest) (information flows left-to-right).
    2. Function style: to(dest, copy(source)) (information flows right-to-left, similar to assignment).

    This pattern is particularly useful for functions that would otherwise require manual allocation or inconsistent argument ordering.

    use rosenpass_to::{to, To};
    
    let mut dst = [0u8; 4];
    // Chained method style
    xor_slice(flip0).to(&mut dst);
    
    // Function style
    to(&mut dst, xor_slice(flip0));
  5. Configure Hash Functions (BLAKE2b vs SHAKE256)

    main

    Rosenpass supports two primary methods for keyed hashing:

    1. BLAKE2b: The standard keyed hash function used in the reference implementation.
    2. SHAKE256: An alternative where the key is prepended to the variable-length data before evaluation. To maintain compatibility without explicit version numbers, SHAKE256 is truncated to 32 bytes.

    These can be configured on a per-peer basis. The cookie mechanism in Rosenpass always uses SHAKE256.

  6. Manage Rosenpass server state and biscuits

    main

    A Rosenpass server maintains several types of state to manage peers and handshakes.

    Global State

    • sskm, spkm: Cryptographic keys.
    • biscuit_key: A randomly chosen key used to encrypt biscuits. Note: This should be rotated frequently. Implementations should keep two keys in memory during rotation to prevent packet loss.
    • biscuit_ctr: Used for retransmission protection.
    • cookie_secret: A randomized secret for deriving cookies sent to peers under load (changes every 120 seconds).
    • peers: Lookup table mapping peer IDs to internal structures.
    • index: Lookup table mapping session IDs to handshakes or live sessions.

    Peer State

    • psk: Pre-shared key.
    • spkt: Peer's public key.
    • biscuit_used: The biscuit_no from the last accepted biscuit.
    • hash_function: The configured hash function (SHAKE256 or BLAKE2b).

    Biscuits (Responder State)

    Because the responder is stateless, it stores its state in a biscuit returned to the initiator in the InitConf packet. The biscuit is encrypted with XAEAD and contains:

    • pidi: Initiator's peer ID.
    • biscuit_no: Derived from biscuit_ctr for retransmission detection.
    • ck: The chaining key.

    Lifecycle: The reference implementation retires biscuits after 5 minutes and erases them after 10 minutes.

  7. Understand Rosenpass protocol packages

    main

    Rosenpass uses several package types within an Envelope to facilitate the handshake and data transmission.

    PackageDescription
    InitHelloThe first handshake package, sent from the initiator to the responder.
    RespHelloThe second handshake package, sent from the responder to the initiator. Contains an encrypted biscuit fragment.
    InitConfThe third handshake package, sent from the initiator to the responder. Contains an encrypted biscuit fragment.
    EmptyDataAn empty payload package used as an acknowledgment to abort data retransmission.
    DataA package for payload data transmission. While specified for WireGuard compatibility, Rosenpass generally focuses on key exchange and lets external applications handle data.
    CookieReplyUsed for denial-of-service (DoS) mitigation via proof-of-IP-ownership.
    biscuitAn encrypted fragment embedded within RespHello and InitConf used to allow the responder to remain stateless.
  8. Understand Rosenpass Protocol Extensions

    main

    Rosenpass is designed to be extensible beyond its primary use case of securing WireGuard. This is achieved through protocol extensions, which allow for alternative osk (OQS Shared Key) labels and namespaces.

    By changing the namespace (e.g., from rosenpass.eu to myorg.eu) and the label (e.g., from wireguard psk to MyApp Symmetric Encryption), the protocol can be used for various symmetric encryption scenarios.

    The standard extension for WireGuard uses the domain separator: [PROTOCOL, "user", "rosenpass.eu", "wireguard psk"].

  9. Understand symmetric key and nonce naming for payload data

    main

    When generating keys and nonces for payload encryption (e.g., for use in an external application), Rosenpass uses a redundant naming scheme to ensure both sides of a transmission use matching keys.

    Format: [direction][type][role]

    • Direction: tx (Transmission), rx (Reception).
    • Type: k (Key), n (Nonce).
    • Role: i (Initiator), r (Responder), m (Mine), t (Theirs).

    Key Logic: If you are the Initiator:

    • Your transmission key is the responder's reception key: txki = rxkr.
    • Your transmission key is also your own transmission key: txkm.
    • Your reception key is the responder's transmission key: rxki = txkr.

    Note: A previous naming scheme using ini_enc and res_enc is deprecated. Use the tx/rx scheme instead.

  10. Understand Rosenpass key and ID naming conventions

    main

    Rosenpass uses a specific four-character naming scheme for KEM (Key Encapsulation Mechanism) variables and two-character schemes for IDs. This ensures clarity regarding the key type, its role, and which peer it belongs to.

    KEM Keypairs and Ciphertexts

    Format: [type][secret/public][k][role]

    • First character (Type): s for Static, e for Ephemeral.
    • Second character (State): s for Secret, p for Public.
    • Third character: Always k.
    • Fourth character (Role): i (Initiator), r (Responder), m (Mine), t (Theirs).

    Example: spki is the Initiator's static public key.

    IDs

    Format: [type][role]

    • First character (Type): sid for Session ID, pid for Peer ID.
    • Second character (Role): i (Initiator), r (Responder), m (Mine), t (Theirs).

    Symmetric Keys

    • psk: A Pre-Shared Key optionally supplied as input.
    • osk: The Output Shared Key generated by Rosenpass (e.g., for WireGuard).
    • ck: The Chaining Key, representing the intermediate protocol state.
  11. Manage handshake biscuits for replay protection

    main

    Biscuits are used to store and load session state. To enable replay protection, the protocol uses biscuit_ctr, biscuit_used, and biscuit_no variables.

    Storing a Biscuit

    When calling store_biscuit(), the biscuit_ctr is incremented. The resulting ciphertext includes the peer ID (pidi), the biscuit number (biscuit_no), and the current chaining key (ck). The biscuit_ct is then mixed into the state.

    Loading a Biscuit

    When calling load_biscuit(biscuit_ct), the protocol:

    1. Decrypts the biscuit using the biscuit_key.
    2. Looks up the peer using pt.pidi.
    3. Verifies replay protection: For protocol versions < 0.3.0, it asserts that pt.biscuit_no >= peer.biscuit_used.
    4. Restores the chaining key (ck ← pt.ck).
    5. Re-applies mix(biscuit_ct) to ensure the chaining key is synchronized.

    Important: Because mix(biscuit_ct) updates the chaining key but that update is not stored inside the biscuit, it must be reapplied during load_biscuit. Handshake code on both the initiator and responder sides must also handle any subsequent mix operations to keep ck in sync.

    fn store_biscuit() {
        biscuit_ctr ← biscuit_ctr + 1;
    
        let k = biscuit_key;
        let n = random_nonce();
        let pt = Biscuit {
          pidi: lhash("peer id", spki),
          biscuit_no: biscuit_ctr,
          ck: ck,
        };
        let ad = lhash(
          "biscuit additional data",
          spkr, sidi, sidr);
        let ct = XAEAD::enc(k, n, pt, ad);
        let biscuit_ct = concat(n, ct);
    
        mix(biscuit_ct)
        biscuit_ct
    }
    
    fn load_biscuit(biscuit_ct) {
        // Decrypt the biscuit
        let k = biscuit_key;
        let concat(n, ct) = biscuit_ct;
        let ad = lhash(
          "biscuit additional data",
          spkr, sidi, sidr);
        let pt : Biscuit = XAEAD::dec(k, n, ct, ad);
    
        // Find the peer and apply retransmission protection
        lookup_peer(pt.pidi);
    
        // In December 2024, the InitConf retransmission mechanism was redesigned
        // in a backwards-compatible way. See the changelog.
        //
        // -- 2024-11-30, Karolin Varner
        if (protocol_version!(< "0.3.0")) {
            // Ensure that the biscuit is used only once
            assert(pt.biscuit_no >= peer.biscuit_used);
        }
    
        // Restore the chaining key
        ck ← pt.ck;
        mix(biscuit_ct);
    
        // Expose the biscuit no, 
        // so the handshake code can differentiate
        // retransmission requests and first time handshake completion
        pt.biscuit_no
    }