diff --git a/protocols/v2/noise-sv2/src/responder.rs b/protocols/v2/noise-sv2/src/responder.rs index f5a3ddcc2c..daff5e86b9 100644 --- a/protocols/v2/noise-sv2/src/responder.rs +++ b/protocols/v2/noise-sv2/src/responder.rs @@ -1,3 +1,62 @@ +// # Noise NX Protocol Responder +// +// This module manages the state and operations of the responder within the Noise NX +// protocol, a cryptographic handshake mechanism designed to establish secure communication +// channels between two parties. +// +// ## Overview +// +// The [`Responder`] is responsible for handling the cryptographic handshake with an initiator, +// including the generation and management of ephemeral and static keys, the derivation of shared +// secrets, and the encryption and decryption of handshake messages. Once the handshake is +// complete, the [`Responder`] facilitates secure communication by managing the necessary cipher +// states. +// +// The module integrates various cryptographic components, such as [`ChaCha20Poly1305`] for +// encryption, [`Secp256k1`] for key generation, and ElligatorSwift for key obfuscation, ensuring +// robust security throughout the handshake and subsequent message exchanges. +// +// ## Usage +// +// The [`Responder`] is typically instantiated with an authority keypair and a certificate validity +// duration. The handshake process is initiated by receiving an ephemeral public key from the +// initiator, after which the responder generates the necessary response message, including its +// own ephemeral public key and encrypted static key. The handshake concludes with the derivation +// of session keys used for ongoing encrypted communication. +// +// The [`Responder`] struct implements the [`CipherState`] and [`HandshakeOp`] traits to manage +// its internal cryptographic state, including key management, nonce generation, and message +// encryption/decryption. These traits abstract away the underlying cryptographic operations, +// ensuring that the protocol's security guarantees are consistently enforced. +// +// ### Example +// +// In this example, the [`Responder`] is initialized, and the first step of the Noise NX handshake +// is performed, resulting in a response message to be sent to the initiator and the setup of +// session ciphers for secure communication. +// +// ```rust +// use secp256k1::{Keypair, Secp256k1}; +// use noise_sv2::Responder; +// use std::time::Duration; +// +// // Initialize the authority keypair and certificate validity duration +// let secp = Secp256k1::new(); +// let authority_keypair = Keypair::new(&secp, &mut rand::thread_rng()); +// let cert_validity = Duration::new(3600, 0); // 1 hour +// +// // Create a new Responder instance +// let mut responder = Responder::new(authority_keypair, cert_validity.as_secs() as u32); +// +// // Simulate receiving an ephemeral public key from the initiator +// let initiator_ephemeral_key = [0u8; 64]; // Example key (normally received securely) +// +// // Perform the first step of the handshake +// let (response_message, noise_codec) = responder.step_1(initiator_ephemeral_key).unwrap(); +// +// println!("Handshake response message: {:?}", response_message); +// // The noise_codec is now ready to be used for secure message exchanges +// ``` use std::{ptr, time::Duration}; use crate::{ @@ -17,22 +76,47 @@ use secp256k1::{ellswift::ElligatorSwift, Keypair, Secp256k1, SecretKey}; const VERSION: u16 = 0; +/// Represents the state and operations of the Noise NX protocol responder. +/// +/// Manages the state of the responder during the Noise NX protocol handshake process. It holds the +/// necessary cryptographic keys, handshake state, and intermediate values needed to perform the +/// key exchange and establish a secure connection with the initiator. pub struct Responder { + // Cipher used for encrypting and decrypting messages during the handshake. + // + // It is initialized once enough information is available from the handshake process. handshake_cipher: Option, + // Optional static key used in the handshake. This key may be derived from the pre-shared key + // (PSK) or generated during the handshake. k: Option<[u8; 32]>, + // Current nonce used in the encryption process. + // + // Ensures that the same plaintext encrypted twice will produce different ciphertexts. n: u64, - // Chaining key + // Chaining key used in the key derivation process to generate new keys throughout the + // handshake. ck: [u8; 32], - // Handshake hash + // Handshake hash which accumulates all handshake messages to ensure integrity and prevent + // tampering. h: [u8; 32], - // ephemeral keypair + // Ephemeral key pair generated by the responder for this session, used for generating the + // shared secret with the initiator. e: Keypair, - // Static pub keypair + // Static key pair of the responder, used to establish long-term identity and authenticity. + // + // Remains consistent across handshakes. s: Keypair, - // Authority pub keypair + // Authority key pair, representing the responder's authority credentials. + // + // Used to sign messages and verify the identity of the responder. a: Keypair, + // First [`CipherState`] used for encrypting messages from the initiator to the responder + // after the handshake is complete. c1: Option, + // Second [`CipherState`] used for encrypting messages from the responder to the initiator + // after the handshake is complete. c2: Option, + // Validity duration of the responder's certificate, in seconds. cert_validity: u32, } @@ -42,19 +126,27 @@ impl std::fmt::Debug for Responder { } } -// Make sure that Respoder is not sync so we do not need to worry about what other memory accessor see -// after that we zeroize k is send cause if we send it the original thread can not access -// anymore it -//impl !Sync for Responder {} -//impl !Copy for Responder {} +// Ensures that the `Cipher` type is not `Sync`, which prevents multiple threads from +// simultaneously accessing the same instance of `Cipher`. This eliminates the need to handle +// potential issues related to visibility of changes across threads. +// +// After sending the `k` value, we immediately clear it to prevent the original thread from +// accessing the value again, thereby enhancing security by ensuring the sensitive data is no +// longer available in memory. +// +// The `Cipher` struct is neither `Sync` nor `Copy` due to its `cipher` field, which implements +// the `AeadCipher` trait. This trait requires mutable access, making the entire struct non-`Sync` +// and non-`Copy`, even though the key and nonce are simple types. impl CipherState for Responder { fn get_k(&mut self) -> &mut Option<[u8; 32]> { &mut self.k } + fn get_n(&self) -> u64 { self.n } + fn set_n(&mut self, n: u64) { self.n = n; } @@ -62,6 +154,7 @@ impl CipherState for Responder { fn set_k(&mut self, k: Option<[u8; 32]>) { self.k = k; } + fn get_cipher(&mut self) -> &mut Option { &mut self.handshake_cipher } @@ -71,6 +164,7 @@ impl HandshakeOp for Responder { fn name(&self) -> String { "Responder".to_string() } + fn get_h(&mut self) -> &mut [u8; 32] { &mut self.h } @@ -93,22 +187,12 @@ impl HandshakeOp for Responder { } impl Responder { - pub fn from_authority_kp( - public: &[u8; 32], - private: &[u8; 32], - cert_validity: Duration, - ) -> Result, Error> { - let secp = Secp256k1::new(); - let secret = SecretKey::from_slice(private).map_err(|_| Error::InvalidRawPrivateKey)?; - let kp = Keypair::from_secret_key(&secp, &secret); - let pub_ = kp.x_only_public_key().0.serialize(); - if public == &pub_[..] { - Ok(Self::new(kp, cert_validity.as_secs() as u32)) - } else { - Err(Error::InvalidRawPublicKey) - } - } - + /// Creates a new [`Responder`] instance with the provided authority keypair and certificate + /// validity. + /// + /// Constructs a new [`Responder`] with the necessary cryptographic state for the Noise NX protocol + /// handshake. It generates ephemeral and static key pairs for the responder and prepares the + /// handshake state. The authority keypair and certificate validity period are also configured. pub fn new(a: Keypair, cert_validity: u32) -> Box { let mut self_ = Self { handshake_cipher: None, @@ -127,40 +211,43 @@ impl Responder { Box::new(self_) } - /// #### 4.5.1.2 Responder - /// - /// 1. receives ephemeral public key message with ElligatorSwift encoding (64 bytes plaintext) - /// 2. parses these 64 byte as PubKey and interprets is as `re.public_key` - /// 3. calls `MixHash(re.public_key)` - /// 4. calls `DecryptAndHash()` on remaining bytes (i.e. on empty data with empty _k_, thus effectively only calls `MixHash()` on empty data) - /// - /// #### 4.5.2.1 Responder + /// Creates a new [`Responder`] instance with the provided 32-byte authority key pair. /// - /// 1. initializes empty output buffer - /// 2. generates ephemeral keypair `e`, appends the 64 bytes ElligatorSwift encoding of `e.public_key` to the buffer - /// 3. calls `MixHash(e.public_key)` - /// 4. calls `MixKey(ECDH(e.private_key, re.public_key))` - /// 5. appends `EncryptAndHash(s.public_key)` (80 bytes: 64 bytes encrypted elliswift public key, 16 bytes MAC) - /// 6. calls `MixKey(ECDH(s.private_key, re.public_key))` - /// 7. appends `EncryptAndHash(SIGNATURE_NOISE_MESSAGE)` (74 + 16 bytes) to the buffer - /// 8. submits the buffer for sending to the initiator - /// 9. return pair of CipherState objects, the first for encrypting transport messages from initiator to responder, and the second for messages in the other direction: - /// 1. sets `temp_k1, temp_k2 = HKDF(ck, zerolen, 2)` - /// 2. creates two new CipherState objects `c1` and `c2` - /// 3. calls `c1.InitializeKey(temp_k1)` and `c2.InitializeKey(temp_k2)` - /// 4. returns the pair `(c1, c2)` + /// Constructs a new [`Responder`] with a given public and private key pair, which represents + /// the responder's authority credentials. It verifies that the provided public key matches the + /// corresponding private key, ensuring the authenticity of the authority key pair. The + /// certificate validity duration is also set here. Fails if the key pair is mismatched. + pub fn from_authority_kp( + public: &[u8; 32], + private: &[u8; 32], + cert_validity: Duration, + ) -> Result, Error> { + let secp = Secp256k1::new(); + let secret = SecretKey::from_slice(private).map_err(|_| Error::InvalidRawPrivateKey)?; + let kp = Keypair::from_secret_key(&secp, &secret); + let pub_ = kp.x_only_public_key().0.serialize(); + if public == &pub_[..] { + Ok(Self::new(kp, cert_validity.as_secs() as u32)) + } else { + Err(Error::InvalidRawPublicKey) + } + } + + /// Processes the first step of the Noise NX protocol handshake for the responder. /// - /// ##### Message format of NX-handshake part 2 + /// This function manages the responder's side of the handshake after receiving the initiator's + /// initial message. It processes the ephemeral public key provided by the initiator, derives + /// the necessary shared secrets, and constructs the response message. The response includes + /// the responder's ephemeral public key (in its ElligatorSwift-encoded form), the encrypted + /// static public key, and a signature noise message. Additionally, it establishes the session + /// ciphers for encrypting and decrypting further communication. /// - /// | Field name | Description | - /// | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | - /// | PUBKEY | Responder's plaintext ephemeral public key | - /// | PUBKEY | Responder's encrypted static public key | - /// | MAC | Message authentication code for responder's static public key | - /// | SIGNATURE_NOISE_MESSAGE | Signed message containing Responder's static key. Signature is issued by authority that is generally known to operate the server acting as the noise responder | - /// | MAC | Message authentication code for SIGNATURE_NOISE_MESSAGE | + /// On success, it returns a tuple containing the response message to be sent back to the + /// initiator and a [`NoiseCodec`] instance, which is configured with the session ciphers for + /// secure transmission of subsequent messages. /// - /// Message length: 234 bytes + /// On failure, the method returns an error if there is an issue during encryption, decryption, + /// or any other step of the handshake process. pub fn step_1( &mut self, elligatorswift_theirs_ephemeral_serialized: [u8; ELLSWIFT_ENCODING_SIZE], @@ -258,6 +345,12 @@ impl Responder { Ok((to_send, codec)) } + // Generates a signature noise message for the responder's certificate. + // + // This method creates a signature noise message that includes the protocol version, + // certificate validity period, and a cryptographic signature. The signature is created using + // the responder's static public key and authority keypair, ensuring that the responder's + // identity and certificate validity are cryptographically verifiable. fn get_signature(&self, version: u16, valid_from: u32, not_valid_after: u32) -> [u8; 74] { let mut ret = [0; 74]; let version = version.to_le_bytes(); @@ -277,6 +370,12 @@ impl Responder { ret } + // Securely erases sensitive data in the responder's memory. + // + // Clears all sensitive cryptographic material within the [`Responder`] to prevent any + // accidental leakage or misuse. It overwrites the stored keys, chaining key, handshake hash, + // and session ciphers with zeros. This function is typically + // called when the [`Responder`] instance is no longer needed or before deallocation. fn erase(&mut self) { if let Some(k) = self.k.as_mut() { for b in k { @@ -302,6 +401,8 @@ impl Responder { } impl Drop for Responder { + /// Ensures that sensitive data is securely erased when the [`Responder`] instance is dropped, + /// preventing any potential leakage of cryptographic material. fn drop(&mut self) { self.erase(); }