1use coset::{
7 Algorithm, CborSerializable, CoseEncrypt, CoseEncrypt0, CoseEncrypt0Builder,
8 CoseEncryptBuilder, Header, HeaderBuilder, iana,
9};
10
11use super::XCHACHA20_POLY1305;
12use crate::{
13 ContentFormat, CoseEncrypt0Bytes, CryptoError, XChaCha20Poly1305Key,
14 error::EncStringParseError,
15 hazmat::symmetric_encryption::{
16 Aead,
17 aes_gcm::{Aes256Gcm, Aes256GcmCiphertext, Aes256GcmNonce},
18 xchacha20::{XChaCha20Poly1305, XChaCha20Poly1305Ciphertext, XChaCha20Poly1305Nonce},
19 },
20};
21
22const TEXT_PAD_BLOCK_SIZE: usize = 32;
23
24fn should_pad_content(format: &ContentFormat) -> bool {
25 matches!(format, ContentFormat::Utf8)
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub(crate) enum CoseContentEncryptionAlgorithm {
36 Aes256Gcm,
38 XChaCha20Poly1305,
40}
41
42impl TryFrom<&Algorithm> for CoseContentEncryptionAlgorithm {
43 type Error = CryptoError;
44
45 fn try_from(algorithm: &Algorithm) -> Result<Self, Self::Error> {
46 match algorithm {
47 Algorithm::Assigned(iana::Algorithm::A256GCM) => Ok(Self::Aes256Gcm),
48 Algorithm::PrivateUse(XCHACHA20_POLY1305) => Ok(Self::XChaCha20Poly1305),
49 _ => Err(CryptoError::WrongKeyType),
50 }
51 }
52}
53
54fn algorithm_from_header(
63 header: &Header,
64 default_algorithm: Option<CoseContentEncryptionAlgorithm>,
65) -> Result<CoseContentEncryptionAlgorithm, CryptoError> {
66 match header.alg.as_ref() {
67 Some(algorithm) => CoseContentEncryptionAlgorithm::try_from(algorithm),
68 None => default_algorithm.ok_or(CryptoError::EncString(
69 EncStringParseError::CoseMissingAlgorithm,
70 )),
71 }
72}
73
74fn ensure_algorithm_matches<C: CoseEncryptCipher>(header: &Header) -> Result<(), CryptoError> {
81 match header.alg.as_ref() {
82 Some(algorithm) if algorithm != &C::COSE_ALGORITHM => Err(CryptoError::WrongKeyType),
83 _ => Ok(()),
84 }
85}
86
87pub(crate) fn encrypt_cose(
99 algorithm: CoseContentEncryptionAlgorithm,
100 builder: CoseEncryptBuilder,
101 protected_header: Header,
102 plaintext: &[u8],
103 cek: &[u8],
104) -> Result<CoseEncrypt, CryptoError> {
105 let mut plaintext = plaintext.to_vec();
106 if let Ok(content_format) = ContentFormat::try_from(&protected_header)
107 && should_pad_content(&content_format)
108 {
109 let min_length = TEXT_PAD_BLOCK_SIZE * (1 + (plaintext.len() / TEXT_PAD_BLOCK_SIZE));
110 crate::keys::utils::pad_bytes(&mut plaintext, min_length)?;
111 }
112 match algorithm {
113 CoseContentEncryptionAlgorithm::Aes256Gcm => {
114 let cek: &<Aes256Gcm as Aead>::Key =
115 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
116 Ok(Aes256Gcm::encrypt_cose(
117 builder,
118 protected_header,
119 &plaintext,
120 cek,
121 ))
122 }
123 CoseContentEncryptionAlgorithm::XChaCha20Poly1305 => {
124 let cek: &<XChaCha20Poly1305 as Aead>::Key =
125 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
126 Ok(XChaCha20Poly1305::encrypt_cose(
127 builder,
128 protected_header,
129 &plaintext,
130 cek,
131 ))
132 }
133 }
134}
135
136pub(crate) fn decrypt_cose(
146 cose_encrypt: &CoseEncrypt,
147 default_algorithm: Option<CoseContentEncryptionAlgorithm>,
148 cek: &[u8],
149) -> Result<Vec<u8>, CryptoError> {
150 let decrypted = match algorithm_from_header(&cose_encrypt.protected.header, default_algorithm)?
151 {
152 CoseContentEncryptionAlgorithm::Aes256Gcm => {
153 let cek: &<Aes256Gcm as Aead>::Key =
154 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
155 Aes256Gcm::decrypt_cose(cose_encrypt, cek)?
156 }
157 CoseContentEncryptionAlgorithm::XChaCha20Poly1305 => {
158 let cek: &<XChaCha20Poly1305 as Aead>::Key =
159 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
160 XChaCha20Poly1305::decrypt_cose(cose_encrypt, cek)?
161 }
162 };
163 if let Ok(content_format) = ContentFormat::try_from(&cose_encrypt.protected.header)
164 && should_pad_content(&content_format)
165 {
166 return Ok(crate::keys::utils::unpad_bytes(&decrypted)?.to_vec());
167 }
168 Ok(decrypted)
169}
170
171pub(crate) fn encrypt_cose0(
178 algorithm: CoseContentEncryptionAlgorithm,
179 builder: CoseEncrypt0Builder,
180 protected_header: Header,
181 plaintext: &[u8],
182 cek: &[u8],
183) -> Result<CoseEncrypt0, CryptoError> {
184 let mut plaintext = plaintext.to_vec();
185 if let Ok(content_format) = ContentFormat::try_from(&protected_header)
186 && should_pad_content(&content_format)
187 {
188 let min_length = TEXT_PAD_BLOCK_SIZE * (1 + (plaintext.len() / TEXT_PAD_BLOCK_SIZE));
189 crate::keys::utils::pad_bytes(&mut plaintext, min_length)?;
190 }
191 match algorithm {
192 CoseContentEncryptionAlgorithm::Aes256Gcm => {
193 let cek: &<Aes256Gcm as Aead>::Key =
194 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
195 Ok(Aes256Gcm::encrypt_cose0(
196 builder,
197 protected_header,
198 &plaintext,
199 cek,
200 ))
201 }
202 CoseContentEncryptionAlgorithm::XChaCha20Poly1305 => {
203 let cek: &<XChaCha20Poly1305 as Aead>::Key =
204 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
205 Ok(XChaCha20Poly1305::encrypt_cose0(
206 builder,
207 protected_header,
208 &plaintext,
209 cek,
210 ))
211 }
212 }
213}
214
215pub(crate) fn decrypt_cose0(
225 cose_encrypt0: &CoseEncrypt0,
226 default_algorithm: Option<CoseContentEncryptionAlgorithm>,
227 cek: &[u8],
228) -> Result<Vec<u8>, CryptoError> {
229 let decrypted = match algorithm_from_header(&cose_encrypt0.protected.header, default_algorithm)?
230 {
231 CoseContentEncryptionAlgorithm::Aes256Gcm => {
232 let cek: &<Aes256Gcm as Aead>::Key =
233 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
234 Aes256Gcm::decrypt_cose0(cose_encrypt0, cek)?
235 }
236 CoseContentEncryptionAlgorithm::XChaCha20Poly1305 => {
237 let cek: &<XChaCha20Poly1305 as Aead>::Key =
238 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
239 XChaCha20Poly1305::decrypt_cose0(cose_encrypt0, cek)?
240 }
241 };
242 if let Ok(content_format) = ContentFormat::try_from(&cose_encrypt0.protected.header)
243 && should_pad_content(&content_format)
244 {
245 return Ok(crate::keys::utils::unpad_bytes(&decrypted)?.to_vec());
246 }
247 Ok(decrypted)
248}
249
250pub(crate) trait CoseEncryptCipher: Aead {
253 const COSE_ALGORITHM: Algorithm;
256
257 fn encrypt_cose(
265 builder: CoseEncryptBuilder,
266 protected_header: Header,
267 plaintext: &[u8],
268 cek: &Self::Key,
269 ) -> CoseEncrypt;
270
271 fn decrypt_cose(cose_encrypt: &CoseEncrypt, cek: &Self::Key) -> Result<Vec<u8>, CryptoError>;
279
280 fn encrypt_cose0(
283 builder: CoseEncrypt0Builder,
284 protected_header: Header,
285 plaintext: &[u8],
286 cek: &Self::Key,
287 ) -> CoseEncrypt0;
288
289 fn decrypt_cose0(cose_encrypt0: &CoseEncrypt0, cek: &Self::Key)
292 -> Result<Vec<u8>, CryptoError>;
293}
294
295impl CoseEncryptCipher for Aes256Gcm {
296 const COSE_ALGORITHM: Algorithm = Algorithm::Assigned(iana::Algorithm::A256GCM);
297
298 fn encrypt_cose(
299 builder: CoseEncryptBuilder,
300 mut protected_header: Header,
301 plaintext: &[u8],
302 cek: &Self::Key,
303 ) -> CoseEncrypt {
304 protected_header.alg = Some(Self::COSE_ALGORITHM);
307
308 let nonce = Aes256GcmNonce::make();
312 builder
313 .protected(protected_header)
314 .unprotected(HeaderBuilder::new().iv(nonce.as_bytes().to_vec()).build())
315 .create_ciphertext(plaintext, &[], |data, aad| {
316 Aes256Gcm::encrypt(cek, &nonce, data, aad)
317 .encrypted_bytes()
318 .to_vec()
319 })
320 .build()
321 }
322
323 fn decrypt_cose(cose_encrypt: &CoseEncrypt, cek: &Self::Key) -> Result<Vec<u8>, CryptoError> {
324 ensure_algorithm_matches::<Self>(&cose_encrypt.protected.header)?;
328
329 let nonce = Aes256GcmNonce::try_from(cose_encrypt)?;
330 cose_encrypt.decrypt_ciphertext(
331 &[],
332 || CryptoError::MissingField("ciphertext"),
333 |data, aad| {
334 Aes256Gcm::decrypt(cek, &nonce, &Aes256GcmCiphertext::from(data.to_vec()), aad)
335 },
336 )
337 }
338
339 fn encrypt_cose0(
340 builder: CoseEncrypt0Builder,
341 mut protected_header: Header,
342 plaintext: &[u8],
343 cek: &Self::Key,
344 ) -> CoseEncrypt0 {
345 protected_header.alg = Some(Self::COSE_ALGORITHM);
346
347 let nonce = Aes256GcmNonce::make();
348 builder
349 .protected(protected_header)
350 .unprotected(HeaderBuilder::new().iv(nonce.as_bytes().to_vec()).build())
351 .create_ciphertext(plaintext, &[], |data, aad| {
352 Aes256Gcm::encrypt(cek, &nonce, data, aad)
353 .encrypted_bytes()
354 .to_vec()
355 })
356 .build()
357 }
358
359 fn decrypt_cose0(
360 cose_encrypt0: &CoseEncrypt0,
361 cek: &Self::Key,
362 ) -> Result<Vec<u8>, CryptoError> {
363 ensure_algorithm_matches::<Self>(&cose_encrypt0.protected.header)?;
364
365 let nonce = Aes256GcmNonce::try_from(cose_encrypt0)?;
366 cose_encrypt0.decrypt_ciphertext(
367 &[],
368 || CryptoError::MissingField("ciphertext"),
369 |data, aad| {
370 Aes256Gcm::decrypt(cek, &nonce, &Aes256GcmCiphertext::from(data.to_vec()), aad)
371 },
372 )
373 }
374}
375
376impl CoseEncryptCipher for XChaCha20Poly1305 {
377 const COSE_ALGORITHM: Algorithm = Algorithm::PrivateUse(XCHACHA20_POLY1305);
378
379 fn encrypt_cose(
380 builder: CoseEncryptBuilder,
381 mut protected_header: Header,
382 plaintext: &[u8],
383 cek: &Self::Key,
384 ) -> CoseEncrypt {
385 protected_header.alg = Some(Self::COSE_ALGORITHM);
386
387 let nonce = XChaCha20Poly1305Nonce::make();
388 builder
389 .protected(protected_header)
390 .unprotected(HeaderBuilder::new().iv(nonce.as_bytes().to_vec()).build())
391 .create_ciphertext(plaintext, &[], |data, aad| {
392 XChaCha20Poly1305::encrypt(cek, &nonce, data, aad)
393 .encrypted_bytes()
394 .to_vec()
395 })
396 .build()
397 }
398
399 fn decrypt_cose(cose_encrypt: &CoseEncrypt, cek: &Self::Key) -> Result<Vec<u8>, CryptoError> {
400 ensure_algorithm_matches::<Self>(&cose_encrypt.protected.header)?;
401
402 let nonce = XChaCha20Poly1305Nonce::try_from(cose_encrypt)?;
403 cose_encrypt.decrypt_ciphertext(
404 &[],
405 || CryptoError::MissingField("ciphertext"),
406 |data, aad| {
407 XChaCha20Poly1305::decrypt(
408 cek,
409 &nonce,
410 &XChaCha20Poly1305Ciphertext::from(data.to_vec()),
411 aad,
412 )
413 },
414 )
415 }
416
417 fn encrypt_cose0(
418 builder: CoseEncrypt0Builder,
419 mut protected_header: Header,
420 plaintext: &[u8],
421 cek: &Self::Key,
422 ) -> CoseEncrypt0 {
423 protected_header.alg = Some(Self::COSE_ALGORITHM);
424
425 let nonce = XChaCha20Poly1305Nonce::make();
426 builder
427 .protected(protected_header)
428 .unprotected(HeaderBuilder::new().iv(nonce.as_bytes().to_vec()).build())
429 .create_ciphertext(plaintext, &[], |data, aad| {
430 XChaCha20Poly1305::encrypt(cek, &nonce, data, aad)
431 .encrypted_bytes()
432 .to_vec()
433 })
434 .build()
435 }
436
437 fn decrypt_cose0(
438 cose_encrypt0: &CoseEncrypt0,
439 cek: &Self::Key,
440 ) -> Result<Vec<u8>, CryptoError> {
441 ensure_algorithm_matches::<Self>(&cose_encrypt0.protected.header)?;
442
443 let nonce = XChaCha20Poly1305Nonce::try_from(cose_encrypt0)?;
444 cose_encrypt0.decrypt_ciphertext(
445 &[],
446 || CryptoError::MissingField("ciphertext"),
447 |data, aad| {
448 XChaCha20Poly1305::decrypt(
449 cek,
450 &nonce,
451 &XChaCha20Poly1305Ciphertext::from(data.to_vec()),
452 aad,
453 )
454 },
455 )
456 }
457}
458
459pub(crate) fn encrypt_xchacha20_poly1305(
461 plaintext: &[u8],
462 key: &XChaCha20Poly1305Key,
463 content_format: ContentFormat,
464) -> Result<CoseEncrypt0Bytes, CryptoError> {
465 let mut plaintext = plaintext.to_vec();
466
467 let header_builder: coset::HeaderBuilder = content_format.into();
468 let mut protected_header = header_builder
469 .key_id(key.key_id.as_slice().to_vec())
470 .build();
471 protected_header.alg = Some(coset::Algorithm::PrivateUse(XCHACHA20_POLY1305));
475
476 if should_pad_content(&content_format) {
477 let min_length = TEXT_PAD_BLOCK_SIZE * (1 + (plaintext.len() / TEXT_PAD_BLOCK_SIZE));
478 crate::keys::utils::pad_bytes(&mut plaintext, min_length)?;
479 }
480
481 let nonce = XChaCha20Poly1305Nonce::make();
482 let cose_encrypt0 = coset::CoseEncrypt0Builder::new()
483 .protected(protected_header)
484 .create_ciphertext(&plaintext, &[], |data, aad| {
485 XChaCha20Poly1305::encrypt(&(*key.enc_key).into(), &nonce, data, aad)
486 .encrypted_bytes()
487 .to_vec()
488 })
489 .unprotected(
490 coset::HeaderBuilder::new()
491 .iv(nonce.as_bytes().to_vec())
492 .build(),
493 )
494 .build();
495
496 cose_encrypt0
497 .to_vec()
498 .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)))
499 .map(CoseEncrypt0Bytes::from)
500}
501
502pub(crate) fn decrypt_xchacha20_poly1305(
504 cose_encrypt0_message: &CoseEncrypt0Bytes,
505 key: &XChaCha20Poly1305Key,
506) -> Result<(Vec<u8>, ContentFormat), CryptoError> {
507 let msg = coset::CoseEncrypt0::from_slice(cose_encrypt0_message.as_ref())
508 .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)))?;
509
510 let Some(ref alg) = msg.protected.header.alg else {
511 return Err(CryptoError::EncString(
512 EncStringParseError::CoseMissingAlgorithm,
513 ));
514 };
515
516 if *alg != coset::Algorithm::PrivateUse(XCHACHA20_POLY1305) {
517 return Err(CryptoError::WrongKeyType);
518 }
519
520 let content_format = ContentFormat::try_from(&msg.protected.header)
521 .map_err(|_| CryptoError::EncString(EncStringParseError::CoseMissingContentType))?;
522
523 if key.key_id.as_slice() != msg.protected.header.key_id {
524 return Err(CryptoError::WrongCoseKeyId);
525 }
526
527 let nonce = XChaCha20Poly1305Nonce::try_from(&msg)?;
528 let decrypted_message = msg.decrypt_ciphertext(
529 &[],
530 || CryptoError::MissingField("ciphertext"),
531 |data, aad| {
532 XChaCha20Poly1305::decrypt(
533 &(*key.enc_key).into(),
534 &nonce,
535 &XChaCha20Poly1305Ciphertext::from(data.to_vec()),
536 aad,
537 )
538 },
539 )?;
540
541 if should_pad_content(&content_format) {
542 let data = crate::keys::utils::unpad_bytes(&decrypted_message)?;
543 return Ok((data.to_vec(), content_format));
544 }
545
546 Ok((decrypted_message, content_format))
547}
548
549#[cfg(test)]
550mod tests {
551 use coset::{CoseEncrypt0Builder, CoseEncryptBuilder, CoseRecipientBuilder, HeaderBuilder};
552 use hybrid_array::Array;
553 use iana::KeyOperation;
554
555 use super::*;
556 use crate::keys::KeyId;
557
558 const CEK: [u8; 32] = [7u8; 32];
559 const PLAINTEXT: &[u8] = b"content-encryption test vector";
560
561 const KEY_ID: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
562 const KEY_DATA: [u8; 32] = [
563 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
564 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
565 0x1e, 0x1f,
566 ];
567 const TEST_VECTOR_PLAINTEXT: &[u8] = b"Message test vector";
568 const TEST_VECTOR_COSE_ENCRYPT0: &[u8] = &[
569 131, 88, 28, 163, 1, 58, 0, 1, 17, 111, 3, 24, 42, 4, 80, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
570 11, 12, 13, 14, 15, 161, 5, 88, 24, 78, 20, 28, 157, 180, 246, 131, 220, 82, 104, 72, 73,
571 75, 43, 69, 139, 216, 167, 145, 220, 67, 168, 144, 173, 88, 35, 127, 234, 194, 83, 189,
572 172, 65, 29, 156, 73, 98, 87, 231, 87, 129, 15, 235, 127, 125, 97, 211, 51, 212, 211, 2,
573 13, 36, 123, 53, 12, 31, 191, 40, 13, 175,
574 ];
575
576 fn algorithms() -> [CoseContentEncryptionAlgorithm; 2] {
577 [
578 CoseContentEncryptionAlgorithm::Aes256Gcm,
579 CoseContentEncryptionAlgorithm::XChaCha20Poly1305,
580 ]
581 }
582
583 fn make_xchacha_key() -> XChaCha20Poly1305Key {
584 XChaCha20Poly1305Key {
585 key_id: KeyId::from(KEY_ID),
586 enc_key: Box::pin(Array::from(KEY_DATA)),
587 supported_operations: vec![
588 KeyOperation::Decrypt,
589 KeyOperation::Encrypt,
590 KeyOperation::WrapKey,
591 KeyOperation::UnwrapKey,
592 ],
593 }
594 }
595
596 #[test]
597 fn test_encrypt_decrypt_cose_roundtrip() {
598 for algorithm in algorithms() {
599 let builder =
600 CoseEncryptBuilder::new().add_recipient(CoseRecipientBuilder::new().build());
601 let cose_encrypt = encrypt_cose(
602 algorithm,
603 builder,
604 HeaderBuilder::new().build(),
605 PLAINTEXT,
606 &CEK,
607 )
608 .unwrap();
609 let decrypted = decrypt_cose(&cose_encrypt, None, &CEK).unwrap();
610 assert_eq!(decrypted, PLAINTEXT);
611 }
612 }
613
614 #[test]
615 fn test_encrypt_decrypt_cose0_roundtrip() {
616 for algorithm in algorithms() {
617 let cose_encrypt0 = encrypt_cose0(
618 algorithm,
619 CoseEncrypt0Builder::new(),
620 HeaderBuilder::new().build(),
621 PLAINTEXT,
622 &CEK,
623 )
624 .unwrap();
625 let decrypted = decrypt_cose0(&cose_encrypt0, None, &CEK).unwrap();
626 assert_eq!(decrypted, PLAINTEXT);
627 }
628 }
629
630 #[test]
631 fn test_decrypt_cose0_wrong_key_fails() {
632 let cose_encrypt0 = encrypt_cose0(
633 CoseContentEncryptionAlgorithm::XChaCha20Poly1305,
634 CoseEncrypt0Builder::new(),
635 HeaderBuilder::new().build(),
636 PLAINTEXT,
637 &CEK,
638 )
639 .unwrap();
640 let wrong_cek = [0u8; 32];
641 assert!(decrypt_cose0(&cose_encrypt0, None, &wrong_cek).is_err());
642 }
643
644 #[test]
645 fn test_decrypt_cose0_missing_algorithm_fails_without_default() {
646 let cose_encrypt0 = CoseEncrypt0Builder::new()
648 .protected(HeaderBuilder::new().build())
649 .create_ciphertext(PLAINTEXT, &[], |data, _| data.to_vec())
650 .build();
651 assert!(matches!(
652 decrypt_cose0(&cose_encrypt0, None, &CEK),
653 Err(CryptoError::EncString(
654 EncStringParseError::CoseMissingAlgorithm
655 ))
656 ));
657 }
658
659 #[test]
660 fn test_decrypt_cose0_missing_algorithm_uses_default() {
661 let nonce = XChaCha20Poly1305Nonce::make();
665 let cose_encrypt0 = CoseEncrypt0Builder::new()
666 .protected(HeaderBuilder::new().build())
667 .unprotected(HeaderBuilder::new().iv(nonce.as_bytes().to_vec()).build())
668 .create_ciphertext(PLAINTEXT, &[], |data, aad| {
669 XChaCha20Poly1305::encrypt(&CEK, &nonce, data, aad)
670 .encrypted_bytes()
671 .to_vec()
672 })
673 .build();
674
675 let decrypted = decrypt_cose0(
676 &cose_encrypt0,
677 Some(CoseContentEncryptionAlgorithm::XChaCha20Poly1305),
678 &CEK,
679 )
680 .unwrap();
681 assert_eq!(decrypted, PLAINTEXT);
682 }
683
684 #[test]
685 fn test_encrypt_decrypt_xchacha20_roundtrip_octetstream() {
686 use crate::SymmetricCryptoKey;
687 let SymmetricCryptoKey::XChaCha20Poly1305Key(ref key) =
688 SymmetricCryptoKey::make_xchacha20_poly1305_key()
689 else {
690 panic!("Failed to create XChaCha20Poly1305Key");
691 };
692
693 let plaintext = b"Hello, world!";
694 let encrypted =
695 encrypt_xchacha20_poly1305(plaintext, key, ContentFormat::OctetStream).unwrap();
696 let decrypted = decrypt_xchacha20_poly1305(&encrypted, key).unwrap();
697 assert_eq!(decrypted, (plaintext.to_vec(), ContentFormat::OctetStream));
698 }
699
700 #[test]
701 fn test_encrypt_decrypt_xchacha20_roundtrip_utf8() {
702 use crate::SymmetricCryptoKey;
703 let SymmetricCryptoKey::XChaCha20Poly1305Key(ref key) =
704 SymmetricCryptoKey::make_xchacha20_poly1305_key()
705 else {
706 panic!("Failed to create XChaCha20Poly1305Key");
707 };
708
709 let plaintext = b"Hello, world!";
710 let encrypted = encrypt_xchacha20_poly1305(plaintext, key, ContentFormat::Utf8).unwrap();
711 let decrypted = decrypt_xchacha20_poly1305(&encrypted, key).unwrap();
712 assert_eq!(decrypted, (plaintext.to_vec(), ContentFormat::Utf8));
713 }
714
715 #[test]
716 fn test_encrypt_decrypt_xchacha20_roundtrip_pkcs8() {
717 use crate::SymmetricCryptoKey;
718 let SymmetricCryptoKey::XChaCha20Poly1305Key(ref key) =
719 SymmetricCryptoKey::make_xchacha20_poly1305_key()
720 else {
721 panic!("Failed to create XChaCha20Poly1305Key");
722 };
723
724 let plaintext = b"Hello, world!";
725 let encrypted =
726 encrypt_xchacha20_poly1305(plaintext, key, ContentFormat::Pkcs8PrivateKey).unwrap();
727 let decrypted = decrypt_xchacha20_poly1305(&encrypted, key).unwrap();
728 assert_eq!(
729 decrypted,
730 (plaintext.to_vec(), ContentFormat::Pkcs8PrivateKey)
731 );
732 }
733
734 #[test]
735 fn test_encrypt_decrypt_xchacha20_roundtrip_cosekey() {
736 use crate::SymmetricCryptoKey;
737 let SymmetricCryptoKey::XChaCha20Poly1305Key(ref key) =
738 SymmetricCryptoKey::make_xchacha20_poly1305_key()
739 else {
740 panic!("Failed to create XChaCha20Poly1305Key");
741 };
742
743 let plaintext = b"Hello, world!";
744 let encrypted = encrypt_xchacha20_poly1305(plaintext, key, ContentFormat::CoseKey).unwrap();
745 let decrypted = decrypt_xchacha20_poly1305(&encrypted, key).unwrap();
746 assert_eq!(decrypted, (plaintext.to_vec(), ContentFormat::CoseKey));
747 }
748
749 #[test]
750 fn test_decrypt_xchacha20_test_vector() {
751 let key = make_xchacha_key();
752 let decrypted =
753 decrypt_xchacha20_poly1305(&CoseEncrypt0Bytes::from(TEST_VECTOR_COSE_ENCRYPT0), &key)
754 .unwrap();
755 assert_eq!(
756 decrypted,
757 (TEST_VECTOR_PLAINTEXT.to_vec(), ContentFormat::OctetStream)
758 );
759 }
760
761 #[test]
762 fn test_decrypt_xchacha20_fail_wrong_key_id() {
763 let key = XChaCha20Poly1305Key {
764 key_id: KeyId::from([1; 16]),
765 enc_key: Box::pin(Array::from(KEY_DATA)),
766 supported_operations: vec![
767 KeyOperation::Decrypt,
768 KeyOperation::Encrypt,
769 KeyOperation::WrapKey,
770 KeyOperation::UnwrapKey,
771 ],
772 };
773 assert!(matches!(
774 decrypt_xchacha20_poly1305(&CoseEncrypt0Bytes::from(TEST_VECTOR_COSE_ENCRYPT0), &key),
775 Err(CryptoError::WrongCoseKeyId)
776 ));
777 }
778
779 #[test]
780 fn test_decrypt_xchacha20_fail_wrong_algorithm() {
781 use coset::iana;
782 let protected_header = coset::HeaderBuilder::new()
783 .algorithm(iana::Algorithm::A256GCM)
784 .key_id(KEY_ID.to_vec())
785 .build();
786 let nonce = [0u8; 16];
787 let cose_encrypt0 = coset::CoseEncrypt0Builder::new()
788 .protected(protected_header)
789 .create_ciphertext(&[], &[], |_, _| Vec::new())
790 .unprotected(coset::HeaderBuilder::new().iv(nonce.to_vec()).build())
791 .build();
792 let serialized_message = CoseEncrypt0Bytes::from(cose_encrypt0.to_vec().unwrap());
793
794 let key = make_xchacha_key();
795 assert!(matches!(
796 decrypt_xchacha20_poly1305(&serialized_message, &key),
797 Err(CryptoError::WrongKeyType)
798 ));
799 }
800}