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