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