Skip to main content

egide_crypto/
keys.rs

1//! Secure key types with automatic memory zeroization.
2//!
3//! All key types implement `Zeroize` and `ZeroizeOnDrop` to ensure
4//! sensitive key material is securely erased from memory when no longer needed.
5
6use zeroize::{Zeroize, ZeroizeOnDrop};
7
8use crate::aead::KEY_SIZE;
9use crate::error::CryptoError;
10use crate::random::generate_key;
11
12/// A 256-bit symmetric encryption key with automatic zeroization.
13///
14/// This type wraps a raw key and ensures it is securely erased
15/// from memory when dropped.
16#[derive(Clone, Zeroize, ZeroizeOnDrop)]
17pub struct SymmetricKey {
18    bytes: [u8; KEY_SIZE],
19}
20
21impl SymmetricKey {
22    /// Generates a new random symmetric key.
23    ///
24    /// # Errors
25    ///
26    /// Returns a [`CryptoError::RandomGenerationFailed`] if the operating
27    /// system's CSPRNG fails to produce output.
28    pub fn generate() -> Result<Self, CryptoError> {
29        let key = generate_key()?;
30        Ok(Self { bytes: *key })
31    }
32
33    /// Creates a symmetric key from raw bytes.
34    ///
35    /// # Errors
36    ///
37    /// Returns an error if the input is not exactly 32 bytes.
38    pub fn from_bytes(bytes: &[u8]) -> Result<Self, CryptoError> {
39        if bytes.len() != KEY_SIZE {
40            return Err(CryptoError::InvalidKey(format!(
41                "expected {} bytes, got {}",
42                KEY_SIZE,
43                bytes.len()
44            )));
45        }
46
47        let mut key_bytes = [0u8; KEY_SIZE];
48        key_bytes.copy_from_slice(bytes);
49
50        Ok(Self { bytes: key_bytes })
51    }
52
53    /// Returns the raw key bytes.
54    ///
55    /// Use with caution - the returned slice is not zeroized automatically.
56    #[must_use]
57    #[inline]
58    pub fn as_bytes(&self) -> &[u8] {
59        &self.bytes
60    }
61}
62
63impl std::fmt::Debug for SymmetricKey {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        f.debug_struct("SymmetricKey")
66            .field("bytes", &"[REDACTED]")
67            .finish()
68    }
69}
70
71/// A master key used for deriving other keys.
72///
73/// Master keys are typically generated during vault initialization
74/// and protected by Shamir's Secret Sharing.
75#[derive(Clone, Zeroize, ZeroizeOnDrop)]
76pub struct MasterKey {
77    bytes: [u8; KEY_SIZE],
78}
79
80impl MasterKey {
81    /// Generates a new random master key.
82    ///
83    /// # Errors
84    ///
85    /// Returns a [`CryptoError::RandomGenerationFailed`] if the operating
86    /// system's CSPRNG fails to produce output.
87    pub fn generate() -> Result<Self, CryptoError> {
88        let key = generate_key()?;
89        Ok(Self { bytes: *key })
90    }
91
92    /// Creates a master key from raw bytes.
93    ///
94    /// # Errors
95    ///
96    /// Returns an error if the input is not exactly 32 bytes.
97    pub fn from_bytes(bytes: &[u8]) -> Result<Self, CryptoError> {
98        if bytes.len() != KEY_SIZE {
99            return Err(CryptoError::InvalidKey(format!(
100                "expected {} bytes, got {}",
101                KEY_SIZE,
102                bytes.len()
103            )));
104        }
105
106        let mut key_bytes = [0u8; KEY_SIZE];
107        key_bytes.copy_from_slice(bytes);
108
109        Ok(Self { bytes: key_bytes })
110    }
111
112    /// Returns the raw key bytes.
113    #[must_use]
114    #[inline]
115    pub fn as_bytes(&self) -> &[u8] {
116        &self.bytes
117    }
118}
119
120impl std::fmt::Debug for MasterKey {
121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122        f.debug_struct("MasterKey")
123            .field("bytes", &"[REDACTED]")
124            .finish()
125    }
126}
127
128#[cfg(test)]
129#[allow(clippy::disallowed_methods)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn test_symmetric_key_generate() {
135        let key = SymmetricKey::generate().unwrap();
136        assert_eq!(key.as_bytes().len(), KEY_SIZE);
137    }
138
139    #[test]
140    fn test_symmetric_key_from_bytes() {
141        let bytes = [0x42u8; KEY_SIZE];
142        let key = SymmetricKey::from_bytes(&bytes).unwrap();
143        assert_eq!(key.as_bytes(), &bytes);
144    }
145
146    #[test]
147    fn test_symmetric_key_invalid_length() {
148        let bytes = [0u8; 16];
149        let result = SymmetricKey::from_bytes(&bytes);
150        assert!(matches!(result, Err(CryptoError::InvalidKey(_))));
151    }
152
153    #[test]
154    fn test_symmetric_key_debug_redacted() {
155        let key = SymmetricKey::generate().unwrap();
156        let debug_str = format!("{key:?}");
157        assert!(debug_str.contains("[REDACTED]"));
158        assert!(!debug_str.contains("42"));
159    }
160
161    #[test]
162    fn test_master_key_generate() {
163        let key = MasterKey::generate().unwrap();
164        assert_eq!(key.as_bytes().len(), KEY_SIZE);
165    }
166
167    #[test]
168    fn test_master_key_from_bytes() {
169        let bytes = [0x42u8; KEY_SIZE];
170        let key = MasterKey::from_bytes(&bytes).unwrap();
171        assert_eq!(key.as_bytes(), &bytes);
172    }
173
174    #[test]
175    fn test_master_key_debug_redacted() {
176        let key = MasterKey::generate().unwrap();
177        let debug_str = format!("{key:?}");
178        assert!(debug_str.contains("[REDACTED]"));
179    }
180
181    #[test]
182    fn test_keys_are_unique() {
183        let key1 = SymmetricKey::generate().unwrap();
184        let key2 = SymmetricKey::generate().unwrap();
185        assert_ne!(key1.as_bytes(), key2.as_bytes());
186    }
187}