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 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 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 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 let mut buffer = Vec::new();
215 buffer.extend_from_slice(&(1000u64).to_be_bytes()); buffer.extend_from_slice(&[0x01, 0x02, 0x03]); 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}