1use std::{borrow::Cow, str::FromStr};
2
3use bitwarden_encoding::{B64, FromStrVisitor};
4use coset::{CborSerializable, iana::KeyOperation};
5use serde::Deserialize;
6use tracing::instrument;
7#[cfg(feature = "wasm")]
8use wasm_bindgen::convert::{FromWasmAbi, IntoWasmAbi, OptionFromWasmAbi};
9
10use super::{check_length, from_b64, from_b64_vec, split_enc_string};
11use crate::{
12 Aes256CbcHmacKey, ContentFormat, CoseEncrypt0Bytes, KeyDecryptable, KeyEncryptable,
13 KeyEncryptableWithContentType, SymmetricCryptoKey, Utf8Bytes, XChaCha20Poly1305Key,
14 cose::XCHACHA20_POLY1305,
15 error::{CryptoError, EncStringParseError, Result, UnsupportedOperationError},
16 keys::KeyId,
17};
18
19#[cfg(feature = "wasm")]
20#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
21const TS_CUSTOM_TYPES: &'static str = r#"
22export type EncString = Tagged<string, "EncString">;
23"#;
24
25#[allow(missing_docs)]
62#[derive(Clone, zeroize::ZeroizeOnDrop, PartialEq)]
63#[allow(unused, non_camel_case_types)]
64pub enum EncString {
65 Aes256Cbc_B64 {
67 iv: [u8; 16],
68 data: Vec<u8>,
69 },
70 Aes256Cbc_HmacSha256_B64 {
73 iv: [u8; 16],
74 mac: [u8; 32],
75 data: Vec<u8>,
76 },
77 Cose_Encrypt0_B64 {
79 data: Vec<u8>,
80 },
81}
82
83#[cfg(feature = "wasm")]
84impl wasm_bindgen::describe::WasmDescribe for EncString {
85 fn describe() {
86 <String as wasm_bindgen::describe::WasmDescribe>::describe();
87 }
88}
89
90#[cfg(feature = "wasm")]
91impl FromWasmAbi for EncString {
92 type Abi = <String as FromWasmAbi>::Abi;
93
94 unsafe fn from_abi(abi: Self::Abi) -> Self {
95 use wasm_bindgen::UnwrapThrowExt;
96
97 let s = unsafe { String::from_abi(abi) };
98 Self::from_str(&s).unwrap_throw()
99 }
100}
101
102#[cfg(feature = "wasm")]
103impl OptionFromWasmAbi for EncString {
104 fn is_none(abi: &Self::Abi) -> bool {
105 <String as OptionFromWasmAbi>::is_none(abi)
106 }
107}
108
109#[cfg(feature = "wasm")]
110impl IntoWasmAbi for EncString {
111 type Abi = <String as IntoWasmAbi>::Abi;
112
113 fn into_abi(self) -> Self::Abi {
114 self.to_string().into_abi()
115 }
116}
117
118impl FromStr for EncString {
120 type Err = CryptoError;
121
122 fn from_str(s: &str) -> Result<Self, Self::Err> {
123 let (enc_type, parts) = split_enc_string(s);
124 match (enc_type, parts.len()) {
125 ("0", 2) => {
126 let iv = from_b64(parts[0])?;
127 let data = from_b64_vec(parts[1])?;
128
129 Ok(EncString::Aes256Cbc_B64 { iv, data })
130 }
131 ("2", 3) => {
132 let iv = from_b64(parts[0])?;
133 let data = from_b64_vec(parts[1])?;
134 let mac = from_b64(parts[2])?;
135
136 Ok(EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data })
137 }
138 ("7", 1) => {
139 let buffer = from_b64_vec(parts[0])?;
140
141 Ok(EncString::Cose_Encrypt0_B64 { data: buffer })
142 }
143 (enc_type, parts) => Err(EncStringParseError::InvalidTypeSymm {
144 enc_type: enc_type.to_string(),
145 parts,
146 }
147 .into()),
148 }
149 }
150}
151
152impl EncString {
153 pub fn try_from_optional(s: Option<String>) -> Result<Option<EncString>, CryptoError> {
155 s.map(|s| s.parse()).transpose()
156 }
157
158 #[allow(missing_docs)]
159 pub fn from_buffer(buf: &[u8]) -> Result<Self> {
160 if buf.is_empty() {
161 return Err(EncStringParseError::NoType.into());
162 }
163 let enc_type = buf[0];
164
165 match enc_type {
166 0 => {
167 check_length(buf, 18)?;
168 let iv = buf[1..17].try_into().expect("Valid length");
169 let data = buf[17..].to_vec();
170
171 Ok(EncString::Aes256Cbc_B64 { iv, data })
172 }
173 2 => {
174 check_length(buf, 50)?;
175 let iv = buf[1..17].try_into().expect("Valid length");
176 let mac = buf[17..49].try_into().expect("Valid length");
177 let data = buf[49..].to_vec();
178
179 Ok(EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data })
180 }
181 7 => Ok(EncString::Cose_Encrypt0_B64 {
182 data: buf[1..].to_vec(),
183 }),
184 _ => Err(EncStringParseError::InvalidTypeSymm {
185 enc_type: enc_type.to_string(),
186 parts: 1,
187 }
188 .into()),
189 }
190 }
191
192 #[allow(missing_docs)]
193 pub fn to_buffer(&self) -> Result<Vec<u8>> {
194 let mut buf;
195
196 match self {
197 EncString::Aes256Cbc_B64 { iv, data } => {
198 buf = Vec::with_capacity(1 + 16 + data.len());
199 buf.push(self.enc_type());
200 buf.extend_from_slice(iv);
201 buf.extend_from_slice(data);
202 }
203 EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data } => {
204 buf = Vec::with_capacity(1 + 16 + 32 + data.len());
205 buf.push(self.enc_type());
206 buf.extend_from_slice(iv);
207 buf.extend_from_slice(mac);
208 buf.extend_from_slice(data);
209 }
210 EncString::Cose_Encrypt0_B64 { data } => {
211 buf = Vec::with_capacity(1 + data.len());
212 buf.push(self.enc_type());
213 buf.extend_from_slice(data);
214 }
215 }
216
217 Ok(buf)
218 }
219}
220
221#[allow(clippy::to_string_trait_impl)]
226impl ToString for EncString {
227 fn to_string(&self) -> String {
228 fn fmt_parts(enc_type: u8, parts: &[&[u8]]) -> String {
229 let encoded_parts: Vec<String> = parts
230 .iter()
231 .map(|part| B64::from(*part).to_string())
232 .collect();
233 format!("{}.{}", enc_type, encoded_parts.join("|"))
234 }
235
236 let enc_type = self.enc_type();
237 match &self {
238 EncString::Aes256Cbc_B64 { iv, data } => fmt_parts(enc_type, &[iv, data]),
239 EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data } => {
240 fmt_parts(enc_type, &[iv, data, mac])
241 }
242 EncString::Cose_Encrypt0_B64 { data } => fmt_parts(enc_type, &[data]),
243 }
244 }
245}
246
247impl std::fmt::Debug for EncString {
248 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
249 match self {
250 EncString::Aes256Cbc_B64 { iv, data } => {
251 let mut debug_struct = f.debug_struct("EncString::Aes256Cbc");
252 #[cfg(feature = "dangerous-crypto-debug")]
253 {
254 debug_struct.field("iv", &hex::encode(iv));
255 debug_struct.field("data", &hex::encode(data));
256 }
257 #[cfg(not(feature = "dangerous-crypto-debug"))]
258 {
259 _ = iv;
260 _ = data;
261 }
262 debug_struct.finish()
263 }
264 EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data } => {
265 let mut debug_struct = f.debug_struct("EncString::Aes256CbcHmacSha256");
266 #[cfg(feature = "dangerous-crypto-debug")]
267 {
268 debug_struct.field("iv", &hex::encode(iv));
269 debug_struct.field("data", &hex::encode(data));
270 debug_struct.field("mac", &hex::encode(mac));
271 }
272 #[cfg(not(feature = "dangerous-crypto-debug"))]
273 {
274 _ = iv;
275 _ = data;
276 _ = mac;
277 }
278 debug_struct.finish()
279 }
280 EncString::Cose_Encrypt0_B64 { data } => {
281 let mut debug_struct = f.debug_struct("EncString::CoseEncrypt0");
282
283 match coset::CoseEncrypt0::from_slice(data.as_slice()) {
284 Ok(msg) => {
285 if let Some(ref alg) = msg.protected.header.alg {
286 let alg_name = match alg {
287 coset::Algorithm::PrivateUse(XCHACHA20_POLY1305) => {
288 "XChaCha20-Poly1305"
289 }
290 other => return debug_struct.field("algorithm", other).finish(),
291 };
292 debug_struct.field("algorithm", &alg_name);
293 }
294
295 let key_id = &msg.protected.header.key_id;
296 if let Ok(key_id) = KeyId::try_from(key_id.as_slice()) {
297 debug_struct.field("key_id", &key_id);
298 }
299 debug_struct.field("nonce", &hex::encode(msg.unprotected.iv.as_slice()));
300 if let Some(ref content_type) = msg.protected.header.content_type {
301 debug_struct.field("content_type", content_type);
302 }
303
304 #[cfg(feature = "dangerous-crypto-debug")]
305 if let Some(ref ciphertext) = msg.ciphertext {
306 debug_struct.field("ciphertext", &hex::encode(ciphertext));
307 }
308 }
309 Err(_) => {
310 debug_struct.field("error", &"INVALID_COSE");
311 }
312 }
313
314 debug_struct.finish()
315 }
316 }
317 }
318}
319
320impl<'de> Deserialize<'de> for EncString {
321 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
322 where
323 D: serde::Deserializer<'de>,
324 {
325 deserializer.deserialize_str(FromStrVisitor::new())
326 }
327}
328
329impl serde::Serialize for EncString {
330 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
331 where
332 S: serde::Serializer,
333 {
334 serializer.serialize_str(&self.to_string())
335 }
336}
337
338impl EncString {
339 pub(crate) fn encrypt_aes256_hmac(
340 data_dec: &[u8],
341 key: &Aes256CbcHmacKey,
342 ) -> Result<EncString> {
343 let (iv, mac, data) =
344 crate::aes::encrypt_aes256_hmac(data_dec, &key.mac_key, &key.enc_key)?;
345 Ok(EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data })
346 }
347
348 pub(crate) fn encrypt_xchacha20_poly1305(
349 data_dec: &[u8],
350 key: &XChaCha20Poly1305Key,
351 content_format: ContentFormat,
352 ) -> Result<EncString> {
353 let data = crate::cose::encrypt_xchacha20_poly1305(data_dec, key, content_format)?;
354 Ok(EncString::Cose_Encrypt0_B64 {
355 data: data.to_vec(),
356 })
357 }
358
359 const fn enc_type(&self) -> u8 {
361 match self {
362 EncString::Aes256Cbc_B64 { .. } => 0,
363 EncString::Aes256Cbc_HmacSha256_B64 { .. } => 2,
364 EncString::Cose_Encrypt0_B64 { .. } => 7,
365 }
366 }
367}
368
369impl KeyEncryptableWithContentType<SymmetricCryptoKey, EncString> for &[u8] {
370 fn encrypt_with_key(
371 self,
372 key: &SymmetricCryptoKey,
373 content_format: ContentFormat,
374 ) -> Result<EncString> {
375 match key {
376 SymmetricCryptoKey::Aes256CbcHmacKey(key) => EncString::encrypt_aes256_hmac(self, key),
377 SymmetricCryptoKey::XChaCha20Poly1305Key(inner_key) => {
378 if !inner_key
379 .supported_operations
380 .contains(&KeyOperation::Encrypt)
381 {
382 return Err(CryptoError::KeyOperationNotSupported(KeyOperation::Encrypt));
383 }
384 EncString::encrypt_xchacha20_poly1305(self, inner_key, content_format)
385 }
386 SymmetricCryptoKey::Aes256CbcKey(_) => Err(CryptoError::OperationNotSupported(
387 UnsupportedOperationError::EncryptionNotImplementedForKey,
388 )),
389 }
390 }
391}
392
393impl KeyDecryptable<SymmetricCryptoKey, Vec<u8>> for EncString {
394 fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result<Vec<u8>> {
395 match (self, key) {
396 (EncString::Aes256Cbc_B64 { iv, data }, SymmetricCryptoKey::Aes256CbcKey(key)) => {
397 crate::aes::decrypt_aes256(iv, data.clone(), &key.enc_key)
398 .map_err(|_| CryptoError::Decrypt)
399 }
400 (
401 EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data },
402 SymmetricCryptoKey::Aes256CbcHmacKey(key),
403 ) => crate::aes::decrypt_aes256_hmac(iv, mac, data.clone(), &key.mac_key, &key.enc_key)
404 .map_err(|_| CryptoError::Decrypt),
405 (
406 EncString::Cose_Encrypt0_B64 { data },
407 SymmetricCryptoKey::XChaCha20Poly1305Key(key),
408 ) => {
409 let (decrypted_message, _) = crate::cose::decrypt_xchacha20_poly1305(
410 &CoseEncrypt0Bytes::from(data.as_slice()),
411 key,
412 )?;
413 Ok(decrypted_message)
414 }
415 _ => Err(CryptoError::WrongKeyType),
416 }
417 }
418}
419
420impl KeyEncryptable<SymmetricCryptoKey, EncString> for String {
421 fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result<EncString> {
422 Utf8Bytes::from(self).encrypt_with_key(key)
423 }
424}
425
426impl KeyEncryptable<SymmetricCryptoKey, EncString> for &str {
427 fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result<EncString> {
428 Utf8Bytes::from(self).encrypt_with_key(key)
429 }
430}
431
432impl KeyDecryptable<SymmetricCryptoKey, String> for EncString {
433 #[instrument(err, skip_all)]
434 fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result<String> {
435 let dec: Vec<u8> = self.decrypt_with_key(key)?;
436 String::from_utf8(dec).map_err(|_| CryptoError::InvalidUtf8String)
437 }
438}
439
440impl schemars::JsonSchema for EncString {
443 fn schema_name() -> Cow<'static, str> {
444 "EncString".into()
445 }
446
447 fn json_schema(generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema {
448 generator.subschema_for::<String>()
449 }
450}
451
452#[cfg(test)]
453mod tests {
454 use coset::iana::KeyOperation;
455 use schemars::schema_for;
456
457 use super::EncString;
458 use crate::{
459 CryptoError, KEY_ID_SIZE, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey,
460 derive_symmetric_key,
461 };
462
463 fn encrypt_with_xchacha20(plaintext: &str) -> EncString {
464 let key_id = [0u8; KEY_ID_SIZE];
465 let enc_key = [0u8; 32];
466 let key = SymmetricCryptoKey::XChaCha20Poly1305Key(crate::XChaCha20Poly1305Key {
467 key_id: key_id.into(),
468 enc_key: Box::pin(enc_key.into()),
469 supported_operations: vec![
470 coset::iana::KeyOperation::Decrypt,
471 coset::iana::KeyOperation::Encrypt,
472 coset::iana::KeyOperation::WrapKey,
473 coset::iana::KeyOperation::UnwrapKey,
474 ],
475 });
476
477 plaintext.encrypt_with_key(&key).expect("encryption works")
478 }
479
480 #[test]
481 #[ignore = "Manual test to verify debug format"]
482 fn test_debug() {
483 let enc_string = encrypt_with_xchacha20("Test debug string");
484 println!("{:?}", enc_string);
485 let enc_string_aes =
486 EncString::encrypt_aes256_hmac(b"Test debug string", &derive_symmetric_key("test"))
487 .unwrap();
488 println!("{:?}", enc_string_aes);
489 }
490
491 #[test]
495 fn test_xchacha20_encstring_string_padding_block_sizes() {
496 let cases = [
497 ("", 32), (&"a".repeat(31), 32), (&"a".repeat(32), 64), (&"a".repeat(63), 64), (&"a".repeat(64), 96), ];
503
504 let ciphertext_lengths: Vec<_> = cases
505 .iter()
506 .map(|(plaintext, _)| encrypt_with_xchacha20(plaintext).to_string().len())
507 .collect();
508
509 assert_eq!(ciphertext_lengths[0], ciphertext_lengths[1]);
511 assert_ne!(ciphertext_lengths[1], ciphertext_lengths[2]);
513 assert_eq!(ciphertext_lengths[2], ciphertext_lengths[3]);
514 assert_ne!(ciphertext_lengths[3], ciphertext_lengths[4]);
516 }
517
518 #[test]
519 fn test_enc_roundtrip_xchacha20() {
520 let key_id = [0u8; KEY_ID_SIZE];
521 let enc_key = [0u8; 32];
522 let key = SymmetricCryptoKey::XChaCha20Poly1305Key(crate::XChaCha20Poly1305Key {
523 key_id: key_id.into(),
524 enc_key: Box::pin(enc_key.into()),
525 supported_operations: vec![
526 coset::iana::KeyOperation::Decrypt,
527 coset::iana::KeyOperation::Encrypt,
528 coset::iana::KeyOperation::WrapKey,
529 coset::iana::KeyOperation::UnwrapKey,
530 ],
531 });
532
533 let test_string = "encrypted_test_string";
534 let cipher = test_string.to_owned().encrypt_with_key(&key).unwrap();
535 let decrypted_str: String = cipher.decrypt_with_key(&key).unwrap();
536 assert_eq!(decrypted_str, test_string);
537 }
538
539 #[test]
540 fn test_enc_string_roundtrip() {
541 let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test"));
542
543 let test_string = "encrypted_test_string";
544 let cipher = test_string.to_string().encrypt_with_key(&key).unwrap();
545
546 let decrypted_str: String = cipher.decrypt_with_key(&key).unwrap();
547 assert_eq!(decrypted_str, test_string);
548 }
549
550 #[test]
551 fn test_enc_roundtrip_xchacha20_empty() {
552 let key_id = [0u8; KEY_ID_SIZE];
553 let enc_key = [0u8; 32];
554 let key = SymmetricCryptoKey::XChaCha20Poly1305Key(crate::XChaCha20Poly1305Key {
555 key_id: key_id.into(),
556 enc_key: Box::pin(enc_key.into()),
557 supported_operations: vec![
558 coset::iana::KeyOperation::Decrypt,
559 coset::iana::KeyOperation::Encrypt,
560 coset::iana::KeyOperation::WrapKey,
561 coset::iana::KeyOperation::UnwrapKey,
562 ],
563 });
564
565 let test_string = "";
566 let cipher = test_string.to_owned().encrypt_with_key(&key).unwrap();
567 let decrypted_str: String = cipher.decrypt_with_key(&key).unwrap();
568 assert_eq!(decrypted_str, test_string);
569 }
570
571 #[test]
572 fn test_enc_string_roundtrip_empty() {
573 let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test"));
574
575 let test_string = "";
576 let cipher = test_string.to_string().encrypt_with_key(&key).unwrap();
577
578 let decrypted_str: String = cipher.decrypt_with_key(&key).unwrap();
579 assert_eq!(decrypted_str, test_string);
580 }
581
582 #[test]
583 fn test_enc_string_ref_roundtrip() {
584 let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test"));
585
586 let test_string: &'static str = "encrypted_test_string";
587 let cipher = test_string.to_string().encrypt_with_key(&key).unwrap();
588
589 let decrypted_str: String = cipher.decrypt_with_key(&key).unwrap();
590 assert_eq!(decrypted_str, test_string);
591 }
592
593 #[test]
594 fn test_enc_string_serialization() {
595 #[derive(serde::Serialize, serde::Deserialize)]
596 struct Test {
597 key: EncString,
598 }
599
600 let cipher = "2.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==|Q6PkuT+KX/axrgN9ubD5Ajk2YNwxQkgs3WJM0S0wtG8=";
601 let serialized = format!("{{\"key\":\"{cipher}\"}}");
602
603 let t = serde_json::from_str::<Test>(&serialized).unwrap();
604 assert_eq!(t.key.enc_type(), 2);
605 assert_eq!(t.key.to_string(), cipher);
606 assert_eq!(serde_json::to_string(&t).unwrap(), serialized);
607 }
608
609 #[test]
610 fn test_enc_from_to_buffer() {
611 let enc_str: &str = "2.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==|Q6PkuT+KX/axrgN9ubD5Ajk2YNwxQkgs3WJM0S0wtG8=";
612 let enc_string: EncString = enc_str.parse().unwrap();
613
614 let enc_buf = enc_string.to_buffer().unwrap();
615
616 assert_eq!(
617 enc_buf,
618 vec![
619 2, 164, 196, 186, 254, 39, 19, 64, 0, 109, 186, 92, 57, 218, 154, 182, 150, 67,
620 163, 228, 185, 63, 138, 95, 246, 177, 174, 3, 125, 185, 176, 249, 2, 57, 54, 96,
621 220, 49, 66, 72, 44, 221, 98, 76, 209, 45, 48, 180, 111, 93, 118, 241, 43, 16, 211,
622 135, 233, 150, 136, 221, 71, 140, 125, 141, 215
623 ]
624 );
625
626 let enc_string_new = EncString::from_buffer(&enc_buf).unwrap();
627
628 assert_eq!(enc_string_new.to_string(), enc_str)
629 }
630
631 #[test]
632 fn test_from_str_cbc256() {
633 let enc_str = "0.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==";
634 let enc_string: EncString = enc_str.parse().unwrap();
635
636 assert_eq!(enc_string.enc_type(), 0);
637 if let EncString::Aes256Cbc_B64 { iv, data } = &enc_string {
638 assert_eq!(
639 iv,
640 &[
641 164, 196, 186, 254, 39, 19, 64, 0, 109, 186, 92, 57, 218, 154, 182, 150
642 ]
643 );
644 assert_eq!(
645 data,
646 &[
647 93, 118, 241, 43, 16, 211, 135, 233, 150, 136, 221, 71, 140, 125, 141, 215
648 ]
649 );
650 } else {
651 panic!("Invalid variant")
652 };
653 }
654
655 #[test]
656 fn test_decrypt_cbc256() {
657 let key = "hvBMMb1t79YssFZkpetYsM3deyVuQv4r88Uj9gvYe08=".to_string();
658 let key = SymmetricCryptoKey::try_from(key).unwrap();
659
660 let enc_str = "0.NQfjHLr6za7VQVAbrpL81w==|wfrjmyJ0bfwkQlySrhw8dA==";
661 let enc_string: EncString = enc_str.parse().unwrap();
662 assert_eq!(enc_string.enc_type(), 0);
663
664 let dec_str: String = enc_string.decrypt_with_key(&key).unwrap();
665 assert_eq!(dec_str, "EncryptMe!");
666 }
667
668 #[test]
669 fn test_decrypt_downgrade_encstring_prevention() {
670 let key = "hvBMMb1t79YssFZkpetYsM3deyVuQv4r88Uj9gvYe0+G8EwxvW3v1iywVmSl61iwzd17JW5C/ivzxSP2C9h7Tw==".to_string();
673 let key = SymmetricCryptoKey::try_from(key).unwrap();
674
675 let enc_str = "0.NQfjHLr6za7VQVAbrpL81w==|wfrjmyJ0bfwkQlySrhw8dA==";
679 let enc_string: EncString = enc_str.parse().unwrap();
680 assert_eq!(enc_string.enc_type(), 0);
681
682 let result: Result<String, CryptoError> = enc_string.decrypt_with_key(&key);
683 assert!(matches!(result, Err(CryptoError::WrongKeyType)));
684 }
685
686 #[test]
687 fn test_encrypt_fails_when_operation_not_allowed() {
688 let key_id = [0u8; KEY_ID_SIZE];
690 let enc_key = [0u8; 32];
691 let key = SymmetricCryptoKey::XChaCha20Poly1305Key(crate::XChaCha20Poly1305Key {
692 key_id: key_id.into(),
693 enc_key: Box::pin(enc_key.into()),
694 supported_operations: vec![KeyOperation::Decrypt],
695 });
696
697 let plaintext = "should fail";
698 let result = plaintext.encrypt_with_key(&key);
699 assert!(
700 matches!(
701 result,
702 Err(CryptoError::KeyOperationNotSupported(KeyOperation::Encrypt))
703 ),
704 "Expected encrypt to fail with KeyOperationNotSupported, got: {result:?}"
705 );
706 }
707
708 #[test]
709 fn test_from_str_invalid() {
710 let enc_str = "8.ABC";
711 let enc_string: Result<EncString, _> = enc_str.parse();
712
713 let err = enc_string.unwrap_err();
714 assert_eq!(
715 err.to_string(),
716 "EncString error, Invalid symmetric type, got type 8 with 1 parts"
717 );
718 }
719
720 #[test]
721 #[cfg(not(feature = "dangerous-crypto-debug"))]
722 fn test_debug_format() {
723 let enc_str = "2.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==|Q6PkuT+KX/axrgN9ubD5Ajk2YNwxQkgs3WJM0S0wtG8=";
724 let enc_string: EncString = enc_str.parse().unwrap();
725 assert_eq!(
726 "EncString::Aes256CbcHmacSha256".to_string(),
727 format!("{:?}", enc_string)
728 );
729 }
730
731 #[test]
732 fn test_json_schema() {
733 let schema = schema_for!(EncString);
734
735 assert_eq!(
736 serde_json::to_string(&schema).unwrap(),
737 r#"{"$schema":"https://json-schema.org/draft/2020-12/schema","title":"EncString","type":"string"}"#
738 );
739 }
740}