diff --git a/Cargo.toml b/Cargo.toml index e6b334a..1cea546 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "belt-mac", "cbc-mac", "cmac", + "gmac", "hmac", "pmac", "retail-mac", diff --git a/gmac/CHANGELOG.md b/gmac/CHANGELOG.md new file mode 100644 index 0000000..c72577e --- /dev/null +++ b/gmac/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +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.1.0 (2026-04-??) +Initial Release \ No newline at end of file diff --git a/gmac/Cargo.toml b/gmac/Cargo.toml new file mode 100644 index 0000000..dea6722 --- /dev/null +++ b/gmac/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "gmac" +version = "0.1.0" +description = "Generic implementation of the Galois Counter Mode Message Authentication Code (GMAC)" +authors = ["RustCrypto Developers"] +license = "MIT OR Apache-2.0" +edition = "2024" +readme = "README.md" +documentation = "https://docs.rs/gmac" +repository = "https://github.com/RustCrypto/MACs" +keywords = ["crypto", "mac", "gmac", "digest"] +categories = ["cryptography", "no-std"] +rust-version = "1.85" + +[dependencies] +common = { version = "0.2", package = "crypto-common" } +aes = "0.8.4" +cipher = "0.5" +digest = { version = "0.11.3", features = ["mac"] } +ghash = "0.5" +generic-array = { version = "0.14.7" } +ctutils = "0.4" + +[dev-dependencies] +digest = { version = "0.11.3", features = ["dev"] } +rand = { version = "0.10.0"} +hex-literal = "1" + +[features] +default = ["zeroize", "rand_core" ] +zeroize = ["digest/zeroize", "ghash/zeroize", "aes/zeroize", "cipher/zeroize", "generic-array/zeroize", "common/zeroize"] +rand_core = ["common/rand_core"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +all-features = true diff --git a/gmac/LICENSE-APACHE b/gmac/LICENSE-APACHE new file mode 100644 index 0000000..78173fa --- /dev/null +++ b/gmac/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/gmac/LICENSE-MIT b/gmac/LICENSE-MIT new file mode 100644 index 0000000..50c6180 --- /dev/null +++ b/gmac/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2023 The RustCrypto Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/gmac/README.md b/gmac/README.md new file mode 100644 index 0000000..ca06746 --- /dev/null +++ b/gmac/README.md @@ -0,0 +1,101 @@ +# [RustCrypto]: GMAC + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +[![Build Status][build-image]][build-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] + +Generic implementation of the [Galois Message Authentication Code (GMAC)][GMAC]. + +GMAC is defined by NIST [SP 800-38D] as an authentication-only specialization of +GCM (Galois Counter Mode). It is equivalent to GCM encryption with an empty plaintext +and data only provided in the AAD. + +**WARNING!** This is a nonce-based MAC and must have a unique nonce for each generation. +This is identical to the issues with nonce-reuse and AES-GCM (which uses GMAC internally). +This also means that it is dangerous to clone an instance of GMAC when generating a MAC. +(It is safe to clone for verification purposes only.) + +**WARNING!** GMAC has known weaknesses when used with variable tag lengths associated with the same key. This is identical to the issues with AES-GCM (which uses GMAC internally). +Ensure that only a single tag length is ever used with any given key. + +## Examples + +We will use AES-128 with a 12 byte IV. +(A good default cipher backed by the `aes` crate.) + +To get the authentication code: + +```rust +use gmac::{KeyIvInit, Gmac, Gmac128, Mac}; +use rand::rngs::SysRng; + +// Use the predefined type of `Gmac128`. +let iv = Gmac128::generate_nonce(SysRng).unwrap(); +let iv = iv.as_slice(); +let mut mac = Gmac128::new_from_slices(b"very secret key.", iv).unwrap(); +mac.update(b"input message"); + +// `result` has type `Output` which is a thin wrapper around array of +// bytes for providing constant time equality check +let result = mac.finalize(); +// To get underlying array use the `into_bytes` method, but be careful, +// since incorrect use of the tag value may permit timing attacks which +// defeat the security provided by the `Output` wrapper +let tag_bytes = result.into_bytes(); +``` + +To verify the message: + +```rust +use gmac::{KeyIvInit, Gmac, Gmac128, Mac}; +use rand::rngs::SysRng; + +# let iv = Gmac128::generate_nonce(SysRng).unwrap(); +# let iv = iv.as_slice(); + +let mut mac = Gmac128::new_from_slices(b"very secret key.", iv).unwrap(); + +mac.update(b"input message"); + +# let tag_bytes = mac.clone().finalize().into_bytes(); +// `verify` will return `Ok(())` if tag is correct, `Err(MacError)` otherwise +mac.verify(&tag_bytes).unwrap(); +``` + +## License + +Licensed under either of: + + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/gmac.svg?logo=rust +[crate-link]: https://crates.io/crates/gmac +[docs-image]: https://docs.rs/gmac/badge.svg +[docs-link]: https://docs.rs/gmac/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.85+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260044-MACs +[build-image]: https://github.com/RustCrypto/MACs/actions/workflows/gmac.yml/badge.svg +[build-link]: https://github.com/RustCrypto/MACs/actions/workflows/gmac.yml + +[//]: # (general links) + +[RustCrypto]: https://github.com/RustCrypto +[GMAC]: https://en.wikipedia.org/wiki/Galois/Counter_Mode +[`aes`]: https://docs.rs/aes +[SP 800-38D]: https://doi.org/10.6028/NIST.SP.800-38D \ No newline at end of file diff --git a/gmac/src/lib.rs b/gmac/src/lib.rs new file mode 100644 index 0000000..077ed99 --- /dev/null +++ b/gmac/src/lib.rs @@ -0,0 +1,289 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg" +)] +#![cfg_attr(docsrs, feature(doc_cfg))] + +pub use aes::cipher::KeyIvInit; +pub use digest::{self, Mac}; + +use aes::{ + Aes128Enc, Aes192Enc, Aes256Enc, + cipher::{BlockEncrypt, BlockSizeUser, IvSizeUser, KeyInit, KeySizeUser}, +}; +use cipher::consts::{U12, U16}; +use core::marker::PhantomData; +use digest::{FixedOutput, MacMarker, OutputSizeUser, Update}; +use generic_array::GenericArray; +use ghash::{GHash, universal_hash::UniversalHash}; + +#[cfg(feature = "rand_core")] +use common::rand_core::{TryCryptoRng, TryRng}; + +/// Marker trait used to identify specific ciphers which may be used with GMAC. +pub trait GmacCipher: BlockEncrypt + BlockSizeUser + KeyInit {} +impl GmacCipher for Aes128Enc {} +impl GmacCipher for Aes192Enc {} +impl GmacCipher for Aes256Enc {} + +/// GMAC with a 128-bit key and 12 byte nonce. +pub type Gmac128 = Gmac; +/// GMAC with a 192-bit key and 12 byte nonce. +pub type Gmac192 = Gmac; +/// GMAC with a 256-bit key and 12 byte nonce. +pub type Gmac256 = Gmac; + +/// GMAC: Generic over an underlying AES implementation and nonce size. +#[derive(Debug, Clone)] +pub struct Gmac +where + Aes: GmacCipher, + NonceSize: generic_array::ArrayLength, +{ + /// Encryption cipher. + cipher: PhantomData, + + /// GHASH authenticator. + ghash: GHash, + + /// Length of the nonce. + nonce_size: PhantomData, + + /// Length of the data processed + data_size: usize, + + /// Mask for final tag creation + mask: ghash::Block, + + /// Buffer for unaligned data + buffer: ghash::Block, + + /// Data available in buffer + buffer_len: usize, +} + +impl Gmac +where + Aes: GmacCipher, + NonceSize: generic_array::ArrayLength, +{ + /// Fills the internal buffered block and returns the number of bytes copied from `data` + #[inline] + fn update_buffer(&mut self, data: &[u8]) -> usize { + let data_to_copy = usize::min(Aes::block_size() - self.buffer_len, data.len()); + let buffer_end = self.buffer_len + data_to_copy; + self.buffer.as_mut_slice()[self.buffer_len..buffer_end] + .copy_from_slice(&data[..data_to_copy]); + self.buffer_len = buffer_end; + data_to_copy + } + + /// Hash the buffered block. Panics (in debug) if an entire block has not been buffered. + #[inline] + fn hash_buffer(&mut self) { + debug_assert_eq!(self.buffer_len, Aes::block_size()); + self.ghash.update(&[self.buffer]); + self.buffer_len = 0; + } + + /// Calculates and sets the mask value used for finalizing the tag value. + // Mostly stolen from aes-gcm + #[inline] + fn init_mask(&mut self, cipher: Aes, nonce: &GenericArray) { + let j0 = if NonceSize::to_usize() == 12 { + let mut block = ghash::Block::default(); + block[..12].copy_from_slice(nonce); + block[15] = 1; + block + } else { + let mut ghash = self.ghash.clone(); + ghash.update_padded(nonce); + + 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() + }; + + self.mask = aes::Block::default(); + self.mask.copy_from_slice(&j0); + cipher.encrypt_block(&mut self.mask); + } + + /// Generate a random nonce for use with GMAC. + /// + /// GMAC accepts a parameter to encryption/decryption called a "nonce" + /// which must be unique every time a MAC is generated and never repeated for the same key. + /// The nonce is often prepended to the tag. The nonce used to produce a given tag must be + /// passed to the verification MAC calculation. + /// + /// Nonces don’t necessarily have to be random, but it is one strategy which is implemented by this function. + /// + /// # ⚠️Security Warning + /// + /// GMAC fails catastrophically if the nonce is ever repeated. + /// + /// Using random nonces runs the risk of repeating them. The best case for GMAC is with a 12 byte nonce. + /// With a 12-byte (96-bit) nonce, you can safely generate 2^32 (4,294,967,296) random nonces before the risk + /// of repeating one becomes too high. + #[cfg(feature = "rand_core")] + #[cfg_attr(docsrs, doc(cfg(feature = "rand_core")))] + #[inline] + pub fn generate_nonce( + mut rng: Rng, + ) -> Result, ::Error> + where + Rng: TryCryptoRng, + { + let mut nonce = GenericArray::::default(); + rng.try_fill_bytes(&mut nonce)?; + Ok(nonce) + } +} + +impl OutputSizeUser for Gmac +where + Aes: GmacCipher, + NonceSize: generic_array::ArrayLength, +{ + type OutputSize = U16; +} + +impl KeySizeUser for Gmac +where + Aes: GmacCipher, + NonceSize: generic_array::ArrayLength, +{ + type KeySize = Aes::KeySize; +} + +impl IvSizeUser for Gmac +where + Aes: GmacCipher, + NonceSize: generic_array::ArrayLength, +{ + type IvSize = NonceSize; +} + +impl MacMarker for Gmac +where + Aes: GmacCipher, + NonceSize: generic_array::ArrayLength, +{ +} + +impl KeyIvInit for Gmac +where + Aes: GmacCipher, + NonceSize: generic_array::ArrayLength, +{ + fn new(key: &aes::cipher::Key, nonce: &aes::cipher::Iv) -> Self { + let cipher = Aes::new(key); + + let mut ghash_key = ghash::Key::default(); + cipher.encrypt_block(&mut ghash_key); + + let ghash = GHash::new(&ghash_key); + + let mut result = Self { + cipher: PhantomData, + ghash, + nonce_size: PhantomData, + data_size: 0, + mask: ghash::Block::default(), + buffer: ghash::Block::default(), + buffer_len: 0, + }; + result.init_mask(cipher, nonce); + result + } +} + +impl Update for Gmac +where + Aes: GmacCipher, + NonceSize: generic_array::ArrayLength, +{ + fn update(&mut self, data: &[u8]) { + self.data_size += data.len(); + // First handle any buffered data + let mut offset = 0; + + if self.buffer_len > 0 { + offset += self.update_buffer(data); + if self.buffer_len < Aes::block_size() { + // We don't have enough data for an entire block, so just return + return; + } + self.hash_buffer(); + } + let data = &data[offset..]; + let tail = data.len() % Aes::block_size(); + let data_end = data.len() - tail; + let (body, tail) = data.split_at(data_end); + debug_assert_eq!(body.len() % Aes::block_size(), 0); + self.ghash.update_padded(body); + if !tail.is_empty() { + self.update_buffer(tail); + } + } +} + +impl FixedOutput for Gmac +where + Aes: GmacCipher, + NonceSize: generic_array::ArrayLength, +{ + fn finalize_into(self, out: &mut digest::Output) { + let mut ghash = self.ghash.clone(); + // First, process any buffered data + if self.buffer_len != 0 { + ghash.update_padded(&self.buffer[..self.buffer_len]); + } + let bits_hashed = (self.data_size as u64) * 8; + let mut block = ghash::Block::default(); + block[..8].copy_from_slice(&bits_hashed.to_be_bytes()); + ghash.update(&[block]); + let tag = ghash.finalize(); + for (r, (a, b)) in out + .as_mut_slice() + .iter_mut() + .zip(tag.as_slice().iter().zip(self.mask.as_slice())) + { + *r = *a ^ *b; + } + } +} + +// Optional features +// Zeroize +#[cfg(feature = "zeroize")] +use digest::zeroize::{Zeroize, ZeroizeOnDrop}; + +#[cfg(feature = "zeroize")] +impl Drop for Gmac +where + Aes: GmacCipher, + NonceSize: generic_array::ArrayLength, +{ + fn drop(&mut self) { + // cipher is PhantomData + // ghash implements ZeroizeOnDrop + // nonce_size is PhantomData + self.data_size.zeroize(); + self.mask.zeroize(); + self.buffer.zeroize(); + // buffer_len is not sensitive + } +} + +#[cfg(feature = "zeroize")] +impl ZeroizeOnDrop for Gmac +where + Aes: GmacCipher, + NonceSize: generic_array::ArrayLength, +{ +} diff --git a/gmac/tests/data/gmac_aes128.blb b/gmac/tests/data/gmac_aes128.blb new file mode 100644 index 0000000..44d66cb Binary files /dev/null and b/gmac/tests/data/gmac_aes128.blb differ diff --git a/gmac/tests/data/gmac_aes192.blb b/gmac/tests/data/gmac_aes192.blb new file mode 100644 index 0000000..2ff25b2 Binary files /dev/null and b/gmac/tests/data/gmac_aes192.blb differ diff --git a/gmac/tests/data/gmac_aes256.blb b/gmac/tests/data/gmac_aes256.blb new file mode 100644 index 0000000..035bc0c Binary files /dev/null and b/gmac/tests/data/gmac_aes256.blb differ diff --git a/gmac/tests/mod.rs b/gmac/tests/mod.rs new file mode 100644 index 0000000..e15aac1 --- /dev/null +++ b/gmac/tests/mod.rs @@ -0,0 +1,159 @@ +//! Test vectors. + +use aes::{ + Aes128Enc, Aes192Enc, Aes256Enc, + cipher::{KeyIvInit, Unsigned}, +}; +use cipher::consts::{U1, U128}; +use digest::dev::blobby; +use digest::dev::initialized_mac_test; +use gmac::*; +use hex_literal::hex; + +#[derive(Copy, Clone, Debug)] +struct GmacTestVector { + pub key: &'static [u8], + pub iv: &'static [u8], + pub data: &'static [u8], + pub tag: &'static [u8], +} + +// Source for CAVP test vectors: https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/CAVP-TESTING-BLOCK-CIPHER-MODES + +blobby::parse_into_structs!( + // All CAVP SP 800-38D test vectors for 128 bit keys and empty plaintexts + include_bytes!("data/gmac_aes128.blb"); + static GMAC_128_KATS: &[GmacTestVector { key, iv, data, tag}]; +); +blobby::parse_into_structs!( + // All CAVP SP 800-38D test vectors for 192 bit keys and empty plaintexts + include_bytes!("data/gmac_aes192.blb"); + static GMAC_192_KATS: &[GmacTestVector { key, iv, data, tag}]; +); +blobby::parse_into_structs!( + // All CAVP SP 800-38D test vectors for 256 bit keys and empty plaintexts + include_bytes!("data/gmac_aes256.blb"); + static GMAC_256_KATS: &[GmacTestVector { key, iv, data, tag}]; +); + +#[test] +fn debugging_kat() { + let key = hex!("2fb45e5b8f993a2bfebc4b15b533e0b4"); + let iv = hex!("5b05755f984d2b90f94b8027"); + let expected = hex!("c75b7832b2a2d9bd827412b6ef5769db"); + + let mut mac = Gmac128::new_from_slices(&key, &iv).unwrap(); + mac.update(&hex!("e85491b2202caf1d7dce03b97e09331c32473941")); + let actual = mac.finalize(); + assert_eq!(&expected, actual.as_bytes().as_slice()); +} + +#[cfg(feature = "rand_core")] +#[test] +fn nonce_generation() { + use rand::rngs::SysRng; + + let fake_key = [0u8; 16]; + let nonce = Gmac128::generate_nonce(SysRng).expect("SysRng failed"); + let _ = Gmac128::new(&fake_key.into(), &nonce); +} + +#[cfg(feature = "rand_core")] +#[test] +fn nonce_generation_16() { + use cipher::consts::U16; + use rand::rngs::SysRng; + + let fake_key = [0u8; 16]; + let nonce = Gmac::::generate_nonce(SysRng).expect("SysRng failed"); + let _ = Gmac::::new(&fake_key.into(), &nonce); +} + +#[test] +fn gmac128_defaults() { + test_kats::("gmac128_defaults", GMAC_128_KATS); +} + +#[test] +fn gmac128_iv8() { + test_kats::>("gmac128_iv8", GMAC_128_KATS); +} + +#[test] +fn gmac128_iv1024() { + test_kats::>("gmac128_iv1024", GMAC_128_KATS); +} + +#[test] +fn gmac192_defaults() { + test_kats::("gmac192_defaults", GMAC_192_KATS); +} + +#[test] +fn gmac192_iv8() { + test_kats::>("gmac192_iv8", GMAC_192_KATS); +} + +#[test] +fn gmac192_iv1024() { + test_kats::>("gmac192_iv1024", GMAC_192_KATS); +} + +#[test] +fn gmac256_defaults() { + test_kats::("gmac256_defaults", GMAC_256_KATS); +} + +#[test] +fn gmac256_iv8() { + test_kats::>("gmac256_iv8", GMAC_256_KATS); +} + +#[test] +fn gmac256_iv1024() { + test_kats::>("gmac256_iv1024", GMAC_256_KATS); +} + +fn test_kats(name: &str, kats: &[GmacTestVector]) +where + MAC: Mac + KeyIvInit + Clone, +{ + for (idx, tv) in kats.iter().enumerate() { + if MAC::KeySize::to_usize() != tv.key.len() { + continue; + } + if MAC::IvSize::to_usize() != tv.iv.len() { + continue; + } + if MAC::OutputSize::to_usize() < tv.tag.len() { + continue; + } + let mac = MAC::new_from_slices(tv.key, tv.iv).expect("Incorrect key or IV length"); + + if MAC::OutputSize::to_usize() == tv.tag.len() { + if let Err(reason) = initialized_mac_test( + mac.clone(), + tv.data, + tv.tag, + digest::dev::MacTruncSide::None, + ) { + panic!( + "\n\ + Failed test {name}#{idx}\n\ + reason:\t{reason:?}\n\ + test vector:\t{tv:?}\n" + ) + } + } + if let Err(reason) = + initialized_mac_test(mac, tv.data, tv.tag, digest::dev::MacTruncSide::Left) + { + panic!( + "\n\ + Failed test (truncated) {name}#{idx}\n\ + reason:\t{reason:?}\n\ + test vector:\t{tv:?}\n" + ) + } + } +}