From 2af2e0f0fdf18911fd188d00623e3af0481c627d Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 25 May 2020 07:37:09 -0700 Subject: [PATCH] Upgrade to `universal-hash` v0.4.0-pre - (Vicariously) bumps `generic-array` to v0.14 - Splits out `NewUniversalHash` from `UniversalHash` - Uses `GenericArray` type aliases - Renames `update_block` to `update` - Gets rid of redundant buffering logic in `poly1305` - Adds notes about the NCC/MobileCoin security audit --- Cargo.lock | 24 +++++----- Cargo.toml | 3 ++ ghash/Cargo.toml | 4 +- ghash/README.md | 18 +++++-- ghash/src/lib.rs | 31 ++++++++---- ghash/tests/lib.rs | 9 ++-- poly1305/Cargo.toml | 9 ++-- poly1305/README.md | 18 +++++-- poly1305/src/lib.rs | 93 +++++++----------------------------- poly1305/src/soft.rs | 46 +++++------------- poly1305/tests/lib.rs | 108 +++++++----------------------------------- polyval/Cargo.toml | 9 ++-- polyval/README.md | 18 +++++-- polyval/src/lib.rs | 39 +++++++++------ polyval/tests/lib.rs | 9 ++-- 15 files changed, 173 insertions(+), 265 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7fea20e..eb49bc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -175,7 +175,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "generic-array" -version = "0.12.3" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -183,10 +183,10 @@ dependencies = [ [[package]] name = "ghash" -version = "0.2.3" +version = "0.3.0-pre" dependencies = [ "hex-literal 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "polyval 0.3.3", + "polyval 0.4.0-pre", "zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -307,21 +307,21 @@ dependencies = [ [[package]] name = "poly1305" -version = "0.5.2" +version = "0.6.0-pre" dependencies = [ - "universal-hash 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "universal-hash 0.4.0-pre (git+https://github.com/RustCrypto/traits)", "zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "polyval" -version = "0.3.3" +version = "0.4.0-pre" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "criterion 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "criterion-cycles-per-byte 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "hex-literal 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "universal-hash 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "universal-hash 0.4.0-pre (git+https://github.com/RustCrypto/traits)", "zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -510,10 +510,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "universal-hash" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.4.0-pre" +source = "git+https://github.com/RustCrypto/traits#42ebb8f1ab9057b78a031e5808a7e4023b749829" dependencies = [ - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", "subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -637,7 +637,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum csv 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "00affe7f6ab566df61b4be3ce8cf16bc2576bca0963ceb0955e45d514bf9a279" "checksum csv-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" "checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" -"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +"checksum generic-array 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2664c2cf08049036f31015b04c6ac3671379a1d86f52ed2416893f16022deb" "checksum hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" "checksum hex-literal 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ddc2928beef125e519d69ae1baa8c37ea2e0d3848545217f6db0179c5eb1d639" "checksum hex-literal-impl 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "520870c3213943eb8d7803e80180d12a6c7ceb4ae74602544529d1643dc4ddda" @@ -679,7 +679,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" -"checksum universal-hash 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df0c900f2f9b4116803415878ff48b63da9edb268668e08cf9292d7503114a01" +"checksum universal-hash 0.4.0-pre (git+https://github.com/RustCrypto/traits)" = "" "checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" "checksum wasm-bindgen 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "e3c7d40d09cdbf0f4895ae58cf57d92e1e57a9dd8ed2e8390514b54a47cc5551" "checksum wasm-bindgen-backend 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "c3972e137ebf830900db522d6c8fd74d1900dcfc733462e9a12e942b00b4ac94" diff --git a/Cargo.toml b/Cargo.toml index a63a9df..d91cd00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,6 @@ members = [ "poly1305", "polyval" ] + +[patch.crates-io] +universal-hash = { git = "https://github.com/RustCrypto/traits" } diff --git a/ghash/Cargo.toml b/ghash/Cargo.toml index a68478d..fc6f9ad 100644 --- a/ghash/Cargo.toml +++ b/ghash/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ghash" -version = "0.2.3" +version = "0.3.0-pre" authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" description = """ @@ -15,7 +15,7 @@ categories = ["cryptography", "no-std"] edition = "2018" [dependencies] -polyval = { version = "0.3", path = "../polyval" } +polyval = { version = "= 0.4.0-pre", path = "../polyval" } zeroize = { version = "1", optional = true, default-features = false } [dev-dependencies] diff --git a/ghash/README.md b/ghash/README.md index ff20712..9867942 100644 --- a/ghash/README.md +++ b/ghash/README.md @@ -13,13 +13,19 @@ Its primary intended use is for implementing [AES-GCM][4]. [Documentation][docs-link] -## Security Warning +## Security Notes -No security audits of this crate have ever been performed, and it has not been -thoroughly assessed to ensure its operation is constant-time on common CPU -architectures. +This crate has received one [security audit by NCC Group][5], with no significant +findings. We would like to thank [MobileCoin][6] for funding the audit. -USE AT YOUR OWN RISK! +All implementations contained in the crate are designed to execute in constant +time, either by relying on hardware intrinsics (i.e. AVX2 on x86/x86_64), or +using a portable implementation which is only constant time on processors which +implement constant-time multiplication. + +It is not suitable for use on processors with a variable-time multiplication +operation (e.g. short circuit on multiply-by-zero / multiply-by-one, such as +certain 32-bit PowerPC CPUs and some non-ARM microcontrollers). ## License @@ -53,3 +59,5 @@ dual licensed as above, without any additional terms or conditions. [2]: https://en.wikipedia.org/wiki/Universal_hashing [3]: https://en.wikipedia.org/wiki/Message_authentication_code [4]: https://en.wikipedia.org/wiki/Galois/Counter_Mode +[5]: https://research.nccgroup.com/2020/02/26/public-report-rustcrypto-aes-gcm-and-chacha20poly1305-implementation-review/ +[6]: https://www.mobilecoin.com/ diff --git a/ghash/src/lib.rs b/ghash/src/lib.rs index 300ce4d..b3fdfd8 100644 --- a/ghash/src/lib.rs +++ b/ghash/src/lib.rs @@ -29,11 +29,19 @@ pub use polyval::universal_hash; use core::convert::TryInto; use polyval::Polyval; -use universal_hash::generic_array::{typenum::U16, GenericArray}; -use universal_hash::{Output, UniversalHash}; +use universal_hash::{consts::U16, NewUniversalHash, UniversalHash}; #[cfg(feature = "zeroize")] use zeroize::Zeroize; +/// GHASH keys (16-bytes) +pub type Key = universal_hash::Key; + +/// GHASH blocks (16-bytes) +pub type Block = universal_hash::Block; + +/// GHASH tags (16-bytes) +pub type Tag = universal_hash::Output; + /// **GHASH**: universal hash over GF(2^128) used by AES-GCM. /// /// GHASH is a universal hash function used for message authentication in @@ -42,12 +50,11 @@ use zeroize::Zeroize; #[repr(align(16))] pub struct GHash(Polyval); -impl UniversalHash for GHash { +impl NewUniversalHash for GHash { type KeySize = U16; - type BlockSize = U16; /// Initialize GHASH with the given `H` field element - fn new(h: &GenericArray) -> Self { + fn new(h: &Key) -> Self { let mut h = *h; h.reverse(); @@ -65,12 +72,16 @@ impl UniversalHash for GHash { result } +} + +impl UniversalHash for GHash { + type BlockSize = U16; /// Input a field element `X` to be authenticated - fn update_block(&mut self, x: &GenericArray) { + fn update(&mut self, x: &Block) { let mut x = *x; x.reverse(); - self.0.update_block(&x); + self.0.update(&x); } /// Reset internal state @@ -79,10 +90,10 @@ impl UniversalHash for GHash { } /// Get GHASH output - fn result(self) -> Output { + fn result(self) -> Tag { let mut output = self.0.result().into_bytes(); output.reverse(); - Output::new(output) + Tag::new(output) } } @@ -92,7 +103,7 @@ impl UniversalHash for GHash { /// /// [1]: https://tools.ietf.org/html/rfc8452#appendix-A #[allow(non_snake_case)] -fn mulX_POLYVAL(block: &GenericArray) -> GenericArray { +fn mulX_POLYVAL(block: &Block) -> Block { let mut v0 = u64::from_le_bytes(block[..8].try_into().unwrap()); let mut v1 = u64::from_le_bytes(block[8..].try_into().unwrap()); diff --git a/ghash/tests/lib.rs b/ghash/tests/lib.rs index 3090edb..6648798 100644 --- a/ghash/tests/lib.rs +++ b/ghash/tests/lib.rs @@ -1,7 +1,10 @@ #[macro_use] extern crate hex_literal; -use ghash::{universal_hash::UniversalHash, GHash}; +use ghash::{ + universal_hash::{NewUniversalHash, UniversalHash}, + GHash, +}; // // Test vectors for GHASH from RFC 8452 Appendix A @@ -18,8 +21,8 @@ const GHASH_RESULT: [u8; 16] = hex!("bd9b3997046731fb96251b91f9c99d7a"); #[test] fn ghash_test_vector() { let mut ghash = GHash::new(&H.into()); - ghash.update_block(&X_1.into()); - ghash.update_block(&X_2.into()); + ghash.update(&X_1.into()); + ghash.update(&X_2.into()); let result = ghash.result(); assert_eq!(&GHASH_RESULT[..], result.into_bytes().as_slice()); diff --git a/poly1305/Cargo.toml b/poly1305/Cargo.toml index 1d03a39..5d60a38 100644 --- a/poly1305/Cargo.toml +++ b/poly1305/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "poly1305" -version = "0.5.2" +version = "0.6.0-pre" authors = ["RustCrypto Developers"] -license = "MIT OR Apache-2.0" +license = "Apache-2.0 OR MIT" description = "The Poly1305 universal hash function and message authentication code" documentation = "https://docs.rs/poly1305" repository = "https://github.com/RustCrypto/universal-hashes" @@ -11,8 +11,11 @@ categories = ["cryptography", "no-std"] readme = "README.md" edition = "2018" +[badges] +maintenance = { status = "passively-maintained" } + [dependencies] -universal-hash = { version = "0.3", default-features = false } +universal-hash = { version = "= 0.4.0-pre", default-features = false } zeroize = { version = "1", optional = true, default-features = false } [features] diff --git a/poly1305/README.md b/poly1305/README.md index 0977d94..8ea126a 100644 --- a/poly1305/README.md +++ b/poly1305/README.md @@ -15,13 +15,19 @@ In practice, Poly1305 is primarily combined with ciphers from the [Documentation][docs-link] -## Security Warning +## Security Notes -No security audits of this crate have ever been performed, and it has not been -thoroughly assessed to ensure its operation is constant-time on common CPU -architectures. +This crate has received one [security audit by NCC Group][7], with no significant +findings. We would like to thank [MobileCoin][8] for funding the audit. -USE AT YOUR OWN RISK! +All implementations contained in the crate are designed to execute in constant +time, either by relying on hardware intrinsics (i.e. AVX2 on x86/x86_64), or +using a portable implementation which is only constant time on processors which +implement constant-time multiplication. + +It is not suitable for use on processors with a variable-time multiplication +operation (e.g. short circuit on multiply-by-zero / multiply-by-one, such as +certain 32-bit PowerPC CPUs and some non-ARM microcontrollers). ## License @@ -57,3 +63,5 @@ dual licensed as above, without any additional terms or conditions. [4]: https://cr.yp.to/snuffle/salsafamily-20071225.pdf [5]: https://github.com/RustCrypto/AEADs/tree/master/chacha20poly1305 [6]: https://github.com/RustCrypto/AEADs/tree/master/xsalsa20poly1305 +[7]: https://research.nccgroup.com/2020/02/26/public-report-rustcrypto-aes-gcm-and-chacha20poly1305-implementation-review/ +[8]: https://www.mobilecoin.com/ diff --git a/poly1305/src/lib.rs b/poly1305/src/lib.rs index 5a96bba..98a474c 100644 --- a/poly1305/src/lib.rs +++ b/poly1305/src/lib.rs @@ -12,31 +12,25 @@ pub use universal_hash; -use core::cmp::min; -use universal_hash::generic_array::{ - typenum::{U16, U32}, - GenericArray, -}; -use universal_hash::{Output, UniversalHash}; -#[cfg(feature = "zeroize")] -use zeroize::Zeroize; +use universal_hash::consts::{U16, U32}; +use universal_hash::{NewUniversalHash, UniversalHash}; mod soft; /// Size of a Poly1305 key pub const KEY_SIZE: usize = 32; -/// Poly1305 keys (32-bytes) -pub type Key = [u8; KEY_SIZE]; - /// Size of the blocks Poly1305 acts upon pub const BLOCK_SIZE: usize = 16; +/// Poly1305 keys (32-bytes) +pub type Key = universal_hash::Key; + /// Poly1305 blocks (16-bytes) -pub type Block = [u8; BLOCK_SIZE]; +pub type Block = universal_hash::Block; /// Poly1305 tags (16-bytes) -pub type Tag = Output; +pub type Tag = universal_hash::Output; /// The Poly1305 universal hash function. /// @@ -46,86 +40,35 @@ pub type Tag = Output; /// For this reason it doesn't impl the `crypto_mac::Mac` trait. #[derive(Clone)] pub struct Poly1305 { - state: soft::Poly1305State, - buffer: Block, - filled: usize, + state: soft::State, } -impl UniversalHash for Poly1305 { +impl NewUniversalHash for Poly1305 { type KeySize = U32; - type BlockSize = U16; /// Initialize Poly1305 with the given key - fn new(key: &GenericArray) -> Poly1305 { + fn new(key: &Key) -> Poly1305 { Poly1305 { - state: soft::Poly1305State::new(key), - buffer: Block::default(), - filled: 0, + state: soft::State::new(key), } } +} + +impl UniversalHash for Poly1305 { + type BlockSize = U16; /// Input data into the Poly1305 universal hash function - fn update_block(&mut self, block: &GenericArray) { - if self.filled > 0 { - // We have a partial block that needs processing. - self.update(block.as_slice()); - } else { - // Pass this block directly to `Poly1305State::compute_block`. - self.state.compute_block(block.as_slice()); - } + fn update(&mut self, block: &Block) { + self.state.update(block); } /// Reset internal state fn reset(&mut self) { self.state.reset(); - self.buffer = Default::default(); - self.filled = 0; } /// Get the hashed output fn result(mut self) -> Tag { - self.state.finalize(&self.buffer[..self.filled]) - } -} - -impl Poly1305 { - /// Input data into the Poly1305 universal hash function - pub fn update(&mut self, mut data: &[u8]) { - // Handle partially-filled buffer from a previous update - if self.filled > 0 { - let want = min(BLOCK_SIZE - self.filled, data.len()); - - self.buffer[self.filled..self.filled + want].copy_from_slice(&data[..want]); - data = &data[want..]; - self.filled += want; - - if self.filled < BLOCK_SIZE { - return; - } - - self.state.compute_block(&self.buffer); - self.filled = 0; - } - - while data.len() >= BLOCK_SIZE { - self.state.compute_block(&data[..BLOCK_SIZE]); - data = &data[BLOCK_SIZE..]; - } - - self.buffer[..data.len()].copy_from_slice(data); - self.filled = data.len(); - } - - /// Process input messages in a chained manner - pub fn chain(mut self, data: &[u8]) -> Self { - self.update(data); - self - } -} - -#[cfg(feature = "zeroize")] -impl Drop for Poly1305 { - fn drop(&mut self) { - self.buffer.zeroize(); + self.state.finalize() } } diff --git a/poly1305/src/soft.rs b/poly1305/src/soft.rs index 9c7ec86..fe14ca7 100644 --- a/poly1305/src/soft.rs +++ b/poly1305/src/soft.rs @@ -13,27 +13,23 @@ // https://github.com/floodyberry/poly1305-donna use core::convert::TryInto; -use universal_hash::generic_array::{typenum::U32, GenericArray}; + #[cfg(feature = "zeroize")] use zeroize::Zeroize; -use crate::{Tag, BLOCK_SIZE}; +use crate::{Block, Key, Tag}; -#[derive(Clone)] -pub(crate) struct Poly1305State { +#[derive(Clone, Default)] +pub(crate) struct State { r: [u32; 5], h: [u32; 5], pad: [u32; 4], } -impl Poly1305State { +impl State { /// Initialize Poly1305State with the given key - pub(crate) fn new(key: &GenericArray) -> Poly1305State { - let mut poly = Poly1305State { - r: [0u32; 5], - h: [0u32; 5], - pad: [0u32; 4], - }; + pub(crate) fn new(key: &Key) -> State { + let mut poly = State::default(); // r &= 0xffffffc0ffffffc0ffffffc0fffffff poly.r[0] = (u32::from_le_bytes(key[0..4].try_into().unwrap())) & 0x3ff_ffff; @@ -56,15 +52,8 @@ impl Poly1305State { } /// Compute a single block of Poly1305 - pub(crate) fn compute_block(&mut self, block: &[u8]) { - self.compute_block_inner(block, false); - } - - fn compute_block_inner(&mut self, block: &[u8], partial: bool) { - assert_eq!(block.len(), BLOCK_SIZE); - - // If this is a partial block, the caller already set the high bit. - let hibit = if partial { 0 } else { 1 << 24 }; + pub(crate) fn update(&mut self, block: &Block) { + let hibit = 1 << 24; let r0 = self.r[0]; let r1 = self.r[1]; @@ -154,18 +143,7 @@ impl Poly1305State { self.h[4] = h4; } - pub(crate) fn finalize(&mut self, data: &[u8]) -> Tag { - if !data.is_empty() { - // Compute last block (remaining data < 16 bytes) - assert!(data.len() < BLOCK_SIZE); - - let mut block = [0; BLOCK_SIZE]; - block[..data.len()].copy_from_slice(data); - block[data.len()] = 1; - - self.compute_block_inner(&block, true); - } - + pub(crate) fn finalize(&mut self) -> Tag { // fully carry h let mut h0 = self.h[0]; let mut h1 = self.h[1]; @@ -247,7 +225,7 @@ impl Poly1305State { f = u64::from(h3) + u64::from(self.pad[3]) + (f >> 32); h3 = f as u32; - let mut tag = GenericArray::default(); + let mut tag = Block::default(); tag[0..4].copy_from_slice(&h0.to_le_bytes()); tag[4..8].copy_from_slice(&h1.to_le_bytes()); tag[8..12].copy_from_slice(&h2.to_le_bytes()); @@ -258,7 +236,7 @@ impl Poly1305State { } #[cfg(feature = "zeroize")] -impl Drop for Poly1305State { +impl Drop for State { fn drop(&mut self) { self.r.zeroize(); self.h.zeroize(); diff --git a/poly1305/tests/lib.rs b/poly1305/tests/lib.rs index b8d12dd..2e83170 100644 --- a/poly1305/tests/lib.rs +++ b/poly1305/tests/lib.rs @@ -1,98 +1,29 @@ -use poly1305::{universal_hash::UniversalHash, Poly1305, KEY_SIZE}; -use std::iter::repeat; - -#[test] -fn test_nacl_vector() { - let key = [ - 0xee, 0xa6, 0xa7, 0x25, 0x1c, 0x1e, 0x72, 0x91, 0x6d, 0x11, 0xc2, 0xcb, 0x21, 0x4d, 0x3c, - 0x25, 0x25, 0x39, 0x12, 0x1d, 0x8e, 0x23, 0x4e, 0x65, 0x2d, 0x65, 0x1f, 0xa4, 0xc8, 0xcf, - 0xf8, 0x80, - ]; - - let msg = [ - 0x8e, 0x99, 0x3b, 0x9f, 0x48, 0x68, 0x12, 0x73, 0xc2, 0x96, 0x50, 0xba, 0x32, 0xfc, 0x76, - 0xce, 0x48, 0x33, 0x2e, 0xa7, 0x16, 0x4d, 0x96, 0xa4, 0x47, 0x6f, 0xb8, 0xc5, 0x31, 0xa1, - 0x18, 0x6a, 0xc0, 0xdf, 0xc1, 0x7c, 0x98, 0xdc, 0xe8, 0x7b, 0x4d, 0xa7, 0xf0, 0x11, 0xec, - 0x48, 0xc9, 0x72, 0x71, 0xd2, 0xc2, 0x0f, 0x9b, 0x92, 0x8f, 0xe2, 0x27, 0x0d, 0x6f, 0xb8, - 0x63, 0xd5, 0x17, 0x38, 0xb4, 0x8e, 0xee, 0xe3, 0x14, 0xa7, 0xcc, 0x8a, 0xb9, 0x32, 0x16, - 0x45, 0x48, 0xe5, 0x26, 0xae, 0x90, 0x22, 0x43, 0x68, 0x51, 0x7a, 0xcf, 0xea, 0xbd, 0x6b, - 0xb3, 0x73, 0x2b, 0xc0, 0xe9, 0xda, 0x99, 0x83, 0x2b, 0x61, 0xca, 0x01, 0xb6, 0xde, 0x56, - 0x24, 0x4a, 0x9e, 0x88, 0xd5, 0xf9, 0xb3, 0x79, 0x73, 0xf6, 0x22, 0xa4, 0x3d, 0x14, 0xa6, - 0x59, 0x9b, 0x1f, 0x65, 0x4c, 0xb4, 0x5a, 0x74, 0xe3, 0x55, 0xa5, - ]; - - let expected = [ - 0xf3, 0xff, 0xc7, 0x70, 0x3f, 0x94, 0x00, 0xe5, 0x2a, 0x7d, 0xfb, 0x4b, 0x3d, 0x33, 0x05, - 0xd9, - ]; - - let result1 = Poly1305::new(key.as_ref().into()).chain(&msg).result(); - assert_eq!(&expected[..], result1.into_bytes().as_slice()); - - let result2 = Poly1305::new(key.as_ref().into()) - .chain(&msg[0..32]) - .chain(&msg[32..96]) - .chain(&msg[96..112]) - .chain(&msg[112..120]) - .chain(&msg[120..124]) - .chain(&msg[124..126]) - .chain(&msg[126..127]) - .chain(&msg[127..128]) - .chain(&msg[128..129]) - .chain(&msg[129..130]) - .chain(&msg[130..131]) - .result(); - - assert_eq!(&expected[..], result2.into_bytes().as_slice()); -} +use poly1305::{ + universal_hash::{NewUniversalHash, UniversalHash}, + Poly1305, BLOCK_SIZE, +}; #[test] fn donna_self_test() { - let wrap_key = [ + let key = [ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; - let wrap_msg = [ + let msg = [ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, ]; - let wrap_mac = [ + let expected = [ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; - let result = Poly1305::new(wrap_key.as_ref().into()) - .chain(&wrap_msg) - .result(); - - assert_eq!(&wrap_mac[..], result.into_bytes().as_slice()); - - let total_key = [ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, - 0x00, 0x00, - ]; - - let total_mac = [ - 0x64, 0xaf, 0xe2, 0xe8, 0xd6, 0xad, 0x7b, 0xbd, 0xd2, 0x87, 0xf9, 0x7c, 0x44, 0x62, 0x3d, - 0x39, - ]; - - let mut tpoly = Poly1305::new(total_key.as_ref().into()); - - for i in 0..256 { - let mut key = [0u8; KEY_SIZE]; - key.copy_from_slice(&repeat(i as u8).take(KEY_SIZE).collect::>()); - - let msg: Vec = repeat(i as u8).take(256).collect(); - let tag = Poly1305::new(key.as_ref().into()).chain(&msg[..i]).result(); - tpoly.update(tag.into_bytes().as_slice()); - } - - assert_eq!(&total_mac[..], tpoly.result().into_bytes().as_slice()); + let mut poly = Poly1305::new(key.as_ref().into()); + poly.update(msg.as_ref().into()); + assert_eq!(&expected[..], poly.result().into_bytes().as_slice()); } #[test] @@ -100,23 +31,20 @@ fn test_tls_vectors() { // from http://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04 let key = b"this is 32-byte key for Poly1305"; - let msg1 = [0u8; 32]; - let expected1 = [ + let msg = [0u8; 32]; + + let expected = [ 0x49, 0xec, 0x78, 0x09, 0x0e, 0x48, 0x1e, 0xc6, 0xc2, 0x6b, 0x33, 0xb9, 0x1c, 0xcc, 0x03, 0x07, ]; - let result1 = Poly1305::new(key.as_ref().into()).chain(&msg1).result(); - assert_eq!(&expected1[..], result1.into_bytes().as_slice()); + let mut poly = Poly1305::new(key.as_ref().into()); - let msg2 = b"Hello world!"; - let expected2 = [ - 0xa6, 0xf7, 0x45, 0x00, 0x8f, 0x81, 0xc9, 0x16, 0xa2, 0x0d, 0xcc, 0x74, 0xee, 0xf2, 0xb2, - 0xf0, - ]; + for chunk in msg.chunks(BLOCK_SIZE) { + poly.update(chunk.into()); + } - let result2 = Poly1305::new(key.as_ref().into()).chain(&msg2[..]).result(); - assert_eq!(&expected2[..], result2.into_bytes().as_slice()); + assert_eq!(&expected[..], poly.result().into_bytes().as_slice()); } #[test] diff --git a/polyval/Cargo.toml b/polyval/Cargo.toml index 504452d..3e0e373 100644 --- a/polyval/Cargo.toml +++ b/polyval/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "polyval" -version = "0.3.3" +version = "0.4.0-pre" authors = ["RustCrypto Developers"] -license = "MIT OR Apache-2.0" +license = "Apache-2.0 OR MIT" description = """ POLYVAL is a GHASH-like universal hash over GF(2^128) useful for constructing a Message Authentication Code (MAC) @@ -14,9 +14,12 @@ keywords = ["aes-gcm-siv", "crypto", "universal-hashing"] categories = ["cryptography", "no-std"] edition = "2018" +[badges] +maintenance = { status = "passively-maintained" } + [dependencies] cfg-if = "0.1" -universal-hash = { version = "0.3", default-features = false } +universal-hash = { version = "= 0.4.0-pre", default-features = false } zeroize = { version = "1", optional = true, default-features = false } [dev-dependencies] diff --git a/polyval/README.md b/polyval/README.md index 5b8ee68..ddce71d 100644 --- a/polyval/README.md +++ b/polyval/README.md @@ -16,13 +16,19 @@ closely related to [GHASH][6] and therefore can also be used to implement [Documentation][docs-link] -## Security Warning +## Security Notes -No security audits of this crate have ever been performed, and it has not been -thoroughly assessed to ensure its operation is constant-time on common CPU -architectures. +This crate has received one [security audit by NCC Group][8], with no significant +findings. We would like to thank [MobileCoin][9] for funding the audit. -USE AT YOUR OWN RISK! +All implementations contained in the crate are designed to execute in constant +time, either by relying on hardware intrinsics (i.e. AVX2 on x86/x86_64), or +using a portable implementation which is only constant time on processors which +implement constant-time multiplication. + +It is not suitable for use on processors with a variable-time multiplication +operation (e.g. short circuit on multiply-by-zero / multiply-by-one, such as +certain 32-bit PowerPC CPUs and some non-ARM microcontrollers). ## License @@ -59,3 +65,5 @@ dual licensed as above, without any additional terms or conditions. [5]: https://en.wikipedia.org/wiki/AES-GCM-SIV [6]: https://en.wikipedia.org/wiki/Galois/Counter_Mode#Mathematical_basis [7]: https://en.wikipedia.org/wiki/Galois/Counter_Mode +[8]: https://research.nccgroup.com/2020/02/26/public-report-rustcrypto-aes-gcm-and-chacha20poly1305-implementation-review/ +[9]: https://www.mobilecoin.com/ diff --git a/polyval/src/lib.rs b/polyval/src/lib.rs index 543e44f..79ef63c 100644 --- a/polyval/src/lib.rs +++ b/polyval/src/lib.rs @@ -50,8 +50,16 @@ mod field; pub use universal_hash; -use universal_hash::generic_array::{typenum::U16, GenericArray}; -use universal_hash::{Output, UniversalHash}; +use universal_hash::{consts::U16, NewUniversalHash, Output, UniversalHash}; + +/// POLYVAL keys (16-bytes) +pub type Key = universal_hash::Key; + +/// POLYVAL blocks (16-bytes) +pub type Block = universal_hash::Block; + +/// POLYVAL tags (16-bytes) +pub type Tag = universal_hash::Output; /// **POLYVAL**: GHASH-like universal hash over GF(2^128). #[allow(non_snake_case)] @@ -65,20 +73,23 @@ pub struct Polyval { S: field::Element, } -impl UniversalHash for Polyval { +impl NewUniversalHash for Polyval { type KeySize = U16; - type BlockSize = U16; /// Initialize POLYVAL with the given `H` field element - fn new(h: &GenericArray) -> Self { + fn new(h: &Key) -> Self { Self { H: field::Element::from_bytes(h.clone().into()), S: field::Element::default(), } } +} + +impl UniversalHash for Polyval { + type BlockSize = U16; /// Input a field element `X` to be authenticated - fn update_block(&mut self, x: &GenericArray) { + fn update(&mut self, x: &Block) { let x = field::Element::from_bytes(x.clone().into()); self.S = (self.S + x) * self.H; } @@ -86,22 +97,20 @@ impl UniversalHash for Polyval { /// Input data into the universal hash function. If the length of the /// data is not a multiple of the block size, the remaining data is /// padded with zeros up to the `BlockSize`. - /// - /// This approach is frequently used by AEAD modes which use - /// Message Authentication Codes (MACs) based on universal hashing. fn update_padded(&mut self, data: &[u8]) { + // NOTE: this code is identical to upstream, but copied into + // here as a performance hack. let mut chunks = data.chunks_exact(16); - for chunk in &mut chunks { - self.update_block(GenericArray::from_slice(chunk)); + self.update(Block::from_slice(chunk)); } let rem = chunks.remainder(); if !rem.is_empty() { - let mut padded_block = GenericArray::default(); + let mut padded_block = Block::default(); padded_block[..rem.len()].copy_from_slice(rem); - self.update_block(&padded_block); + self.update(&padded_block); } } @@ -111,7 +120,7 @@ impl UniversalHash for Polyval { } /// Get POLYVAL result (i.e. computed `S` field element) - fn result(self) -> Output { - Output::new(GenericArray::from(self.S.to_bytes())) + fn result(self) -> Tag { + Output::new(self.S.to_bytes().into()) } } diff --git a/polyval/tests/lib.rs b/polyval/tests/lib.rs index 916eee6..8b81ea0 100644 --- a/polyval/tests/lib.rs +++ b/polyval/tests/lib.rs @@ -1,7 +1,10 @@ #[macro_use] extern crate hex_literal; -use polyval::{universal_hash::UniversalHash, Polyval}; +use polyval::{ + universal_hash::{NewUniversalHash, UniversalHash}, + Polyval, +}; // // Test vectors for POLYVAL from RFC 8452 Appendix A @@ -18,8 +21,8 @@ const POLYVAL_RESULT: [u8; 16] = hex!("f7a3b47b846119fae5b7866cf5e5b77e"); #[test] fn polyval_test_vector() { let mut poly = Polyval::new(&H.into()); - poly.update_block(&X_1.into()); - poly.update_block(&X_2.into()); + poly.update(&X_1.into()); + poly.update(&X_2.into()); let result = poly.result(); assert_eq!(&POLYVAL_RESULT[..], result.into_bytes().as_slice());