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