diff --git a/.github/workflows/aes-gcm.yml b/.github/workflows/aes-gcm.yml index 089365c..b88d1b0 100644 --- a/.github/workflows/aes-gcm.yml +++ b/.github/workflows/aes-gcm.yml @@ -33,7 +33,7 @@ jobs: - uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 with: - toolchain: 1.51.0 # MSRV (highest in repo) + toolchain: 1.56.0 # MSRV (highest in repo) components: clippy override: true profile: minimal @@ -62,7 +62,7 @@ jobs: strategy: matrix: rust: - - 1.51.0 # MSRV + - 1.56.0 # MSRV - stable target: - armv7a-none-eabi @@ -85,7 +85,7 @@ jobs: include: # 32-bit Linux - target: i686-unknown-linux-gnu - rust: 1.51.0 # MSRV + rust: 1.56.0 # MSRV deps: sudo apt update && sudo apt install gcc-multilib - target: i686-unknown-linux-gnu rust: stable @@ -93,7 +93,7 @@ jobs: # 64-bit Linux - target: x86_64-unknown-linux-gnu - rust: 1.51.0 # MSRV + rust: 1.56.0 # MSRV - target: x86_64-unknown-linux-gnu rust: stable steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c15c0c..004e828 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,28 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.10.1 (2022-07-31) +### Fixed +- rustdoc typos and formatting ([#461], [#462]) + +[#461]: https://github.com/RustCrypto/AEADs/pull/461 +[#462]: https://github.com/RustCrypto/AEADs/pull/462 + +## 0.10.0 (2022-07-31) +### Added +- `getrandom` feature ([#446]) + +### Changed +- Bump `aes` dependency to v0.8 ([#430]) +- Rust 2021 edition upgrade; MSRV 1.56+ ([#435]) +- Bump `aead` dependency to v0.5 ([#444]) +- Bump `ghash` dependency to v0.5 ([#454]) + +[#435]: https://github.com/RustCrypto/AEADs/pull/435 +[#444]: https://github.com/RustCrypto/AEADs/pull/444 +[#446]: https://github.com/RustCrypto/AEADs/pull/446 +[#454]: https://github.com/RustCrypto/AEADs/pull/454 + ## 0.9.4 (2021-08-28) ### Changed - Relax `subtle` and `zeroize` requirements ([#360]) diff --git a/Cargo.toml b/Cargo.toml index 3416a04..30ca4ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mc-oblivious-aes-gcm" -version = "0.9.4" +version = "0.10.1" description = """ WARNING: This crate is not intended for general use, you should use the official RustCrypto crate instead. @@ -10,33 +10,32 @@ authors = [ "MobileCoin", "RustCrypto Developers" ] -edition = "2018" +edition = "2021" license = "Apache-2.0 OR MIT" readme = "README.md" documentation = "https://docs.rs/mc-oblivious-aes-gcm" repository = "https://github.com/mobilecoinfoundation/oblivious-aes-gcm" [dependencies] -aead = { version = "0.4", default-features = false } -aes = { version = "0.7.5", optional = true } -cipher = "0.3" -ctr = "0.8" -ghash = { version = "0.4.2", default-features = false } -subtle = { version = ">=2, <2.5", default-features = false } -zeroize = { version = ">=1, <1.4", optional = true, default-features = false } +aead = { version = "0.5", default-features = false } +aes = { version = "0.8", optional = true } +cipher = "0.4" +ctr = "0.9" +ghash = { version = "0.5", default-features = false } +subtle = { version = "2", default-features = false } +zeroize = { version = "1", optional = true, default-features = false } [dev-dependencies] -aead = { version = "0.4", features = ["dev"], default-features = false } +aead = { version = "0.5", features = ["dev"], default-features = false } hex-literal = "0.3" [features] -default = ["aes", "alloc"] -std = ["aead/std", "alloc"] -alloc = ["aead/alloc"] -armv8 = ["aes/armv8", "ghash/armv8"] # nightly-only -force-soft = ["aes/force-soft", "ghash/force-soft"] -heapless = ["aead/heapless"] -stream = ["aead/stream"] +default = ["aes", "alloc", "getrandom"] +std = ["aead/std", "alloc"] +alloc = ["aead/alloc"] +getrandom = ["aead/getrandom"] +heapless = ["aead/heapless"] +stream = ["aead/stream"] [package.metadata.docs.rs] all-features = true diff --git a/src/ct.rs b/src/ct.rs index d80f222..243bcc1 100644 --- a/src/ct.rs +++ b/src/ct.rs @@ -5,7 +5,7 @@ use aead::AeadInPlace; use cipher::{ consts::U16, generic_array::{ArrayLength, GenericArray}, - Block, BlockCipher, BlockEncrypt, StreamCipher, + BlockCipher, BlockEncrypt, BlockSizeUser, StreamCipherCore, }; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; use zeroize::Zeroize; @@ -13,8 +13,6 @@ use zeroize::Zeroize; #[cfg(all(feature = "alloc", not(feature = "std")))] use alloc::vec::Vec; -use aes::NewBlockCipher; - /// API for Aead in-place decryption which is constant-time with respect to /// the mac check failing /// @@ -74,8 +72,7 @@ impl From for bool { impl CtAeadDecrypt for AesGcm where - Aes: BlockCipher + BlockEncrypt + NewBlockCipher, - Aes::ParBlocks: ArrayLength>, + Aes: BlockCipher + BlockSizeUser + BlockEncrypt, NonceSize: ArrayLength, { /// A constant time version of the original @@ -93,17 +90,16 @@ where return CtDecryptResult(Choice::from(0)); } + let (ctr, mask) = self.init_ctr(nonce); + // TODO(tarcieri): interleave encryption with GHASH // See: - let mut expected_tag = self.compute_tag(associated_data, buffer); - let mut ctr = self.init_ctr(nonce); + let expected_tag = self.compute_tag(mask, associated_data, buffer); let mut ciphertext = Vec::with_capacity(len); ciphertext.extend_from_slice(buffer); + ctr.apply_keystream_partial(ciphertext.as_mut_slice().into()); - ctr.apply_keystream(expected_tag.as_mut_slice()); - ctr.apply_keystream(&mut ciphertext); - - let result = expected_tag.ct_eq(&tag); + let result = expected_tag.ct_eq(tag); // Conditionally copy the actual plaintext _only_ if the tag verified // correctly, in order to increase misuse resistance and reduce attack diff --git a/src/lib.rs b/src/lib.rs index ea9df2c..9cb7530 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,22 +43,22 @@ //! //! Simple usage (allocating, no associated data): //! -//! ``` -//! use mc_oblivious_aes_gcm::{Aes256Gcm, Key, Nonce}; // Or `Aes128Gcm` -//! use mc_oblivious_aes_gcm::aead::{Aead, NewAead}; -//! -//! let key = Key::from_slice(b"an example very very secret key."); -//! let cipher = Aes256Gcm::new(key); -//! +#![cfg_attr(all(feature = "getrandom", feature = "std"), doc = "```")] +#![cfg_attr(not(all(feature = "getrandom", feature = "std")), doc = "```ignore")] +//! # fn main() -> Result<(), Box> { +//! use mc_oblivious_aes_gcm::{ +//! aead::{Aead, KeyInit, OsRng}, +//! Aes256Gcm, Nonce // Or `Aes128Gcm` +//! }; +//! +//! let key = Aes256Gcm::generate_key(&mut OsRng); +//! let cipher = Aes256Gcm::new(&key); //! let nonce = Nonce::from_slice(b"unique nonce"); // 96-bits; unique per message -//! -//! let ciphertext = cipher.encrypt(nonce, b"plaintext message".as_ref()) -//! .expect("encryption failure!"); // NOTE: handle this error to avoid panics! -//! -//! let plaintext = cipher.decrypt(nonce, ciphertext.as_ref()) -//! .expect("decryption failure!"); // NOTE: handle this error to avoid panics! -//! +//! let ciphertext = cipher.encrypt(nonce, b"plaintext message".as_ref())?; +//! let plaintext = cipher.decrypt(nonce, ciphertext.as_ref())?; //! assert_eq!(&plaintext, b"plaintext message"); +//! # Ok(()) +//! # } //! ``` //! //! ## In-place Usage (eliminates `alloc` requirement) @@ -76,30 +76,37 @@ //! which can then be passed as the `buffer` parameter to the in-place encrypt //! and decrypt methods: //! -//! ``` -//! # #[cfg(feature = "heapless")] -//! # { -//! use mc_oblivious_aes_gcm::{Aes256Gcm, Key, Nonce}; // Or `Aes128Gcm` -//! use mc_oblivious_aes_gcm::aead::{AeadInPlace, NewAead}; -//! use mc_oblivious_aes_gcm::aead::heapless::Vec; -//! -//! let key = Key::from_slice(b"an example very very secret key."); -//! let cipher = Aes256Gcm::new(key); -//! +#![cfg_attr( + all(feature = "getrandom", feature = "heapless", feature = "std"), + doc = "```" +)] +#![cfg_attr( + not(all(feature = "getrandom", feature = "heapless", feature = "std")), + doc = "```ignore" +)] +//! # fn main() -> Result<(), Box> { +//! use mc_oblivious_aes_gcm::{ +//! aead::{AeadInPlace, KeyInit, OsRng, heapless::Vec}, +//! Aes256Gcm, Nonce, // Or `Aes128Gcm` +//! }; +//! +//! let key = Aes256Gcm::generate_key(&mut OsRng); +//! let cipher = Aes256Gcm::new(&key); //! let nonce = Nonce::from_slice(b"unique nonce"); // 96-bits; unique per message //! -//! let mut buffer: Vec = Vec::new(); // Buffer needs 16-bytes overhead for GCM tag +//! let mut buffer: Vec = Vec::new(); // Note: buffer needs 16-bytes overhead for auth tag tag //! buffer.extend_from_slice(b"plaintext message"); //! //! // Encrypt `buffer` in-place, replacing the plaintext contents with ciphertext -//! cipher.encrypt_in_place(nonce, b"", &mut buffer).expect("encryption failure!"); +//! cipher.encrypt_in_place(nonce, b"", &mut buffer)?; //! //! // `buffer` now contains the message ciphertext //! assert_ne!(&buffer, b"plaintext message"); //! //! // Decrypt `buffer` in-place, replacing its ciphertext context with the original plaintext -//! cipher.decrypt_in_place(nonce, b"", &mut buffer).expect("decryption failure!"); +//! cipher.decrypt_in_place(nonce, b"", &mut buffer)?; //! assert_eq!(&buffer, b"plaintext message"); +//! # Ok(()) //! # } //! ``` //! @@ -124,24 +131,18 @@ mod ct; #[cfg(all(feature = "zeroize", any(feature = "alloc", feature = "std")))] pub use crate::ct::{CtAeadDecrypt, CtDecryptResult}; - -pub use aead::{self, AeadCore, AeadInPlace, Error, NewAead}; +pub use aead::{self, AeadCore, AeadInPlace, Error, Key, KeyInit, KeySizeUser}; #[cfg(feature = "aes")] pub use aes; use cipher::{ consts::{U0, U16}, - generic_array::{typenum::Unsigned, ArrayLength, GenericArray}, - Block, BlockCipher, BlockCipherKey, BlockEncrypt, FromBlockCipher, NewBlockCipher, - StreamCipher, StreamCipherSeek, + generic_array::{ArrayLength, GenericArray}, + BlockCipher, BlockEncrypt, BlockSizeUser, InnerIvInit, StreamCipherCore, }; use core::marker::PhantomData; -use ctr::Ctr32BE; -use ghash::{ - universal_hash::{NewUniversalHash, UniversalHash}, - GHash, -}; +use ghash::{universal_hash::UniversalHash, GHash}; #[cfg(feature = "zeroize")] use zeroize::Zeroize; @@ -149,34 +150,37 @@ use zeroize::Zeroize; #[cfg(feature = "aes")] use aes::{cipher::consts::U12, Aes128, Aes256}; -/// Maximum length of associated data +/// Maximum length of associated data. pub const A_MAX: u64 = 1 << 36; -/// Maximum length of plaintext +/// Maximum length of plaintext. pub const P_MAX: u64 = 1 << 36; -/// Maximum length of ciphertext +/// Maximum length of ciphertext. pub const C_MAX: u64 = (1 << 36) + 16; -/// AES-GCM keys -pub type Key = GenericArray; - -/// AES-GCM nonces +/// AES-GCM nonces. pub type Nonce = GenericArray; -/// AES-GCM tags +/// AES-GCM tags. pub type Tag = GenericArray; -/// AES-GCM with a 128-bit key and 96-bit nonce +/// AES-GCM with a 128-bit key and 96-bit nonce. #[cfg(feature = "aes")] #[cfg_attr(docsrs, doc(cfg(feature = "aes")))] pub type Aes128Gcm = AesGcm; -/// AES-GCM with a 256-bit key and 96-bit nonce +/// AES-GCM with a 256-bit key and 96-bit nonce. #[cfg(feature = "aes")] #[cfg_attr(docsrs, doc(cfg(feature = "aes")))] pub type Aes256Gcm = AesGcm; +/// AES block. +type Block = GenericArray; + +/// Counter mode with a 32-bit big endian counter. +type Ctr32BE = ctr::CtrCore; + /// AES-GCM: generic over an underlying AES implementation and nonce size. /// /// This type is generic to support substituting alternative AES implementations @@ -191,40 +195,36 @@ pub type Aes256Gcm = AesGcm; /// /// If in doubt, use the built-in [`Aes128Gcm`] and [`Aes256Gcm`] type aliases. #[derive(Clone)] -pub struct AesGcm -where - Aes: BlockCipher + BlockEncrypt, - Aes::ParBlocks: ArrayLength>, - NonceSize: ArrayLength, -{ - /// Encryption cipher +pub struct AesGcm { + /// Encryption cipher. cipher: Aes, - /// GHASH authenticator + /// GHASH authenticator. ghash: GHash, - /// Length of the nonce + /// Length of the nonce. nonce_size: PhantomData, } -impl NewAead for AesGcm +impl KeySizeUser for AesGcm where - Aes: NewBlockCipher + BlockCipher + BlockEncrypt, - Aes::ParBlocks: ArrayLength>, - NonceSize: ArrayLength, + Aes: KeySizeUser, { type KeySize = Aes::KeySize; +} - fn new(key: &BlockCipherKey) -> Self { +impl KeyInit for AesGcm +where + Aes: BlockSizeUser + BlockEncrypt + KeyInit, +{ + fn new(key: &Key) -> Self { Aes::new(key).into() } } impl From for AesGcm where - Aes: NewBlockCipher + BlockCipher + BlockEncrypt, - Aes::ParBlocks: ArrayLength>, - NonceSize: ArrayLength, + Aes: BlockSizeUser + BlockEncrypt, { fn from(cipher: Aes) -> Self { let mut ghash_key = ghash::Key::default(); @@ -245,8 +245,6 @@ where impl AeadCore for AesGcm where - Aes: NewBlockCipher + BlockCipher + BlockEncrypt, - Aes::ParBlocks: ArrayLength>, NonceSize: ArrayLength, { type NonceSize = NonceSize; @@ -256,13 +254,12 @@ where impl AeadInPlace for AesGcm where - Aes: NewBlockCipher + BlockCipher + BlockEncrypt, - Aes::ParBlocks: ArrayLength>, + Aes: BlockCipher + BlockSizeUser + BlockEncrypt, NonceSize: ArrayLength, { fn encrypt_in_place_detached( &self, - nonce: &Nonce, + nonce: &Nonce, associated_data: &[u8], buffer: &mut [u8], ) -> Result { @@ -270,22 +267,17 @@ where return Err(Error); } + let (ctr, mask) = self.init_ctr(nonce); + // TODO(tarcieri): interleave encryption with GHASH // See: - let mut ctr = self.init_ctr(nonce); - ctr.seek(Aes::BlockSize::to_usize()); - ctr.apply_keystream(buffer); - - let mut tag = self.compute_tag(associated_data, buffer); - ctr.seek(0); - ctr.apply_keystream(tag.as_mut_slice()); - - Ok(tag) + ctr.apply_keystream_partial(buffer.into()); + Ok(self.compute_tag(mask, associated_data, buffer)) } fn decrypt_in_place_detached( &self, - nonce: &Nonce, + nonce: &Nonce, associated_data: &[u8], buffer: &mut [u8], tag: &Tag, @@ -294,15 +286,15 @@ where return Err(Error); } + let (ctr, mask) = self.init_ctr(nonce); + // TODO(tarcieri): interleave encryption with GHASH // See: - let mut expected_tag = self.compute_tag(associated_data, buffer); - let mut ctr = self.init_ctr(nonce); - ctr.apply_keystream(expected_tag.as_mut_slice()); + let expected_tag = self.compute_tag(mask, associated_data, buffer); + ctr.apply_keystream_partial(buffer.into()); use subtle::ConstantTimeEq; - if expected_tag.ct_eq(&tag).unwrap_u8() == 1 { - ctr.apply_keystream(buffer); + if expected_tag.ct_eq(tag).into() { Ok(()) } else { Err(Error) @@ -312,8 +304,7 @@ where impl AesGcm where - Aes: NewBlockCipher + BlockCipher + BlockEncrypt, - Aes::ParBlocks: ArrayLength>, + Aes: BlockCipher + BlockSizeUser + BlockEncrypt, NonceSize: ArrayLength, { /// Initialize counter mode. @@ -325,7 +316,7 @@ where /// > If len(IV)=96, then J0 = IV || 0{31} || 1. /// > If len(IV) ≠ 96, then let s = 128 ⎡len(IV)/128⎤-len(IV), and /// > J0=GHASH(IV||0s+64||[len(IV)]64). - fn init_ctr(&self, nonce: &Nonce) -> Ctr32BE<&Aes> { + fn init_ctr(&self, nonce: &Nonce) -> (Ctr32BE<&Aes>, Block) { let j0 = if NonceSize::to_usize() == 12 { let mut block = ghash::Block::default(); block[..12].copy_from_slice(nonce); @@ -338,15 +329,18 @@ where let mut block = ghash::Block::default(); let nonce_bits = (NonceSize::to_usize() as u64) * 8; block[8..].copy_from_slice(&nonce_bits.to_be_bytes()); - ghash.update(&block); - ghash.finalize().into_bytes() + ghash.update(&[block]); + ghash.finalize() }; - Ctr32BE::from_block_cipher(&self.cipher, &j0) + let mut ctr = Ctr32BE::inner_iv_init(&self.cipher, &j0); + let mut tag_mask = Block::default(); + ctr.write_keystream_block(&mut tag_mask); + (ctr, tag_mask) } - /// Authenticate the given plaintext and associated data using GHASH - fn compute_tag(&self, associated_data: &[u8], buffer: &[u8]) -> Tag { + /// Authenticate the given plaintext and associated data using GHASH. + fn compute_tag(&self, mask: Block, associated_data: &[u8], buffer: &[u8]) -> Tag { let mut ghash = self.ghash.clone(); ghash.update_padded(associated_data); ghash.update_padded(buffer); @@ -357,7 +351,13 @@ where let mut block = ghash::Block::default(); block[..8].copy_from_slice(&associated_data_bits.to_be_bytes()); block[8..].copy_from_slice(&buffer_bits.to_be_bytes()); - ghash.update(&block); - ghash.finalize().into_bytes() + ghash.update(&[block]); + + let mut tag = ghash.finalize(); + for (a, b) in tag.as_mut_slice().iter_mut().zip(mask.as_slice()) { + *a ^= *b; + } + + tag } } diff --git a/tests/aes128gcm.rs b/tests/aes128gcm.rs index 3eacd95..e44e0ec 100644 --- a/tests/aes128gcm.rs +++ b/tests/aes128gcm.rs @@ -1,17 +1,16 @@ -//! AES-128-GCM tests - -#[macro_use] -extern crate hex_literal; +//! AES-128-auth tag tests #[macro_use] mod common; use self::common::TestVector; use mc_oblivious_aes_gcm::{ - aead::{generic_array::GenericArray, Aead, NewAead, Payload}, + aead::{generic_array::GenericArray, Aead, KeyInit, Payload}, Aes128Gcm, }; +use hex_literal::hex; + /// NIST CAVS vectors /// /// diff --git a/tests/aes256gcm.rs b/tests/aes256gcm.rs index 468978d..b44939e 100644 --- a/tests/aes256gcm.rs +++ b/tests/aes256gcm.rs @@ -1,17 +1,16 @@ -//! AES-256-GCM tests - -#[macro_use] -extern crate hex_literal; +//! AES-256-auth tag tests #[macro_use] mod common; use self::common::TestVector; use mc_oblivious_aes_gcm::{ - aead::{generic_array::GenericArray, Aead, NewAead, Payload}, + aead::{generic_array::GenericArray, Aead, KeyInit, Payload}, Aes256Gcm, }; +use hex_literal::hex; + /// NIST CAVS vectors /// /// diff --git a/tests/other_ivlen.rs b/tests/other_ivlen.rs index beb82f7..226d942 100644 --- a/tests/other_ivlen.rs +++ b/tests/other_ivlen.rs @@ -1,19 +1,15 @@ -//! Tests for AES-GCM when used with non-96-bit IVs. +//! Tests for AES-GCM when used with non-96-bit nonces. //! -//! Vectors taken from NIST CAVS vectors' `gcmEncryptExtIV128.rsp` file -/// +//! Vectors taken from NIST CAVS vectors' `gcmEncryptExtIV128.rsp` file: +//! -#[macro_use] -extern crate hex_literal; - -use mc_oblivious_aes_gcm::{ - aead::{ - generic_array::{typenum, GenericArray}, - Aead, NewAead, - }, - aes::Aes128, - AesGcm, +use aead::{ + generic_array::{typenum, GenericArray}, + Aead, KeyInit, }; +use aes::Aes128; +use hex_literal::hex; +use mc_oblivious_aes_gcm::AesGcm; /// Based on the following `gcmEncryptExtIV128.rsp` test vector: ///