rust_crypto_utils/
keyexchange.rs

1//! X25519 Key Exchange module for secure key agreement v2.0
2//!
3//! Provides Elliptic Curve Diffie-Hellman (ECDH) key exchange using X25519.
4
5use rand::rngs::OsRng;
6use x25519_dalek::{EphemeralSecret, PublicKey, StaticSecret};
7use zeroize::{Zeroize, ZeroizeOnDrop};
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11/// Key exchange errors
12#[derive(Error, Debug)]
13pub enum KeyExchangeError {
14    #[error("Invalid public key")]
15    InvalidPublicKey,
16
17    #[error("Key exchange failed")]
18    ExchangeFailed,
19
20    #[error("Invalid key length")]
21    InvalidKeyLength,
22}
23
24/// X25519 public key for key exchange
25#[derive(Clone, Serialize, Deserialize)]
26pub struct X25519PublicKey {
27    bytes: [u8; 32],
28}
29
30impl X25519PublicKey {
31    /// Create from raw bytes
32    pub fn from_bytes(bytes: &[u8]) -> Result<Self, KeyExchangeError> {
33        if bytes.len() != 32 {
34            return Err(KeyExchangeError::InvalidKeyLength);
35        }
36        let mut arr = [0u8; 32];
37        arr.copy_from_slice(bytes);
38        Ok(Self { bytes: arr })
39    }
40
41    /// Get raw bytes
42    pub fn as_bytes(&self) -> &[u8; 32] {
43        &self.bytes
44    }
45
46    /// Convert to hex string
47    pub fn to_hex(&self) -> String {
48        hex::encode(&self.bytes)
49    }
50
51    /// Create from hex string
52    pub fn from_hex(hex_str: &str) -> Result<Self, KeyExchangeError> {
53        let bytes = hex::decode(hex_str).map_err(|_| KeyExchangeError::InvalidPublicKey)?;
54        Self::from_bytes(&bytes)
55    }
56
57    fn to_dalek(&self) -> PublicKey {
58        PublicKey::from(self.bytes)
59    }
60}
61
62impl std::fmt::Debug for X25519PublicKey {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        write!(f, "X25519PublicKey({}...)", &self.to_hex()[..16])
65    }
66}
67
68/// Shared secret derived from key exchange
69#[derive(Zeroize, ZeroizeOnDrop)]
70pub struct SharedSecret {
71    bytes: [u8; 32],
72}
73
74impl SharedSecret {
75    /// Get raw bytes
76    pub fn as_bytes(&self) -> &[u8; 32] {
77        &self.bytes
78    }
79
80    /// Derive an encryption key using HKDF
81    pub fn derive_key(&self, info: &[u8]) -> [u8; 32] {
82        use sha2::Sha256;
83        use hkdf::Hkdf;
84
85        let hk = Hkdf::<Sha256>::new(None, &self.bytes);
86        let mut okm = [0u8; 32];
87        hk.expand(info, &mut okm).expect("HKDF expand failed");
88        okm
89    }
90}
91
92/// X25519 key pair for key exchange
93pub struct X25519KeyPair {
94    secret: StaticSecret,
95    public: X25519PublicKey,
96}
97
98impl X25519KeyPair {
99    /// Generate a new key pair
100    pub fn generate() -> Self {
101        let secret = StaticSecret::random_from_rng(OsRng);
102        let public_key = PublicKey::from(&secret);
103        Self {
104            secret,
105            public: X25519PublicKey {
106                bytes: public_key.to_bytes(),
107            },
108        }
109    }
110
111    /// Create from existing secret key bytes
112    pub fn from_secret_bytes(bytes: &[u8]) -> Result<Self, KeyExchangeError> {
113        if bytes.len() != 32 {
114            return Err(KeyExchangeError::InvalidKeyLength);
115        }
116        let mut arr = [0u8; 32];
117        arr.copy_from_slice(bytes);
118        let secret = StaticSecret::from(arr);
119        let public_key = PublicKey::from(&secret);
120        Ok(Self {
121            secret,
122            public: X25519PublicKey {
123                bytes: public_key.to_bytes(),
124            },
125        })
126    }
127
128    /// Get public key
129    pub fn public_key(&self) -> &X25519PublicKey {
130        &self.public
131    }
132
133    /// Perform key exchange with a peer's public key
134    pub fn exchange(&self, peer_public: &X25519PublicKey) -> SharedSecret {
135        let shared = self.secret.diffie_hellman(&peer_public.to_dalek());
136        SharedSecret {
137            bytes: shared.to_bytes(),
138        }
139    }
140}
141
142/// Ephemeral X25519 key pair (for single use)
143pub struct EphemeralX25519KeyPair {
144    secret: Option<EphemeralSecret>,
145    public: X25519PublicKey,
146}
147
148impl EphemeralX25519KeyPair {
149    /// Generate a new ephemeral key pair
150    pub fn generate() -> Self {
151        let secret = EphemeralSecret::random_from_rng(OsRng);
152        let public_key = PublicKey::from(&secret);
153        Self {
154            secret: Some(secret),
155            public: X25519PublicKey {
156                bytes: public_key.to_bytes(),
157            },
158        }
159    }
160
161    /// Get public key
162    pub fn public_key(&self) -> &X25519PublicKey {
163        &self.public
164    }
165
166    /// Perform key exchange (consumes the ephemeral secret)
167    pub fn exchange(mut self, peer_public: &X25519PublicKey) -> Result<SharedSecret, KeyExchangeError> {
168        let secret = self.secret.take().ok_or(KeyExchangeError::ExchangeFailed)?;
169        let shared = secret.diffie_hellman(&peer_public.to_dalek());
170        Ok(SharedSecret {
171            bytes: shared.to_bytes(),
172        })
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn test_key_exchange() {
182        let alice = X25519KeyPair::generate();
183        let bob = X25519KeyPair::generate();
184
185        let alice_shared = alice.exchange(bob.public_key());
186        let bob_shared = bob.exchange(alice.public_key());
187
188        assert_eq!(alice_shared.as_bytes(), bob_shared.as_bytes());
189    }
190
191    #[test]
192    fn test_ephemeral_key_exchange() {
193        let alice = EphemeralX25519KeyPair::generate();
194        let bob = X25519KeyPair::generate();
195
196        let alice_public = alice.public_key().clone();
197        let alice_shared = alice.exchange(bob.public_key()).unwrap();
198        let bob_shared = bob.exchange(&alice_public);
199
200        assert_eq!(alice_shared.as_bytes(), bob_shared.as_bytes());
201    }
202
203    #[test]
204    fn test_public_key_serialization() {
205        let keypair = X25519KeyPair::generate();
206        let hex = keypair.public_key().to_hex();
207        let restored = X25519PublicKey::from_hex(&hex).unwrap();
208        assert_eq!(keypair.public_key().as_bytes(), restored.as_bytes());
209    }
210
211    #[test]
212    fn test_derive_key() {
213        let alice = X25519KeyPair::generate();
214        let bob = X25519KeyPair::generate();
215
216        let shared = alice.exchange(bob.public_key());
217        let key1 = shared.derive_key(b"encryption");
218        let key2 = shared.derive_key(b"authentication");
219
220        assert_ne!(key1, key2);
221    }
222
223    #[test]
224    fn test_from_secret_bytes() {
225        let original = X25519KeyPair::generate();
226
227        // Get the public key from the original for comparison
228        let original_public = original.public_key().clone();
229
230        // We can't directly extract secret bytes from StaticSecret,
231        // but we can test the from_bytes function
232        let test_bytes = [42u8; 32];
233        let restored = X25519KeyPair::from_secret_bytes(&test_bytes).unwrap();
234
235        // Just verify it creates a valid keypair
236        assert!(restored.public_key().as_bytes().len() == 32);
237    }
238}