Skip to content

Commit

Permalink
Add docs for responder.rs
Browse files Browse the repository at this point in the history
  • Loading branch information
Shourya742 committed Oct 8, 2024
1 parent 5bdb19a commit 49b5db0
Showing 1 changed file with 157 additions and 56 deletions.
213 changes: 157 additions & 56 deletions protocols/v2/noise-sv2/src/responder.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand All @@ -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<ChaCha20Poly1305>,
// 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<GenericCipher>,
// Second [`CipherState`] used for encrypting messages from the responder to the initiator
// after the handshake is complete.
c2: Option<GenericCipher>,
// Validity duration of the responder's certificate, in seconds.
cert_validity: u32,
}

Expand All @@ -42,26 +126,35 @@ 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<C: AeadCipher> !Sync for Responder<C> {}
//impl<C: AeadCipher> !Copy for Responder<C> {}
// 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<ChaCha20Poly1305> 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;
}

fn set_k(&mut self, k: Option<[u8; 32]>) {
self.k = k;
}

fn get_cipher(&mut self) -> &mut Option<ChaCha20Poly1305> {
&mut self.handshake_cipher
}
Expand All @@ -71,6 +164,7 @@ impl HandshakeOp<ChaCha20Poly1305> for Responder {
fn name(&self) -> String {
"Responder".to_string()
}

fn get_h(&mut self) -> &mut [u8; 32] {
&mut self.h
}
Expand All @@ -93,22 +187,12 @@ impl HandshakeOp<ChaCha20Poly1305> for Responder {
}

impl Responder {
pub fn from_authority_kp(
public: &[u8; 32],
private: &[u8; 32],
cert_validity: Duration,
) -> Result<Box<Self>, 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<Self> {
let mut self_ = Self {
handshake_cipher: None,
Expand All @@ -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<Box<Self>, 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],
Expand Down Expand Up @@ -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();
Expand All @@ -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 {
Expand All @@ -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();
}
Expand Down

0 comments on commit 49b5db0

Please sign in to comment.