1use super::aead_ctx::AeadCtx;
5use super::{
6 Algorithm, Nonce, Tag, AES_128_GCM, AES_128_GCM_SIV, AES_192_GCM, AES_256_GCM, AES_256_GCM_SIV,
7 CHACHA20_POLY1305, MAX_KEY_LEN, MAX_TAG_LEN, NONCE_LEN,
8};
9use crate::aws_lc::{
10 EVP_AEAD_CTX_open, EVP_AEAD_CTX_open_gather, EVP_AEAD_CTX_seal, EVP_AEAD_CTX_seal_scatter,
11};
12use crate::error::Unspecified;
13use crate::fips::indicator_check;
14use crate::hkdf;
15use crate::iv::FixedLength;
16use core::fmt::Debug;
17use core::mem::MaybeUninit;
18use core::ops::RangeFrom;
19use core::ptr::null;
20
21const MAX_NONCE_LEN: usize = NONCE_LEN;
23
24const MAX_TAG_NONCE_BUFFER_LEN: usize = MAX_TAG_LEN + MAX_NONCE_LEN;
26
27pub struct UnboundKey {
29 ctx: AeadCtx,
30 algorithm: &'static Algorithm,
31}
32
33#[allow(clippy::missing_fields_in_debug)]
34impl Debug for UnboundKey {
35 fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
36 f.debug_struct("UnboundKey")
37 .field("algorithm", &self.algorithm)
38 .finish()
39 }
40}
41
42impl UnboundKey {
43 pub fn new(algorithm: &'static Algorithm, key_bytes: &[u8]) -> Result<Self, Unspecified> {
47 Ok(Self {
48 ctx: (algorithm.init)(key_bytes, algorithm.tag_len())?,
49 algorithm,
50 })
51 }
52
53 #[inline]
54 pub(crate) fn open_within<'in_out>(
55 &self,
56 nonce: Nonce,
57 aad: &[u8],
58 in_out: &'in_out mut [u8],
59 ciphertext_and_tag: RangeFrom<usize>,
60 ) -> Result<&'in_out mut [u8], Unspecified> {
61 let in_prefix_len = ciphertext_and_tag.start;
62 let ciphertext_and_tag_len = in_out.len().checked_sub(in_prefix_len).ok_or(Unspecified)?;
63 let ciphertext_len = ciphertext_and_tag_len
64 .checked_sub(self.algorithm().tag_len())
65 .ok_or(Unspecified)?;
66 self.check_per_nonce_max_bytes(ciphertext_len)?;
67
68 match self.ctx {
69 AeadCtx::AES_128_GCM_RANDNONCE(_) | AeadCtx::AES_256_GCM_RANDNONCE(_) => {
70 self.open_combined_randnonce(nonce, aad, &mut in_out[in_prefix_len..])
71 }
72 _ => self.open_combined(nonce, aad.as_ref(), &mut in_out[in_prefix_len..]),
73 }?;
74
75 in_out.copy_within(in_prefix_len..in_prefix_len + ciphertext_len, 0);
77
78 Ok(&mut in_out[..ciphertext_len])
80 }
81
82 #[inline]
83 pub(crate) fn open_separate_gather(
84 &self,
85 nonce: &Nonce,
86 aad: &[u8],
87 in_ciphertext: &[u8],
88 in_tag: &[u8],
89 out_plaintext: &mut [u8],
90 ) -> Result<(), Unspecified> {
91 self.open_separate_gather_impl(
92 nonce,
93 aad,
94 in_ciphertext.as_ptr(),
95 in_ciphertext.len(),
96 in_tag,
97 out_plaintext.as_mut_ptr(),
98 out_plaintext.len(),
99 )
100 }
101
102 #[inline]
103 pub(crate) fn open_in_place_separate_tag(
104 &self,
105 nonce: &Nonce,
106 aad: &[u8],
107 in_tag: &[u8],
108 in_out: &mut [u8],
109 ) -> Result<(), Unspecified> {
110 let ptr = in_out.as_mut_ptr();
111 let len = in_out.len();
112 self.open_separate_gather_impl(nonce, aad, ptr.cast_const(), len, in_tag, ptr, len)
113 }
114
115 #[inline]
126 #[allow(clippy::too_many_arguments)]
127 fn open_separate_gather_impl(
128 &self,
129 nonce: &Nonce,
130 aad: &[u8],
131 in_ciphertext: *const u8,
132 in_ciphertext_len: usize,
133 in_tag: &[u8],
134 out_plaintext: *mut u8,
135 out_plaintext_len: usize,
136 ) -> Result<(), Unspecified> {
137 self.check_per_nonce_max_bytes(in_ciphertext_len)?;
138
139 if in_ciphertext_len != out_plaintext_len {
141 return Err(Unspecified);
142 }
143
144 unsafe {
145 let aead_ctx = self.ctx.as_ref();
146 let nonce = nonce.as_ref();
147
148 if 1 != EVP_AEAD_CTX_open_gather(
149 aead_ctx.as_const_ptr(),
150 out_plaintext,
151 nonce.as_ptr(),
152 nonce.len(),
153 in_ciphertext,
154 in_ciphertext_len,
155 in_tag.as_ptr(),
156 in_tag.len(),
157 aad.as_ptr(),
158 aad.len(),
159 ) {
160 return Err(Unspecified);
161 }
162 Ok(())
163 }
164 }
165
166 #[inline]
167 pub(crate) fn seal_in_place_append_tag<'a, InOut>(
168 &self,
169 nonce: Option<Nonce>,
170 aad: &[u8],
171 in_out: &'a mut InOut,
172 ) -> Result<Nonce, Unspecified>
173 where
174 InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
175 {
176 self.check_per_nonce_max_bytes(in_out.as_mut().len())?;
177 match nonce {
178 Some(nonce) => self.seal_combined(nonce, aad, in_out),
179 None => self.seal_combined_randnonce(aad, in_out),
180 }
181 }
182
183 #[inline]
184 pub(crate) fn seal_in_place_separate_tag(
185 &self,
186 nonce: Option<Nonce>,
187 aad: &[u8],
188 in_out: &mut [u8],
189 ) -> Result<(Nonce, Tag), Unspecified> {
190 self.check_per_nonce_max_bytes(in_out.len())?;
191 match nonce {
192 Some(nonce) => self.seal_separate(nonce, aad, in_out),
193 None => self.seal_separate_randnonce(aad, in_out),
194 }
195 }
196
197 #[inline]
198 #[allow(clippy::needless_pass_by_value)]
199 pub(crate) fn seal_out_of_place_scatter(
200 &self,
201 nonce: Nonce,
202 aad: &[u8],
203 in_plaintext: &[u8],
204 out_ciphertext: &mut [u8],
205 extra_in: &[u8],
206 extra_out_and_tag: &mut [u8],
207 ) -> Result<(), Unspecified> {
208 self.check_per_nonce_max_bytes(in_plaintext.len() + extra_in.len())?;
209 if out_ciphertext.len() != in_plaintext.len()
210 || extra_out_and_tag.len() != extra_in.len() + self.algorithm().tag_len()
211 {
212 return Err(Unspecified);
213 }
214
215 let nonce = nonce.as_ref();
216 let mut out_tag_len = 0;
219
220 if 1 != unsafe {
221 EVP_AEAD_CTX_seal_scatter(
222 self.ctx.as_ref().as_const_ptr(),
223 out_ciphertext.as_mut_ptr(),
224 extra_out_and_tag.as_mut_ptr(),
225 &mut out_tag_len,
226 extra_out_and_tag.len(),
227 nonce.as_ptr(),
228 nonce.len(),
229 in_plaintext.as_ptr(),
230 in_plaintext.len(),
231 extra_in.as_ptr(),
232 extra_in.len(),
233 aad.as_ptr(),
234 aad.len(),
235 )
236 } {
237 return Err(Unspecified);
238 }
239 debug_assert_eq!(out_tag_len, extra_out_and_tag.len());
240 Ok(())
241 }
242
243 #[inline]
244 #[allow(clippy::needless_pass_by_value)]
245 pub(crate) fn seal_in_place_separate_scatter(
246 &self,
247 nonce: Nonce,
248 aad: &[u8],
249 in_out: &mut [u8],
250 extra_in: &[u8],
251 extra_out_and_tag: &mut [u8],
252 ) -> Result<(), Unspecified> {
253 self.check_per_nonce_max_bytes(in_out.len())?;
254 {
256 let actual = extra_in.len() + self.algorithm().tag_len();
257 let expected = extra_out_and_tag.len();
258
259 if actual != expected {
260 return Err(Unspecified);
261 }
262 }
263
264 let nonce = nonce.as_ref();
265 let mut out_tag_len = extra_out_and_tag.len();
266
267 if 1 != unsafe {
268 EVP_AEAD_CTX_seal_scatter(
269 self.ctx.as_ref().as_const_ptr(),
270 in_out.as_mut_ptr(),
271 extra_out_and_tag.as_mut_ptr(),
272 &mut out_tag_len,
273 extra_out_and_tag.len(),
274 nonce.as_ptr(),
275 nonce.len(),
276 in_out.as_ptr(),
277 in_out.len(),
278 extra_in.as_ptr(),
279 extra_in.len(),
280 aad.as_ptr(),
281 aad.len(),
282 )
283 } {
284 return Err(Unspecified);
285 }
286 Ok(())
287 }
288
289 #[inline]
291 #[must_use]
292 pub fn algorithm(&self) -> &'static Algorithm {
293 self.algorithm
294 }
295
296 #[inline]
297 pub(crate) fn check_per_nonce_max_bytes(&self, in_out_len: usize) -> Result<(), Unspecified> {
298 if in_out_len as u64 > self.algorithm().max_input_len {
299 return Err(Unspecified);
300 }
301 Ok(())
302 }
303
304 #[inline]
305 #[allow(clippy::needless_pass_by_value)]
306 fn open_combined(
307 &self,
308 nonce: Nonce,
309 aad: &[u8],
310 in_out: &mut [u8],
311 ) -> Result<(), Unspecified> {
312 let nonce = nonce.as_ref();
313
314 debug_assert_eq!(nonce.len(), self.algorithm().nonce_len());
315
316 let plaintext_len = in_out.len() - self.algorithm().tag_len();
317
318 let mut out_len = MaybeUninit::<usize>::uninit();
319 if 1 != indicator_check!(unsafe {
320 EVP_AEAD_CTX_open(
321 self.ctx.as_ref().as_const_ptr(),
322 in_out.as_mut_ptr(),
323 out_len.as_mut_ptr(),
324 plaintext_len,
325 nonce.as_ptr(),
326 nonce.len(),
327 in_out.as_ptr(),
328 plaintext_len + self.algorithm().tag_len(),
329 aad.as_ptr(),
330 aad.len(),
331 )
332 }) {
333 return Err(Unspecified);
334 }
335
336 Ok(())
337 }
338
339 #[inline]
340 #[allow(clippy::needless_pass_by_value)]
341 fn open_combined_randnonce(
342 &self,
343 nonce: Nonce,
344 aad: &[u8],
345 in_out: &mut [u8],
346 ) -> Result<(), Unspecified> {
347 let nonce = nonce.as_ref();
348
349 let alg_nonce_len = self.algorithm().nonce_len();
350 let alg_tag_len = self.algorithm().tag_len();
351
352 debug_assert_eq!(nonce.len(), alg_nonce_len);
353 debug_assert!(alg_tag_len + alg_nonce_len <= MAX_TAG_NONCE_BUFFER_LEN);
354
355 let plaintext_len = in_out.len() - alg_tag_len;
356
357 let mut tag_buffer = [0u8; MAX_TAG_NONCE_BUFFER_LEN];
358
359 tag_buffer[..alg_tag_len]
360 .copy_from_slice(&in_out[plaintext_len..plaintext_len + alg_tag_len]);
361 tag_buffer[alg_tag_len..alg_tag_len + alg_nonce_len].copy_from_slice(nonce);
362
363 let tag_slice = &tag_buffer[0..alg_tag_len + alg_nonce_len];
364
365 if 1 != indicator_check!(unsafe {
366 EVP_AEAD_CTX_open_gather(
367 self.ctx.as_ref().as_const_ptr(),
368 in_out.as_mut_ptr(),
369 null(),
370 0,
371 in_out.as_ptr(),
372 plaintext_len,
373 tag_slice.as_ptr(),
374 tag_slice.len(),
375 aad.as_ptr(),
376 aad.len(),
377 )
378 }) {
379 return Err(Unspecified);
380 }
381
382 Ok(())
383 }
384
385 #[inline]
386 fn seal_combined<InOut>(
387 &self,
388 nonce: Nonce,
389 aad: &[u8],
390 in_out: &mut InOut,
391 ) -> Result<Nonce, Unspecified>
392 where
393 InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
394 {
395 let plaintext_len = in_out.as_mut().len();
396
397 let alg_tag_len = self.algorithm().tag_len();
398
399 debug_assert!(alg_tag_len <= MAX_TAG_LEN);
400
401 let tag_buffer = [0u8; MAX_TAG_LEN];
402
403 in_out.extend(tag_buffer[..alg_tag_len].iter());
404
405 let mut_in_out = in_out.as_mut();
408 let out_capacity = mut_in_out.len();
409 let expected_len = plaintext_len.checked_add(alg_tag_len).ok_or(Unspecified)?;
410 if out_capacity != expected_len {
412 return Err(Unspecified);
413 }
414
415 let mut out_len = MaybeUninit::<usize>::uninit();
416
417 {
418 let nonce = nonce.as_ref();
419
420 debug_assert_eq!(nonce.len(), self.algorithm().nonce_len());
421
422 if 1 != indicator_check!(unsafe {
423 EVP_AEAD_CTX_seal(
424 self.ctx.as_ref().as_const_ptr(),
425 mut_in_out.as_mut_ptr(),
426 out_len.as_mut_ptr(),
427 out_capacity,
428 nonce.as_ptr(),
429 nonce.len(),
430 mut_in_out.as_ptr(),
431 plaintext_len,
432 aad.as_ptr(),
433 aad.len(),
434 )
435 }) {
436 return Err(Unspecified);
437 }
438 }
439
440 Ok(nonce)
441 }
442
443 #[inline]
444 fn seal_combined_randnonce<InOut>(
445 &self,
446 aad: &[u8],
447 in_out: &mut InOut,
448 ) -> Result<Nonce, Unspecified>
449 where
450 InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
451 {
452 let mut tag_buffer = [0u8; MAX_TAG_NONCE_BUFFER_LEN];
453
454 let mut out_tag_len = MaybeUninit::<usize>::uninit();
455 let plaintext_len;
456
457 {
458 let mut_in_out = in_out.as_mut();
461 plaintext_len = mut_in_out.len();
462
463 if 1 != indicator_check!(unsafe {
464 EVP_AEAD_CTX_seal_scatter(
465 self.ctx.as_ref().as_const_ptr(),
466 mut_in_out.as_mut_ptr(),
467 tag_buffer.as_mut_ptr(),
468 out_tag_len.as_mut_ptr(),
469 tag_buffer.len(),
470 null(),
471 0,
472 mut_in_out.as_ptr(),
473 plaintext_len,
474 null(),
475 0,
476 aad.as_ptr(),
477 aad.len(),
478 )
479 }) {
480 return Err(Unspecified);
481 }
482 }
483
484 let tag_len = self.algorithm().tag_len();
485 let nonce_len = self.algorithm().nonce_len();
486
487 let nonce = Nonce(FixedLength::<NONCE_LEN>::try_from(
488 &tag_buffer[tag_len..tag_len + nonce_len],
489 )?);
490
491 in_out.extend(&tag_buffer[..tag_len]);
492
493 let expected_len = plaintext_len.checked_add(tag_len).ok_or(Unspecified)?;
494 if in_out.as_mut().len() != expected_len {
495 return Err(Unspecified);
496 }
497
498 Ok(nonce)
499 }
500
501 #[inline]
502 fn seal_separate(
503 &self,
504 nonce: Nonce,
505 aad: &[u8],
506 in_out: &mut [u8],
507 ) -> Result<(Nonce, Tag), Unspecified> {
508 let mut tag = [0u8; MAX_TAG_LEN];
509 let mut out_tag_len = MaybeUninit::<usize>::uninit();
510 {
511 let nonce = nonce.as_ref();
512
513 debug_assert_eq!(nonce.len(), self.algorithm().nonce_len());
514
515 if 1 != indicator_check!(unsafe {
516 EVP_AEAD_CTX_seal_scatter(
517 self.ctx.as_ref().as_const_ptr(),
518 in_out.as_mut_ptr(),
519 tag.as_mut_ptr(),
520 out_tag_len.as_mut_ptr(),
521 tag.len(),
522 nonce.as_ptr(),
523 nonce.len(),
524 in_out.as_ptr(),
525 in_out.len(),
526 null(),
527 0usize,
528 aad.as_ptr(),
529 aad.len(),
530 )
531 }) {
532 return Err(Unspecified);
533 }
534 }
535 Ok((nonce, Tag(tag, unsafe { out_tag_len.assume_init() })))
536 }
537
538 #[inline]
539 fn seal_separate_randnonce(
540 &self,
541 aad: &[u8],
542 in_out: &mut [u8],
543 ) -> Result<(Nonce, Tag), Unspecified> {
544 let mut tag_buffer = [0u8; MAX_TAG_NONCE_BUFFER_LEN];
545
546 debug_assert!(
547 self.algorithm().tag_len() + self.algorithm().nonce_len() <= tag_buffer.len()
548 );
549
550 let mut out_tag_len = MaybeUninit::<usize>::uninit();
551
552 if 1 != indicator_check!(unsafe {
553 EVP_AEAD_CTX_seal_scatter(
554 self.ctx.as_ref().as_const_ptr(),
555 in_out.as_mut_ptr(),
556 tag_buffer.as_mut_ptr(),
557 out_tag_len.as_mut_ptr(),
558 tag_buffer.len(),
559 null(),
560 0,
561 in_out.as_ptr(),
562 in_out.len(),
563 null(),
564 0usize,
565 aad.as_ptr(),
566 aad.len(),
567 )
568 }) {
569 return Err(Unspecified);
570 }
571
572 let tag_len = self.algorithm().tag_len();
573 let nonce_len = self.algorithm().nonce_len();
574
575 let nonce = Nonce(FixedLength::<NONCE_LEN>::try_from(
576 &tag_buffer[tag_len..tag_len + nonce_len],
577 )?);
578
579 let mut tag = [0u8; MAX_TAG_LEN];
580 tag.copy_from_slice(&tag_buffer[..tag_len]);
581
582 Ok((nonce, Tag(tag, tag_len)))
583 }
584}
585
586impl From<AeadCtx> for UnboundKey {
587 fn from(value: AeadCtx) -> Self {
588 let algorithm = match value {
589 AeadCtx::AES_128_GCM(_)
590 | AeadCtx::AES_128_GCM_TLS12(_)
591 | AeadCtx::AES_128_GCM_TLS13(_)
592 | AeadCtx::AES_128_GCM_RANDNONCE(_) => &AES_128_GCM,
593 AeadCtx::AES_192_GCM(_) => &AES_192_GCM,
594 AeadCtx::AES_128_GCM_SIV(_) => &AES_128_GCM_SIV,
595 AeadCtx::AES_256_GCM(_)
596 | AeadCtx::AES_256_GCM_RANDNONCE(_)
597 | AeadCtx::AES_256_GCM_TLS12(_)
598 | AeadCtx::AES_256_GCM_TLS13(_) => &AES_256_GCM,
599 AeadCtx::AES_256_GCM_SIV(_) => &AES_256_GCM_SIV,
600 AeadCtx::CHACHA20_POLY1305(_) => &CHACHA20_POLY1305,
601 };
602 Self {
603 ctx: value,
604 algorithm,
605 }
606 }
607}
608
609impl From<hkdf::Okm<'_, &'static Algorithm>> for UnboundKey {
610 fn from(okm: hkdf::Okm<&'static Algorithm>) -> Self {
611 let mut key_bytes = [0; MAX_KEY_LEN];
612 let key_bytes = &mut key_bytes[..okm.len().key_len];
613 let algorithm = *okm.len();
614 okm.fill(key_bytes).unwrap();
615 Self::new(algorithm, key_bytes).unwrap()
616 }
617}
618
619#[cfg(test)]
620mod tests {
621 use super::*;
622
623 struct NormalBuffer(Vec<u8>);
624
625 impl AsMut<[u8]> for NormalBuffer {
626 fn as_mut(&mut self) -> &mut [u8] {
627 self.0.as_mut_slice()
628 }
629 }
630
631 impl<'a> Extend<&'a u8> for NormalBuffer {
632 fn extend<T: IntoIterator<Item = &'a u8>>(&mut self, iter: T) {
633 self.0.extend(iter);
634 }
635 }
636
637 struct NoGrowBuffer(Vec<u8>);
638
639 impl AsMut<[u8]> for NoGrowBuffer {
640 fn as_mut(&mut self) -> &mut [u8] {
641 self.0.as_mut_slice()
642 }
643 }
644
645 impl<'a> Extend<&'a u8> for NoGrowBuffer {
646 fn extend<T: IntoIterator<Item = &'a u8>>(&mut self, _iter: T) {}
647 }
648
649 struct ShortExtendBuffer(Vec<u8>);
650
651 impl AsMut<[u8]> for ShortExtendBuffer {
652 fn as_mut(&mut self) -> &mut [u8] {
653 self.0.as_mut_slice()
654 }
655 }
656
657 impl<'a> Extend<&'a u8> for ShortExtendBuffer {
658 fn extend<T: IntoIterator<Item = &'a u8>>(&mut self, iter: T) {
659 self.0.extend(iter.into_iter().take(1));
660 }
661 }
662
663 struct ShrinkingBuffer(Vec<u8>);
664
665 impl AsMut<[u8]> for ShrinkingBuffer {
666 fn as_mut(&mut self) -> &mut [u8] {
667 self.0.as_mut_slice()
668 }
669 }
670
671 impl<'a> Extend<&'a u8> for ShrinkingBuffer {
672 fn extend<T: IntoIterator<Item = &'a u8>>(&mut self, _iter: T) {
673 let new_len = self.0.len().saturating_sub(1);
674 self.0.truncate(new_len);
675 }
676 }
677
678 fn test_key() -> UnboundKey {
679 UnboundKey::new(&AES_128_GCM, &[0x42u8; 16]).unwrap()
680 }
681
682 fn test_randnonce_key() -> UnboundKey {
683 UnboundKey::from(
684 AeadCtx::aes_128_gcm_randnonce(
685 &[0x42u8; 16],
686 AES_128_GCM.tag_len(),
687 AES_128_GCM.nonce_len(),
688 )
689 .unwrap(),
690 )
691 }
692
693 fn test_nonce() -> Nonce {
694 Nonce::try_assume_unique_for_key(&[0x24u8; NONCE_LEN]).unwrap()
695 }
696
697 #[test]
698 fn seal_combined_normal_extend_succeeds_and_roundtrips() {
699 let key = test_key();
700 let plaintext = b"seal_combined soundness regression test".to_vec();
701 let mut in_out = NormalBuffer(plaintext.clone());
702
703 let nonce = key
704 .seal_combined(test_nonce(), &[], &mut in_out)
705 .expect("a normal, Vec-like Extend impl must succeed");
706
707 assert_eq!(in_out.0.len(), plaintext.len() + key.algorithm().tag_len());
708
709 let opened: &[u8] = key
710 .open_within(nonce, &[], &mut in_out.0, 0..)
711 .expect("the sealed output must open back to the original plaintext");
712 assert_eq!(opened, plaintext.as_slice());
713 }
714
715 #[test]
716 fn seal_combined_rejects_no_grow_extend() {
717 let key = test_key();
718 let plaintext = b"some plaintext".to_vec();
719 let original_len = plaintext.len();
720 let mut in_out = NoGrowBuffer(plaintext);
721
722 let result = key.seal_combined(test_nonce(), &[], &mut in_out);
723
724 assert!(
725 result.is_err(),
726 "a no-op Extend impl must not be trusted to have appended the tag"
727 );
728 assert_eq!(in_out.0.len(), original_len);
729 }
730
731 #[test]
732 fn seal_combined_rejects_short_extend() {
733 let key = test_key();
734 let mut in_out = ShortExtendBuffer(b"some plaintext".to_vec());
735
736 let result = key.seal_combined(test_nonce(), &[], &mut in_out);
737
738 assert!(
739 result.is_err(),
740 "an Extend impl that appends fewer bytes than the tag length must be rejected"
741 );
742 }
743
744 #[test]
745 fn seal_combined_rejects_shrinking_extend() {
746 let key = test_key();
747 let mut in_out = ShrinkingBuffer(b"some plaintext".to_vec());
748
749 let result = key.seal_combined(test_nonce(), &[], &mut in_out);
750
751 assert!(
752 result.is_err(),
753 "an Extend impl that shrinks the collection must be rejected"
754 );
755 }
756
757 #[test]
758 fn seal_combined_randnonce_normal_extend_succeeds_and_roundtrips() {
759 let key = test_randnonce_key();
760 let plaintext = b"seal_combined_randnonce soundness regression test".to_vec();
761 let mut in_out = NormalBuffer(plaintext.clone());
762
763 let nonce = key
764 .seal_combined_randnonce(&[], &mut in_out)
765 .expect("a normal, Vec-like Extend impl must succeed");
766
767 assert_eq!(in_out.0.len(), plaintext.len() + key.algorithm().tag_len());
768
769 let opened = key
770 .open_within(nonce, &[], &mut in_out.0, 0..)
771 .expect("the sealed output must open back to the original plaintext");
772 assert_eq!(opened, plaintext.as_slice());
773 }
774
775 #[test]
776 fn seal_combined_randnonce_rejects_no_grow_extend() {
777 let key = test_randnonce_key();
778 let plaintext = b"some plaintext".to_vec();
779 let original_len = plaintext.len();
780 let mut in_out = NoGrowBuffer(plaintext);
781
782 let result = key.seal_combined_randnonce(&[], &mut in_out);
783
784 assert!(
785 result.is_err(),
786 "a no-op Extend impl must not be trusted to have appended the tag"
787 );
788 assert_eq!(in_out.0.len(), original_len);
789 }
790
791 #[test]
792 fn seal_combined_randnonce_rejects_short_extend() {
793 let key = test_randnonce_key();
794 let mut in_out = ShortExtendBuffer(b"some plaintext".to_vec());
795
796 let result = key.seal_combined_randnonce(&[], &mut in_out);
797
798 assert!(
799 result.is_err(),
800 "an Extend impl that appends fewer bytes than the tag length must be rejected"
801 );
802 }
803
804 #[test]
805 fn seal_combined_randnonce_rejects_shrinking_extend() {
806 let key = test_randnonce_key();
807 let mut in_out = ShrinkingBuffer(b"some plaintext".to_vec());
808
809 let result = key.seal_combined_randnonce(&[], &mut in_out);
810
811 assert!(
812 result.is_err(),
813 "an Extend impl that shrinks the collection must be rejected"
814 );
815 }
816}