Skip to content

Commit 1cbe889

Browse files
wpt-oaijustsmth
andauthored
Adds LessSafeKey::open_in_place_separate_tag (#1116)
* Adds LessSafeKey::open_in_place_separate_tag for in-place but out of band tags * Expands test coverage for open_in_place_separate_tag Expand tests to cover AES-256-GCM, ChaCha20-Poly1305, empty plaintext, and wrong-tag rejection. * Additional cleanup --------- Co-authored-by: Justin Smith <justsmth@amazon.com>
1 parent ce8bccc commit 1cbe889

3 files changed

Lines changed: 193 additions & 12 deletions

File tree

aws-lc-rs/src/aead.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,37 @@ impl LessSafeKey {
736736
self.open_within(nonce, aad, in_out, 0..)
737737
}
738738

739+
/// Like [`OpeningKey::open_in_place()`], except the authentication tag is
740+
/// passed separately.
741+
///
742+
/// `in_out` contains the ciphertext on input and is overwritten with the
743+
/// plaintext on success. `tag` is the authentication tag, e.g. as produced
744+
/// by [`Self::seal_in_place_separate_tag()`].
745+
///
746+
/// `nonce` must be unique for every use of the key to open data.
747+
// # FIPS
748+
// This method must not be used.
749+
//
750+
/// # Errors
751+
/// `error::Unspecified` when ciphertext is invalid. In this case, `in_out` may
752+
/// have been overwritten in an unspecified way.
753+
#[inline]
754+
#[allow(clippy::needless_pass_by_value)]
755+
pub fn open_in_place_separate_tag<'in_out, A>(
756+
&self,
757+
nonce: Nonce,
758+
aad: Aad<A>,
759+
tag: &[u8],
760+
in_out: &'in_out mut [u8],
761+
) -> Result<&'in_out mut [u8], Unspecified>
762+
where
763+
A: AsRef<[u8]>,
764+
{
765+
self.key
766+
.open_in_place_separate_tag(&nonce, aad.as_ref(), tag, in_out)?;
767+
Ok(in_out)
768+
}
769+
739770
/// Like [`OpeningKey::open_within()`], except it accepts an arbitrary nonce.
740771
///
741772
/// `nonce` must be unique for every use of the key to open data.
@@ -764,7 +795,7 @@ impl LessSafeKey {
764795
.open_within(nonce, aad.as_ref(), in_out, ciphertext_and_tag)
765796
}
766797

767-
/// Authenticates and decrypts (opens) data into another provided slice.
798+
/// Authenticates and decrypts ("opens") data into another provided slice.
768799
///
769800
/// `aad` is the additional authenticated data (AAD), if any.
770801
///

aws-lc-rs/src/aead/unbound_key.rs

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,16 +88,57 @@ impl UnboundKey {
8888
in_tag: &[u8],
8989
out_plaintext: &mut [u8],
9090
) -> Result<(), Unspecified> {
91-
self.check_per_nonce_max_bytes(in_ciphertext.len())?;
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+
}
92101

93-
// ensure that the lengths match
94-
{
95-
let actual = in_ciphertext.len();
96-
let expected = out_plaintext.len();
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+
}
97114

98-
if actual != expected {
99-
return Err(Unspecified);
100-
}
115+
/// Common FFI path for `EVP_AEAD_CTX_open_gather`-based opening.
116+
///
117+
/// `in_ciphertext` / `out_plaintext` may alias (exactly, i.e. same base
118+
/// pointer and length). `EVP_AEAD_CTX_open_gather` explicitly permits
119+
/// `out == in`, which is how `open_in_place_separate_tag` works.
120+
///
121+
/// Callers must ensure:
122+
/// * `in_ciphertext` is valid for reads of `in_ciphertext_len` bytes.
123+
/// * `out_plaintext` is valid for writes of `out_plaintext_len` bytes.
124+
/// * If the two pointers alias, they must alias exactly (same base, same length).
125+
#[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+
// ensure that the lengths match
140+
if in_ciphertext_len != out_plaintext_len {
141+
return Err(Unspecified);
101142
}
102143

103144
unsafe {
@@ -106,11 +147,11 @@ impl UnboundKey {
106147

107148
if 1 != EVP_AEAD_CTX_open_gather(
108149
aead_ctx.as_const_ptr(),
109-
out_plaintext.as_mut_ptr(),
150+
out_plaintext,
110151
nonce.as_ptr(),
111152
nonce.len(),
112-
in_ciphertext.as_ptr(),
113-
in_ciphertext.len(),
153+
in_ciphertext,
154+
in_ciphertext_len,
114155
in_tag.as_ptr(),
115156
in_tag.len(),
116157
aad.as_ptr(),

aws-lc-rs/tests/aead_test.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,115 @@ fn test_aead_thread_safeness() {
606606
}
607607
}
608608

609+
fn test_open_in_place_separate_tag_for(
610+
algorithm: &'static aead::Algorithm,
611+
key_bytes: &[u8],
612+
plaintext: &[u8],
613+
) {
614+
let nonce_bytes = [0x24; NONCE_LEN];
615+
let aad = b"detached-tag";
616+
617+
let sealing_key = make_less_safe_key(algorithm, key_bytes);
618+
let opening_key = make_less_safe_key(algorithm, key_bytes);
619+
let mut in_out = plaintext.to_vec();
620+
621+
let tag = sealing_key
622+
.seal_in_place_separate_tag(
623+
Nonce::try_assume_unique_for_key(&nonce_bytes).unwrap(),
624+
Aad::from(aad.as_slice()),
625+
&mut in_out,
626+
)
627+
.unwrap();
628+
629+
let result = opening_key
630+
.open_in_place_separate_tag(
631+
Nonce::try_assume_unique_for_key(&nonce_bytes).unwrap(),
632+
Aad::from(aad.as_slice()),
633+
tag.as_ref(),
634+
&mut in_out,
635+
)
636+
.unwrap();
637+
638+
assert_eq!(plaintext, result);
639+
}
640+
641+
#[test]
642+
fn test_less_safe_key_open_in_place_separate_tag() {
643+
let plaintext = b"open detached tags in place";
644+
645+
test_open_in_place_separate_tag_for(&aead::AES_128_GCM, &[0x42; 16], plaintext);
646+
test_open_in_place_separate_tag_for(&aead::AES_256_GCM, &[0x42; 32], plaintext);
647+
test_open_in_place_separate_tag_for(&aead::CHACHA20_POLY1305, &[0x42; 32], plaintext);
648+
}
649+
650+
#[test]
651+
fn test_less_safe_key_open_in_place_separate_tag_empty_plaintext() {
652+
test_open_in_place_separate_tag_for(&aead::AES_128_GCM, &[0x42; 16], b"");
653+
test_open_in_place_separate_tag_for(&aead::AES_256_GCM, &[0x42; 32], b"");
654+
test_open_in_place_separate_tag_for(&aead::CHACHA20_POLY1305, &[0x42; 32], b"");
655+
}
656+
657+
#[test]
658+
fn test_less_safe_key_open_in_place_separate_tag_wrong_tag() {
659+
let key_bytes = [0x42; 16];
660+
let nonce_bytes = [0x24; NONCE_LEN];
661+
let aad = b"detached-tag";
662+
let plaintext = b"open detached tags in place";
663+
664+
let sealing_key = make_less_safe_key(&aead::AES_128_GCM, &key_bytes);
665+
let opening_key = make_less_safe_key(&aead::AES_128_GCM, &key_bytes);
666+
let mut in_out = plaintext.to_vec();
667+
668+
let tag = sealing_key
669+
.seal_in_place_separate_tag(
670+
Nonce::try_assume_unique_for_key(&nonce_bytes).unwrap(),
671+
Aad::from(aad.as_slice()),
672+
&mut in_out,
673+
)
674+
.unwrap();
675+
676+
let mut bad_tag = tag.as_ref().to_vec();
677+
bad_tag[0] ^= 0xff;
678+
679+
let result = opening_key.open_in_place_separate_tag(
680+
Nonce::try_assume_unique_for_key(&nonce_bytes).unwrap(),
681+
Aad::from(aad.as_slice()),
682+
&bad_tag,
683+
&mut in_out,
684+
);
685+
686+
assert!(result.is_err());
687+
}
688+
689+
#[test]
690+
fn test_less_safe_key_open_in_place_separate_tag_wrong_aad() {
691+
let key_bytes = [0x42; 16];
692+
let nonce_bytes = [0x24; NONCE_LEN];
693+
let aad = b"detached-tag";
694+
let plaintext = b"open detached tags in place";
695+
696+
let sealing_key = make_less_safe_key(&aead::AES_128_GCM, &key_bytes);
697+
let opening_key = make_less_safe_key(&aead::AES_128_GCM, &key_bytes);
698+
let mut in_out = plaintext.to_vec();
699+
700+
let tag = sealing_key
701+
.seal_in_place_separate_tag(
702+
Nonce::try_assume_unique_for_key(&nonce_bytes).unwrap(),
703+
Aad::from(aad.as_slice()),
704+
&mut in_out,
705+
)
706+
.unwrap();
707+
708+
let result = opening_key.open_in_place_separate_tag(
709+
Nonce::try_assume_unique_for_key(&nonce_bytes).unwrap(),
710+
Aad::from(b"wrong-aad".as_slice()),
711+
tag.as_ref(),
712+
&mut in_out,
713+
);
714+
715+
assert!(result.is_err());
716+
}
717+
609718
#[test]
610719
fn test_aead_key_debug() {
611720
let key_bytes = [0; 32];

0 commit comments

Comments
 (0)