mc_oblivious_aes_gcm/lib.rs
1//!
2//! # WARNING
3//!
4//! You should use the [`aes-gcm`](https://github.com/RustCrypto/AEADs) crate,
5//! not this one. This crate is a patch/fork of the execellent RustCrypto crate
6//! to support a very, very niche use-case for MobileCoin, and as such it's
7//! maintenance and security are necessarily going to lag behind that of
8//! RustCrypto's crate.
9//!
10//! # Original README
11//!
12//! AES-GCM: [Authenticated Encryption and Associated Data (AEAD)][1] cipher
13//! based on AES in [Galois/Counter Mode][2].
14//!
15//! ## Performance Notes
16//!
17//! By default this crate will use software implementations of both AES and
18//! the POLYVAL universal hash function.
19//!
20//! When targeting modern x86/x86_64 CPUs, use the following `RUSTFLAGS` to
21//! take advantage of high performance AES-NI and CLMUL CPU intrinsics:
22//!
23//! ```text
24//! RUSTFLAGS="-Ctarget-cpu=sandybridge -Ctarget-feature=+aes,+sse2,+sse4.1,+ssse3"
25//! ```
26//!
27//! ## Security Notes
28//!
29//! This crate has received one [security audit by NCC Group][3], with no
30//! significant findings. We would like to thank [MobileCoin][4] for funding the
31//! audit.
32//!
33//! All implementations contained in the crate are designed to execute in
34//! constant time, either by relying on hardware intrinsics (i.e. AES-NI and
35//! CLMUL on x86/x86_64), or using a portable implementation which is only
36//! constant time on processors which implement constant-time multiplication.
37//!
38//! It is not suitable for use on processors with a variable-time multiplication
39//! operation (e.g. short circuit on multiply-by-zero / multiply-by-one, such as
40//! certain 32-bit PowerPC CPUs and some non-ARM microcontrollers).
41//!
42//! # Usage
43//!
44//! Simple usage (allocating, no associated data):
45//!
46#![cfg_attr(all(feature = "getrandom", feature = "std"), doc = "```")]
47#![cfg_attr(not(all(feature = "getrandom", feature = "std")), doc = "```ignore")]
48//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
49//! use mc_oblivious_aes_gcm::{
50//!     aead::{Aead, KeyInit, OsRng},
51//!     Aes256Gcm, Nonce // Or `Aes128Gcm`
52//! };
53//!
54//! let key = Aes256Gcm::generate_key(&mut OsRng);
55//! let cipher = Aes256Gcm::new(&key);
56//! let nonce = Nonce::from_slice(b"unique nonce"); // 96-bits; unique per message
57//! let ciphertext = cipher.encrypt(nonce, b"plaintext message".as_ref())?;
58//! let plaintext = cipher.decrypt(nonce, ciphertext.as_ref())?;
59//! assert_eq!(&plaintext, b"plaintext message");
60//! # Ok(())
61//! # }
62//! ```
63//!
64//! ## In-place Usage (eliminates `alloc` requirement)
65//!
66//! This crate has an optional `alloc` feature which can be disabled in e.g.
67//! microcontroller environments that don't have a heap.
68//!
69//! The [`AeadInPlace::encrypt_in_place`] and [`AeadInPlace::decrypt_in_place`]
70//! methods accept any type that impls the [`aead::Buffer`] trait which
71//! contains the plaintext for encryption or ciphertext for decryption.
72//!
73//! Note that if you enable the `heapless` feature of this crate,
74//! you will receive an impl of [`aead::Buffer`] for `heapless::Vec`
75//! (re-exported from the [`aead`] crate as [`aead::heapless::Vec`]),
76//! which can then be passed as the `buffer` parameter to the in-place encrypt
77//! and decrypt methods:
78//!
79#![cfg_attr(
80    all(feature = "getrandom", feature = "heapless", feature = "std"),
81    doc = "```"
82)]
83#![cfg_attr(
84    not(all(feature = "getrandom", feature = "heapless", feature = "std")),
85    doc = "```ignore"
86)]
87//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
88//! use mc_oblivious_aes_gcm::{
89//!     aead::{AeadInPlace, KeyInit, OsRng, heapless::Vec},
90//!     Aes256Gcm, Nonce, // Or `Aes128Gcm`
91//! };
92//!
93//! let key = Aes256Gcm::generate_key(&mut OsRng);
94//! let cipher = Aes256Gcm::new(&key);
95//! let nonce = Nonce::from_slice(b"unique nonce"); // 96-bits; unique per message
96//!
97//! let mut buffer: Vec<u8, 128> = Vec::new(); // Note: buffer needs 16-bytes overhead for auth tag tag
98//! buffer.extend_from_slice(b"plaintext message");
99//!
100//! // Encrypt `buffer` in-place, replacing the plaintext contents with ciphertext
101//! cipher.encrypt_in_place(nonce, b"", &mut buffer)?;
102//!
103//! // `buffer` now contains the message ciphertext
104//! assert_ne!(&buffer, b"plaintext message");
105//!
106//! // Decrypt `buffer` in-place, replacing its ciphertext context with the original plaintext
107//! cipher.decrypt_in_place(nonce, b"", &mut buffer)?;
108//! assert_eq!(&buffer, b"plaintext message");
109//! # Ok(())
110//! # }
111//! ```
112//!
113//! [1]: https://en.wikipedia.org/wiki/Authenticated_encryption
114//! [2]: https://en.wikipedia.org/wiki/Galois/Counter_Mode
115//! [3]: https://research.nccgroup.com/2020/02/26/public-report-rustcrypto-aes-gcm-and-chacha20poly1305-implementation-review/
116//! [4]: https://www.mobilecoin.com/
117
118#![cfg_attr(not(feature = "std"), no_std)]
119#![cfg_attr(docsrs, feature(doc_cfg))]
120#![doc(
121    html_logo_url = "https://avatars.githubusercontent.com/u/73257032?s=400&u=6b0eb2dc706116234b190b40f3cbbf82c3e9118b&v=4"
122)]
123#![deny(unsafe_code)]
124#![warn(missing_docs, rust_2018_idioms)]
125
126#[cfg(all(feature = "zeroize", feature = "alloc", not(feature = "std")))]
127extern crate alloc;
128
129#[cfg(all(feature = "zeroize", any(feature = "alloc", feature = "std")))]
130mod ct;
131
132#[cfg(all(feature = "zeroize", any(feature = "alloc", feature = "std")))]
133pub use crate::ct::{CtAeadDecrypt, CtDecryptResult};
134pub use aead::{self, AeadCore, AeadInPlace, Error, Key, KeyInit, KeySizeUser};
135
136#[cfg(feature = "aes")]
137pub use aes;
138
139use cipher::{
140    consts::{U0, U16},
141    generic_array::{ArrayLength, GenericArray},
142    BlockCipher, BlockEncrypt, BlockSizeUser, InnerIvInit, StreamCipherCore,
143};
144use core::marker::PhantomData;
145use ghash::{universal_hash::UniversalHash, GHash};
146
147#[cfg(feature = "zeroize")]
148use zeroize::Zeroize;
149
150#[cfg(feature = "aes")]
151use aes::{cipher::consts::U12, Aes128, Aes256};
152
153/// Maximum length of associated data.
154pub const A_MAX: u64 = 1 << 36;
155
156/// Maximum length of plaintext.
157pub const P_MAX: u64 = 1 << 36;
158
159/// Maximum length of ciphertext.
160pub const C_MAX: u64 = (1 << 36) + 16;
161
162/// AES-GCM nonces.
163pub type Nonce<NonceSize> = GenericArray<u8, NonceSize>;
164
165/// AES-GCM tags.
166pub type Tag = GenericArray<u8, U16>;
167
168/// AES-GCM with a 128-bit key and 96-bit nonce.
169#[cfg(feature = "aes")]
170#[cfg_attr(docsrs, doc(cfg(feature = "aes")))]
171pub type Aes128Gcm = AesGcm<Aes128, U12>;
172
173/// AES-GCM with a 256-bit key and 96-bit nonce.
174#[cfg(feature = "aes")]
175#[cfg_attr(docsrs, doc(cfg(feature = "aes")))]
176pub type Aes256Gcm = AesGcm<Aes256, U12>;
177
178/// AES block.
179type Block = GenericArray<u8, U16>;
180
181/// Counter mode with a 32-bit big endian counter.
182type Ctr32BE<Aes> = ctr::CtrCore<Aes, ctr::flavors::Ctr32BE>;
183
184/// AES-GCM: generic over an underlying AES implementation and nonce size.
185///
186/// This type is generic to support substituting alternative AES implementations
187/// (e.g. embedded hardware implementations)
188///
189/// It is NOT intended to be instantiated with any block cipher besides AES!
190/// Doing so runs the risk of unintended cryptographic properties!
191///
192/// The `N` generic parameter can be used to instantiate AES-GCM with other
193/// nonce sizes, however it's recommended to use it with `typenum::U12`,
194/// the default of 96-bits.
195///
196/// If in doubt, use the built-in [`Aes128Gcm`] and [`Aes256Gcm`] type aliases.
197#[derive(Clone)]
198pub struct AesGcm<Aes, NonceSize> {
199    /// Encryption cipher.
200    cipher: Aes,
201
202    /// GHASH authenticator.
203    ghash: GHash,
204
205    /// Length of the nonce.
206    nonce_size: PhantomData<NonceSize>,
207}
208
209impl<Aes, NonceSize> KeySizeUser for AesGcm<Aes, NonceSize>
210where
211    Aes: KeySizeUser,
212{
213    type KeySize = Aes::KeySize;
214}
215
216impl<Aes, NonceSize> KeyInit for AesGcm<Aes, NonceSize>
217where
218    Aes: BlockSizeUser<BlockSize = U16> + BlockEncrypt + KeyInit,
219{
220    fn new(key: &Key<Self>) -> Self {
221        Aes::new(key).into()
222    }
223}
224
225impl<Aes, NonceSize> From<Aes> for AesGcm<Aes, NonceSize>
226where
227    Aes: BlockSizeUser<BlockSize = U16> + BlockEncrypt,
228{
229    fn from(cipher: Aes) -> Self {
230        let mut ghash_key = ghash::Key::default();
231        cipher.encrypt_block(&mut ghash_key);
232
233        let ghash = GHash::new(&ghash_key);
234
235        #[cfg(feature = "zeroize")]
236        ghash_key.zeroize();
237
238        Self {
239            cipher,
240            ghash,
241            nonce_size: PhantomData,
242        }
243    }
244}
245
246impl<Aes, NonceSize> AeadCore for AesGcm<Aes, NonceSize>
247where
248    NonceSize: ArrayLength<u8>,
249{
250    type NonceSize = NonceSize;
251    type TagSize = U16;
252    type CiphertextOverhead = U0;
253}
254
255impl<Aes, NonceSize> AeadInPlace for AesGcm<Aes, NonceSize>
256where
257    Aes: BlockCipher + BlockSizeUser<BlockSize = U16> + BlockEncrypt,
258    NonceSize: ArrayLength<u8>,
259{
260    fn encrypt_in_place_detached(
261        &self,
262        nonce: &Nonce<NonceSize>,
263        associated_data: &[u8],
264        buffer: &mut [u8],
265    ) -> Result<Tag, Error> {
266        if buffer.len() as u64 > P_MAX || associated_data.len() as u64 > A_MAX {
267            return Err(Error);
268        }
269
270        let (ctr, mask) = self.init_ctr(nonce);
271
272        // TODO(tarcieri): interleave encryption with GHASH
273        // See: <https://github.com/RustCrypto/AEADs/issues/74>
274        ctr.apply_keystream_partial(buffer.into());
275        Ok(self.compute_tag(mask, associated_data, buffer))
276    }
277
278    fn decrypt_in_place_detached(
279        &self,
280        nonce: &Nonce<NonceSize>,
281        associated_data: &[u8],
282        buffer: &mut [u8],
283        tag: &Tag,
284    ) -> Result<(), Error> {
285        if buffer.len() as u64 > C_MAX || associated_data.len() as u64 > A_MAX {
286            return Err(Error);
287        }
288
289        let (ctr, mask) = self.init_ctr(nonce);
290
291        // TODO(tarcieri): interleave encryption with GHASH
292        // See: <https://github.com/RustCrypto/AEADs/issues/74>
293        let expected_tag = self.compute_tag(mask, associated_data, buffer);
294        ctr.apply_keystream_partial(buffer.into());
295
296        use subtle::ConstantTimeEq;
297        if expected_tag.ct_eq(tag).into() {
298            Ok(())
299        } else {
300            Err(Error)
301        }
302    }
303}
304
305impl<Aes, NonceSize> AesGcm<Aes, NonceSize>
306where
307    Aes: BlockCipher + BlockSizeUser<BlockSize = U16> + BlockEncrypt,
308    NonceSize: ArrayLength<u8>,
309{
310    /// Initialize counter mode.
311    ///
312    /// See algorithm described in Section 7.2 of NIST SP800-38D:
313    /// <https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf>
314    ///
315    /// > Define a block, J0, as follows:
316    /// > If len(IV)=96, then J0 = IV || 0{31} || 1.
317    /// > If len(IV) ≠ 96, then let s = 128 ⎡len(IV)/128⎤-len(IV), and
318    /// >     J0=GHASH(IV||0s+64||[len(IV)]64).
319    fn init_ctr(&self, nonce: &Nonce<NonceSize>) -> (Ctr32BE<&Aes>, Block) {
320        let j0 = if NonceSize::to_usize() == 12 {
321            let mut block = ghash::Block::default();
322            block[..12].copy_from_slice(nonce);
323            block[15] = 1;
324            block
325        } else {
326            let mut ghash = self.ghash.clone();
327            ghash.update_padded(nonce);
328
329            let mut block = ghash::Block::default();
330            let nonce_bits = (NonceSize::to_usize() as u64) * 8;
331            block[8..].copy_from_slice(&nonce_bits.to_be_bytes());
332            ghash.update(&[block]);
333            ghash.finalize()
334        };
335
336        let mut ctr = Ctr32BE::inner_iv_init(&self.cipher, &j0);
337        let mut tag_mask = Block::default();
338        ctr.write_keystream_block(&mut tag_mask);
339        (ctr, tag_mask)
340    }
341
342    /// Authenticate the given plaintext and associated data using GHASH.
343    fn compute_tag(&self, mask: Block, associated_data: &[u8], buffer: &[u8]) -> Tag {
344        let mut ghash = self.ghash.clone();
345        ghash.update_padded(associated_data);
346        ghash.update_padded(buffer);
347
348        let associated_data_bits = (associated_data.len() as u64) * 8;
349        let buffer_bits = (buffer.len() as u64) * 8;
350
351        let mut block = ghash::Block::default();
352        block[..8].copy_from_slice(&associated_data_bits.to_be_bytes());
353        block[8..].copy_from_slice(&buffer_bits.to_be_bytes());
354        ghash.update(&[block]);
355
356        let mut tag = ghash.finalize();
357        for (a, b) in tag.as_mut_slice().iter_mut().zip(mask.as_slice()) {
358            *a ^= *b;
359        }
360
361        tag
362    }
363}