1use coset::{
7 Algorithm, CborSerializable, CoseEncrypt, CoseEncrypt0, CoseEncrypt0Builder,
8 CoseEncryptBuilder, Header, HeaderBuilder, iana,
9};
10
11use super::{AES_256_CBC_HMAC_SHA256_AEAD, XAES_256_GCM, XCHACHA20_POLY1305};
12use crate::{
13 ContentFormat, CoseEncrypt0Bytes, CryptoError, XAes256GcmKey, XChaCha20Poly1305Key,
14 error::EncStringParseError,
15 hazmat::symmetric_encryption::{
16 Aead,
17 aes_gcm::{Aes256Gcm, Aes256GcmCiphertext, Aes256GcmNonce},
18 aes256_cbc_hmac_sha256_aead::{
19 Aes256CbcHmacSha256Aead, Aes256CbcHmacSha256AeadCiphertext,
20 Aes256CbcHmacSha256AeadNonce,
21 },
22 xaes_256_gcm::{XAes256Gcm, XAes256GcmCiphertext, XAes256GcmNonce},
23 xchacha20::{XChaCha20Poly1305, XChaCha20Poly1305Ciphertext, XChaCha20Poly1305Nonce},
24 },
25};
26
27const TEXT_PAD_BLOCK_SIZE: usize = 32;
28
29fn should_pad_content(format: &ContentFormat) -> bool {
30 matches!(format, ContentFormat::Utf8)
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub(crate) enum CoseContentEncryptionAlgorithm {
40 Aes256Gcm,
42 XAes256Gcm,
44 XChaCha20Poly1305,
46 Aes256CbcHmacSha256,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub(crate) enum CoseAlgorithmPolicy {
53 RequireProtectedHeaderAlgorithm,
55 Exactly(CoseContentEncryptionAlgorithm),
57 ProtectedHeaderAlgorithmOrLegacyDefault(CoseContentEncryptionAlgorithm),
59}
60
61impl TryFrom<&Algorithm> for CoseContentEncryptionAlgorithm {
62 type Error = CryptoError;
63
64 fn try_from(algorithm: &Algorithm) -> Result<Self, Self::Error> {
65 match algorithm {
66 Algorithm::Assigned(iana::Algorithm::A256GCM) => Ok(Self::Aes256Gcm),
67 Algorithm::PrivateUse(XAES_256_GCM) => Ok(Self::XAes256Gcm),
68 Algorithm::PrivateUse(XCHACHA20_POLY1305) => Ok(Self::XChaCha20Poly1305),
69 Algorithm::PrivateUse(AES_256_CBC_HMAC_SHA256_AEAD) => Ok(Self::Aes256CbcHmacSha256),
70 _ => Err(CryptoError::WrongKeyType),
71 }
72 }
73}
74
75fn algorithm_from_header(
83 header: &Header,
84 policy: CoseAlgorithmPolicy,
85) -> Result<CoseContentEncryptionAlgorithm, CryptoError> {
86 let declared = header
87 .alg
88 .as_ref()
89 .map(CoseContentEncryptionAlgorithm::try_from)
90 .transpose()?;
91
92 match (policy, declared) {
93 (CoseAlgorithmPolicy::ProtectedHeaderAlgorithmOrLegacyDefault(default), None) => {
94 Ok(default)
95 }
96 (_, None) => Err(CryptoError::EncString(
97 EncStringParseError::CoseMissingAlgorithm,
98 )),
99 (CoseAlgorithmPolicy::Exactly(expected), Some(actual)) if actual != expected => {
100 Err(CryptoError::WrongKeyType)
101 }
102 (_, Some(actual)) => Ok(actual),
103 }
104}
105
106fn ensure_algorithm_matches<C: CoseEncryptCipher>(header: &Header) -> Result<(), CryptoError> {
113 match header.alg.as_ref() {
114 Some(algorithm) if algorithm != &C::COSE_ALGORITHM => Err(CryptoError::WrongKeyType),
115 _ => Ok(()),
116 }
117}
118
119pub(crate) fn encrypt_cose(
131 algorithm: CoseContentEncryptionAlgorithm,
132 builder: CoseEncryptBuilder,
133 protected_header: Header,
134 plaintext: &[u8],
135 cek: &[u8],
136) -> Result<CoseEncrypt, CryptoError> {
137 let mut plaintext = plaintext.to_vec();
138 if let Ok(content_format) = ContentFormat::try_from(&protected_header)
139 && should_pad_content(&content_format)
140 {
141 let min_length = TEXT_PAD_BLOCK_SIZE * (1 + (plaintext.len() / TEXT_PAD_BLOCK_SIZE));
142 crate::keys::utils::pad_bytes(&mut plaintext, min_length)?;
143 }
144 match algorithm {
145 CoseContentEncryptionAlgorithm::Aes256Gcm => {
146 let cek: &<Aes256Gcm as Aead>::Key =
147 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
148 Ok(Aes256Gcm::encrypt_cose(
149 builder,
150 protected_header,
151 &plaintext,
152 cek,
153 ))
154 }
155 CoseContentEncryptionAlgorithm::XAes256Gcm => {
156 let cek: &<XAes256Gcm as Aead>::Key =
157 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
158 Ok(XAes256Gcm::encrypt_cose(
159 builder,
160 protected_header,
161 &plaintext,
162 cek,
163 ))
164 }
165 CoseContentEncryptionAlgorithm::XChaCha20Poly1305 => {
166 let cek: &<XChaCha20Poly1305 as Aead>::Key =
167 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
168 Ok(XChaCha20Poly1305::encrypt_cose(
169 builder,
170 protected_header,
171 &plaintext,
172 cek,
173 ))
174 }
175 CoseContentEncryptionAlgorithm::Aes256CbcHmacSha256 => {
176 let cek: &<Aes256CbcHmacSha256Aead as Aead>::Key =
177 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
178 Ok(Aes256CbcHmacSha256Aead::encrypt_cose(
179 builder,
180 protected_header,
181 &plaintext,
182 cek,
183 ))
184 }
185 }
186}
187
188pub(crate) fn decrypt_cose(
194 cose_encrypt: &CoseEncrypt,
195 policy: CoseAlgorithmPolicy,
196 cek: &[u8],
197) -> Result<Vec<u8>, CryptoError> {
198 let decrypted = match algorithm_from_header(&cose_encrypt.protected.header, policy)? {
199 CoseContentEncryptionAlgorithm::Aes256Gcm => {
200 let cek: &<Aes256Gcm as Aead>::Key =
201 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
202 Aes256Gcm::decrypt_cose(cose_encrypt, cek)?
203 }
204 CoseContentEncryptionAlgorithm::XAes256Gcm => {
205 let cek: &<XAes256Gcm as Aead>::Key =
206 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
207 XAes256Gcm::decrypt_cose(cose_encrypt, cek)?
208 }
209 CoseContentEncryptionAlgorithm::XChaCha20Poly1305 => {
210 let cek: &<XChaCha20Poly1305 as Aead>::Key =
211 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
212 XChaCha20Poly1305::decrypt_cose(cose_encrypt, cek)?
213 }
214 CoseContentEncryptionAlgorithm::Aes256CbcHmacSha256 => {
215 let cek: &<Aes256CbcHmacSha256Aead as Aead>::Key =
216 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
217 Aes256CbcHmacSha256Aead::decrypt_cose(cose_encrypt, cek)?
218 }
219 };
220 if let Ok(content_format) = ContentFormat::try_from(&cose_encrypt.protected.header)
221 && should_pad_content(&content_format)
222 {
223 return Ok(crate::keys::utils::unpad_bytes(&decrypted)?.to_vec());
224 }
225 Ok(decrypted)
226}
227
228pub(crate) fn encrypt_cose0(
235 algorithm: CoseContentEncryptionAlgorithm,
236 builder: CoseEncrypt0Builder,
237 protected_header: Header,
238 plaintext: &[u8],
239 cek: &[u8],
240) -> Result<CoseEncrypt0, CryptoError> {
241 let mut plaintext = plaintext.to_vec();
242 if let Ok(content_format) = ContentFormat::try_from(&protected_header)
243 && should_pad_content(&content_format)
244 {
245 let min_length = TEXT_PAD_BLOCK_SIZE * (1 + (plaintext.len() / TEXT_PAD_BLOCK_SIZE));
246 crate::keys::utils::pad_bytes(&mut plaintext, min_length)?;
247 }
248 match algorithm {
249 CoseContentEncryptionAlgorithm::Aes256Gcm => {
250 let cek: &<Aes256Gcm as Aead>::Key =
251 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
252 Ok(Aes256Gcm::encrypt_cose0(
253 builder,
254 protected_header,
255 &plaintext,
256 cek,
257 ))
258 }
259 CoseContentEncryptionAlgorithm::XAes256Gcm => {
260 let cek: &<XAes256Gcm as Aead>::Key =
261 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
262 Ok(XAes256Gcm::encrypt_cose0(
263 builder,
264 protected_header,
265 &plaintext,
266 cek,
267 ))
268 }
269 CoseContentEncryptionAlgorithm::XChaCha20Poly1305 => {
270 let cek: &<XChaCha20Poly1305 as Aead>::Key =
271 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
272 Ok(XChaCha20Poly1305::encrypt_cose0(
273 builder,
274 protected_header,
275 &plaintext,
276 cek,
277 ))
278 }
279 CoseContentEncryptionAlgorithm::Aes256CbcHmacSha256 => {
280 let cek: &<Aes256CbcHmacSha256Aead as Aead>::Key =
281 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
282 Ok(Aes256CbcHmacSha256Aead::encrypt_cose0(
283 builder,
284 protected_header,
285 &plaintext,
286 cek,
287 ))
288 }
289 }
290}
291
292pub(crate) fn decrypt_cose0(
298 cose_encrypt0: &CoseEncrypt0,
299 policy: CoseAlgorithmPolicy,
300 cek: &[u8],
301) -> Result<Vec<u8>, CryptoError> {
302 let decrypted = match algorithm_from_header(&cose_encrypt0.protected.header, policy)? {
303 CoseContentEncryptionAlgorithm::Aes256Gcm => {
304 let cek: &<Aes256Gcm as Aead>::Key =
305 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
306 Aes256Gcm::decrypt_cose0(cose_encrypt0, cek)?
307 }
308 CoseContentEncryptionAlgorithm::XAes256Gcm => {
309 let cek: &<XAes256Gcm as Aead>::Key =
310 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
311 XAes256Gcm::decrypt_cose0(cose_encrypt0, cek)?
312 }
313 CoseContentEncryptionAlgorithm::XChaCha20Poly1305 => {
314 let cek: &<XChaCha20Poly1305 as Aead>::Key =
315 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
316 XChaCha20Poly1305::decrypt_cose0(cose_encrypt0, cek)?
317 }
318 CoseContentEncryptionAlgorithm::Aes256CbcHmacSha256 => {
319 let cek: &<Aes256CbcHmacSha256Aead as Aead>::Key =
320 cek.try_into().map_err(|_| CryptoError::InvalidKeyLen)?;
321 Aes256CbcHmacSha256Aead::decrypt_cose0(cose_encrypt0, cek)?
322 }
323 };
324 if let Ok(content_format) = ContentFormat::try_from(&cose_encrypt0.protected.header)
325 && should_pad_content(&content_format)
326 {
327 return Ok(crate::keys::utils::unpad_bytes(&decrypted)?.to_vec());
328 }
329 Ok(decrypted)
330}
331
332pub(crate) trait CoseEncryptCipher: Aead {
335 const COSE_ALGORITHM: Algorithm;
338
339 fn encrypt_cose(
347 builder: CoseEncryptBuilder,
348 protected_header: Header,
349 plaintext: &[u8],
350 cek: &Self::Key,
351 ) -> CoseEncrypt;
352
353 fn decrypt_cose(cose_encrypt: &CoseEncrypt, cek: &Self::Key) -> Result<Vec<u8>, CryptoError>;
361
362 fn encrypt_cose0(
365 builder: CoseEncrypt0Builder,
366 protected_header: Header,
367 plaintext: &[u8],
368 cek: &Self::Key,
369 ) -> CoseEncrypt0;
370
371 fn decrypt_cose0(cose_encrypt0: &CoseEncrypt0, cek: &Self::Key)
374 -> Result<Vec<u8>, CryptoError>;
375}
376
377impl CoseEncryptCipher for Aes256Gcm {
378 const COSE_ALGORITHM: Algorithm = Algorithm::Assigned(iana::Algorithm::A256GCM);
379
380 fn encrypt_cose(
381 builder: CoseEncryptBuilder,
382 mut protected_header: Header,
383 plaintext: &[u8],
384 cek: &Self::Key,
385 ) -> CoseEncrypt {
386 protected_header.alg = Some(Self::COSE_ALGORITHM);
389
390 let nonce = Aes256GcmNonce::make();
394 builder
395 .protected(protected_header)
396 .unprotected(HeaderBuilder::new().iv(nonce.as_bytes().to_vec()).build())
397 .create_ciphertext(plaintext, &[], |data, aad| {
398 Aes256Gcm::encrypt(cek, &nonce, data, aad)
399 .encrypted_bytes()
400 .to_vec()
401 })
402 .build()
403 }
404
405 fn decrypt_cose(cose_encrypt: &CoseEncrypt, cek: &Self::Key) -> Result<Vec<u8>, CryptoError> {
406 ensure_algorithm_matches::<Self>(&cose_encrypt.protected.header)?;
410
411 let nonce = Aes256GcmNonce::try_from(cose_encrypt)?;
412 cose_encrypt.decrypt_ciphertext(
413 &[],
414 || CryptoError::MissingField("ciphertext"),
415 |data, aad| {
416 Aes256Gcm::decrypt(cek, &nonce, &Aes256GcmCiphertext::from(data.to_vec()), aad)
417 },
418 )
419 }
420
421 fn encrypt_cose0(
422 builder: CoseEncrypt0Builder,
423 mut protected_header: Header,
424 plaintext: &[u8],
425 cek: &Self::Key,
426 ) -> CoseEncrypt0 {
427 protected_header.alg = Some(Self::COSE_ALGORITHM);
428
429 let nonce = Aes256GcmNonce::make();
430 builder
431 .protected(protected_header)
432 .unprotected(HeaderBuilder::new().iv(nonce.as_bytes().to_vec()).build())
433 .create_ciphertext(plaintext, &[], |data, aad| {
434 Aes256Gcm::encrypt(cek, &nonce, data, aad)
435 .encrypted_bytes()
436 .to_vec()
437 })
438 .build()
439 }
440
441 fn decrypt_cose0(
442 cose_encrypt0: &CoseEncrypt0,
443 cek: &Self::Key,
444 ) -> Result<Vec<u8>, CryptoError> {
445 ensure_algorithm_matches::<Self>(&cose_encrypt0.protected.header)?;
446
447 let nonce = Aes256GcmNonce::try_from(cose_encrypt0)?;
448 cose_encrypt0.decrypt_ciphertext(
449 &[],
450 || CryptoError::MissingField("ciphertext"),
451 |data, aad| {
452 Aes256Gcm::decrypt(cek, &nonce, &Aes256GcmCiphertext::from(data.to_vec()), aad)
453 },
454 )
455 }
456}
457
458impl CoseEncryptCipher for XAes256Gcm {
459 const COSE_ALGORITHM: Algorithm = Algorithm::PrivateUse(XAES_256_GCM);
460
461 fn encrypt_cose(
462 builder: CoseEncryptBuilder,
463 mut protected_header: Header,
464 plaintext: &[u8],
465 cek: &Self::Key,
466 ) -> CoseEncrypt {
467 protected_header.alg = Some(Self::COSE_ALGORITHM);
468
469 let nonce = XAes256GcmNonce::make();
470 builder
471 .protected(protected_header)
472 .unprotected(HeaderBuilder::new().iv(nonce.as_bytes().to_vec()).build())
473 .create_ciphertext(plaintext, &[], |data, aad| {
474 XAes256Gcm::encrypt(cek, &nonce, data, aad)
475 .encrypted_bytes()
476 .to_vec()
477 })
478 .build()
479 }
480
481 fn decrypt_cose(cose_encrypt: &CoseEncrypt, cek: &Self::Key) -> Result<Vec<u8>, CryptoError> {
482 ensure_algorithm_matches::<Self>(&cose_encrypt.protected.header)?;
483
484 let nonce = XAes256GcmNonce::try_from(cose_encrypt)?;
485 cose_encrypt.decrypt_ciphertext(
486 &[],
487 || CryptoError::MissingField("ciphertext"),
488 |data, aad| {
489 XAes256Gcm::decrypt(cek, &nonce, &XAes256GcmCiphertext::from(data.to_vec()), aad)
490 },
491 )
492 }
493
494 fn encrypt_cose0(
495 builder: CoseEncrypt0Builder,
496 mut protected_header: Header,
497 plaintext: &[u8],
498 cek: &Self::Key,
499 ) -> CoseEncrypt0 {
500 protected_header.alg = Some(Self::COSE_ALGORITHM);
501
502 let nonce = XAes256GcmNonce::make();
503 builder
504 .protected(protected_header)
505 .unprotected(HeaderBuilder::new().iv(nonce.as_bytes().to_vec()).build())
506 .create_ciphertext(plaintext, &[], |data, aad| {
507 XAes256Gcm::encrypt(cek, &nonce, data, aad)
508 .encrypted_bytes()
509 .to_vec()
510 })
511 .build()
512 }
513
514 fn decrypt_cose0(
515 cose_encrypt0: &CoseEncrypt0,
516 cek: &Self::Key,
517 ) -> Result<Vec<u8>, CryptoError> {
518 ensure_algorithm_matches::<Self>(&cose_encrypt0.protected.header)?;
519
520 let nonce = XAes256GcmNonce::try_from(cose_encrypt0)?;
521 cose_encrypt0.decrypt_ciphertext(
522 &[],
523 || CryptoError::MissingField("ciphertext"),
524 |data, aad| {
525 XAes256Gcm::decrypt(cek, &nonce, &XAes256GcmCiphertext::from(data.to_vec()), aad)
526 },
527 )
528 }
529}
530
531impl CoseEncryptCipher for XChaCha20Poly1305 {
532 const COSE_ALGORITHM: Algorithm = Algorithm::PrivateUse(XCHACHA20_POLY1305);
533
534 fn encrypt_cose(
535 builder: CoseEncryptBuilder,
536 mut protected_header: Header,
537 plaintext: &[u8],
538 cek: &Self::Key,
539 ) -> CoseEncrypt {
540 protected_header.alg = Some(Self::COSE_ALGORITHM);
541
542 let nonce = XChaCha20Poly1305Nonce::make();
543 builder
544 .protected(protected_header)
545 .unprotected(HeaderBuilder::new().iv(nonce.as_bytes().to_vec()).build())
546 .create_ciphertext(plaintext, &[], |data, aad| {
547 XChaCha20Poly1305::encrypt(cek, &nonce, data, aad)
548 .encrypted_bytes()
549 .to_vec()
550 })
551 .build()
552 }
553
554 fn decrypt_cose(cose_encrypt: &CoseEncrypt, cek: &Self::Key) -> Result<Vec<u8>, CryptoError> {
555 ensure_algorithm_matches::<Self>(&cose_encrypt.protected.header)?;
556
557 let nonce = XChaCha20Poly1305Nonce::try_from(cose_encrypt)?;
558 cose_encrypt.decrypt_ciphertext(
559 &[],
560 || CryptoError::MissingField("ciphertext"),
561 |data, aad| {
562 XChaCha20Poly1305::decrypt(
563 cek,
564 &nonce,
565 &XChaCha20Poly1305Ciphertext::from(data.to_vec()),
566 aad,
567 )
568 },
569 )
570 }
571
572 fn encrypt_cose0(
573 builder: CoseEncrypt0Builder,
574 mut protected_header: Header,
575 plaintext: &[u8],
576 cek: &Self::Key,
577 ) -> CoseEncrypt0 {
578 protected_header.alg = Some(Self::COSE_ALGORITHM);
579
580 let nonce = XChaCha20Poly1305Nonce::make();
581 builder
582 .protected(protected_header)
583 .unprotected(HeaderBuilder::new().iv(nonce.as_bytes().to_vec()).build())
584 .create_ciphertext(plaintext, &[], |data, aad| {
585 XChaCha20Poly1305::encrypt(cek, &nonce, data, aad)
586 .encrypted_bytes()
587 .to_vec()
588 })
589 .build()
590 }
591
592 fn decrypt_cose0(
593 cose_encrypt0: &CoseEncrypt0,
594 cek: &Self::Key,
595 ) -> Result<Vec<u8>, CryptoError> {
596 ensure_algorithm_matches::<Self>(&cose_encrypt0.protected.header)?;
597
598 let nonce = XChaCha20Poly1305Nonce::try_from(cose_encrypt0)?;
599 cose_encrypt0.decrypt_ciphertext(
600 &[],
601 || CryptoError::MissingField("ciphertext"),
602 |data, aad| {
603 XChaCha20Poly1305::decrypt(
604 cek,
605 &nonce,
606 &XChaCha20Poly1305Ciphertext::from(data.to_vec()),
607 aad,
608 )
609 },
610 )
611 }
612}
613
614impl CoseEncryptCipher for Aes256CbcHmacSha256Aead {
615 const COSE_ALGORITHM: Algorithm = Algorithm::PrivateUse(AES_256_CBC_HMAC_SHA256_AEAD);
616
617 fn encrypt_cose(
618 builder: CoseEncryptBuilder,
619 mut protected_header: Header,
620 plaintext: &[u8],
621 cek: &Self::Key,
622 ) -> CoseEncrypt {
623 protected_header.alg = Some(Self::COSE_ALGORITHM);
624
625 let nonce = Aes256CbcHmacSha256AeadNonce::make();
626 builder
627 .protected(protected_header)
628 .unprotected(HeaderBuilder::new().iv(nonce.as_bytes().to_vec()).build())
629 .create_ciphertext(plaintext, &[], |data, aad| {
630 Aes256CbcHmacSha256Aead::encrypt(cek, &nonce, data, aad)
631 .encrypted_bytes()
632 .to_vec()
633 })
634 .build()
635 }
636
637 fn decrypt_cose(cose_encrypt: &CoseEncrypt, cek: &Self::Key) -> Result<Vec<u8>, CryptoError> {
638 ensure_algorithm_matches::<Self>(&cose_encrypt.protected.header)?;
639
640 let nonce = Aes256CbcHmacSha256AeadNonce::try_from(cose_encrypt)?;
641 cose_encrypt.decrypt_ciphertext(
642 &[],
643 || CryptoError::MissingField("ciphertext"),
644 |data, aad| {
645 Aes256CbcHmacSha256Aead::decrypt(
646 cek,
647 &nonce,
648 &Aes256CbcHmacSha256AeadCiphertext::from(data.to_vec()),
649 aad,
650 )
651 },
652 )
653 }
654
655 fn encrypt_cose0(
656 builder: CoseEncrypt0Builder,
657 mut protected_header: Header,
658 plaintext: &[u8],
659 cek: &Self::Key,
660 ) -> CoseEncrypt0 {
661 protected_header.alg = Some(Self::COSE_ALGORITHM);
662
663 let nonce = Aes256CbcHmacSha256AeadNonce::make();
664 builder
665 .protected(protected_header)
666 .unprotected(HeaderBuilder::new().iv(nonce.as_bytes().to_vec()).build())
667 .create_ciphertext(plaintext, &[], |data, aad| {
668 Aes256CbcHmacSha256Aead::encrypt(cek, &nonce, data, aad)
669 .encrypted_bytes()
670 .to_vec()
671 })
672 .build()
673 }
674
675 fn decrypt_cose0(
676 cose_encrypt0: &CoseEncrypt0,
677 cek: &Self::Key,
678 ) -> Result<Vec<u8>, CryptoError> {
679 ensure_algorithm_matches::<Self>(&cose_encrypt0.protected.header)?;
680
681 let nonce = Aes256CbcHmacSha256AeadNonce::try_from(cose_encrypt0)?;
682 cose_encrypt0.decrypt_ciphertext(
683 &[],
684 || CryptoError::MissingField("ciphertext"),
685 |data, aad| {
686 Aes256CbcHmacSha256Aead::decrypt(
687 cek,
688 &nonce,
689 &Aes256CbcHmacSha256AeadCiphertext::from(data.to_vec()),
690 aad,
691 )
692 },
693 )
694 }
695}
696
697pub(crate) fn encrypt_xchacha20_poly1305(
699 plaintext: &[u8],
700 key: &XChaCha20Poly1305Key,
701 content_format: ContentFormat,
702) -> Result<CoseEncrypt0Bytes, CryptoError> {
703 let mut plaintext = plaintext.to_vec();
704
705 let header_builder: coset::HeaderBuilder = content_format.into();
706 let mut protected_header = header_builder
707 .key_id(key.key_id.as_slice().to_vec())
708 .build();
709 protected_header.alg = Some(coset::Algorithm::PrivateUse(XCHACHA20_POLY1305));
713
714 if should_pad_content(&content_format) {
715 let min_length = TEXT_PAD_BLOCK_SIZE * (1 + (plaintext.len() / TEXT_PAD_BLOCK_SIZE));
716 crate::keys::utils::pad_bytes(&mut plaintext, min_length)?;
717 }
718
719 let nonce = XChaCha20Poly1305Nonce::make();
720 let cose_encrypt0 = coset::CoseEncrypt0Builder::new()
721 .protected(protected_header)
722 .create_ciphertext(&plaintext, &[], |data, aad| {
723 XChaCha20Poly1305::encrypt(&(*key.enc_key).into(), &nonce, data, aad)
724 .encrypted_bytes()
725 .to_vec()
726 })
727 .unprotected(
728 coset::HeaderBuilder::new()
729 .iv(nonce.as_bytes().to_vec())
730 .build(),
731 )
732 .build();
733
734 cose_encrypt0
735 .to_vec()
736 .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)))
737 .map(CoseEncrypt0Bytes::from)
738}
739
740pub(crate) fn decrypt_xchacha20_poly1305(
742 cose_encrypt0_message: &CoseEncrypt0Bytes,
743 key: &XChaCha20Poly1305Key,
744) -> Result<(Vec<u8>, ContentFormat), CryptoError> {
745 let msg = coset::CoseEncrypt0::from_slice(cose_encrypt0_message.as_ref())
746 .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)))?;
747
748 let Some(ref alg) = msg.protected.header.alg else {
749 return Err(CryptoError::EncString(
750 EncStringParseError::CoseMissingAlgorithm,
751 ));
752 };
753
754 if *alg != coset::Algorithm::PrivateUse(XCHACHA20_POLY1305) {
755 return Err(CryptoError::WrongKeyType);
756 }
757
758 let content_format = ContentFormat::try_from(&msg.protected.header)
759 .map_err(|_| CryptoError::EncString(EncStringParseError::CoseMissingContentType))?;
760
761 if key.key_id.as_slice() != msg.protected.header.key_id {
762 return Err(CryptoError::WrongCoseKeyId);
763 }
764
765 let nonce = XChaCha20Poly1305Nonce::try_from(&msg)?;
766 let decrypted_message = msg.decrypt_ciphertext(
767 &[],
768 || CryptoError::MissingField("ciphertext"),
769 |data, aad| {
770 XChaCha20Poly1305::decrypt(
771 &(*key.enc_key).into(),
772 &nonce,
773 &XChaCha20Poly1305Ciphertext::from(data.to_vec()),
774 aad,
775 )
776 },
777 )?;
778
779 if should_pad_content(&content_format) {
780 let data = crate::keys::utils::unpad_bytes(&decrypted_message)?;
781 return Ok((data.to_vec(), content_format));
782 }
783
784 Ok((decrypted_message, content_format))
785}
786
787pub(crate) fn encrypt_xaes256_gcm(
789 plaintext: &[u8],
790 key: &XAes256GcmKey,
791 content_format: ContentFormat,
792) -> Result<CoseEncrypt0Bytes, CryptoError> {
793 let mut plaintext = plaintext.to_vec();
794 let mut protected_header: Header = HeaderBuilder::from(content_format)
795 .key_id(key.key_id.as_slice().to_vec())
796 .build();
797 protected_header.alg = Some(Algorithm::PrivateUse(XAES_256_GCM));
798
799 if should_pad_content(&content_format) {
802 let min_length = TEXT_PAD_BLOCK_SIZE * (1 + (plaintext.len() / TEXT_PAD_BLOCK_SIZE));
803 crate::keys::utils::pad_bytes(&mut plaintext, min_length)?;
804 }
805
806 let nonce = XAes256GcmNonce::make();
807 CoseEncrypt0Builder::new()
808 .protected(protected_header)
809 .unprotected(HeaderBuilder::new().iv(nonce.as_bytes().to_vec()).build())
810 .create_ciphertext(&plaintext, &[], |data, aad| {
811 XAes256Gcm::encrypt(&(*key.enc_key).into(), &nonce, data, aad)
812 .encrypted_bytes()
813 .to_vec()
814 })
815 .build()
816 .to_vec()
817 .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)))
818 .map(CoseEncrypt0Bytes::from)
819}
820
821pub(crate) fn decrypt_xaes256_gcm(
823 message: &CoseEncrypt0Bytes,
824 key: &XAes256GcmKey,
825) -> Result<(Vec<u8>, ContentFormat), CryptoError> {
826 let msg = CoseEncrypt0::from_slice(message.as_ref())
827 .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)))?;
828
829 let Some(ref algorithm) = msg.protected.header.alg else {
830 return Err(CryptoError::EncString(
831 EncStringParseError::CoseMissingAlgorithm,
832 ));
833 };
834 if *algorithm != Algorithm::PrivateUse(XAES_256_GCM) {
835 return Err(CryptoError::WrongKeyType);
836 }
837
838 let content_format = ContentFormat::try_from(&msg.protected.header)
839 .map_err(|_| CryptoError::EncString(EncStringParseError::CoseMissingContentType))?;
840 if key.key_id.as_slice() != msg.protected.header.key_id {
841 return Err(CryptoError::WrongCoseKeyId);
842 }
843
844 let nonce = XAes256GcmNonce::try_from(&msg)?;
845 let decrypted = msg.decrypt_ciphertext(
846 &[],
847 || CryptoError::MissingField("ciphertext"),
848 |data, aad| {
849 XAes256Gcm::decrypt(
850 &(*key.enc_key).into(),
851 &nonce,
852 &XAes256GcmCiphertext::from(data.to_vec()),
853 aad,
854 )
855 },
856 )?;
857
858 if should_pad_content(&content_format) {
859 return Ok((
860 crate::keys::utils::unpad_bytes(&decrypted)?.to_vec(),
861 content_format,
862 ));
863 }
864
865 Ok((decrypted, content_format))
866}
867
868#[cfg(test)]
869mod tests {
870 use coset::{CoseEncrypt0Builder, CoseEncryptBuilder, CoseRecipientBuilder, HeaderBuilder};
871 use hybrid_array::Array;
872 use iana::KeyOperation;
873
874 use super::*;
875 use crate::keys::KeyId;
876
877 const CEK: [u8; 32] = [7u8; 32];
878 const CEK_64: [u8; 64] = [7u8; 64];
880 const PLAINTEXT: &[u8] = b"content-encryption test vector";
881
882 const KEY_ID: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
883 const KEY_DATA: [u8; 32] = [
884 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
885 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
886 0x1e, 0x1f,
887 ];
888 const TEST_VECTOR_PLAINTEXT: &[u8] = b"Message test vector";
889 const TEST_VECTOR_COSE_ENCRYPT0: &[u8] = &[
890 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,
891 11, 12, 13, 14, 15, 161, 5, 88, 24, 78, 20, 28, 157, 180, 246, 131, 220, 82, 104, 72, 73,
892 75, 43, 69, 139, 216, 167, 145, 220, 67, 168, 144, 173, 88, 35, 127, 234, 194, 83, 189,
893 172, 65, 29, 156, 73, 98, 87, 231, 87, 129, 15, 235, 127, 125, 97, 211, 51, 212, 211, 2,
894 13, 36, 123, 53, 12, 31, 191, 40, 13, 175,
895 ];
896
897 fn algorithms() -> [(CoseContentEncryptionAlgorithm, &'static [u8]); 4] {
900 [
901 (CoseContentEncryptionAlgorithm::Aes256Gcm, &CEK),
902 (CoseContentEncryptionAlgorithm::XAes256Gcm, &CEK),
903 (CoseContentEncryptionAlgorithm::XChaCha20Poly1305, &CEK),
904 (CoseContentEncryptionAlgorithm::Aes256CbcHmacSha256, &CEK_64),
905 ]
906 }
907
908 fn make_xaes_key() -> XAes256GcmKey {
909 XAes256GcmKey {
910 key_id: KeyId::from(KEY_ID),
911 enc_key: Box::pin(Array::from(KEY_DATA)),
912 supported_operations: vec![
913 KeyOperation::Decrypt,
914 KeyOperation::Encrypt,
915 KeyOperation::WrapKey,
916 KeyOperation::UnwrapKey,
917 ],
918 }
919 }
920
921 fn make_xchacha_key() -> XChaCha20Poly1305Key {
922 XChaCha20Poly1305Key {
923 key_id: KeyId::from(KEY_ID),
924 enc_key: Box::pin(Array::from(KEY_DATA)),
925 supported_operations: vec![
926 KeyOperation::Decrypt,
927 KeyOperation::Encrypt,
928 KeyOperation::WrapKey,
929 KeyOperation::UnwrapKey,
930 ],
931 }
932 }
933
934 #[test]
935 fn test_encrypt_decrypt_cose_roundtrip() {
936 for (algorithm, cek) in algorithms() {
937 let builder =
938 CoseEncryptBuilder::new().add_recipient(CoseRecipientBuilder::new().build());
939 let cose_encrypt = encrypt_cose(
940 algorithm,
941 builder,
942 HeaderBuilder::new().build(),
943 PLAINTEXT,
944 cek,
945 )
946 .unwrap();
947 let decrypted =
948 decrypt_cose(&cose_encrypt, CoseAlgorithmPolicy::Exactly(algorithm), cek).unwrap();
949 assert_eq!(decrypted, PLAINTEXT);
950 }
951 }
952
953 #[test]
954 fn test_encrypt_decrypt_cose0_roundtrip() {
955 for (algorithm, cek) in algorithms() {
956 let cose_encrypt0 = encrypt_cose0(
957 algorithm,
958 CoseEncrypt0Builder::new(),
959 HeaderBuilder::new().build(),
960 PLAINTEXT,
961 cek,
962 )
963 .unwrap();
964 let decrypted =
965 decrypt_cose0(&cose_encrypt0, CoseAlgorithmPolicy::Exactly(algorithm), cek)
966 .unwrap();
967 assert_eq!(decrypted, PLAINTEXT);
968 }
969 }
970
971 #[test]
974 fn test_aes256_cbc_hmac_rejects_wrong_length_cek() {
975 assert!(matches!(
976 encrypt_cose0(
977 CoseContentEncryptionAlgorithm::Aes256CbcHmacSha256,
978 CoseEncrypt0Builder::new(),
979 HeaderBuilder::new().build(),
980 PLAINTEXT,
981 &CEK,
982 ),
983 Err(CryptoError::InvalidKeyLen)
984 ));
985 }
986
987 #[test]
988 fn test_aes256_cbc_hmac_cose0_fails_with_wrong_key() {
989 let message = encrypt_cose0(
990 CoseContentEncryptionAlgorithm::Aes256CbcHmacSha256,
991 CoseEncrypt0Builder::new(),
992 HeaderBuilder::new().build(),
993 PLAINTEXT,
994 &CEK_64,
995 )
996 .unwrap();
997
998 let mut wrong_cek = CEK_64;
999 wrong_cek[0] ^= 1;
1000 assert!(
1001 decrypt_cose0(
1002 &message,
1003 CoseAlgorithmPolicy::Exactly(CoseContentEncryptionAlgorithm::Aes256CbcHmacSha256),
1004 &wrong_cek,
1005 )
1006 .is_err()
1007 );
1008
1009 let mut wrong_mac_key = CEK_64;
1011 wrong_mac_key[32] ^= 1;
1012 assert!(
1013 decrypt_cose0(
1014 &message,
1015 CoseAlgorithmPolicy::Exactly(CoseContentEncryptionAlgorithm::Aes256CbcHmacSha256),
1016 &wrong_mac_key,
1017 )
1018 .is_err()
1019 );
1020 }
1021
1022 #[test]
1023 fn test_aes256_cbc_hmac_cose0_fails_when_ciphertext_tampered() {
1024 let mut message = encrypt_cose0(
1025 CoseContentEncryptionAlgorithm::Aes256CbcHmacSha256,
1026 CoseEncrypt0Builder::new(),
1027 HeaderBuilder::new().build(),
1028 PLAINTEXT,
1029 &CEK_64,
1030 )
1031 .unwrap();
1032
1033 message.ciphertext.as_mut().unwrap()[0] ^= 1;
1034 assert!(
1035 decrypt_cose0(
1036 &message,
1037 CoseAlgorithmPolicy::Exactly(CoseContentEncryptionAlgorithm::Aes256CbcHmacSha256),
1038 &CEK_64,
1039 )
1040 .is_err()
1041 );
1042 }
1043
1044 #[test]
1047 fn test_aes256_cbc_hmac_cose0_rejects_mismatched_policy() {
1048 let message = encrypt_cose0(
1049 CoseContentEncryptionAlgorithm::Aes256CbcHmacSha256,
1050 CoseEncrypt0Builder::new(),
1051 HeaderBuilder::new().build(),
1052 PLAINTEXT,
1053 &CEK_64,
1054 )
1055 .unwrap();
1056
1057 assert_eq!(
1058 message.protected.header.alg,
1059 Some(Algorithm::PrivateUse(AES_256_CBC_HMAC_SHA256_AEAD))
1060 );
1061 assert!(matches!(
1062 decrypt_cose0(
1063 &message,
1064 CoseAlgorithmPolicy::Exactly(CoseContentEncryptionAlgorithm::Aes256Gcm),
1065 &CEK_64,
1066 ),
1067 Err(CryptoError::WrongKeyType)
1068 ));
1069 }
1070
1071 #[test]
1072 fn test_decrypt_cose_algorithm_policies() {
1073 let builder =
1074 || CoseEncryptBuilder::new().add_recipient(CoseRecipientBuilder::new().build());
1075 let message = encrypt_cose(
1076 CoseContentEncryptionAlgorithm::XChaCha20Poly1305,
1077 builder(),
1078 HeaderBuilder::new().build(),
1079 PLAINTEXT,
1080 &CEK,
1081 )
1082 .unwrap();
1083
1084 assert_eq!(
1085 decrypt_cose(
1086 &message,
1087 CoseAlgorithmPolicy::Exactly(CoseContentEncryptionAlgorithm::XChaCha20Poly1305,),
1088 &CEK,
1089 )
1090 .unwrap(),
1091 PLAINTEXT
1092 );
1093 assert!(matches!(
1094 decrypt_cose(
1095 &message,
1096 CoseAlgorithmPolicy::Exactly(CoseContentEncryptionAlgorithm::Aes256Gcm),
1097 &CEK,
1098 ),
1099 Err(CryptoError::WrongKeyType)
1100 ));
1101 assert_eq!(
1102 decrypt_cose(
1103 &message,
1104 CoseAlgorithmPolicy::ProtectedHeaderAlgorithmOrLegacyDefault(
1105 CoseContentEncryptionAlgorithm::Aes256Gcm,
1106 ),
1107 &CEK,
1108 )
1109 .unwrap(),
1110 PLAINTEXT
1111 );
1112
1113 let missing_algorithm = builder()
1114 .protected(HeaderBuilder::new().build())
1115 .create_ciphertext(PLAINTEXT, &[], |data, _| data.to_vec())
1116 .build();
1117 for policy in [
1118 CoseAlgorithmPolicy::RequireProtectedHeaderAlgorithm,
1119 CoseAlgorithmPolicy::Exactly(CoseContentEncryptionAlgorithm::XChaCha20Poly1305),
1120 ] {
1121 assert!(matches!(
1122 decrypt_cose(&missing_algorithm, policy, &CEK),
1123 Err(CryptoError::EncString(
1124 EncStringParseError::CoseMissingAlgorithm
1125 ))
1126 ));
1127 }
1128
1129 let nonce = XChaCha20Poly1305Nonce::make();
1130 let legacy_message = builder()
1131 .protected(HeaderBuilder::new().build())
1132 .unprotected(HeaderBuilder::new().iv(nonce.as_bytes().to_vec()).build())
1133 .create_ciphertext(PLAINTEXT, &[], |data, aad| {
1134 XChaCha20Poly1305::encrypt(&CEK, &nonce, data, aad)
1135 .encrypted_bytes()
1136 .to_vec()
1137 })
1138 .build();
1139 assert_eq!(
1140 decrypt_cose(
1141 &legacy_message,
1142 CoseAlgorithmPolicy::ProtectedHeaderAlgorithmOrLegacyDefault(
1143 CoseContentEncryptionAlgorithm::XChaCha20Poly1305,
1144 ),
1145 &CEK,
1146 )
1147 .unwrap(),
1148 PLAINTEXT
1149 );
1150 }
1151
1152 #[test]
1153 fn test_decrypt_cose0_algorithm_policies() {
1154 let message = encrypt_cose0(
1155 CoseContentEncryptionAlgorithm::XChaCha20Poly1305,
1156 CoseEncrypt0Builder::new(),
1157 HeaderBuilder::new().build(),
1158 PLAINTEXT,
1159 &CEK,
1160 )
1161 .unwrap();
1162
1163 assert_eq!(
1164 decrypt_cose0(
1165 &message,
1166 CoseAlgorithmPolicy::Exactly(CoseContentEncryptionAlgorithm::XChaCha20Poly1305,),
1167 &CEK,
1168 )
1169 .unwrap(),
1170 PLAINTEXT
1171 );
1172 assert!(matches!(
1173 decrypt_cose0(
1174 &message,
1175 CoseAlgorithmPolicy::Exactly(CoseContentEncryptionAlgorithm::Aes256Gcm),
1176 &CEK,
1177 ),
1178 Err(CryptoError::WrongKeyType)
1179 ));
1180 assert_eq!(
1181 decrypt_cose0(
1182 &message,
1183 CoseAlgorithmPolicy::ProtectedHeaderAlgorithmOrLegacyDefault(
1184 CoseContentEncryptionAlgorithm::Aes256Gcm,
1185 ),
1186 &CEK,
1187 )
1188 .unwrap(),
1189 PLAINTEXT
1190 );
1191
1192 let missing_algorithm = CoseEncrypt0Builder::new()
1193 .protected(HeaderBuilder::new().build())
1194 .create_ciphertext(PLAINTEXT, &[], |data, _| data.to_vec())
1195 .build();
1196 assert!(matches!(
1197 decrypt_cose0(
1198 &missing_algorithm,
1199 CoseAlgorithmPolicy::Exactly(CoseContentEncryptionAlgorithm::XChaCha20Poly1305),
1200 &CEK,
1201 ),
1202 Err(CryptoError::EncString(
1203 EncStringParseError::CoseMissingAlgorithm
1204 ))
1205 ));
1206 }
1207
1208 #[test]
1209 fn test_decrypt_cose0_wrong_key_fails() {
1210 let cose_encrypt0 = encrypt_cose0(
1211 CoseContentEncryptionAlgorithm::XChaCha20Poly1305,
1212 CoseEncrypt0Builder::new(),
1213 HeaderBuilder::new().build(),
1214 PLAINTEXT,
1215 &CEK,
1216 )
1217 .unwrap();
1218 let wrong_cek = [0u8; 32];
1219 assert!(
1220 decrypt_cose0(
1221 &cose_encrypt0,
1222 CoseAlgorithmPolicy::Exactly(CoseContentEncryptionAlgorithm::XChaCha20Poly1305),
1223 &wrong_cek
1224 )
1225 .is_err()
1226 );
1227 }
1228
1229 #[test]
1230 fn test_decrypt_xaes256_gcm_wrong_key_fails() {
1231 let cose_encrypt0 = encrypt_cose0(
1232 CoseContentEncryptionAlgorithm::XAes256Gcm,
1233 CoseEncrypt0Builder::new(),
1234 HeaderBuilder::new().build(),
1235 PLAINTEXT,
1236 &CEK,
1237 )
1238 .unwrap();
1239
1240 assert!(matches!(
1241 decrypt_cose0(
1242 &cose_encrypt0,
1243 CoseAlgorithmPolicy::Exactly(CoseContentEncryptionAlgorithm::XAes256Gcm),
1244 &[0u8; 32]
1245 ),
1246 Err(CryptoError::KeyDecrypt)
1247 ));
1248 }
1249
1250 #[test]
1251 fn test_xaes256_gcm_emits_24_byte_nonce() {
1252 let cose_encrypt = encrypt_cose(
1253 CoseContentEncryptionAlgorithm::XAes256Gcm,
1254 CoseEncryptBuilder::new().add_recipient(CoseRecipientBuilder::new().build()),
1255 HeaderBuilder::new().build(),
1256 PLAINTEXT,
1257 &CEK,
1258 )
1259 .unwrap();
1260 let cose_encrypt0 = encrypt_cose0(
1261 CoseContentEncryptionAlgorithm::XAes256Gcm,
1262 CoseEncrypt0Builder::new(),
1263 HeaderBuilder::new().build(),
1264 PLAINTEXT,
1265 &CEK,
1266 )
1267 .unwrap();
1268
1269 assert_eq!(cose_encrypt.unprotected.iv.len(), 24);
1270 assert_eq!(cose_encrypt0.unprotected.iv.len(), 24);
1271 }
1272
1273 #[test]
1274 fn test_decrypt_xaes256_gcm_wrong_nonce_fails() {
1275 let mut cose_encrypt0 = encrypt_cose0(
1276 CoseContentEncryptionAlgorithm::XAes256Gcm,
1277 CoseEncrypt0Builder::new(),
1278 HeaderBuilder::new().build(),
1279 PLAINTEXT,
1280 &CEK,
1281 )
1282 .unwrap();
1283 cose_encrypt0.unprotected.iv[0] ^= 1;
1284
1285 assert!(matches!(
1286 decrypt_cose0(
1287 &cose_encrypt0,
1288 CoseAlgorithmPolicy::Exactly(CoseContentEncryptionAlgorithm::XAes256Gcm),
1289 &CEK
1290 ),
1291 Err(CryptoError::KeyDecrypt)
1292 ));
1293 }
1294
1295 #[test]
1296 fn test_decrypt_xaes256_gcm_malformed_nonce_fails() {
1297 let mut cose_encrypt0 = encrypt_cose0(
1298 CoseContentEncryptionAlgorithm::XAes256Gcm,
1299 CoseEncrypt0Builder::new(),
1300 HeaderBuilder::new().build(),
1301 PLAINTEXT,
1302 &CEK,
1303 )
1304 .unwrap();
1305 cose_encrypt0.unprotected.iv.pop();
1306
1307 assert!(matches!(
1308 decrypt_cose0(
1309 &cose_encrypt0,
1310 CoseAlgorithmPolicy::Exactly(CoseContentEncryptionAlgorithm::XAes256Gcm),
1311 &CEK
1312 ),
1313 Err(CryptoError::InvalidNonceLength)
1314 ));
1315 }
1316
1317 #[test]
1318 fn test_decrypt_cose0_missing_algorithm_fails_without_default() {
1319 let cose_encrypt0 = CoseEncrypt0Builder::new()
1321 .protected(HeaderBuilder::new().build())
1322 .create_ciphertext(PLAINTEXT, &[], |data, _| data.to_vec())
1323 .build();
1324 assert!(matches!(
1325 decrypt_cose0(
1326 &cose_encrypt0,
1327 CoseAlgorithmPolicy::RequireProtectedHeaderAlgorithm,
1328 &CEK
1329 ),
1330 Err(CryptoError::EncString(
1331 EncStringParseError::CoseMissingAlgorithm
1332 ))
1333 ));
1334 }
1335
1336 #[test]
1337 fn test_decrypt_cose0_missing_algorithm_uses_default() {
1338 let nonce = XChaCha20Poly1305Nonce::make();
1342 let cose_encrypt0 = CoseEncrypt0Builder::new()
1343 .protected(HeaderBuilder::new().build())
1344 .unprotected(HeaderBuilder::new().iv(nonce.as_bytes().to_vec()).build())
1345 .create_ciphertext(PLAINTEXT, &[], |data, aad| {
1346 XChaCha20Poly1305::encrypt(&CEK, &nonce, data, aad)
1347 .encrypted_bytes()
1348 .to_vec()
1349 })
1350 .build();
1351
1352 let decrypted = decrypt_cose0(
1353 &cose_encrypt0,
1354 CoseAlgorithmPolicy::ProtectedHeaderAlgorithmOrLegacyDefault(
1355 CoseContentEncryptionAlgorithm::XChaCha20Poly1305,
1356 ),
1357 &CEK,
1358 )
1359 .unwrap();
1360 assert_eq!(decrypted, PLAINTEXT);
1361 }
1362
1363 #[test]
1364 fn test_encrypt_decrypt_xchacha20_roundtrip_octetstream() {
1365 use crate::SymmetricCryptoKey;
1366 let SymmetricCryptoKey::XChaCha20Poly1305Key(ref key) =
1367 SymmetricCryptoKey::make_xchacha20_poly1305_key()
1368 else {
1369 panic!("Failed to create XChaCha20Poly1305Key");
1370 };
1371
1372 let plaintext = b"Hello, world!";
1373 let encrypted =
1374 encrypt_xchacha20_poly1305(plaintext, key, ContentFormat::OctetStream).unwrap();
1375 let decrypted = decrypt_xchacha20_poly1305(&encrypted, key).unwrap();
1376 assert_eq!(decrypted, (plaintext.to_vec(), ContentFormat::OctetStream));
1377 }
1378
1379 #[test]
1380 fn test_encrypt_decrypt_xchacha20_roundtrip_utf8() {
1381 use crate::SymmetricCryptoKey;
1382 let SymmetricCryptoKey::XChaCha20Poly1305Key(ref key) =
1383 SymmetricCryptoKey::make_xchacha20_poly1305_key()
1384 else {
1385 panic!("Failed to create XChaCha20Poly1305Key");
1386 };
1387
1388 let plaintext = b"Hello, world!";
1389 let encrypted = encrypt_xchacha20_poly1305(plaintext, key, ContentFormat::Utf8).unwrap();
1390 let decrypted = decrypt_xchacha20_poly1305(&encrypted, key).unwrap();
1391 assert_eq!(decrypted, (plaintext.to_vec(), ContentFormat::Utf8));
1392 }
1393
1394 #[test]
1395 fn test_encrypt_decrypt_xchacha20_roundtrip_pkcs8() {
1396 use crate::SymmetricCryptoKey;
1397 let SymmetricCryptoKey::XChaCha20Poly1305Key(ref key) =
1398 SymmetricCryptoKey::make_xchacha20_poly1305_key()
1399 else {
1400 panic!("Failed to create XChaCha20Poly1305Key");
1401 };
1402
1403 let plaintext = b"Hello, world!";
1404 let encrypted =
1405 encrypt_xchacha20_poly1305(plaintext, key, ContentFormat::Pkcs8PrivateKey).unwrap();
1406 let decrypted = decrypt_xchacha20_poly1305(&encrypted, key).unwrap();
1407 assert_eq!(
1408 decrypted,
1409 (plaintext.to_vec(), ContentFormat::Pkcs8PrivateKey)
1410 );
1411 }
1412
1413 #[test]
1414 fn test_encrypt_decrypt_xchacha20_roundtrip_cosekey() {
1415 use crate::SymmetricCryptoKey;
1416 let SymmetricCryptoKey::XChaCha20Poly1305Key(ref key) =
1417 SymmetricCryptoKey::make_xchacha20_poly1305_key()
1418 else {
1419 panic!("Failed to create XChaCha20Poly1305Key");
1420 };
1421
1422 let plaintext = b"Hello, world!";
1423 let encrypted = encrypt_xchacha20_poly1305(plaintext, key, ContentFormat::CoseKey).unwrap();
1424 let decrypted = decrypt_xchacha20_poly1305(&encrypted, key).unwrap();
1425 assert_eq!(decrypted, (plaintext.to_vec(), ContentFormat::CoseKey));
1426 }
1427
1428 #[test]
1429 fn test_decrypt_xchacha20_test_vector() {
1430 let key = make_xchacha_key();
1431 let decrypted =
1432 decrypt_xchacha20_poly1305(&CoseEncrypt0Bytes::from(TEST_VECTOR_COSE_ENCRYPT0), &key)
1433 .unwrap();
1434 assert_eq!(
1435 decrypted,
1436 (TEST_VECTOR_PLAINTEXT.to_vec(), ContentFormat::OctetStream)
1437 );
1438 }
1439
1440 #[test]
1441 fn test_decrypt_xchacha20_fail_wrong_key_id() {
1442 let key = XChaCha20Poly1305Key {
1443 key_id: KeyId::from([1; 16]),
1444 enc_key: Box::pin(Array::from(KEY_DATA)),
1445 supported_operations: vec![
1446 KeyOperation::Decrypt,
1447 KeyOperation::Encrypt,
1448 KeyOperation::WrapKey,
1449 KeyOperation::UnwrapKey,
1450 ],
1451 };
1452 assert!(matches!(
1453 decrypt_xchacha20_poly1305(&CoseEncrypt0Bytes::from(TEST_VECTOR_COSE_ENCRYPT0), &key),
1454 Err(CryptoError::WrongCoseKeyId)
1455 ));
1456 }
1457
1458 #[test]
1459 fn test_decrypt_xchacha20_fail_wrong_algorithm() {
1460 use coset::iana;
1461 let protected_header = coset::HeaderBuilder::new()
1462 .algorithm(iana::Algorithm::A256GCM)
1463 .key_id(KEY_ID.to_vec())
1464 .build();
1465 let nonce = [0u8; 16];
1466 let cose_encrypt0 = coset::CoseEncrypt0Builder::new()
1467 .protected(protected_header)
1468 .create_ciphertext(&[], &[], |_, _| Vec::new())
1469 .unprotected(coset::HeaderBuilder::new().iv(nonce.to_vec()).build())
1470 .build();
1471 let serialized_message = CoseEncrypt0Bytes::from(cose_encrypt0.to_vec().unwrap());
1472
1473 let key = make_xchacha_key();
1474 assert!(matches!(
1475 decrypt_xchacha20_poly1305(&serialized_message, &key),
1476 Err(CryptoError::WrongKeyType)
1477 ));
1478 }
1479
1480 fn xaes_message(content_format: ContentFormat) -> CoseEncrypt0 {
1481 let encoded =
1482 encrypt_xaes256_gcm(TEST_VECTOR_PLAINTEXT, &make_xaes_key(), content_format).unwrap();
1483 CoseEncrypt0::from_slice(encoded.as_ref()).unwrap()
1484 }
1485
1486 fn rebuild_xaes_message(message: CoseEncrypt0, protected: Header) -> CoseEncrypt0Bytes {
1487 CoseEncrypt0Builder::new()
1488 .protected(protected)
1489 .unprotected(message.unprotected)
1490 .ciphertext(message.ciphertext.unwrap())
1491 .build()
1492 .to_vec()
1493 .unwrap()
1494 .into()
1495 }
1496
1497 #[test]
1498 fn test_xaes256_gcm_roundtrip_content_formats() {
1499 for content_format in [
1500 ContentFormat::OctetStream,
1501 ContentFormat::Utf8,
1502 ContentFormat::Pkcs8PrivateKey,
1503 ContentFormat::CoseKey,
1504 ] {
1505 let encrypted =
1506 encrypt_xaes256_gcm(TEST_VECTOR_PLAINTEXT, &make_xaes_key(), content_format)
1507 .unwrap();
1508 assert_eq!(
1509 decrypt_xaes256_gcm(&encrypted, &make_xaes_key()).unwrap(),
1510 (TEST_VECTOR_PLAINTEXT.to_vec(), content_format)
1511 );
1512 }
1513 }
1514
1515 #[test]
1516 fn test_xaes256_gcm_key_id_and_authentication_failures() {
1517 let encrypted = encrypt_xaes256_gcm(
1518 TEST_VECTOR_PLAINTEXT,
1519 &make_xaes_key(),
1520 ContentFormat::OctetStream,
1521 )
1522 .unwrap();
1523
1524 let mut wrong_bytes = make_xaes_key();
1525 wrong_bytes.enc_key[0] ^= 1;
1526 assert!(matches!(
1527 decrypt_xaes256_gcm(&encrypted, &wrong_bytes),
1528 Err(CryptoError::KeyDecrypt)
1529 ));
1530
1531 let mut wrong_id = make_xaes_key();
1532 wrong_id.key_id = KeyId::from([1; 16]);
1533 assert!(matches!(
1534 decrypt_xaes256_gcm(&encrypted, &wrong_id),
1535 Err(CryptoError::WrongCoseKeyId)
1536 ));
1537 }
1538
1539 #[test]
1540 fn test_xaes256_gcm_rejects_invalid_protected_headers() {
1541 let key = make_xaes_key();
1542
1543 let mut message = xaes_message(ContentFormat::OctetStream);
1544 message.protected.header.alg = Some(Algorithm::Assigned(iana::Algorithm::A256GCM));
1545 let encrypted = rebuild_xaes_message(message.clone(), message.protected.header.clone());
1546 assert!(matches!(
1547 decrypt_xaes256_gcm(&encrypted, &key),
1548 Err(CryptoError::WrongKeyType)
1549 ));
1550
1551 message.protected.header.alg = None;
1552 let encrypted = rebuild_xaes_message(message.clone(), message.protected.header.clone());
1553 assert!(matches!(
1554 decrypt_xaes256_gcm(&encrypted, &key),
1555 Err(CryptoError::EncString(
1556 EncStringParseError::CoseMissingAlgorithm
1557 ))
1558 ));
1559
1560 for content_type in [
1561 None,
1562 Some(coset::ContentType::Text("application/unsupported".into())),
1563 ] {
1564 message.protected.header.alg = Some(Algorithm::PrivateUse(XAES_256_GCM));
1565 message.protected.header.content_type = content_type;
1566 let encrypted = rebuild_xaes_message(message.clone(), message.protected.header.clone());
1567 assert!(matches!(
1568 decrypt_xaes256_gcm(&encrypted, &key),
1569 Err(CryptoError::EncString(
1570 EncStringParseError::CoseMissingContentType
1571 ))
1572 ));
1573 }
1574 }
1575
1576 #[test]
1577 fn test_xaes256_gcm_rejects_missing_or_malformed_fields() {
1578 let key = make_xaes_key();
1579 let mut message = xaes_message(ContentFormat::OctetStream);
1580 message.ciphertext = None;
1581 let encrypted = message.to_vec().unwrap().into();
1582 assert!(matches!(
1583 decrypt_xaes256_gcm(&encrypted, &key),
1584 Err(CryptoError::MissingField("ciphertext"))
1585 ));
1586
1587 let mut message = xaes_message(ContentFormat::OctetStream);
1588 message.unprotected.iv.pop();
1589 let encrypted = message.to_vec().unwrap().into();
1590 assert!(matches!(
1591 decrypt_xaes256_gcm(&encrypted, &key),
1592 Err(CryptoError::InvalidNonceLength)
1593 ));
1594
1595 assert!(matches!(
1596 decrypt_xaes256_gcm(&CoseEncrypt0Bytes::from([0xff].as_slice()), &key),
1597 Err(CryptoError::EncString(
1598 EncStringParseError::InvalidCoseEncoding(_)
1599 ))
1600 ));
1601 }
1602
1603 #[test]
1604 fn test_xaes256_gcm_rejects_invalid_padding() {
1605 let key = make_xaes_key();
1606 let nonce = XAes256GcmNonce::make();
1607 let mut protected = HeaderBuilder::from(ContentFormat::Utf8)
1608 .key_id(KEY_ID.to_vec())
1609 .build();
1610 protected.alg = Some(Algorithm::PrivateUse(XAES_256_GCM));
1611 let message = CoseEncrypt0Builder::new()
1612 .protected(protected)
1613 .unprotected(HeaderBuilder::new().iv(nonce.as_bytes().to_vec()).build())
1614 .create_ciphertext(&[0], &[], |data, aad| {
1615 XAes256Gcm::encrypt(&KEY_DATA, &nonce, data, aad)
1616 .encrypted_bytes()
1617 .to_vec()
1618 })
1619 .build()
1620 .to_vec()
1621 .unwrap()
1622 .into();
1623
1624 assert!(matches!(
1625 decrypt_xaes256_gcm(&message, &key),
1626 Err(CryptoError::InvalidPadding)
1627 ));
1628 }
1629}