diff --git a/aws-lc-rs/src/aead.rs b/aws-lc-rs/src/aead.rs index 1667b5d4bc2..1c0e743bab0 100644 --- a/aws-lc-rs/src/aead.rs +++ b/aws-lc-rs/src/aead.rs @@ -55,7 +55,7 @@ //! # Nonce Sequence APIs //! //! The [`UnboundKey`], [`OpeningKey`], [`SealingKey`], and [`LessSafeKey`] types are the -//! AEAD API's provided for compatability with the original *ring* API. +//! AEAD API's provided for compatibility with the original *ring* API. //! //! Users should prefer [`RandomizedNonceKey`] which provides a simplified experience around //! Nonce construction. @@ -113,10 +113,77 @@ //! //! assert_eq!(plaintext, decrypted_plaintext); //! ``` +//! +//! ## Prepared Nonce API's with Nonce Sequence +//! +//! If you prefer to use the [NonceSequence] based API's, and need to know the [Nonce] explicit nonce used for a +//! cryptographic key operation operation, then [SealingKeyPreparedNonce] and +//! [OpeningKeyPreparedNonce] are available to you. +//! +//! ```rust +//! # use std::error::Error; +//! # +//! # fn main() -> Result<(), Box> { +//! use aws_lc_rs::aead::{ +//! nonce_sequence::Counter32Builder, Aad, BoundKey, OpeningKey, SealingKey, UnboundKey, +//! AES_128_GCM, +//! }; +//! use std::vec::Vec; +//! +//! let key_bytes = &[ +//! 0xa5, 0xf3, 0x8d, 0x0d, 0x2d, 0x7c, 0x48, 0x56, 0xe7, 0xf3, 0xc3, 0x63, 0x0d, 0x40, 0x5b, +//! 0x9e, +//! ]; +//! +//! // Create AES-128-GCM SealingKey +//! let mut sealing_key = SealingKey::new( +//! UnboundKey::new(&AES_128_GCM, key_bytes)?, +//! Counter32Builder::new() +//! .identifier([0, 1, 2, 3, 4, 5, 6, 7]) +//! .build(), +//! ); +//! +//! // Create AES-128-GCM OpeningKey +//! let mut opening_key = OpeningKey::new( +//! UnboundKey::new(&AES_128_GCM, key_bytes)?, +//! Counter32Builder::new() +//! .identifier([0, 1, 2, 3, 4, 5, 6, 7]) +//! .build(), +//! ); +//! +//! let message = "test message"; +//! let mut in_out = Vec::from(message); +//! +//! // Create a SealingKeyPreparedNonce which consumes a nonce from the underlying sequence +//! let seal_prepared_nonce = sealing_key.prepare_nonce()?; +//! +//! // Query the nonce that will be used for our seal operation with our prepared nonce +//! let seal_nonce_bytes = Vec::from(seal_prepared_nonce.nonce().as_ref()); +//! +//! // Use the prepared nonce and seal the plaintext +//! seal_prepared_nonce.seal_in_place_append_tag(Aad::empty(), &mut in_out)?; +//! +//! // Create a OpeningKeyPreparedNonce which consumes a nonce from the underlying sequence +//! let open_prepared_nonce = opening_key.prepare_nonce()?; +//! +//! // Query the nonce that will be used for our seal operation with our prepared nonce +//! let open_nonce_bytes = Vec::from(open_prepared_nonce.nonce().as_ref()); +//! +//! // Since we initialized the Counter32Builder the same between both builders the nonce here +//! // will match the one from the opening key. +//! assert_eq!(seal_nonce_bytes.as_slice(), open_nonce_bytes.as_slice()); +//! +//! let plaintext = open_prepared_nonce.open_in_place(Aad::empty(), &mut in_out)?; +//! +//! assert_eq!(message.as_bytes(), plaintext); +//! # Ok(()) +//! # } +//! ``` use crate::{derive_debug_via_id, error::Unspecified, hkdf}; use aead_ctx::AeadCtx; -use core::{fmt::Debug, ops::RangeFrom}; +use core::{fmt::Debug, ops::RangeFrom, stringify}; +use paste::paste; mod aead_ctx; mod aes_gcm; @@ -214,7 +281,6 @@ impl OpeningKey { /// plaintext without the tag. /// /// Prefer [`RandomizedNonceKey::open_in_place`]. - /// // # FIPS // Use this method with one of the following algorithms: // * `AES_128_GCM` @@ -280,7 +346,6 @@ impl OpeningKey { /// This reassembly be accomplished with three calls to `open_within()`. /// /// Prefer [`RandomizedNonceKey::open_in_place`]. - /// // # FIPS // Use this method with one of the following algorithms: // * `AES_128_GCM` @@ -307,6 +372,21 @@ impl OpeningKey { ciphertext_and_tag, ) } + + /// Returns a `OpeningKeyPreparedNonce` containing the next computed `Nonce` consumed from `NonceSequence`. + /// + /// The encapsulated Nonce will be used **if and only if** either + /// [OpeningKeyPreparedNonce::open_in_place] or [OpeningKeyPreparedNonce::open_within] + /// are invoked. Dropping `OpeningKeyPreparedNonce` without invoking either method results in the nonce remaining + /// consumed and unused within the associated `NonceSequence`. Subsequent calls to [OpeningKey] methods will + /// always use a proceeding nonce from the `NonceSequence` regardless of whether + /// a `OpeningKeyPreparedNonce` is consumed or not. + /// + /// # Errors + /// `Unspecified` if there is a failure computing the nonce for the next operation, i.e. `NonceSequence` exhausted. + pub fn prepare_nonce(&mut self) -> Result, Unspecified> { + OpeningKeyPreparedNonce::new(self) + } } /// An AEAD key for encrypting and signing ("sealing"), bound to a nonce @@ -347,7 +427,6 @@ impl SealingKey { /// Deprecated. Renamed to `seal_in_place_append_tag`. /// /// Prefer [`RandomizedNonceKey::seal_in_place_append_tag`]. - /// // # FIPS // This method must not be used. // @@ -378,7 +457,6 @@ impl SealingKey { /// ``` /// /// Prefer [`RandomizedNonceKey::seal_in_place_append_tag`]. - /// // # FIPS // This method must not be used. // @@ -413,7 +491,6 @@ impl SealingKey { /// The tag will be `self.algorithm.tag_len()` bytes long. /// /// Prefer [`RandomizedNonceKey::seal_in_place_separate_tag`]. - /// // # FIPS // This method must not be used. // @@ -433,6 +510,149 @@ impl SealingKey { .seal_in_place_separate_tag(Some(self.nonce_sequence.advance()?), aad.as_ref(), in_out) .map(|(_, tag)| tag) } + + /// Returns a `SealingKeyPreparedNonce` containing the next computed `Nonce` consumed from `NonceSequence`. + /// + /// The encapsulated Nonce will be used **if and only if** either + /// [SealingKeyPreparedNonce::seal_in_place_append_tag] or [SealingKeyPreparedNonce::seal_in_place_separate_tag] + /// are invoked. Dropping `SealingKeyPreparedNonce` without invoking either method results in the nonce remaining + /// consumed and unused within the associated `NonceSequence`. Subsequent calls to [SealingKey] methods will + /// always use a proceeding nonce from the `NonceSequence` regardless of whether + /// a `SealingKeyPreparedNonce` is consumed or not. + /// + /// # Errors + /// `Unspecified` if there is a failure computing the nonce for the next operation, i.e. `NonceSequence` exhausted. + pub fn prepare_nonce(&mut self) -> Result, Unspecified> { + SealingKeyPreparedNonce::new(self) + } +} + +macro_rules! nonce_seq_key_op_mut { + ($name:ident) => { + paste! { + /// A key operation with a precomputed nonce from a key's associated `NonceSequence`. + pub struct [<$name PreparedNonce>]<'a, N: NonceSequence> { + key: &'a mut $name, + nonce: Nonce, + } + + impl<'a, N: NonceSequence> [<$name PreparedNonce>]<'a, N> { + fn new(key: &'a mut $name) -> Result { + let nonce = key.nonce_sequence.advance()?; + Ok(Self { + key, + nonce, + }) + } + } + + impl [<$name PreparedNonce>]<'_, N> { + /// Returns the prepared Nonce that is used for key methods invoked on [Self]. + #[must_use] + pub fn nonce(&self) -> &Nonce { + &self.nonce + } + } + + impl Debug for [<$name PreparedNonce>]<'_, N> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + f.debug_struct(stringify!([<$name PreparedNonce>])).finish_non_exhaustive() + } + } + } + }; +} + +nonce_seq_key_op_mut!(OpeningKey); +nonce_seq_key_op_mut!(SealingKey); + +impl OpeningKeyPreparedNonce<'_, N> { + /// Authenticates and decrypts (“opens”) data in place. + /// + /// See [OpeningKey::open_in_place] for additional API information. + /// + /// # Errors + /// `error::Unspecified` when ciphertext is invalid. In this case, `in_out` may have been + /// overwritten in an unspecified way. + #[inline] + #[allow(clippy::needless_pass_by_value)] + pub fn open_in_place(self, aad: Aad, in_out: &mut [u8]) -> Result<&mut [u8], Unspecified> + where + A: AsRef<[u8]>, + { + self.open_within(aad, in_out, 0..) + } + + /// Authenticates and decrypts (“opens”) data in place, with a shift. + /// + /// See [OpeningKey::open_within] for additional API information. + /// + /// # Errors + /// `error::Unspecified` when ciphertext is invalid. In this case, `in_out` may have been + /// overwritten in an unspecified way. + #[inline] + #[allow(clippy::needless_pass_by_value)] + pub fn open_within( + self, + aad: Aad, + in_out: &mut [u8], + ciphertext_and_tag: RangeFrom, + ) -> Result<&mut [u8], Unspecified> + where + A: AsRef<[u8]>, + { + self.key + .key + .open_within(self.nonce, aad.as_ref(), in_out, ciphertext_and_tag) + } +} + +impl SealingKeyPreparedNonce<'_, N> { + /// Encrypts and signs (“seals”) data in place, appending the tag to the + /// resulting ciphertext. + /// + /// See [SealingKey::seal_in_place_append_tag] for additional API information. + /// + /// # Errors + /// `error::Unspecified` when `nonce_sequence` cannot be advanced. + #[inline] + #[allow(clippy::needless_pass_by_value)] + pub fn seal_in_place_append_tag( + self, + aad: Aad, + in_out: &mut InOut, + ) -> Result<(), Unspecified> + where + A: AsRef<[u8]>, + InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>, + { + self.key + .key + .seal_in_place_append_tag(Some(self.nonce), aad.as_ref(), in_out) + .map(|_| ()) + } + + /// Encrypts and signs (“seals”) data in place. + /// + /// See [`SealingKey::seal_in_place_separate_tag`] for additional API information. + /// + /// # Errors + /// `error::Unspecified` when `nonce_sequence` cannot be advanced. + #[inline] + #[allow(clippy::needless_pass_by_value)] + pub fn seal_in_place_separate_tag( + self, + aad: Aad, + in_out: &mut [u8], + ) -> Result + where + A: AsRef<[u8]>, + { + self.key + .key + .seal_in_place_separate_tag(Some(self.nonce), aad.as_ref(), in_out) + .map(|(_, tag)| tag) + } } /// The additionally authenticated data (AAD) for an opening or sealing @@ -478,7 +698,6 @@ impl hkdf::KeyType for &'static Algorithm { /// `NonceSequence` cannot reasonably be used. /// /// Prefer [`RandomizedNonceKey`] when practical. -/// // # FIPS // The following conditions must be met: // * `UnboundKey`'s algorithm is one of: @@ -501,7 +720,6 @@ impl LessSafeKey { /// `nonce` must be unique for every use of the key to open data. /// /// Prefer [`RandomizedNonceKey::open_in_place`]. - /// // # FIPS // Use this method with one of the following algorithms: // * `AES_128_GCM` @@ -527,7 +745,6 @@ impl LessSafeKey { /// `nonce` must be unique for every use of the key to open data. /// /// Prefer [`RandomizedNonceKey::open_in_place`]. - /// // # FIPS // Use this method with one of the following algorithms: // * `AES_128_GCM` @@ -563,7 +780,6 @@ impl LessSafeKey { /// # Errors /// `error::Unspecified` when ciphertext is invalid. In this case, `out_plaintext` may /// have been overwritten in an unspecified way. - /// #[inline] #[allow(clippy::needless_pass_by_value)] pub fn open_separate_gather( @@ -584,7 +800,6 @@ impl LessSafeKey { /// Deprecated. Renamed to `seal_in_place_append_tag()`. /// /// Prefer [`RandomizedNonceKey::seal_in_place_append_tag`]. - /// // # FIPS // This method must not be used. // @@ -610,7 +825,6 @@ impl LessSafeKey { /// `nonce` must be unique for every use of the key to seal data. /// /// Prefer [`RandomizedNonceKey::seal_in_place_append_tag`]. - /// // # FIPS // This method must not be used. // @@ -639,7 +853,6 @@ impl LessSafeKey { /// `nonce` must be unique for every use of the key to seal data. /// /// Prefer [`RandomizedNonceKey::seal_in_place_separate_tag`]. - /// // # FIPS // This method must not be used. // @@ -674,7 +887,6 @@ impl LessSafeKey { /// The `extra_out_and_tag` length must be equal to the `extra_len` and `self.algorithm.tag_len()`. /// /// `nonce` must be unique for every use of the key to seal data. - /// // # FIPS // This method must not be used. // @@ -786,6 +998,12 @@ impl AsRef<[u8]> for Tag { } } +impl core::fmt::Debug for Tag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_tuple("Tag").finish() + } +} + #[allow(dead_code)] const MAX_KEY_LEN: usize = 32; @@ -797,6 +1015,8 @@ pub const MAX_TAG_LEN: usize = TAG_LEN; #[cfg(test)] mod tests { + use nonce_sequence::Counter32Builder; + use super::*; use crate::{iv::FixedLength, test::from_hex}; @@ -853,4 +1073,26 @@ mod tests { assert_eq!(plaintext, in_out[..plaintext.len()]); } + + #[test] + fn debug_prepared_nonce() { + let mut sk = SealingKey::new( + UnboundKey::new(&AES_128_GCM, &[0u8; 16]).unwrap(), + Counter32Builder::new().build(), + ); + let mut ok = OpeningKey::new( + UnboundKey::new(&AES_128_GCM, &[0u8; 16]).unwrap(), + Counter32Builder::new().build(), + ); + let so = sk.prepare_nonce().unwrap(); + let oo = ok.prepare_nonce().unwrap(); + assert_eq!("SealingKeyPreparedNonce { .. }", format!("{so:?}")); + assert_eq!("OpeningKeyPreparedNonce { .. }", format!("{oo:?}")); + } + + #[test] + fn debug_tag() { + let tag = Tag([0u8; MAX_TAG_LEN], MAX_TAG_LEN); + assert_eq!("Tag", format!("{tag:?}")); + } } diff --git a/aws-lc-rs/tests/aead_test.rs b/aws-lc-rs/tests/aead_test.rs index 3b521dd71b5..1423fff187d 100644 --- a/aws-lc-rs/tests/aead_test.rs +++ b/aws-lc-rs/tests/aead_test.rs @@ -3,9 +3,12 @@ // Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR ISC +use aws_lc_rs::aead::nonce_sequence::Counter32Builder; use aws_lc_rs::{aead, error, test, test_file}; -use aws_lc_rs::aead::{Nonce, NONCE_LEN}; +use aws_lc_rs::aead::{ + Aad, BoundKey, Nonce, OpeningKey, SealingKey, UnboundKey, AES_128_GCM, NONCE_LEN, +}; use core::ops::RangeFrom; #[test] @@ -660,3 +663,90 @@ impl aead::NonceSequence for OneNonceSequence { self.0.take().ok_or(error::Unspecified) } } + +#[test] +fn prepare_nonce() { + const KEY: &[u8] = &[ + 0x52, 0x05, 0x19, 0x7a, 0xcc, 0x88, 0xdb, 0x78, 0x39, 0x59, 0xbc, 0x03, 0xb8, 0x1d, 0x4a, + 0x6c, + ]; + const MESSAGE: &[u8] = &[ + 0x52, 0x61, 0x63, 0x63, 0x6f, 0x6f, 0x6e, 0x20, 0x4d, 0x69, 0x73, 0x63, 0x68, 0x69, 0x65, + 0x66, + ]; + const LIMIT: u32 = 10; + + let mut sk = SealingKey::new( + UnboundKey::new(&AES_128_GCM, KEY).unwrap(), + Counter32Builder::new().limit(LIMIT).build(), + ); + let mut ok = OpeningKey::new( + UnboundKey::new(&AES_128_GCM, KEY).unwrap(), + Counter32Builder::new().limit(LIMIT).build(), + ); + + let mut nonces: Vec> = vec![]; + + for _ in 0..(LIMIT / 2) { + let skpn = sk.prepare_nonce().unwrap(); + let okpn = ok.prepare_nonce().unwrap(); + let so_nonce = Vec::from(skpn.nonce().as_ref()); + let oo_nonce = Vec::from(okpn.nonce().as_ref()); + + assert_eq!(so_nonce.as_slice(), oo_nonce.as_slice()); + assert!(!nonces.contains(&so_nonce)); + nonces.push(so_nonce); + nonces.push(oo_nonce); + + let mut message: Vec = vec![]; + message.extend_from_slice(MESSAGE); + + skpn.seal_in_place_append_tag(Aad::empty(), &mut message) + .unwrap(); + assert_ne!(MESSAGE, message.as_slice()); + + let message = okpn.open_in_place(Aad::empty(), &mut message).unwrap(); + assert_eq!(MESSAGE, message); + + let so = sk.prepare_nonce().unwrap(); + let oo = ok.prepare_nonce().unwrap(); + let so_nonce = Vec::from(so.nonce().as_ref()); + let oo_nonce = Vec::from(oo.nonce().as_ref()); + + assert_eq!(so_nonce.as_slice(), oo_nonce.as_slice()); + assert!(!nonces.contains(&so_nonce)); + nonces.push(so_nonce); + nonces.push(oo_nonce); + + let mut message: Vec = vec![]; + message.extend_from_slice(MESSAGE); + + let tag = so + .seal_in_place_separate_tag(Aad::empty(), &mut message) + .unwrap(); + assert_ne!(MESSAGE, message.as_slice()); + message.extend_from_slice(tag.as_ref()); + + let message = oo.open_within(Aad::empty(), &mut message, 0..).unwrap(); + assert_eq!(MESSAGE, message); + } + + let nonce_chunks = nonces.chunks_exact(2); + assert_eq!(0, nonce_chunks.remainder().len()); + for chunk in nonce_chunks { + assert_eq!(chunk[0].as_slice(), chunk[1].as_slice()); + } + + let mut message: Vec = vec![]; + message.extend_from_slice(MESSAGE); + + // Subsequent usage should fail now since the sequence is exhausted in each key. + sk.prepare_nonce().expect_err("sequence limit reached"); + ok.prepare_nonce().expect_err("sequence limit reached"); + sk.seal_in_place_append_tag(Aad::empty(), &mut message) + .expect_err("sequence limit reached"); + sk.seal_in_place_separate_tag(Aad::empty(), &mut message) + .expect_err("sequence limit reached"); + ok.open_in_place(Aad::empty(), &mut message) + .expect_err("sequence limit reached"); +}