rust_keyvault/
key.rs

1//! Key type definition and traits
2
3use crate::{Algorithm, Error, KeyMetadata, Result};
4use std::fmt;
5use subtle::ConstantTimeEq;
6use zeroize::{Zeroize, ZeroizeOnDrop};
7
8/// A cryptographic key is automatically zeroed on drop
9#[derive(Clone, Zeroize, ZeroizeOnDrop)]
10pub struct SecretKey {
11    /// The actual key material
12    bytes: Vec<u8>,
13    /// Algorithm this key is for
14    algorithm: Algorithm,
15}
16
17impl SecretKey {
18    /// Create a new SecretKey from raw bytes
19    ///
20    /// #Errors
21    /// Return error if the key doesn't match the algorithm
22    pub fn from_bytes(bytes: Vec<u8>, algorithm: Algorithm) -> Result<Self> {
23        if bytes.len() != algorithm.key_size() {
24            return Err(Error::crypto(
25                "key_validation",
26                &format!(
27                    "invalid key size: expected {}, got {}",
28                    algorithm.key_size(),
29                    bytes.len()
30                ),
31            ));
32        }
33
34        Ok(Self { bytes, algorithm })
35    }
36
37    /// Generate a new random key for the given algorithm
38    pub fn generate(algorithm: Algorithm) -> Result<Self> {
39        use crate::crypto::{KeyGenerator, SimpleSymmetricKeyGenerator};
40        use rand_chacha::ChaCha20Rng;
41        use rand_core::SeedableRng;
42
43        let mut rng = ChaCha20Rng::from_entropy();
44        let generator = SimpleSymmetricKeyGenerator;
45        let params = crate::crypto::KeyGenParams {
46            algorithm,
47            seed: None,
48            key_size: None,
49        };
50
51        generator.generate_with_params(&mut rng, params)
52    }
53
54    /// Get the algorithm for this key
55    pub fn algorithm(&self) -> Algorithm {
56        self.algorithm
57    }
58
59    /// Expose the raw key material
60    ///
61    /// #Safety
62    /// This exposes the raw key raw materials. The caller is responsible
63    /// for ensuring it doesn't leak
64    pub fn expose_secret(&self) -> &[u8] {
65        &self.bytes
66    }
67
68    /// Constant-time equality comparison
69    pub fn ct_eq(&self, other: &Self) -> bool {
70        if self.algorithm != other.algorithm {
71            return false;
72        }
73        self.bytes.ct_eq(&other.bytes).into()
74    }
75}
76
77impl fmt::Debug for SecretKey {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        f.debug_struct("SecretKey")
80            .field("algorithm", &self.algorithm)
81            .field("bytes", &"[REDACTED]")
82            .finish()
83    }
84}
85
86/// A versioned key with metadata
87#[derive(Clone, Debug)]
88pub struct VersionedKey {
89    /// The secret key material
90    pub key: SecretKey,
91    /// Metadata about this key
92    pub metadata: KeyMetadata,
93}
94
95impl VersionedKey {
96    /// Check if this key has expired
97    pub fn is_expired(&self) -> bool {
98        if let Some(expires_at) = self.metadata.expires_at {
99            std::time::SystemTime::now() > expires_at
100        } else {
101            false
102        }
103    }
104
105    /// Check if this key can be used for encryption/signing
106    pub fn can_encrypt(&self) -> bool {
107        matches!(
108            self.metadata.state,
109            crate::KeyState::Active | crate::KeyState::Rotating
110        ) && !self.is_expired()
111    }
112
113    /// Check is this key can be used for decryption/verification
114    pub fn can_decrypt(&self) -> bool {
115        !matches!(self.metadata.state, crate::KeyState::Revoked) && !self.is_expired()
116    }
117}
118
119/// Trait for the key derivation functions
120///
121/// Enables secure storage or transport of keys by encrypting them
122pub trait KeyDerivation {
123    /// Derive a key from the input material
124    fn derive(&self, input: &[u8], salt: &[u8], info: &[u8]) -> Result<SecretKey>;
125}
126
127/// Trait for key wrapping/unwrapping
128pub trait KeyWrap {
129    /// Wrap a key for a secure storage or transport
130    fn wrap(&self, key: &SecretKey, kek: &SecretKey) -> Result<Vec<u8>>;
131
132    /// Unwrap a previously wrapped key
133    fn unwrap(&self, wrapped: &[u8], kek: &SecretKey, algorithm: Algorithm) -> Result<SecretKey>;
134}
135
136/// HKDF-SHA256 key derivation implementation
137pub struct HkdfSha256;
138
139impl KeyDerivation for HkdfSha256 {
140    fn derive(&self, input: &[u8], salt: &[u8], info: &[u8]) -> Result<SecretKey> {
141        use hkdf::Hkdf;
142        use sha2::Sha256;
143
144        let hkdf = Hkdf::<Sha256>::new(Some(salt), input);
145        let mut okm = vec![0u8; 32]; // 32 bytes for symmetric keys
146        hkdf.expand(info, &mut okm)
147            .map_err(|e| Error::crypto("hkdf_expand", &format!("HKDF expansion failed: {}", e)))?;
148
149        SecretKey::from_bytes(okm, Algorithm::ChaCha20Poly1305)
150    }
151}
152
153/// HKDF-SHA512 key derivation implementation for higher security
154pub struct HkdfSha512;
155
156impl KeyDerivation for HkdfSha512 {
157    fn derive(&self, input: &[u8], salt: &[u8], info: &[u8]) -> Result<SecretKey> {
158        use hkdf::Hkdf;
159        use sha2::Sha512;
160
161        let hkdf = Hkdf::<Sha512>::new(Some(salt), input);
162        let mut okm = vec![0u8; 32];
163        hkdf.expand(info, &mut okm)
164            .map_err(|e| Error::crypto("hkdf_expand", &format!("HKDF expansion failed: {}", e)))?;
165
166        SecretKey::from_bytes(okm, Algorithm::ChaCha20Poly1305)
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[test]
175    fn test_secret_key_zeroize() {
176        // Test ChaCha2--Poly1305
177        {
178            let original_bytes = vec![0x42; 32];
179            let key = SecretKey::from_bytes(original_bytes, Algorithm::ChaCha20Poly1305).unwrap();
180            let _key_ptr = key.expose_secret().as_ptr();
181            assert_eq!(key.expose_secret()[0], 0x42);
182
183            drop(key);
184
185            // We can't actually verify the memory that was zeroized here
186            // because the memory may have been reused.  So basically, this
187            // is more of a "does it panic" test and verification that zeroize is called properly
188        }
189
190        // Test AES-256-GCM
191        {
192            let key = SecretKey::from_bytes(vec![0x33; 32], Algorithm::Aes256Gcm).unwrap();
193            assert_eq!(key.expose_secret().len(), 32);
194            assert_eq!(key.algorithm(), Algorithm::Aes256Gcm);
195        }
196
197        // Test invalid keys sizes (should fail)
198        {
199            let result = SecretKey::from_bytes(vec![0x11; 1], Algorithm::ChaCha20Poly1305);
200            assert!(result.is_err());
201
202            let result = SecretKey::from_bytes(vec![0x11; 16], Algorithm::ChaCha20Poly1305);
203            assert!(result.is_err());
204
205            let result = SecretKey::from_bytes(vec![0x11; 64], Algorithm::ChaCha20Poly1305);
206            assert!(result.is_err());
207        }
208    }
209
210    #[test]
211    fn test_hkdf_sha256_derivation() {
212        let kdf = HkdfSha256;
213        let input = b"input key material for testing";
214        let salt = b"unique random salt";
215        let info = b"application context info";
216
217        // Test basic derivation
218        let key1 = kdf.derive(input, salt, info).unwrap();
219        assert_eq!(key1.expose_secret().len(), 32);
220        assert_eq!(key1.algorithm(), Algorithm::ChaCha20Poly1305);
221
222        // Test determinism - same inputs = same output
223        let key2 = kdf.derive(input, salt, info).unwrap();
224        assert!(key1.ct_eq(&key2));
225
226        // Test different info = different output
227        let key3 = kdf.derive(input, salt, b"different context").unwrap();
228        assert!(!key1.ct_eq(&key3));
229
230        // Test different salt = different output
231        let key4 = kdf.derive(input, b"different salt", info).unwrap();
232        assert!(!key1.ct_eq(&key4));
233    }
234
235    #[test]
236    fn test_hkdf_sha512_derivation() {
237        let kdf = HkdfSha512;
238        let input = b"test input material";
239        let salt = b"test salt";
240        let info = b"test info";
241
242        let key = kdf.derive(input, salt, info).unwrap();
243        assert_eq!(key.expose_secret().len(), 32);
244
245        // Verify SHA512 produces different output than SHA256
246        let kdf256 = HkdfSha256;
247        let key256 = kdf256.derive(input, salt, info).unwrap();
248        assert!(!key.ct_eq(&key256));
249    }
250
251    #[test]
252    fn test_hkdf_use_case_session_key() {
253        // Realistic use case: derive multiple session keys from master secret
254        let kdf = HkdfSha256;
255        let master_secret = b"shared master secret from ECDH";
256        let salt = b"session-2024-01-01";
257
258        let encryption_key = kdf.derive(master_secret, salt, b"encryption-key").unwrap();
259        let mac_key = kdf.derive(master_secret, salt, b"mac-key").unwrap();
260        let iv_key = kdf.derive(master_secret, salt, b"iv-key").unwrap();
261
262        // All keys should be different
263        assert!(!encryption_key.ct_eq(&mac_key));
264        assert!(!encryption_key.ct_eq(&iv_key));
265        assert!(!mac_key.ct_eq(&iv_key));
266    }
267}