1use crate::{Algorithm, Error, KeyMetadata, Result};
4use std::fmt;
5use subtle::ConstantTimeEq;
6use zeroize::{Zeroize, ZeroizeOnDrop};
7
8#[derive(Clone, Zeroize, ZeroizeOnDrop)]
10pub struct SecretKey {
11 bytes: Vec<u8>,
13 algorithm: Algorithm,
15}
16
17impl SecretKey {
18 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 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 pub fn algorithm(&self) -> Algorithm {
56 self.algorithm
57 }
58
59 pub fn expose_secret(&self) -> &[u8] {
65 &self.bytes
66 }
67
68 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#[derive(Clone, Debug)]
88pub struct VersionedKey {
89 pub key: SecretKey,
91 pub metadata: KeyMetadata,
93}
94
95impl VersionedKey {
96 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 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 pub fn can_decrypt(&self) -> bool {
115 !matches!(self.metadata.state, crate::KeyState::Revoked) && !self.is_expired()
116 }
117}
118
119pub trait KeyDerivation {
123 fn derive(&self, input: &[u8], salt: &[u8], info: &[u8]) -> Result<SecretKey>;
125}
126
127pub trait KeyWrap {
129 fn wrap(&self, key: &SecretKey, kek: &SecretKey) -> Result<Vec<u8>>;
131
132 fn unwrap(&self, wrapped: &[u8], kek: &SecretKey, algorithm: Algorithm) -> Result<SecretKey>;
134}
135
136pub 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]; 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
153pub 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 {
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 }
189
190 {
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 {
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 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 let key2 = kdf.derive(input, salt, info).unwrap();
224 assert!(key1.ct_eq(&key2));
225
226 let key3 = kdf.derive(input, salt, b"different context").unwrap();
228 assert!(!key1.ct_eq(&key3));
229
230 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 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 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 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}