Skip to main content

kms_aead/
types.rs

1use crate::errors::*;
2use crate::KmsAeadResult;
3use rvstruct::*;
4use secret_vault_value::SecretValue;
5use subtle::ConstantTimeEq;
6
7#[derive(Debug, Clone, ValueStruct)]
8pub struct CipherText(pub Vec<u8>);
9
10impl CipherText {
11    pub fn to_hex_string(&self) -> String {
12        hex::encode(self.value())
13    }
14}
15
16impl ConstantTimeEq for CipherText {
17    fn ct_eq(&self, other: &Self) -> subtle::Choice {
18        self.value().ct_eq(other.value())
19    }
20}
21
22impl PartialEq for CipherText {
23    fn eq(&self, other: &Self) -> bool {
24        self.ct_eq(other).into()
25    }
26}
27
28#[derive(Debug, Clone, ValueStruct)]
29pub struct DataEncryptionKey(pub SecretValue);
30
31impl ConstantTimeEq for DataEncryptionKey {
32    fn ct_eq(&self, other: &Self) -> subtle::Choice {
33        self.value()
34            .as_sensitive_bytes()
35            .ct_eq(other.value().as_sensitive_bytes())
36    }
37}
38
39impl PartialEq for DataEncryptionKey {
40    fn eq(&self, other: &Self) -> bool {
41        self.ct_eq(other).into()
42    }
43}
44
45#[derive(Debug, Clone, ValueStruct)]
46pub struct EncryptedDataEncryptionKey(pub Vec<u8>);
47
48impl EncryptedDataEncryptionKey {
49    pub fn to_hex_string(&self) -> String {
50        hex::encode(self.value())
51    }
52}
53
54impl ConstantTimeEq for EncryptedDataEncryptionKey {
55    fn ct_eq(&self, other: &Self) -> subtle::Choice {
56        self.value().ct_eq(other.value())
57    }
58}
59
60impl PartialEq for EncryptedDataEncryptionKey {
61    fn eq(&self, other: &Self) -> bool {
62        self.ct_eq(other).into()
63    }
64}
65
66#[derive(Debug, Clone)]
67pub struct CipherTextWithEncryptedKey(pub Vec<u8>);
68
69impl CipherTextWithEncryptedKey {
70    pub fn new(cipher_text: &CipherText, encrypted_dek: &EncryptedDataEncryptionKey) -> Self {
71        let mut value = Vec::with_capacity(
72            std::mem::size_of::<u64>() + encrypted_dek.value().len() + cipher_text.value().len(),
73        );
74        value.extend_from_slice(&(encrypted_dek.value().len() as u64).to_be_bytes());
75        value.extend_from_slice(encrypted_dek.value());
76        value.extend_from_slice(cipher_text.value());
77
78        value.into()
79    }
80
81    pub fn separate(&self) -> KmsAeadResult<(CipherText, EncryptedDataEncryptionKey)> {
82        let us_len = std::mem::size_of::<u64>();
83
84        if self.value().len() < us_len {
85            return Err(KmsAeadEncryptionError::create(
86                "INVALID_CIPHER_TEXT_FORMAT",
87                "Unexpected len of cipher text to decode",
88            ));
89        }
90
91        let len_slice = &self.0.as_slice()[0..us_len];
92        let dek_len = usize::from_be_bytes(len_slice.try_into().unwrap());
93
94        if self.value().len() < us_len + dek_len {
95            return Err(KmsAeadEncryptionError::create(
96                "INVALID_CIPHER_TEXT_FORMAT",
97                "Unexpected len of cipher text to decode: DEK len is more than buffer",
98            ));
99        }
100
101        let dek: EncryptedDataEncryptionKey =
102            self.0.as_slice()[us_len..us_len + dek_len].to_vec().into();
103
104        let cipher_text: CipherText = self.0.as_slice()[us_len + dek_len..].to_vec().into();
105        Ok((cipher_text, dek))
106    }
107
108    pub fn to_hex_string(&self) -> String {
109        hex::encode(self.value())
110    }
111
112    #[inline]
113    pub fn value(&self) -> &[u8] {
114        &self.0
115    }
116}
117
118impl From<Vec<u8>> for CipherTextWithEncryptedKey {
119    fn from(value: Vec<u8>) -> Self {
120        Self(value)
121    }
122}
123
124impl ConstantTimeEq for CipherTextWithEncryptedKey {
125    fn ct_eq(&self, other: &Self) -> subtle::Choice {
126        self.value().ct_eq(other.value())
127    }
128}
129
130impl PartialEq for CipherTextWithEncryptedKey {
131    fn eq(&self, other: &Self) -> bool {
132        self.ct_eq(other).into()
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139    use proptest::prelude::*;
140
141    pub fn generate_cipher_text() -> BoxedStrategy<CipherText> {
142        ("[a-zA-Z0-9]+")
143            .prop_map(|(mock_str)| CipherText::from(mock_str.as_bytes().to_vec()))
144            .boxed()
145    }
146
147    pub fn generate_encrypted_dek() -> BoxedStrategy<EncryptedDataEncryptionKey> {
148        ("[a-zA-Z0-9]+")
149            .prop_map(|(mock_str)| EncryptedDataEncryptionKey::from(mock_str.as_bytes().to_vec()))
150            .boxed()
151    }
152
153    proptest! {
154        #[test]
155        fn cipher_text_with_key_encoding_test(mock_cipher_text in generate_cipher_text(), mock_encrypted_dek in generate_encrypted_dek()) {
156            let cipher_text_with_key = CipherTextWithEncryptedKey::new(&mock_cipher_text, &mock_encrypted_dek);
157            let (decoded_cipher_text,decoded_dek) = cipher_text_with_key.separate().unwrap();
158            assert_eq!(decoded_cipher_text, mock_cipher_text);
159            assert_eq!(decoded_dek, mock_encrypted_dek);
160        }
161    }
162
163    #[test]
164    fn test_constant_time_encrypted_dek_comparison() {
165        let dek1 = EncryptedDataEncryptionKey::from(vec![1, 2, 3, 4, 5]);
166        let dek2 = EncryptedDataEncryptionKey::from(vec![1, 2, 3, 4, 5]);
167        let dek3 = EncryptedDataEncryptionKey::from(vec![1, 2, 3, 4, 6]);
168
169        // PartialEq uses constant-time comparison internally
170        assert_eq!(dek1, dek2);
171        assert_ne!(dek1, dek3);
172    }
173
174    #[test]
175    fn test_constant_time_cipher_text_with_key_comparison() {
176        let ct1 = CipherTextWithEncryptedKey::from(vec![1, 2, 3, 4, 5]);
177        let ct2 = CipherTextWithEncryptedKey::from(vec![1, 2, 3, 4, 5]);
178        let ct3 = CipherTextWithEncryptedKey::from(vec![1, 2, 3, 4, 6]);
179
180        // PartialEq uses constant-time comparison internally
181        assert_eq!(ct1, ct2);
182        assert_ne!(ct1, ct3);
183    }
184
185    #[test]
186    fn test_cipher_text_hex_encoding() {
187        let cipher = CipherText::from(vec![0xDE, 0xAD, 0xBE, 0xEF]);
188        assert_eq!(cipher.to_hex_string(), "deadbeef");
189    }
190
191    #[test]
192    fn test_encrypted_dek_hex_encoding() {
193        let dek = EncryptedDataEncryptionKey::from(vec![0xCA, 0xFE, 0xBA, 0xBE]);
194        assert_eq!(dek.to_hex_string(), "cafebabe");
195    }
196
197    #[test]
198    fn test_cipher_text_with_key_hex_encoding() {
199        let ct = CipherTextWithEncryptedKey::from(vec![0x01, 0x02, 0x03, 0x04]);
200        assert_eq!(ct.to_hex_string(), "01020304");
201    }
202
203    #[test]
204    fn test_separate_invalid_too_short() {
205        // Buffer too short to contain length prefix
206        let too_short = CipherTextWithEncryptedKey::from(vec![0x00, 0x00, 0x00]);
207        let result = too_short.separate();
208        assert!(result.is_err());
209    }
210
211    #[test]
212    fn test_separate_invalid_dek_len_exceeds_buffer() {
213        // DEK length claims more bytes than available
214        let mut buffer = Vec::new();
215        buffer.extend_from_slice(&(1000u64).to_be_bytes()); // Claims 1000 bytes
216        buffer.extend_from_slice(&[0x01, 0x02, 0x03]); // Only 3 bytes available
217
218        let invalid = CipherTextWithEncryptedKey::from(buffer);
219        let result = invalid.separate();
220        assert!(result.is_err());
221    }
222
223    #[test]
224    fn test_separate_empty_dek() {
225        let cipher = CipherText::from(vec![0xAA, 0xBB, 0xCC]);
226        let dek = EncryptedDataEncryptionKey::from(vec![]);
227
228        let combined = CipherTextWithEncryptedKey::new(&cipher, &dek);
229        let (decoded_cipher, decoded_dek) = combined.separate().unwrap();
230
231        assert_eq!(decoded_cipher, cipher);
232        assert_eq!(decoded_dek, dek);
233    }
234
235    #[test]
236    fn test_separate_empty_cipher_text() {
237        let cipher = CipherText::from(vec![]);
238        let dek = EncryptedDataEncryptionKey::from(vec![0x01, 0x02, 0x03]);
239
240        let combined = CipherTextWithEncryptedKey::new(&cipher, &dek);
241        let (decoded_cipher, decoded_dek) = combined.separate().unwrap();
242
243        assert_eq!(decoded_cipher, cipher);
244        assert_eq!(decoded_dek, dek);
245    }
246
247    #[test]
248    fn test_separate_both_empty() {
249        let cipher = CipherText::from(vec![]);
250        let dek = EncryptedDataEncryptionKey::from(vec![]);
251
252        let combined = CipherTextWithEncryptedKey::new(&cipher, &dek);
253        let (decoded_cipher, decoded_dek) = combined.separate().unwrap();
254
255        assert_eq!(decoded_cipher, cipher);
256        assert_eq!(decoded_dek, dek);
257    }
258
259    #[test]
260    fn test_separate_large_values() {
261        let cipher = CipherText::from(vec![0x42; 10000]);
262        let dek = EncryptedDataEncryptionKey::from(vec![0xFF; 5000]);
263
264        let combined = CipherTextWithEncryptedKey::new(&cipher, &dek);
265        let (decoded_cipher, decoded_dek) = combined.separate().unwrap();
266
267        assert_eq!(decoded_cipher, cipher);
268        assert_eq!(decoded_dek, dek);
269    }
270
271    proptest! {
272        #[test]
273        fn separate_roundtrip_fuzz(cipher_len in 0usize..1000, dek_len in 0usize..1000) {
274            let cipher = CipherText::from(vec![0x42; cipher_len]);
275            let dek = EncryptedDataEncryptionKey::from(vec![0xFF; dek_len]);
276
277            let combined = CipherTextWithEncryptedKey::new(&cipher, &dek);
278            let (decoded_cipher, decoded_dek) = combined.separate().unwrap();
279
280            assert_eq!(decoded_cipher, cipher);
281            assert_eq!(decoded_dek, dek);
282        }
283    }
284}