snow

repository·main·Indexed 21 days ago

https://github.com/mcginty/snow

A pure-Rust implementation of the Noise Protocol Framework (version 0.10.0) designed for ease of use and safety. It provides tools to establish secure, encrypted communication channels using the `snow::Builder` to configure handshake patterns, manage `HandshakeState` for key exchange, and transition into transport mode for encrypted data exchange. Supports `no_std` environments with `alloc` and offers optional acceleration via the `ring` library.

Tokens
10.6K
Snippets
40
Records
53
Agent score
75%

What's inside snow

  1. Configure Snow for `no_std` environments

    main

    Snow supports no_std environments provided that alloc is available. To use Snow in no_std, you must set default-features = false in your Cargo.toml and manually select the required components.

    Note that default-resolver is currently the only built-in resolver that supports no_std.

  2. Use `ring` for accelerated cryptography

    main

    By default, Snow uses pure-Rust implementations. For significantly faster performance, you can use the ring library.

    1. Enable the ring-resolver feature to include the resolvers::ring module and the RingAcceleratedResolver.
    2. Use Builder::with_resolver(RingAcceleratedResolver) to manually select it.
    3. Alternatively, enable the ring-accelerated feature to make Snow default to ring's implementations when available.
  3. Implement a Noise handshake with `snow::Builder`

    main

    To use Snow, use the snow::Builder to create an initiator or responder. You must provide a handshake pattern string (e.g., "Noise_NN_25519_ChaChaPoly_BLAKE2s") which is parsed into a pattern. After performing the handshake exchange using write_message and read_message, you must call into_transport_mode() to transition the state machine from the handshake phase into the transport phase for encrypted data exchange.

    let mut noise = snow::Builder::new("Noise_NN_25519_ChaChaPoly_BLAKE2s".parse()?)
                        .build_initiator()?;
    
    let mut buf = [0u8; 65535];
    
    // write first handshake message
    noise.write_message(&[], &mut buf)?;
    
    // receive response message
    let incoming = receive_message_from_the_mysterious_ether();
    noise.read_message(&incoming, &mut buf)?;
    
    // complete handshake, and transition the state machine into transport mode
    let mut noise = noise.into_transport_mode()?;
  4. Configure cryptographic providers with Builder

    main

    Snow allows you to swap cryptographic implementations using Builder::with_resolver().

    By default, Snow uses pure-Rust implementations. However, you can use the ring library for significantly better performance by enabling the ring-resolver feature and passing a RingAcceleratedResolver to the builder.

    // Example of using the ring resolver if enabled
    let mut initiator = snow::Builder::new(PATTERN.parse()?)
        .with_resolver(snow::resolvers::RingAcceleratedResolver)
        .build_initiator()?;
  5. Manage the Noise handshake lifecycle with HandshakeState

    main

    The HandshakeState struct is a state machine that manages the handshake phase of a Noise session. It handles key exchange, hashing, and message encryption/decryption according to the chosen Noise pattern.

    Note: You should typically use the Builder to instantiate a HandshakeState rather than calling its internal constructor directly.

    Key capabilities include:

    • Writing handshake messages (write_message).
    • Reading handshake messages (read_message).
    • Managing Preshared Keys (PSKs) via set_psk.
    • Transitioning to transport mode once the handshake is complete using into_transport_mode or into_stateless_transport_mode.
    // Note: Use the Builder to get started
    let mut session = Builder::new("Noise_NN_25519_AESGCM_SHA256".parse()?.params)
        .build_initiator()?;
  6. Apply HandshakeModifiers to patterns

    main

    Modifiers can be applied to a base HandshakePattern to alter its behavior. These are parsed via HandshakeModifier and can be combined in a HandshakeModifierList using the + delimiter.

    Supported modifiers:

    • psk<N>: Inserts a Pre-Shared Key (PSK) at the $N$-th message position (where $N$ is 1-indexed).
    • fallback: Modifies the base pattern to its fallback form.
    • hfs: (Requires hfs feature) Modifies the pattern to use Hybrid-Forward-Secrecy.

    Note: hfs cannot be combined with one-way handshake patterns.

    // Example of parsing a pattern with a PSK modifier
    let choice: HandshakeChoice = "NK+psk1".parse().expect("Invalid pattern");
    assert!(choice.is_psk());
  7. Use StatelessTransportState for Noise transport phase

    main

    The StatelessTransportState is a state machine used for the transport phase of a Noise session after a handshake has finished. It manages two CipherStates (one for sending and one for receiving) derived from a HandshakeState. It allows for encrypting/decrypting messages and performing rekeying operations without maintaining the full handshake context.

    To transition from a handshake to transport, you can use TryFrom to convert a HandshakeState into a StatelessTransportState.

    use std::convert::TryFrom;
    // Assuming handshake_state is an instance of HandshakeState
    let transport_state = StatelessTransportState::try_from(handshake_state)?;
  8. Manage encrypted data exchange with TransportState

    main

    The TransportState struct manages the transport phase of a Noise session after a handshake has completed. It maintains two CipherStates (one for sending and one for receiving) to handle encrypted data exchange.

    To transition from a handshake to the transport phase, you can convert a HandshakeState into a TransportState using TryFrom or TransportState::new(). This will fail with StateProblem::HandshakeNotFinished if the handshake is not yet complete.

    Key capabilities include:

    • Encrypting and decrypting messages with optional additional authenticated data (AAD).
    • Managing symmetric key rekeying (automatic or manual).
    • Accessing the remote party's static public key.
    • Managing nonces for use on lossy transports.
    use snow::TransportState;
    use snow::handshakestate::HandshakeState;
    
    // Assuming 'handshake' is a completed HandshakeState
    let mut transport: TransportState = handshake.try_into().expect("Handshake must be finished");
  9. Use KEM choices with the HFS extension

    main

    If the hfs feature is enabled, NoiseParams supports Post-Quantum KEMs (Key Encapsulation Mechanisms). This is integrated into the handshake part of the parameter string using a + delimiter.

    Supported KEMs:

    • Kyber1024 (string: Kyber1024)

    Example String Format: Noise_XX+Kyber1024_25519_AESGCM_SHA256

    Note that when using hfs, the kem field in NoiseParams will be Some(KemChoice) and the handshake must be recognized as an HFS handshake.

  10. Configure and construct Noise handshakes with Builder

    main

    The Builder struct is used to configure the parameters for a Noise handshake and construct a HandshakeState. You can specify cryptographic parameters, static keys, remote public keys, pre-shared keys (PSKs), and handshakes prologues.

    Once configured, you call .build_initiator() to create a state for the side sending the first message, or .build_responder() for the side receiving the first message.

    Note that many configuration methods can only be called once; attempting to overwrite a parameter (like local_private_key or prologue) will return an Error with InitStage::ParameterOverwrite.

    # use snow::Builder;
    # fn main() -> Result<(), Box<dyn std::error::Error>> {
    let my_long_term_key = [0u8; 32];
    let their_pub_key = [0u8; 32];
    let noise = Builder::new("Noise_XX_25519_ChaChaPoly_BLAKE2s".parse()?)
        .local_private_key(&my_long_term_key)?
        .remote_public_key(&their_pub_key)?
        .prologue("noise is just swell".as_bytes())?
        .build_initiator()?;
    # Ok(())
    # }
  11. How to perform a Noise handshake with Snow

    main

    The typical workflow in Snow involves using a Builder to create a HandshakeState for both an initiator and a responder. Once the handshake messages are exchanged and the handshake is complete, you transition the state into either TransportState (for reliable transports like TCP where internal counters are managed) or StatelessTransportState (for unreliable transports like UDP where you manage the message counter).

    use snow::Error;
    
    // Define a Noise pattern (e.g., Noise_NN_25519_ChaChaPoly_BLAKE2s)
    static PATTERN: &'static str = "Noise_NN_25519_ChaChaPoly_BLAKE2s";
    
    // 1. Initialize initiator and responder
    let mut initiator = snow::Builder::new(PATTERN.parse()?)
        .build_initiator()?;
    let mut responder = snow::Builder::new(PATTERN.parse()?)
        .build_responder()?;
    
    let (mut read_buf, mut first_msg, mut second_msg) = ([0u8; 1024], [0u8; 1024], [0u8; 1024]);
    
    // 2. Handshake: Initiator sends first message
    let len = initiator.write_message(&[], &mut first_msg)?;
    
    // 3. Handshake: Responder processes first message and sends second
    responder.read_message(&first_msg[..len], &mut read_buf)?;
    let len = responder.write_message(&[], &mut second_msg)?;
    
    // 4. Handshake: Initiator processes second message
    initiator.read_message(&second_msg[..len], &mut read_buf)?;
    
    // 5. Transition to transport mode
    let initiator = initiator.into_transport_mode();
    let responder = responder.into_transport_mode();
  12. Example: `no_std` configuration for Curve25519 + ChaChaPoly + BLAKE2

    main

    This configuration is suitable for environments without the standard library.

    default-features = false
    features = [
        "use-curve25519",
        "use-chacha20poly1305",
        "use-blake2",
    ]