1use std::str::FromStr;
2
3use base64::{engine::general_purpose::STANDARD, Engine};
4use coset::CborSerializable;
5use serde::Deserialize;
6
7use super::{check_length, from_b64, from_b64_vec, split_enc_string};
8use crate::{
9 error::{CryptoError, EncStringParseError, Result, UnsupportedOperation},
10 Aes256CbcHmacKey, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, XChaCha20Poly1305Key,
11};
12
13#[cfg(feature = "wasm")]
14#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
15const TS_CUSTOM_TYPES: &'static str = r#"
16export type EncString = string;
17"#;
18
19#[derive(Clone, zeroize::ZeroizeOnDrop, PartialEq)]
56#[allow(unused, non_camel_case_types)]
57pub enum EncString {
58 Aes256Cbc_B64 {
60 iv: [u8; 16],
61 data: Vec<u8>,
62 },
63 Aes256Cbc_HmacSha256_B64 {
66 iv: [u8; 16],
67 mac: [u8; 32],
68 data: Vec<u8>,
69 },
70 Cose_Encrypt0_B64 {
72 data: Vec<u8>,
73 },
74}
75
76impl FromStr for EncString {
78 type Err = CryptoError;
79
80 fn from_str(s: &str) -> Result<Self, Self::Err> {
81 let (enc_type, parts) = split_enc_string(s);
82 match (enc_type, parts.len()) {
83 ("0", 2) => {
84 let iv = from_b64(parts[0])?;
85 let data = from_b64_vec(parts[1])?;
86
87 Ok(EncString::Aes256Cbc_B64 { iv, data })
88 }
89 ("2", 3) => {
90 let iv = from_b64(parts[0])?;
91 let data = from_b64_vec(parts[1])?;
92 let mac = from_b64(parts[2])?;
93
94 Ok(EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data })
95 }
96 ("7", 1) => {
97 let buffer = from_b64_vec(parts[0])?;
98
99 Ok(EncString::Cose_Encrypt0_B64 { data: buffer })
100 }
101 (enc_type, parts) => Err(EncStringParseError::InvalidTypeSymm {
102 enc_type: enc_type.to_string(),
103 parts,
104 }
105 .into()),
106 }
107 }
108}
109
110impl EncString {
111 pub fn try_from_optional(s: Option<String>) -> Result<Option<EncString>, CryptoError> {
113 s.map(|s| s.parse()).transpose()
114 }
115
116 pub fn from_buffer(buf: &[u8]) -> Result<Self> {
117 if buf.is_empty() {
118 return Err(EncStringParseError::NoType.into());
119 }
120 let enc_type = buf[0];
121
122 match enc_type {
123 0 => {
124 check_length(buf, 18)?;
125 let iv = buf[1..17].try_into().expect("Valid length");
126 let data = buf[17..].to_vec();
127
128 Ok(EncString::Aes256Cbc_B64 { iv, data })
129 }
130 2 => {
131 check_length(buf, 50)?;
132 let iv = buf[1..17].try_into().expect("Valid length");
133 let mac = buf[17..49].try_into().expect("Valid length");
134 let data = buf[49..].to_vec();
135
136 Ok(EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data })
137 }
138 7 => Ok(EncString::Cose_Encrypt0_B64 {
139 data: buf[1..].to_vec(),
140 }),
141 _ => Err(EncStringParseError::InvalidTypeSymm {
142 enc_type: enc_type.to_string(),
143 parts: 1,
144 }
145 .into()),
146 }
147 }
148
149 pub fn to_buffer(&self) -> Result<Vec<u8>> {
150 let mut buf;
151
152 match self {
153 EncString::Aes256Cbc_B64 { iv, data } => {
154 buf = Vec::with_capacity(1 + 16 + data.len());
155 buf.push(self.enc_type());
156 buf.extend_from_slice(iv);
157 buf.extend_from_slice(data);
158 }
159 EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data } => {
160 buf = Vec::with_capacity(1 + 16 + 32 + data.len());
161 buf.push(self.enc_type());
162 buf.extend_from_slice(iv);
163 buf.extend_from_slice(mac);
164 buf.extend_from_slice(data);
165 }
166 EncString::Cose_Encrypt0_B64 { data } => {
167 buf = Vec::with_capacity(1 + data.len());
168 buf.push(self.enc_type());
169 buf.extend_from_slice(data);
170 }
171 }
172
173 Ok(buf)
174 }
175}
176
177#[allow(clippy::to_string_trait_impl)]
182impl ToString for EncString {
183 fn to_string(&self) -> String {
184 fn fmt_parts(enc_type: u8, parts: &[&[u8]]) -> String {
185 let encoded_parts: Vec<String> =
186 parts.iter().map(|part| STANDARD.encode(part)).collect();
187 format!("{}.{}", enc_type, encoded_parts.join("|"))
188 }
189
190 let enc_type = self.enc_type();
191 match &self {
192 EncString::Aes256Cbc_B64 { iv, data } => fmt_parts(enc_type, &[iv, data]),
193 EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data } => {
194 fmt_parts(enc_type, &[iv, data, mac])
195 }
196 EncString::Cose_Encrypt0_B64 { data } => fmt_parts(enc_type, &[data]),
197 }
198 }
199}
200
201impl std::fmt::Debug for EncString {
202 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203 fn fmt_parts(
204 f: &mut std::fmt::Formatter<'_>,
205 enc_type: u8,
206 parts: &[&[u8]],
207 ) -> std::fmt::Result {
208 let encoded_parts: Vec<String> =
209 parts.iter().map(|part| STANDARD.encode(part)).collect();
210 write!(f, "{}.{}", enc_type, encoded_parts.join("|"))
211 }
212
213 let enc_type = self.enc_type();
214
215 match self {
216 EncString::Aes256Cbc_B64 { iv, data } => fmt_parts(f, enc_type, &[iv, data]),
217 EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data } => {
218 fmt_parts(f, enc_type, &[iv, data, mac])
219 }
220 EncString::Cose_Encrypt0_B64 { data } => {
221 let msg = coset::CoseEncrypt0::from_slice(data.as_slice())
222 .map(|msg| format!("{:?}", msg))
223 .unwrap_or_else(|_| "INVALID_COSE".to_string());
224 write!(f, "{}.{}", enc_type, msg)
225 }
226 }
227 }
228}
229
230impl<'de> Deserialize<'de> for EncString {
231 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
232 where
233 D: serde::Deserializer<'de>,
234 {
235 deserializer.deserialize_str(super::FromStrVisitor::new())
236 }
237}
238
239impl serde::Serialize for EncString {
240 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
241 where
242 S: serde::Serializer,
243 {
244 serializer.serialize_str(&self.to_string())
245 }
246}
247
248impl EncString {
249 pub(crate) fn encrypt_aes256_hmac(
250 data_dec: &[u8],
251 key: &Aes256CbcHmacKey,
252 ) -> Result<EncString> {
253 let (iv, mac, data) =
254 crate::aes::encrypt_aes256_hmac(data_dec, &key.mac_key, &key.enc_key)?;
255 Ok(EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data })
256 }
257
258 pub(crate) fn encrypt_xchacha20_poly1305(
259 data_dec: &[u8],
260 key: &XChaCha20Poly1305Key,
261 ) -> Result<EncString> {
262 let data = crate::cose::encrypt_xchacha20_poly1305(data_dec, key)?;
263 Ok(EncString::Cose_Encrypt0_B64 { data })
264 }
265
266 const fn enc_type(&self) -> u8 {
268 match self {
269 EncString::Aes256Cbc_B64 { .. } => 0,
270 EncString::Aes256Cbc_HmacSha256_B64 { .. } => 2,
271 EncString::Cose_Encrypt0_B64 { .. } => 7,
272 }
273 }
274}
275
276impl KeyEncryptable<SymmetricCryptoKey, EncString> for &[u8] {
277 fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result<EncString> {
278 match key {
279 SymmetricCryptoKey::Aes256CbcHmacKey(key) => EncString::encrypt_aes256_hmac(self, key),
280 SymmetricCryptoKey::XChaCha20Poly1305Key(inner_key) => {
281 EncString::encrypt_xchacha20_poly1305(self, inner_key)
282 }
283 SymmetricCryptoKey::Aes256CbcKey(_) => Err(CryptoError::OperationNotSupported(
284 UnsupportedOperation::EncryptionNotImplementedForKey,
285 )),
286 }
287 }
288}
289
290impl KeyDecryptable<SymmetricCryptoKey, Vec<u8>> for EncString {
291 fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result<Vec<u8>> {
292 match (self, key) {
293 (EncString::Aes256Cbc_B64 { iv, data }, SymmetricCryptoKey::Aes256CbcKey(key)) => {
294 crate::aes::decrypt_aes256(iv, data.clone(), &key.enc_key)
295 }
296 (
297 EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data },
298 SymmetricCryptoKey::Aes256CbcHmacKey(key),
299 ) => crate::aes::decrypt_aes256_hmac(iv, mac, data.clone(), &key.mac_key, &key.enc_key),
300 (
301 EncString::Cose_Encrypt0_B64 { data },
302 SymmetricCryptoKey::XChaCha20Poly1305Key(key),
303 ) => {
304 let decrypted_message =
305 crate::cose::decrypt_xchacha20_poly1305(data.as_slice(), key)?;
306 Ok(decrypted_message)
307 }
308 _ => Err(CryptoError::WrongKeyType),
309 }
310 }
311}
312
313impl KeyEncryptable<SymmetricCryptoKey, EncString> for String {
314 fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result<EncString> {
315 self.as_bytes().encrypt_with_key(key)
316 }
317}
318
319impl KeyEncryptable<SymmetricCryptoKey, EncString> for &str {
320 fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result<EncString> {
321 self.as_bytes().encrypt_with_key(key)
322 }
323}
324
325impl KeyDecryptable<SymmetricCryptoKey, String> for EncString {
326 fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result<String> {
327 let dec: Vec<u8> = self.decrypt_with_key(key)?;
328 String::from_utf8(dec).map_err(|_| CryptoError::InvalidUtf8String)
329 }
330}
331
332impl schemars::JsonSchema for EncString {
335 fn schema_name() -> String {
336 "EncString".to_string()
337 }
338
339 fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
340 generator.subschema_for::<String>()
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use schemars::schema_for;
347
348 use super::EncString;
349 use crate::{
350 derive_symmetric_key, CryptoError, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey,
351 KEY_ID_SIZE,
352 };
353
354 #[test]
355 fn test_enc_roundtrip_xchacha20() {
356 let key_id = [0u8; KEY_ID_SIZE];
357 let enc_key = [0u8; 32];
358 let key = SymmetricCryptoKey::XChaCha20Poly1305Key(crate::XChaCha20Poly1305Key {
359 key_id,
360 enc_key: Box::pin(enc_key.into()),
361 });
362
363 let test_string = "encrypted_test_string";
364 let cipher = test_string.to_owned().encrypt_with_key(&key).unwrap();
365 let decrypted_str: String = cipher.decrypt_with_key(&key).unwrap();
366 assert_eq!(decrypted_str, test_string);
367 }
368
369 #[test]
370 fn test_enc_string_roundtrip() {
371 let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test"));
372
373 let test_string = "encrypted_test_string";
374 let cipher = test_string.to_owned().encrypt_with_key(&key).unwrap();
375
376 let decrypted_str: String = cipher.decrypt_with_key(&key).unwrap();
377 assert_eq!(decrypted_str, test_string);
378 }
379
380 #[test]
381 fn test_enc_string_ref_roundtrip() {
382 let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test"));
383
384 let test_string = "encrypted_test_string";
385 let cipher = test_string.encrypt_with_key(&key).unwrap();
386
387 let decrypted_str: String = cipher.decrypt_with_key(&key).unwrap();
388 assert_eq!(decrypted_str, test_string);
389 }
390
391 #[test]
392 fn test_enc_string_serialization() {
393 #[derive(serde::Serialize, serde::Deserialize)]
394 struct Test {
395 key: EncString,
396 }
397
398 let cipher = "2.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==|Q6PkuT+KX/axrgN9ubD5Ajk2YNwxQkgs3WJM0S0wtG8=";
399 let serialized = format!("{{\"key\":\"{cipher}\"}}");
400
401 let t = serde_json::from_str::<Test>(&serialized).unwrap();
402 assert_eq!(t.key.enc_type(), 2);
403 assert_eq!(t.key.to_string(), cipher);
404 assert_eq!(serde_json::to_string(&t).unwrap(), serialized);
405 }
406
407 #[test]
408 fn test_enc_from_to_buffer() {
409 let enc_str: &str = "2.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==|Q6PkuT+KX/axrgN9ubD5Ajk2YNwxQkgs3WJM0S0wtG8=";
410 let enc_string: EncString = enc_str.parse().unwrap();
411
412 let enc_buf = enc_string.to_buffer().unwrap();
413
414 assert_eq!(
415 enc_buf,
416 vec![
417 2, 164, 196, 186, 254, 39, 19, 64, 0, 109, 186, 92, 57, 218, 154, 182, 150, 67,
418 163, 228, 185, 63, 138, 95, 246, 177, 174, 3, 125, 185, 176, 249, 2, 57, 54, 96,
419 220, 49, 66, 72, 44, 221, 98, 76, 209, 45, 48, 180, 111, 93, 118, 241, 43, 16, 211,
420 135, 233, 150, 136, 221, 71, 140, 125, 141, 215
421 ]
422 );
423
424 let enc_string_new = EncString::from_buffer(&enc_buf).unwrap();
425
426 assert_eq!(enc_string_new.to_string(), enc_str)
427 }
428
429 #[test]
430 fn test_from_str_cbc256() {
431 let enc_str = "0.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==";
432 let enc_string: EncString = enc_str.parse().unwrap();
433
434 assert_eq!(enc_string.enc_type(), 0);
435 if let EncString::Aes256Cbc_B64 { iv, data } = &enc_string {
436 assert_eq!(
437 iv,
438 &[164, 196, 186, 254, 39, 19, 64, 0, 109, 186, 92, 57, 218, 154, 182, 150]
439 );
440 assert_eq!(
441 data,
442 &[93, 118, 241, 43, 16, 211, 135, 233, 150, 136, 221, 71, 140, 125, 141, 215]
443 );
444 } else {
445 panic!("Invalid variant")
446 };
447 }
448
449 #[test]
450 fn test_decrypt_cbc256() {
451 let key = "hvBMMb1t79YssFZkpetYsM3deyVuQv4r88Uj9gvYe08=".to_string();
452 let key = SymmetricCryptoKey::try_from(key).unwrap();
453
454 let enc_str = "0.NQfjHLr6za7VQVAbrpL81w==|wfrjmyJ0bfwkQlySrhw8dA==";
455 let enc_string: EncString = enc_str.parse().unwrap();
456 assert_eq!(enc_string.enc_type(), 0);
457
458 let dec_str: String = enc_string.decrypt_with_key(&key).unwrap();
459 assert_eq!(dec_str, "EncryptMe!");
460 }
461
462 #[test]
463 fn test_decrypt_downgrade_encstring_prevention() {
464 let key = "hvBMMb1t79YssFZkpetYsM3deyVuQv4r88Uj9gvYe0+G8EwxvW3v1iywVmSl61iwzd17JW5C/ivzxSP2C9h7Tw==".to_string();
467 let key = SymmetricCryptoKey::try_from(key).unwrap();
468
469 let enc_str = "0.NQfjHLr6za7VQVAbrpL81w==|wfrjmyJ0bfwkQlySrhw8dA==";
473 let enc_string: EncString = enc_str.parse().unwrap();
474 assert_eq!(enc_string.enc_type(), 0);
475
476 let result: Result<String, CryptoError> = enc_string.decrypt_with_key(&key);
477 assert!(matches!(result, Err(CryptoError::WrongKeyType)));
478 }
479
480 #[test]
481 fn test_from_str_invalid() {
482 let enc_str = "8.ABC";
483 let enc_string: Result<EncString, _> = enc_str.parse();
484
485 let err = enc_string.unwrap_err();
486 assert_eq!(
487 err.to_string(),
488 "EncString error, Invalid symmetric type, got type 8 with 1 parts"
489 );
490 }
491
492 #[test]
493 fn test_debug_format() {
494 let enc_str = "2.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==|Q6PkuT+KX/axrgN9ubD5Ajk2YNwxQkgs3WJM0S0wtG8=";
495 let enc_string: EncString = enc_str.parse().unwrap();
496
497 let debug_string = format!("{:?}", enc_string);
498 assert_eq!(debug_string, enc_str);
499 }
500
501 #[test]
502 fn test_json_schema() {
503 let schema = schema_for!(EncString);
504
505 assert_eq!(
506 serde_json::to_string(&schema).unwrap(),
507 r#"{"$schema":"http://json-schema.org/draft-07/schema#","title":"EncString","type":"string"}"#
508 );
509 }
510}