diff --git a/.github/workflows/crypto-common.yml b/.github/workflows/crypto-common.yml index 287c4953..1155de5c 100644 --- a/.github/workflows/crypto-common.yml +++ b/.github/workflows/crypto-common.yml @@ -35,7 +35,7 @@ jobs: target: ${{ matrix.target }} override: true profile: minimal - - run: cargo build --no-default-features --release --target ${{ matrix.target }} + - run: cargo build --target ${{ matrix.target }} test: runs-on: ubuntu-latest strategy: @@ -52,6 +52,5 @@ jobs: profile: minimal - run: cargo check --all-features - run: cargo test - - run: cargo test --features core-api - run: cargo test --features std - run: cargo test --all-features diff --git a/.github/workflows/crypto-mac.yml b/.github/workflows/crypto-mac.yml deleted file mode 100644 index 29186ab7..00000000 --- a/.github/workflows/crypto-mac.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: crypto-mac - -on: - pull_request: - paths: - - "crypto-mac/**" - - "Cargo.*" - push: - branches: master - -defaults: - run: - working-directory: crypto-mac - -env: - CARGO_INCREMENTAL: 0 - RUSTFLAGS: "-Dwarnings" - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.41.0 # MSRV - - stable - target: - - thumbv7em-none-eabi - - wasm32-unknown-unknown - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - override: true - profile: minimal - - run: cargo build --target ${{ matrix.target }} --release --no-default-features - - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features rand_core - - test: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.41.0 # MSRV - - stable - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.rust }} - override: true - profile: minimal - - run: cargo check --all-features - - run: cargo test - - run: cargo test --features core-api - - run: cargo test --features dev - - run: cargo test --features std - - run: cargo test --all-features diff --git a/.github/workflows/digest.yml b/.github/workflows/digest.yml index a518cd23..18b7a1bd 100644 --- a/.github/workflows/digest.yml +++ b/.github/workflows/digest.yml @@ -35,7 +35,7 @@ jobs: target: ${{ matrix.target }} override: true profile: minimal - - run: cargo build --no-default-features --release --target ${{ matrix.target }} + - run: cargo build --target ${{ matrix.target }} test: runs-on: ubuntu-latest strategy: @@ -51,9 +51,9 @@ jobs: override: true profile: minimal - run: cargo check --all-features - - run: cargo test --release - - run: cargo test --features core-api --release - - run: cargo test --features dev --release - - run: cargo test --features alloc --release - - run: cargo test --features std --release - - run: cargo test --all-features --release + - run: cargo test --no-default-features + - run: cargo test + - run: cargo test --features dev + - run: cargo test --features alloc + - run: cargo test --features std + - run: cargo test --all-features diff --git a/Cargo.lock b/Cargo.lock index fe92941c..cf9a6902 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,9 +39,9 @@ checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" [[package]] name = "blobby" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc52553543ecb104069b0ff9e0fcc5c739ad16202935528a112d974e8f1a4ee8" +checksum = "847495c209977a90e8aad588b959d0ca9f5dc228096d29a6bd3defd53f35eaec" [[package]] name = "block-buffer" @@ -54,19 +54,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.0-pre.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b13c429c0b48d55a541108e23c7795eb821ee65b50c2f719f4f7fc5a022dcf" -dependencies = [ - "block-padding", - "generic-array", -] - -[[package]] -name = "block-padding" -version = "0.3.0-pre" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3992d179d1dd2fa87869057217d43cf88ad31d4e44738d159a5a6caafdf63ae6" +checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" dependencies = [ "generic-array", ] @@ -88,7 +78,6 @@ name = "cipher" version = "0.4.0-pre" dependencies = [ "blobby", - "block-buffer 0.10.0-pre.4", "crypto-common", "generic-array", "rand_core", @@ -105,22 +94,10 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.0-pre" -dependencies = [ - "block-buffer 0.10.0-pre.4", - "generic-array", -] - -[[package]] -name = "crypto-mac" -version = "0.12.0-pre" +version = "0.1.0" dependencies = [ - "blobby", - "cipher", - "crypto-common", "generic-array", "rand_core", - "subtle", ] [[package]] @@ -134,11 +111,13 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.0-pre.3" +version = "0.10.0" dependencies = [ "blobby", + "block-buffer 0.10.0", "crypto-common", "generic-array", + "subtle", ] [[package]] @@ -203,9 +182,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.107" +version = "0.2.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219" +checksum = "f98a04dce437184842841303488f70d0188c5f51437d2a834dc097eafa909a01" [[package]] name = "lock_api" @@ -239,9 +218,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +checksum = "fb37d2df5df740e582f28f8560cf425f52bb267d872fe58358eadb554909f07a" dependencies = [ "unicode-xid", ] @@ -327,9 +306,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 10ed45ce..c0e881aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,6 @@ members = [ "aead", "cipher", "crypto-common", - "crypto-mac", "digest", "password-hash", "signature", diff --git a/README.md b/README.md index ff4332a3..05f79cd1 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,32 @@ -# RustCrypto: Traits [![Project Chat][chat-image]][chat-link] [![dependency status][deps-image]][deps-link] +# RustCrypto: Traits + +[![Project Chat][chat-image]][chat-link] [![dependency status][deps-image]][deps-link] ![Apache2/MIT licensed][license-image] Collection of traits which describe functionality of cryptographic primitives. ## Crates -| Crate name | Algorithm | Crates.io | Docs | Build Status | -|---------------------|-------------------------------|-----------|-------|--------------| -| [`aead`] | [Authenticated encryption] | [![crates.io](https://img.shields.io/crates/v/aead.svg)](https://crates.io/crates/aead) | [![Documentation](https://docs.rs/aead/badge.svg)](https://docs.rs/aead) | ![build](https://github.com/RustCrypto/traits/workflows/aead/badge.svg?branch=master&event=push) | -| [`async‑signature`] | [Digital signature] | [![crates.io](https://img.shields.io/crates/v/async-signature.svg)](https://crates.io/crates/async-signature) | [![Documentation](https://docs.rs/async-signature/badge.svg)](https://docs.rs/async-signature) | ![build](https://github.com/RustCrypto/traits/workflows/async-signature/badge.svg?branch=master&event=push) | -| [`cipher`] | [Block] and [stream cipher] | [![crates.io](https://img.shields.io/crates/v/cipher.svg)](https://crates.io/crates/cipher) | [![Documentation](https://docs.rs/cipher/badge.svg)](https://docs.rs/cipher) | ![build](https://github.com/RustCrypto/traits/workflows/cipher/badge.svg?branch=master&event=push) | -| [`crypto‑mac`] | [Message authentication code] | [![crates.io](https://img.shields.io/crates/v/crypto-mac.svg)](https://crates.io/crates/crypto-mac) | [![Documentation](https://docs.rs/crypto-mac/badge.svg)](https://docs.rs/crypto-mac) | ![build](https://github.com/RustCrypto/traits/workflows/crypto-mac/badge.svg?branch=master&event=push) | -| [`digest`] | [Cryptographic hash function] | [![crates.io](https://img.shields.io/crates/v/digest.svg)](https://crates.io/crates/digest) | [![Documentation](https://docs.rs/digest/badge.svg)](https://docs.rs/digest) | ![build](https://github.com/RustCrypto/traits/workflows/digest/badge.svg?branch=master&event=push) | -| [`elliptic‑curve`] | [Elliptic curve cryptography] | [![crates.io](https://img.shields.io/crates/v/elliptic-curve.svg)](https://crates.io/crates/elliptic-curve) | [![Documentation](https://docs.rs/elliptic-curve/badge.svg)](https://docs.rs/elliptic-curve) | ![build](https://github.com/RustCrypto/traits/workflows/elliptic-curve/badge.svg?branch=master&event=push) | -| [`password-hash`] | [Password hashing] | [![crates.io](https://img.shields.io/crates/v/password-hash.svg)](https://crates.io/crates/password-hash) | [![Documentation](https://docs.rs/password-hash/badge.svg)](https://docs.rs/password-hash) | ![build](https://github.com/RustCrypto/traits/workflows/password-hash/badge.svg?branch=master&event=push) | -| [`signature`] | [Digital signature] | [![crates.io](https://img.shields.io/crates/v/signature.svg)](https://crates.io/crates/signature) | [![Documentation](https://docs.rs/signature/badge.svg)](https://docs.rs/signature) | ![build](https://github.com/RustCrypto/traits/workflows/signature/badge.svg?branch=master&event=push) | -| [`universal‑hash`] | [Universal hash function] | [![crates.io](https://img.shields.io/crates/v/universal-hash.svg)](https://crates.io/crates/universal-hash) | [![Documentation](https://docs.rs/universal-hash/badge.svg)](https://docs.rs/universal-hash) | ![build](https://github.com/RustCrypto/traits/workflows/universal-hash/badge.svg?branch=master&event=push) | - -### Additional crates +| Name | Algorithm | Crates.io | Docs | MSRV | +|---------------------|-----------|:---------:|:-----:|:----:| +| [`aead`] | [Authenticated encryption] | [![crates.io](https://img.shields.io/crates/v/aead.svg)](https://crates.io/crates/aead) | [![Documentation](https://docs.rs/aead/badge.svg)](https://docs.rs/aead) | ![MSRV 1.41][msrv-1.41] | +| [`async‑signature`] | [Digital signature] | [![crates.io](https://img.shields.io/crates/v/async-signature.svg)](https://crates.io/crates/async-signature) | [![Documentation](https://docs.rs/async-signature/badge.svg)](https://docs.rs/async-signature) | ![MSRV 1.41][msrv-1.41] | +| [`cipher`] | [Block] and [stream cipher] | [![crates.io](https://img.shields.io/crates/v/cipher.svg)](https://crates.io/crates/cipher) | [![Documentation](https://docs.rs/cipher/badge.svg)](https://docs.rs/cipher) | ![MSRV 1.41][msrv-1.41] | +| [`crypto‑common`] | Common cryptographic traits | [![crates.io](https://img.shields.io/crates/v/crypto-common.svg)](https://crates.io/crates/crypto-common) | [![Documentation](https://docs.rs/crypto-common/badge.svg)](https://docs.rs/crypto-common) | ![MSRV 1.41][msrv-1.41] | +| [`digest`] | [Cryptographic hash function] | [![crates.io](https://img.shields.io/crates/v/digest.svg)](https://crates.io/crates/digest) | [![Documentation](https://docs.rs/digest/badge.svg)](https://docs.rs/digest) | ![MSRV 1.41][msrv-1.41] | +| [`elliptic‑curve`] | [Elliptic curve cryptography] | [![crates.io](https://img.shields.io/crates/v/elliptic-curve.svg)](https://crates.io/crates/elliptic-curve) | [![Documentation](https://docs.rs/elliptic-curve/badge.svg)](https://docs.rs/elliptic-curve) | ![MSRV 1.56][msrv-1.56] | +| [`password-hash`] | [Password hashing] | [![crates.io](https://img.shields.io/crates/v/password-hash.svg)](https://crates.io/crates/password-hash) | [![Documentation](https://docs.rs/password-hash/badge.svg)](https://docs.rs/password-hash) | ![MSRV 1.47][msrv-1.47] | +| [`signature`] | [Digital signature] | [![crates.io](https://img.shields.io/crates/v/signature.svg)](https://crates.io/crates/signature) | [![Documentation](https://docs.rs/signature/badge.svg)](https://docs.rs/signature) | ![MSRV 1.41][msrv-1.41] | +| [`universal‑hash`] | [Universal hash function] | [![crates.io](https://img.shields.io/crates/v/universal-hash.svg)](https://crates.io/crates/universal-hash) | [![Documentation](https://docs.rs/universal-hash/badge.svg)](https://docs.rs/universal-hash) | ![MSRV 1.41][msrv-1.41] | -| Crate name | Description | Crates.io | Docs | Build Status | -|------------|-------------------------|-----------|-------|--------------| -| [`crypto`] | Facade for trait crates | [![crates.io](https://img.shields.io/crates/v/crypto.svg)](https://crates.io/crates/crypto) | [![Documentation](https://docs.rs/crypto/badge.svg)](https://docs.rs/crypto) | ![build](https://github.com/RustCrypto/traits/workflows/crypto/badge.svg?branch=master&event=push) +### Additional Crates -### Minimum Supported Rust Version +| Crate name | Description | Crates.io | Docs | MSRV | +|------------|-------------------------|:---------:|:-----:|:----:| +| [`crypto`] | Facade for trait crates | [![crates.io](https://img.shields.io/crates/v/crypto.svg)](https://crates.io/crates/crypto) | [![Documentation](https://docs.rs/crypto/badge.svg)](https://docs.rs/crypto) | ![MSRV 1.56][msrv-1.56] | -All crates in this repository support **Rust 1.41** or higher unless otherwise noted. +### Minimum Supported Rust Version (MSRV) Policy -In future minimally supported version of Rust can be changed, but it will be done -with the minor version bump. +MSRV bumps are considered breaking changes and will be performed only with minor version bump. ## License @@ -40,29 +39,31 @@ 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. +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) [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg [chat-link]: https://rustcrypto.zulipchat.com/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg [deps-image]: https://deps.rs/repo/github/RustCrypto/traits/status.svg [deps-link]: https://deps.rs/repo/github/RustCrypto/traits +[msrv-1.41]: https://img.shields.io/badge/rustc-1.41.0+-blue.svg +[msrv-1.47]: https://img.shields.io/badge/rustc-1.47.0+-blue.svg +[msrv-1.56]: https://img.shields.io/badge/rustc-1.56.0+-blue.svg [//]: # (crates) -[`aead`]: https://github.com/RustCrypto/traits/tree/master/aead -[`async‑signature`]: https://github.com/RustCrypto/traits/tree/master/signature/async -[`cipher`]: https://github.com/RustCrypto/traits/tree/master/cipher -[`crypto‑mac`]: https://github.com/RustCrypto/traits/tree/master/crypto-mac -[`crypto`]: https://github.com/RustCrypto/traits/tree/master/crypto -[`digest`]: https://github.com/RustCrypto/traits/tree/master/digest -[`elliptic‑curve`]: https://github.com/RustCrypto/traits/tree/master/elliptic-curve -[`password-hash`]: https://github.com/RustCrypto/traits/tree/master/password-hash -[`signature`]: https://github.com/RustCrypto/traits/tree/master/signature -[`universal‑hash`]: https://github.com/RustCrypto/traits/tree/master/universal-hash +[`aead`]: ./aead +[`async‑signature`]: ./signature/async +[`cipher`]: ./cipher +[`crypto‑common`]: ./crypto-common +[`crypto`]: ./crypto +[`digest`]: ./digest +[`elliptic‑curve`]: ./elliptic-curve +[`password-hash`]: ./password-hash +[`signature`]: ./signature +[`universal‑hash`]: ./universal-hash [//]: # (algorithms) diff --git a/cipher/Cargo.toml b/cipher/Cargo.toml index 017e3c02..49a2b4c4 100644 --- a/cipher/Cargo.toml +++ b/cipher/Cargo.toml @@ -13,17 +13,13 @@ categories = ["cryptography", "no-std"] [dependencies] generic-array = "0.14" -crypto-common = { version = "=0.1.0-pre", path = "../crypto-common" } +crypto-common = { version = "0.1", path = "../crypto-common" } -# optional dependencies -block-buffer = { version = "=0.10.0-pre.4", features = ["block-padding"], optional = true } blobby = { version = "0.3", optional = true } rand_core = { version = "0.6", optional = true } [features] -default = ["mode_wrapper"] std = ["crypto-common/std", "rand_core/std"] -mode_wrapper = ["block-buffer"] dev = ["blobby"] [package.metadata.docs.rs] diff --git a/crypto-common/CHANGELOG.md b/crypto-common/CHANGELOG.md new file mode 100644 index 00000000..822c0bec --- /dev/null +++ b/crypto-common/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 (2021-12-07) +- Initial release diff --git a/crypto-common/Cargo.toml b/crypto-common/Cargo.toml index 3fa429be..b38f4bb9 100644 --- a/crypto-common/Cargo.toml +++ b/crypto-common/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crypto-common" description = "Common cryptographic traits" -version = "0.1.0-pre" +version = "0.1.0" authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -13,9 +13,7 @@ categories = ["cryptography", "no-std"] [dependencies] generic-array = "0.14" -block-buffer = { version = "0.10.0-pre.2", optional = true } +rand_core = { version = "0.6", optional = true } [features] -block-padding = ["block-buffer/block-padding"] -core-api = ["block-buffer"] std = [] diff --git a/crypto-common/src/core_api.rs b/crypto-common/src/core_api.rs deleted file mode 100644 index 7def7a07..00000000 --- a/crypto-common/src/core_api.rs +++ /dev/null @@ -1,132 +0,0 @@ -//! Low-level core API traits. -use super::{FixedOutput, FixedOutputReset, Reset, Update}; -use block_buffer::DigestBuffer; -use core::fmt; -use generic_array::{ArrayLength, GenericArray}; - -/// Trait for types which consume data in blocks. -#[cfg(feature = "core-api")] -#[cfg_attr(docsrs, doc(cfg(feature = "core-api")))] -pub trait UpdateCore { - /// Block size in bytes. - type BlockSize: ArrayLength; - /// Block buffer type over which value operates. - type Buffer: DigestBuffer; - - /// Update state using the provided data blocks. - fn update_blocks(&mut self, blocks: &[GenericArray]); -} - -/// Core trait for hash functions with fixed output size. -#[cfg(feature = "core-api")] -#[cfg_attr(docsrs, doc(cfg(feature = "core-api")))] -pub trait FixedOutputCore: UpdateCore { - /// Size of result in bytes. - type OutputSize: ArrayLength; - - /// Finalize state using remaining data stored in the provided block buffer, - /// write result into provided array using and leave value in a dirty state. - fn finalize_fixed_core( - &mut self, - buffer: &mut Self::Buffer, - out: &mut GenericArray, - ); -} - -/// Trait which stores algorithm name constant, used in `Debug` implementations. -pub trait AlgorithmName { - /// Write algorithm name into `f`. - fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result; -} - -/// Wrapper around [`UpdateCore`] implementations. -/// -/// It handles data buffering and implements the slice-based traits. -#[derive(Clone, Default)] -pub struct CoreWrapper { - core: T, - buffer: T::Buffer, -} - -impl CoreWrapper { - /// Create new wrapper from `core`. - #[inline] - pub fn from_core(core: T) -> Self { - let buffer = Default::default(); - Self { core, buffer } - } - - /// Decompose wrapper into inner parts. - #[inline] - pub fn decompose(self) -> (T, T::Buffer) { - let Self { core, buffer } = self; - (core, buffer) - } -} - -impl CoreWrapper { - /// Apply function to core and buffer, return its result, - /// and reset core and buffer. - pub fn apply_reset(&mut self, mut f: impl FnMut(&mut T, &mut T::Buffer) -> V) -> V { - let Self { core, buffer } = self; - let res = f(core, buffer); - core.reset(); - buffer.reset(); - res - } -} - -impl fmt::Debug for CoreWrapper { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - T::write_alg_name(f)?; - f.write_str(" { .. }") - } -} - -impl Reset for CoreWrapper { - #[inline] - fn reset(&mut self) { - self.core.reset(); - self.buffer.reset(); - } -} - -impl Update for CoreWrapper { - #[inline] - fn update(&mut self, input: &[u8]) { - let Self { core, buffer } = self; - buffer.digest_blocks(input, |blocks| core.update_blocks(blocks)); - } -} - -impl FixedOutput for CoreWrapper { - type OutputSize = D::OutputSize; - - #[inline] - fn finalize_into(mut self, out: &mut GenericArray) { - let Self { core, buffer } = &mut self; - core.finalize_fixed_core(buffer, out); - } -} - -impl FixedOutputReset for CoreWrapper { - #[inline] - fn finalize_into_reset(&mut self, out: &mut GenericArray) { - self.apply_reset(|core, buffer| core.finalize_fixed_core(buffer, out)); - } -} - -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] -impl std::io::Write for CoreWrapper { - #[inline] - fn write(&mut self, buf: &[u8]) -> std::io::Result { - Update::update(self, buf); - Ok(buf.len()) - } - - #[inline] - fn flush(&mut self) -> std::io::Result<()> { - Ok(()) - } -} diff --git a/crypto-common/src/lib.rs b/crypto-common/src/lib.rs index 5869bc67..a11626d3 100644 --- a/crypto-common/src/lib.rs +++ b/crypto-common/src/lib.rs @@ -12,56 +12,262 @@ #[cfg(feature = "std")] extern crate std; -use generic_array::{ArrayLength, GenericArray}; +use core::fmt; +use generic_array::{typenum::Unsigned, ArrayLength, GenericArray}; +#[cfg(feature = "rand_core")] +use rand_core::{CryptoRng, RngCore}; -#[cfg(feature = "core-api")] -#[cfg_attr(docsrs, doc(cfg(feature = "core-api")))] -pub use block_buffer; +/// Block on which [`BlockSizeUser`] implementors operate. +pub type Block = GenericArray::BlockSize>; +/// Output array of [`OutputSizeUser`] implementors. +pub type Output = GenericArray::OutputSize>; +/// Key used by [`KeySizeUser`] implementors. +pub type Key = GenericArray::KeySize>; +/// Initialization vector (nonce) used by [`IvSizeUser`] implementors. +pub type Iv = GenericArray::IvSize>; -#[cfg(feature = "core-api")] -#[cfg_attr(docsrs, doc(cfg(feature = "core-api")))] -pub mod core_api; +/// Types which process data in blocks. +pub trait BlockSizeUser { + /// Size of the block in bytes. + type BlockSize: ArrayLength + 'static; +} -/// Trait for types which consume data. -pub trait Update { - /// Update state using the provided data. - fn update(&mut self, data: &[u8]); +impl BlockSizeUser for &T { + type BlockSize = T::BlockSize; } -/// Trait for types which return fixed-sized result after finalization. -pub trait FixedOutput: Sized { - /// Size of result in bytes. - type OutputSize: ArrayLength; +impl BlockSizeUser for &mut T { + type BlockSize = T::BlockSize; +} - /// Consume value and write result into provided array. - fn finalize_into(self, out: &mut GenericArray); +/// Types which return data with the given size. +pub trait OutputSizeUser { + /// Size of the output in bytes. + type OutputSize: ArrayLength + 'static; +} + +/// Types which use key for initialization. +/// +/// Generally it's used indirectly via [`KeyInit`] or [`KeyIvInit`]. +pub trait KeySizeUser { + /// Key size in bytes. + type KeySize: ArrayLength + 'static; +} + +/// Types which use initialization vector (nonce) for initialization. +/// +/// Generally it's used indirectly via [`KeyIvInit`] or [`InnerIvInit`]. +pub trait IvSizeUser { + /// Initialization vector size in bytes. + type IvSize: ArrayLength + 'static; +} + +/// Types which use another type for initialization. +/// +/// Generally it's used indirectly via [`InnerInit`] or [`InnerIvInit`]. +pub trait InnerUser { + /// Inner type. + type Inner; +} - /// Retrieve result and consume the hasher instance. +/// Resettable types. +pub trait Reset { + /// Reset state to its initial value. + fn reset(&mut self); +} + +/// Trait which stores algorithm name constant, used in `Debug` implementations. +pub trait AlgorithmName { + /// Write algorithm name into `f`. + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result; +} + +/// Types which can be initialized from key. +pub trait KeyInit: KeySizeUser + Sized { + /// Create new value from fixed size key. + fn new(key: &Key) -> Self; + + /// Create new value from variable size key. + fn new_from_slice(key: &[u8]) -> Result { + if key.len() != Self::KeySize::to_usize() { + Err(InvalidLength) + } else { + Ok(Self::new(Key::::from_slice(key))) + } + } + + /// Generate random key using the provided [`CryptoRng`]. + #[cfg(feature = "rand_core")] + #[cfg_attr(docsrs, doc(cfg(feature = "rand_core")))] #[inline] - fn finalize_fixed(self) -> GenericArray { - let mut out = Default::default(); - self.finalize_into(&mut out); - out + fn generate_key(mut rng: impl CryptoRng + RngCore) -> Key { + let mut key = Key::::default(); + rng.fill_bytes(&mut key); + key } } -/// Trait for types which return fixed-sized result after finalization and reset -/// values into its initial state. -pub trait FixedOutputReset: FixedOutput + Reset { - /// Write result into provided array and reset value to its initial state. - fn finalize_into_reset(&mut self, out: &mut GenericArray); +/// Types which can be initialized from key and initialization vector (nonce). +pub trait KeyIvInit: KeySizeUser + IvSizeUser + Sized { + /// Create new value from fixed length key and nonce. + fn new(key: &Key, iv: &Iv) -> Self; + + /// Create new value from variable length key and nonce. + #[inline] + fn new_from_slices(key: &[u8], iv: &[u8]) -> Result { + let key_len = Self::KeySize::USIZE; + let iv_len = Self::IvSize::USIZE; + if key.len() != key_len || iv.len() != iv_len { + Err(InvalidLength) + } else { + Ok(Self::new( + Key::::from_slice(key), + Iv::::from_slice(iv), + )) + } + } + + /// Generate random key using the provided [`CryptoRng`]. + #[cfg(feature = "rand_core")] + #[cfg_attr(docsrs, doc(cfg(feature = "rand_core")))] + #[inline] + fn generate_key(mut rng: impl CryptoRng + RngCore) -> Key { + let mut key = Key::::default(); + rng.fill_bytes(&mut key); + key + } + + /// Generate random IV using the provided [`CryptoRng`]. + #[cfg(feature = "rand_core")] + #[cfg_attr(docsrs, doc(cfg(feature = "rand_core")))] + #[inline] + fn generate_iv(mut rng: impl CryptoRng + RngCore) -> Iv { + let mut iv = Iv::::default(); + rng.fill_bytes(&mut iv); + iv + } - /// Retrieve result and reset the hasher instance. + /// Generate random key and nonce using the provided [`CryptoRng`]. + #[cfg(feature = "rand_core")] + #[cfg_attr(docsrs, doc(cfg(feature = "rand_core")))] #[inline] - fn finalize_fixed_reset(&mut self) -> GenericArray { - let mut out = Default::default(); - self.finalize_into_reset(&mut out); - out + fn generate_key_iv(mut rng: impl CryptoRng + RngCore) -> (Key, Iv) { + (Self::generate_key(&mut rng), Self::generate_iv(&mut rng)) } } -/// Trait for resetting values to initial state. -pub trait Reset { - /// Reset value to its initial state. - fn reset(&mut self); +/// Types which can be initialized from another type (usually block ciphers). +/// +/// Usually used for initializing types from block ciphers. +pub trait InnerInit: InnerUser + Sized { + /// Initialize value from the `inner`. + fn inner_init(inner: Self::Inner) -> Self; } + +/// Types which can be initialized from another type and additional initialization +/// vector/nonce. +/// +/// Usually used for initializing types from block ciphers. +pub trait InnerIvInit: InnerUser + IvSizeUser + Sized { + /// Initialize value using `inner` and `iv` array. + fn inner_iv_init(inner: Self::Inner, iv: &Iv) -> Self; + + /// Initialize value using `inner` and `iv` slice. + fn inner_iv_slice_init(inner: Self::Inner, iv: &[u8]) -> Result { + if iv.len() != Self::IvSize::to_usize() { + Err(InvalidLength) + } else { + Ok(Self::inner_iv_init(inner, Iv::::from_slice(iv))) + } + } + + /// Generate random IV using the provided [`CryptoRng`]. + #[cfg(feature = "rand_core")] + #[cfg_attr(docsrs, doc(cfg(feature = "rand_core")))] + #[inline] + fn generate_iv(mut rng: impl CryptoRng + RngCore) -> Iv { + let mut iv = Iv::::default(); + rng.fill_bytes(&mut iv); + iv + } +} + +impl KeySizeUser for T +where + T: InnerUser, + T::Inner: KeySizeUser, +{ + type KeySize = ::KeySize; +} + +impl KeyIvInit for T +where + T: InnerIvInit, + T::Inner: KeyInit, +{ + #[inline] + fn new(key: &Key, iv: &Iv) -> Self { + Self::inner_iv_init(T::Inner::new(key), iv) + } + + #[inline] + fn new_from_slices(key: &[u8], iv: &[u8]) -> Result { + T::Inner::new_from_slice(key).and_then(|i| T::inner_iv_slice_init(i, iv)) + } +} + +impl KeyInit for T +where + T: InnerInit, + T::Inner: KeyInit, +{ + #[inline] + fn new(key: &Key) -> Self { + Self::inner_init(T::Inner::new(key)) + } + + #[inline] + fn new_from_slice(key: &[u8]) -> Result { + T::Inner::new_from_slice(key) + .map_err(|_| InvalidLength) + .map(Self::inner_init) + } +} + +// Unfortunately this blanket impl is impossible without mutually +// exclusive traits, see: https://github.com/rust-lang/rfcs/issues/1053 +// or at the very least without: https://github.com/rust-lang/rust/issues/20400 +/* +impl KeyIvInit for T +where + T: InnerInit, + T::Inner: KeyIvInit, +{ + #[inline] + fn new(key: &Key, iv: &Iv) -> Self { + Self::inner_init(T::Inner::new(key, iv)) + } + + #[inline] + fn new_from_slices(key: &[u8], iv: &[u8]) -> Result { + T::Inner::new_from_slice(key) + .map_err(|_| InvalidLength) + .map(Self::inner_init) + } +} +*/ + +/// The error type returned when key and/or IV used in the [`KeyInit`], +/// [`KeyIvInit`], and [`InnerIvInit`] slice-based methods had +/// an invalid length. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct InvalidLength; + +impl fmt::Display for InvalidLength { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str("Invalid Length") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for InvalidLength {} diff --git a/crypto-mac/CHANGELOG.md b/crypto-mac/CHANGELOG.md deleted file mode 100644 index 7488081b..00000000 --- a/crypto-mac/CHANGELOG.md +++ /dev/null @@ -1,104 +0,0 @@ -# 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). - -## [Unreleased] -### Added -- Re-export `rand_core` ([#683]) - -[#683]: https://github.com/RustCrypto/traits/pull/683 - -## 0.11.1 (2021-07-20) -### Changed -- Pin `subtle` dependency to v2.4 ([#691]) - -[#691]: https://github.com/RustCrypto/traits/pull/691 - -## 0.11.0 (2021-04-28) -### Added -- `generate_key` method to `New*` trait ([#513]) - -### Changed -- Renamed `new_var` to `new_from_slice` ([#442]) -- Bump `cipher` dependency to v0.3 ([#621]) - -[#442]: https://github.com/RustCrypto/traits/pull/442 -[#513]: https://github.com/RustCrypto/traits/pull/513 -[#621]: https://github.com/RustCrypto/traits/pull/621 - -## 0.10.1 (2021-07-20) -### Changed -- Pin `subtle` dependency to v2.4 ([#690]) - -[#690]: https://github.com/RustCrypto/traits/pull/690 - -## 0.10.0 (2020-10-15) -### Changed -- Replace `block-cipher` crate with new `cipher` crate ([#337], [#338]) - -[#338]: https://github.com/RustCrypto/traits/pull/338 -[#337]: https://github.com/RustCrypto/traits/pull/337 - -## 0.9.1 (2020-08-12) -### Added -- Re-export the `block-cipher` crate ([#257]) - -[#257]: https://github.com/RustCrypto/traits/pull/257 - -## 0.9.0 (2020-08-10) -### Added -- `FromBlockCipher` trait and blanket implementation of the `NewMac` trait -for it ([#217]) - -### Changed -- Updated test vectors storage to `blobby v0.3` ([#217]) - -### Removed -- `impl_write!` macro ([#217]) - -[#217]: https://github.com/RustCrypto/traits/pull/217 - -## 0.8.0 (2020-06-04) -### Added -- `impl_write!` macro ([#134]) - -### Changed -- Bump `generic-array` dependency to v0.14 ([#144]) -- Split `Mac` initialization into `NewMac` trait ([#133]) -- Rename `MacResult` => `Output`, `code` => `into_bytes` ([#114]) -- Rename `Input::input` to `Update::update` ([#111]) -- Update to 2018 edition ([#108]) -- Bump `subtle` dependency from v1.0 to v2.0 ([#33]) - -[#144]: https://github.com/RustCrypto/traits/pull/95 -[#134]: https://github.com/RustCrypto/traits/pull/134 -[#133]: https://github.com/RustCrypto/traits/pull/133 -[#114]: https://github.com/RustCrypto/traits/pull/114 -[#111]: https://github.com/RustCrypto/traits/pull/111 -[#108]: https://github.com/RustCrypto/traits/pull/108 -[#33]: https://github.com/RustCrypto/traits/pull/33 - -## 0.7.0 (2018-10-01) - -## 0.6.2 (2018-06-21) - -## 0.6.1 (2018-06-20) - -## 0.6.0 (2017-11-26) - -## 0.5.2 (2017-11-20) - -## 0.5.1 (2017-11-15) - -## 0.5.0 (2017-11-14) - -## 0.4.0 (2017-06-12) - -## 0.3.0 (2017-05-14) - -## 0.2.0 (2017-05-14) - -## 0.1.0 (2016-10-14) diff --git a/crypto-mac/Cargo.toml b/crypto-mac/Cargo.toml deleted file mode 100644 index 725f75fa..00000000 --- a/crypto-mac/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "crypto-mac" -description = "Trait for Message Authentication Code (MAC) algorithms" -version = "0.12.0-pre" # Also update html_root_url in lib.rs when bumping this -authors = ["RustCrypto Developers"] -license = "MIT OR Apache-2.0" -readme = "README.md" -edition = "2018" -documentation = "https://docs.rs/crypto-mac" -repository = "https://github.com/RustCrypto/traits" -keywords = ["crypto", "mac"] -categories = ["cryptography", "no-std"] - -[dependencies] -generic-array = "0.14" -crypto-common = { version = "=0.1.0-pre", path = "../crypto-common" } -cipher = { version = "=0.4.0-pre", path = "../cipher" } -subtle = { version = "=2.4", default-features = false } - -blobby = { version = "0.3", optional = true } -rand_core = { version = "0.6", optional = true } - -[features] -dev = ["blobby"] -core-api = ["crypto-common/core-api"] -std = ["crypto-common/std", "rand_core/std"] - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] diff --git a/crypto-mac/LICENSE-APACHE b/crypto-mac/LICENSE-APACHE deleted file mode 100644 index 78173fa2..00000000 --- a/crypto-mac/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - 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/crypto-mac/LICENSE-MIT b/crypto-mac/LICENSE-MIT deleted file mode 100644 index 8dcb85b3..00000000 --- a/crypto-mac/LICENSE-MIT +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2017 Artyom Pavlov - -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/crypto-mac/README.md b/crypto-mac/README.md deleted file mode 100644 index 889d6c24..00000000 --- a/crypto-mac/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# RustCrypto: Message Authentication Code Traits - -[![crate][crate-image]][crate-link] -[![Docs][docs-image]][docs-link] -![Apache2/MIT licensed][license-image] -![Rust Version][rustc-image] -[![Project Chat][chat-image]][chat-link] -[![Build Status][build-image]][build-link] - -Traits for [Message Authentication Code] (MAC) algorithms. - -See [RustCrypto/MACs] for implementations which use this trait. - -[Documentation][docs-link] - -## Minimum Supported Rust Version - -Rust **1.41** or higher. - -Minimum supported Rust version can be changed in the future, but it will be -done with a minor version bump. - -## SemVer Policy - -- All on-by-default features of this library are covered by SemVer -- MSRV is considered exempt from SemVer as noted above - -## 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/crypto-mac.svg -[crate-link]: https://crates.io/crates/crypto-mac -[docs-image]: https://docs.rs/crypto-mac/badge.svg -[docs-link]: https://docs.rs/crypto-mac/ -[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.41+-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/traits/workflows/crypto-mac/badge.svg?branch=master&event=push -[build-link]: https://github.com/RustCrypto/traits/actions?query=workflow%3Acrypto-mac - -[//]: # (general links) - -[Message Authentication Code]: https://en.wikipedia.org/wiki/Message_authentication_code -[RustCrypto/MACs]: https://github.com/RustCrypto/MACs diff --git a/crypto-mac/src/core_api.rs b/crypto-mac/src/core_api.rs deleted file mode 100644 index 7f7e6a6e..00000000 --- a/crypto-mac/src/core_api.rs +++ /dev/null @@ -1,2 +0,0 @@ -//! Low-level core API traits. -pub use crypto_common::core_api::{AlgorithmName, CoreWrapper, FixedOutputCore, UpdateCore}; diff --git a/crypto-mac/src/dev.rs b/crypto-mac/src/dev.rs deleted file mode 100644 index 1c397e63..00000000 --- a/crypto-mac/src/dev.rs +++ /dev/null @@ -1,148 +0,0 @@ -//! Development-related functionality - -pub use blobby; - -/// Define test -#[macro_export] -#[cfg_attr(docsrs, doc(cfg(feature = "dev")))] -macro_rules! new_test { - ($name:ident, $test_name:expr, $mac:ty) => { - #[test] - fn $name() { - use crypto_mac::dev::blobby::Blob3Iterator; - use crypto_mac::{Mac, NewMac}; - - fn run_test(key: &[u8], input: &[u8], tag: &[u8]) -> Option<&'static str> { - let mut mac = <$mac as NewMac>::new_from_slice(key).unwrap(); - mac.update(input); - let result = mac.finalize_reset(); - if &result.into_bytes()[..] != tag { - return Some("whole message"); - } - // test if reset worked correctly - mac.update(input); - if mac.verify(&tag).is_err() { - return Some("after reset"); - } - - let mut mac = <$mac as NewMac>::new_from_slice(key).unwrap(); - // test reading byte by byte - for i in 0..input.len() { - mac.update(&input[i..i + 1]); - } - if let Err(_) = mac.verify(tag) { - return Some("message byte-by-byte"); - } - None - } - - let data = include_bytes!(concat!("data/", $test_name, ".blb")); - - for (i, row) in Blob3Iterator::new(data).unwrap().enumerate() { - let [key, input, tag] = row.unwrap(); - if let Some(desc) = run_test(key, input, tag) { - panic!( - "\n\ - Failed test №{}: {}\n\ - key:\t{:?}\n\ - input:\t{:?}\n\ - tag:\t{:?}\n", - i, desc, key, input, tag, - ); - } - } - } - }; -} - -/// Define test that allows for truncated tag. -#[macro_export] -#[cfg_attr(docsrs, doc(cfg(feature = "dev")))] -macro_rules! new_trunc_test { - ($name:ident, $test_name:expr, $mac:ty) => { - #[test] - fn $name() { - use crypto_mac::dev::blobby::Blob3Iterator; - use crypto_mac::generic_array::typenum::Unsigned; - use crypto_mac::{Mac, NewMac}; - - fn run_test(key: &[u8], input: &[u8], tag: &[u8]) -> Option<&'static str> { - let mut mac = <$mac as NewMac>::new_from_slice(key).unwrap(); - mac.update(input); - let result = mac.finalize_reset(); - let mut len = <$mac as Mac>::OutputSize::to_usize(); - if tag.len() < len { - len = tag.len(); - } - if &result.into_bytes()[..len] != tag { - return Some("whole message"); - } - // test if reset worked correctly - mac.update(input); - let result = mac.finalize(); - if &result.into_bytes()[..len] != tag { - return Some("after reset"); - } - - let mut mac = <$mac as NewMac>::new_from_slice(key).unwrap(); - // test reading byte by byte - for i in 0..input.len() { - mac.update(&input[i..i + 1]); - } - let result = mac.finalize(); - if &result.into_bytes()[..len] != tag { - return Some("message byte-by-byte"); - } - None - } - - let data = include_bytes!(concat!("data/", $test_name, ".blb")); - - for (i, row) in Blob3Iterator::new(data).unwrap().enumerate() { - let [key, input, tag] = row.unwrap(); - if let Some(desc) = run_test(key, input, tag) { - panic!( - "\n\ - Failed test №{}: {}\n\ - key:\t{:?}\n\ - input:\t{:?}\n\ - tag:\t{:?}\n", - i, desc, key, input, tag, - ); - } - } - } - }; -} - -/// Define benchmark -#[macro_export] -#[cfg_attr(docsrs, doc(cfg(feature = "dev")))] -macro_rules! bench { - ($name:ident, $engine:path, $bs:expr) => { - #[bench] - fn $name(b: &mut Bencher) { - let key = Default::default(); - let mut mac = <$engine>::new(&key); - let data = [0; $bs]; - - b.iter(|| { - mac.update(&data); - }); - - b.bytes = $bs; - } - }; - - ($engine:path) => { - extern crate test; - - use crypto_mac::{Mac, NewMac}; - use test::Bencher; - - $crate::bench!(bench1_10, $engine, 10); - $crate::bench!(bench2_100, $engine, 100); - $crate::bench!(bench3_1000, $engine, 1000); - $crate::bench!(bench3_10000, $engine, 10000); - }; -} diff --git a/crypto-mac/src/lib.rs b/crypto-mac/src/lib.rs deleted file mode 100644 index e5e2f2ba..00000000 --- a/crypto-mac/src/lib.rs +++ /dev/null @@ -1,123 +0,0 @@ -//! This crate provides trait for Message Authentication Code (MAC) algorithms. - -#![no_std] -#![cfg_attr(docsrs, feature(doc_cfg))] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", - html_root_url = "https://docs.rs/crypto-mac/0.12.0-pre" -)] -#![forbid(unsafe_code)] -#![warn(missing_docs, rust_2018_idioms)] - -#[cfg(feature = "std")] -extern crate std; - -#[cfg(feature = "rand_core")] -#[cfg_attr(docsrs, doc(cfg(feature = "rand_core")))] -pub use rand_core; - -#[cfg(feature = "cipher")] -pub use cipher; -#[cfg(feature = "cipher")] -use cipher::{BlockCipher, NewBlockCipher}; - -#[cfg(feature = "dev")] -#[cfg_attr(docsrs, doc(cfg(feature = "dev")))] -pub mod dev; - -#[cfg(feature = "core-api")] -#[cfg_attr(docsrs, doc(cfg(feature = "core-api")))] -pub mod core_api; - -pub use cipher::{errors::InvalidLength, FromKey}; -pub use crypto_common::{FixedOutput, FixedOutputReset, Reset, Update}; -pub use generic_array::{self, typenum::consts}; - -use core::fmt; -use generic_array::GenericArray; -use subtle::{Choice, ConstantTimeEq}; - -/// Key for an algorithm that implements [`FromKey`]. -pub type Key = GenericArray::KeySize>; - -/// Convinience super-trait covering functionality of Message Authentication algorithms. -pub trait Mac: FromKey + Update + FixedOutput { - /// Obtain the result of a [`Mac`] computation as a [`Output`] and consume - /// [`Mac`] instance. - fn finalize(self) -> Output { - Output::new(self.finalize_fixed()) - } - - /// Obtain the result of a [`Mac`] computation as a [`Output`] and reset - /// [`Mac`] instance. - fn finalize_reset(&mut self) -> Output - where - Self: FixedOutputReset, - { - Output::new(self.finalize_fixed_reset()) - } - - /// Check if tag/code value is correct for the processed input. - fn verify(self, tag: &[u8]) -> Result<(), MacError> { - let choice = self.finalize().bytes.ct_eq(tag); - - if choice.unwrap_u8() == 1 { - Ok(()) - } else { - Err(MacError) - } - } -} - -impl Mac for T {} - -/// [`Output`] is a thin wrapper around bytes array which provides a safe `Eq` -/// implementation that runs in a fixed time. -#[derive(Clone)] -pub struct Output { - bytes: GenericArray, -} - -impl Output { - /// Create a new MAC [`Output`]. - pub fn new(bytes: GenericArray) -> Output { - Output { bytes } - } - - /// Get the MAC tag/code value as a byte array. - /// - /// Be very careful using this method, since incorrect use of the tag value - /// may permit timing attacks which defeat the security provided by the - /// [`Mac`] trait. - pub fn into_bytes(self) -> GenericArray { - self.bytes - } -} - -impl ConstantTimeEq for Output { - fn ct_eq(&self, other: &Self) -> Choice { - self.bytes.ct_eq(&other.bytes) - } -} - -impl PartialEq for Output { - fn eq(&self, x: &Output) -> bool { - self.ct_eq(x).unwrap_u8() == 1 - } -} - -impl Eq for Output {} - -/// Error type for signaling failed MAC verification -#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] -pub struct MacError; - -impl fmt::Display for MacError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("failed MAC verification") - } -} - -#[cfg(feature = "std")] -impl std::error::Error for MacError {} diff --git a/digest/CHANGELOG.md b/digest/CHANGELOG.md index 0b8547c6..60dfb44d 100644 --- a/digest/CHANGELOG.md +++ b/digest/CHANGELOG.md @@ -5,13 +5,13 @@ 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.0 (2021-01-18) -### Breaking changes +## 0.10.0 (2021-12-07) +### Changed - Dirty traits are removed and instead block-level traits are introduced. -Variable output traits are removed as well in favor of fixed output tratis, -implementors of variable output hashes are expected to be generic over -output size. ([#380]) +Variable output traits reworked and now support both run and compile time selection of output size. ([#380], [#819]) +- The `crypto-mac` traits are reworked and merged in. ([#819]) +[#819]: https://github.com/RustCrypto/traits/pull/819 [#380]: https://github.com/RustCrypto/traits/pull/380 ## 0.9.0 (2020-06-09) diff --git a/digest/Cargo.toml b/digest/Cargo.toml index 61852b9d..f7359bb2 100644 --- a/digest/Cargo.toml +++ b/digest/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "digest" description = "Traits for cryptographic hash functions" -version = "0.10.0-pre.3" +version = "0.10.0" authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -13,16 +13,19 @@ categories = ["cryptography", "no-std"] [dependencies] generic-array = "0.14" -crypto-common = { version = "=0.1.0-pre", path = "../crypto-common" } +crypto-common = { version = "0.1", path = "../crypto-common" } +block-buffer = { version = "0.10", optional = true } +subtle = { version = "=2.4", default-features = false, optional = true } blobby = { version = "0.3", optional = true } [features] +default = ["core-api"] +core-api = ["block-buffer"] # Enable Core API traits +mac = ["subtle"] # Enable MAC traits alloc = [] std = ["alloc", "crypto-common/std"] dev = ["blobby"] -core-api = ["crypto-common/core-api"] -block-padding = ["crypto-common/block-padding"] [package.metadata.docs.rs] all-features = true diff --git a/digest/src/core_api.rs b/digest/src/core_api.rs index 63e573d3..6783e198 100644 --- a/digest/src/core_api.rs +++ b/digest/src/core_api.rs @@ -1,80 +1,117 @@ -//! Low-level core API traits. +//! Low-level traits operating on blocks and wrappers around them. //! //! Usage of traits in this module in user code is discouraged. Instead use //! core algorithm wrapped by the wrapper types, which implement the //! higher-level traits. use crate::InvalidOutputSize; -use crate::{ExtendableOutput, Reset}; -use generic_array::{ArrayLength, GenericArray}; +use generic_array::typenum::{IsLess, Le, NonZero, U256}; -pub use crypto_common::core_api::{AlgorithmName, CoreWrapper, FixedOutputCore, UpdateCore}; +pub use crypto_common::{AlgorithmName, Block, BlockSizeUser, OutputSizeUser, Reset}; + +use block_buffer::{BlockBuffer, BufferKind}; +use crypto_common::Output; mod ct_variable; mod rt_variable; +mod wrapper; mod xof_reader; pub use ct_variable::CtVariableCoreWrapper; pub use rt_variable::RtVariableCoreWrapper; +pub use wrapper::{CoreProxy, CoreWrapper}; pub use xof_reader::XofReaderCoreWrapper; +/// Buffer type used by type which implements [`BufferKindUser`]. +pub type Buffer = + BlockBuffer<::BlockSize, ::BufferKind>; + +/// Types which consume data in blocks. +pub trait UpdateCore: BlockSizeUser { + /// Update state using the provided data blocks. + fn update_blocks(&mut self, blocks: &[Block]); +} + +/// Types which use [`BlockBuffer`] functionality. +pub trait BufferKindUser: BlockSizeUser { + /// Block buffer kind over which type operates. + type BufferKind: BufferKind; +} + +/// Core trait for hash functions with fixed output size. +pub trait FixedOutputCore: UpdateCore + BufferKindUser + OutputSizeUser +where + Self::BlockSize: IsLess, + Le: NonZero, +{ + /// Finalize state using remaining data stored in the provided block buffer, + /// write result into provided array and leave `self` in a dirty state. + fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output); +} + /// Core trait for hash functions with extendable (XOF) output size. -pub trait ExtendableOutputCore: UpdateCore { +pub trait ExtendableOutputCore: UpdateCore + BufferKindUser +where + Self::BlockSize: IsLess, + Le: NonZero, +{ /// XOF reader core state. type ReaderCore: XofReaderCore; /// Retrieve XOF reader using remaining data stored in the block buffer /// and leave hasher in a dirty state. - fn finalize_xof_core(&mut self, buffer: &mut Self::Buffer) -> Self::ReaderCore; + fn finalize_xof_core(&mut self, buffer: &mut Buffer) -> Self::ReaderCore; } /// Core reader trait for extendable-output function (XOF) result. -pub trait XofReaderCore { - /// Block size in bytes. - type BlockSize: ArrayLength; - +pub trait XofReaderCore: BlockSizeUser { /// Read next XOF block. - fn read_block(&mut self) -> GenericArray; + fn read_block(&mut self) -> Block; } /// Core trait for hash functions with variable output size. -pub trait VariableOutputCore: UpdateCore + Sized { - /// Maximum output size. - type MaxOutputSize: ArrayLength; +/// +/// Maximum output size is equal to [`OutputSizeUser::OutputSize`]. +/// Users are expected to truncate result returned by the +/// [`finalize_variable_core`] to `output_size` passed to the [`new`] method +/// during construction. Truncation side is defined by the [`TRUNC_SIDE`] +/// associated constant. +/// +/// [`finalize_variable_core`]: VariableOutputCore::finalize_variable_core +/// [`new`]: VariableOutputCore::new +/// [`TRUNC_SIDE`]: VariableOutputCore::TRUNC_SIDE +pub trait VariableOutputCore: UpdateCore + OutputSizeUser + BufferKindUser + Sized +where + Self::BlockSize: IsLess, + Le: NonZero, +{ + /// Side which should be used in a truncated result. + const TRUNC_SIDE: TruncSide; /// Initialize hasher state for given output size. /// - /// Returns [`InvalidOutputSize`] if `output_size` is equal to zero or - /// bigger than `Self::MaxOutputSize`. + /// Returns [`InvalidOutputSize`] if `output_size` is not valid for + /// the algorithm, e.g. if it's bigger than the [`OutputSize`] + /// associated type. + /// + /// [`OutputSize`]: OutputSizeUser::OutputSize fn new(output_size: usize) -> Result; - /// Finalize hasher and return result of lenght `output_size` via closure `f`. + /// Finalize hasher and write full hashing result into the `out` buffer. /// - /// `output_size` must be equal to `output_size` used during construction. - fn finalize_variable_core( - &mut self, - buffer: &mut Self::Buffer, - output_size: usize, - f: impl FnOnce(&[u8]), - ); + /// The result must be truncated to `output_size` used during hasher + /// construction. Truncation side is defined by the [`TRUNC_SIDE`] + /// associated constant. + /// + /// [`TRUNC_SIDE`]: VariableOutputCore::TRUNC_SIDE + fn finalize_variable_core(&mut self, buffer: &mut Buffer, out: &mut Output); } -impl ExtendableOutput for CoreWrapper { - type Reader = XofReaderCoreWrapper; - - #[inline] - fn finalize_xof(self) -> Self::Reader { - let (mut core, mut buffer) = self.decompose(); - let core = core.finalize_xof_core(&mut buffer); - let buffer = Default::default(); - Self::Reader { core, buffer } - } - - #[inline] - fn finalize_xof_reset(&mut self) -> Self::Reader { - self.apply_reset(|core, buffer| { - let core = core.finalize_xof_core(buffer); - let buffer = Default::default(); - Self::Reader { core, buffer } - }) - } +/// Type which used for defining truncation side in the [`VariableOutputCore`] +/// trait. +#[derive(Copy, Clone, Debug)] +pub enum TruncSide { + /// Truncate left side, i.e. `&out[..n]`. + Left, + /// Truncate right side, i.e. `&out[m..]`. + Right, } diff --git a/digest/src/core_api/ct_variable.rs b/digest/src/core_api/ct_variable.rs index 11474326..c6954491 100644 --- a/digest/src/core_api/ct_variable.rs +++ b/digest/src/core_api/ct_variable.rs @@ -1,7 +1,14 @@ -use super::{AlgorithmName, FixedOutputCore, Reset, UpdateCore, VariableOutputCore}; +use super::{ + AlgorithmName, Buffer, BufferKindUser, FixedOutputCore, Reset, TruncSide, UpdateCore, + VariableOutputCore, +}; +use crate::HashMarker; +#[cfg(feature = "mac")] +use crate::MacMarker; use core::{fmt, marker::PhantomData}; +use crypto_common::{Block, BlockSizeUser, OutputSizeUser}; use generic_array::{ - typenum::{IsLessOrEqual, LeEq, NonZero}, + typenum::{IsLess, IsLessOrEqual, Le, LeEq, NonZero, U256}, ArrayLength, GenericArray, }; @@ -11,58 +18,121 @@ use generic_array::{ pub struct CtVariableCoreWrapper where T: VariableOutputCore, - OutSize: ArrayLength + IsLessOrEqual, - LeEq: NonZero, + OutSize: ArrayLength + IsLessOrEqual, + LeEq: NonZero, + T::BlockSize: IsLess, + Le: NonZero, { inner: T, _out: PhantomData, } -impl UpdateCore for CtVariableCoreWrapper +impl HashMarker for CtVariableCoreWrapper +where + T: VariableOutputCore + HashMarker, + OutSize: ArrayLength + IsLessOrEqual, + LeEq: NonZero, + T::BlockSize: IsLess, + Le: NonZero, +{ +} + +#[cfg(feature = "mac")] +impl MacMarker for CtVariableCoreWrapper +where + T: VariableOutputCore + MacMarker, + OutSize: ArrayLength + IsLessOrEqual, + LeEq: NonZero, + T::BlockSize: IsLess, + Le: NonZero, +{ +} + +impl BlockSizeUser for CtVariableCoreWrapper where T: VariableOutputCore, - OutSize: ArrayLength + IsLessOrEqual, - LeEq: NonZero, + OutSize: ArrayLength + IsLessOrEqual, + LeEq: NonZero, + T::BlockSize: IsLess, + Le: NonZero, { type BlockSize = T::BlockSize; - type Buffer = T::Buffer; +} +impl UpdateCore for CtVariableCoreWrapper +where + T: VariableOutputCore, + OutSize: ArrayLength + IsLessOrEqual, + LeEq: NonZero, + T::BlockSize: IsLess, + Le: NonZero, +{ #[inline] - fn update_blocks(&mut self, blocks: &[GenericArray]) { + fn update_blocks(&mut self, blocks: &[Block]) { self.inner.update_blocks(blocks); } } -impl FixedOutputCore for CtVariableCoreWrapper +impl OutputSizeUser for CtVariableCoreWrapper where T: VariableOutputCore, - OutSize: ArrayLength + IsLessOrEqual, - LeEq: NonZero, + OutSize: ArrayLength + IsLessOrEqual + 'static, + LeEq: NonZero, + T::BlockSize: IsLess, + Le: NonZero, { type OutputSize = OutSize; +} +impl BufferKindUser for CtVariableCoreWrapper +where + T: VariableOutputCore, + OutSize: ArrayLength + IsLessOrEqual, + LeEq: NonZero, + T::BlockSize: IsLess, + Le: NonZero, +{ + type BufferKind = T::BufferKind; +} + +impl FixedOutputCore for CtVariableCoreWrapper +where + T: VariableOutputCore, + OutSize: ArrayLength + IsLessOrEqual + 'static, + LeEq: NonZero, + T::BlockSize: IsLess, + Le: NonZero, +{ #[inline] fn finalize_fixed_core( &mut self, - buffer: &mut Self::Buffer, + buffer: &mut Buffer, out: &mut GenericArray, ) { - self.inner - .finalize_variable_core(buffer, out.len(), |r| out.copy_from_slice(r)); + let mut full_res = Default::default(); + self.inner.finalize_variable_core(buffer, &mut full_res); + let n = out.len(); + let m = full_res.len() - n; + match T::TRUNC_SIDE { + TruncSide::Left => out.copy_from_slice(&full_res[..n]), + TruncSide::Right => out.copy_from_slice(&full_res[m..]), + } } } impl Default for CtVariableCoreWrapper where T: VariableOutputCore, - OutSize: ArrayLength + IsLessOrEqual, - LeEq: NonZero, + OutSize: ArrayLength + IsLessOrEqual, + LeEq: NonZero, + T::BlockSize: IsLess, + Le: NonZero, { #[inline] fn default() -> Self { Self { inner: T::new(OutSize::USIZE).unwrap(), - _out: Default::default(), + _out: PhantomData, } } } @@ -70,8 +140,10 @@ where impl Reset for CtVariableCoreWrapper where T: VariableOutputCore, - OutSize: ArrayLength + IsLessOrEqual, - LeEq: NonZero, + OutSize: ArrayLength + IsLessOrEqual, + LeEq: NonZero, + T::BlockSize: IsLess, + Le: NonZero, { #[inline] fn reset(&mut self) { @@ -82,8 +154,10 @@ where impl AlgorithmName for CtVariableCoreWrapper where T: VariableOutputCore + AlgorithmName, - OutSize: ArrayLength + IsLessOrEqual, - LeEq: NonZero, + OutSize: ArrayLength + IsLessOrEqual, + LeEq: NonZero, + T::BlockSize: IsLess, + Le: NonZero, { fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { T::write_alg_name(f)?; diff --git a/digest/src/core_api/rt_variable.rs b/digest/src/core_api/rt_variable.rs index c9af06ae..0c2233fe 100644 --- a/digest/src/core_api/rt_variable.rs +++ b/digest/src/core_api/rt_variable.rs @@ -1,8 +1,11 @@ -use super::{AlgorithmName, UpdateCore, VariableOutputCore}; -use crate::{InvalidOutputSize, Reset, Update, VariableOutput}; +use super::{AlgorithmName, TruncSide, UpdateCore, VariableOutputCore}; +#[cfg(feature = "mac")] +use crate::MacMarker; +use crate::{HashMarker, InvalidBufferSize}; +use crate::{InvalidOutputSize, Reset, Update, VariableOutput, VariableOutputReset}; +use block_buffer::BlockBuffer; use core::fmt; -use crypto_common::block_buffer::DigestBuffer; -use generic_array::typenum::Unsigned; +use generic_array::typenum::{IsLess, Le, NonZero, Unsigned, U256}; /// Wrapper around [`VariableOutputCore`] which selects output size /// at run time. @@ -10,32 +13,78 @@ use generic_array::typenum::Unsigned; pub struct RtVariableCoreWrapper where T: VariableOutputCore + UpdateCore, + T::BlockSize: IsLess, + Le: NonZero, { core: T, - buffer: T::Buffer, + buffer: BlockBuffer, output_size: usize, } +impl RtVariableCoreWrapper +where + T: VariableOutputCore, + T::BlockSize: IsLess, + Le: NonZero, +{ + #[inline] + fn finalize_dirty(&mut self, out: &mut [u8]) -> Result<(), InvalidBufferSize> { + let Self { + core, + buffer, + output_size, + } = self; + if out.len() != *output_size || out.len() > Self::MAX_OUTPUT_SIZE { + return Err(InvalidBufferSize); + } + let mut full_res = Default::default(); + core.finalize_variable_core(buffer, &mut full_res); + let n = out.len(); + let m = full_res.len() - n; + match T::TRUNC_SIDE { + TruncSide::Left => out.copy_from_slice(&full_res[..n]), + TruncSide::Right => out.copy_from_slice(&full_res[m..]), + } + Ok(()) + } +} + +impl HashMarker for RtVariableCoreWrapper +where + T: VariableOutputCore + HashMarker, + T::BlockSize: IsLess, + Le: NonZero, +{ +} + +#[cfg(feature = "mac")] +#[cfg_attr(docsrs, doc(cfg(feature = "mac")))] +impl MacMarker for RtVariableCoreWrapper +where + T: VariableOutputCore + MacMarker, + T::BlockSize: IsLess, + Le: NonZero, +{ +} + impl Reset for RtVariableCoreWrapper where - T: VariableOutputCore + UpdateCore, + T: VariableOutputCore + UpdateCore + Reset, + T::BlockSize: IsLess, + Le: NonZero, { #[inline] fn reset(&mut self) { - // For correct implementations `new` should always return `Ok` - // since wrapper can be only created with valid `output_size` - if let Ok(v) = T::new(self.output_size) { - self.core = v; - } else { - debug_assert!(false); - } self.buffer.reset(); + self.core.reset(); } } impl Update for RtVariableCoreWrapper where T: VariableOutputCore + UpdateCore, + T::BlockSize: IsLess, + Le: NonZero, { #[inline] fn update(&mut self, input: &[u8]) { @@ -47,8 +96,10 @@ where impl VariableOutput for RtVariableCoreWrapper where T: VariableOutputCore + UpdateCore, + T::BlockSize: IsLess, + Le: NonZero, { - const MAX_OUTPUT_SIZE: usize = T::MaxOutputSize::USIZE; + const MAX_OUTPUT_SIZE: usize = T::OutputSize::USIZE; fn new(output_size: usize) -> Result { let buffer = Default::default(); @@ -63,29 +114,30 @@ where self.output_size } - fn finalize_variable(mut self, f: impl FnOnce(&[u8])) { - let Self { - core, - buffer, - output_size, - } = &mut self; - core.finalize_variable_core(buffer, *output_size, f); + fn finalize_variable(mut self, out: &mut [u8]) -> Result<(), InvalidBufferSize> { + self.finalize_dirty(out) } +} - fn finalize_variable_reset(&mut self, f: impl FnOnce(&[u8])) { - let Self { - core, - buffer, - output_size, - } = self; - core.finalize_variable_core(buffer, *output_size, f); - self.reset() +impl VariableOutputReset for RtVariableCoreWrapper +where + T: VariableOutputCore + UpdateCore + Reset, + T::BlockSize: IsLess, + Le: NonZero, +{ + fn finalize_variable_reset(&mut self, out: &mut [u8]) -> Result<(), InvalidBufferSize> { + self.finalize_dirty(out)?; + self.core.reset(); + self.buffer.reset(); + Ok(()) } } impl fmt::Debug for RtVariableCoreWrapper where T: VariableOutputCore + UpdateCore + AlgorithmName, + T::BlockSize: IsLess, + Le: NonZero, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { T::write_alg_name(f)?; @@ -98,6 +150,8 @@ where impl std::io::Write for RtVariableCoreWrapper where T: VariableOutputCore + UpdateCore, + T::BlockSize: IsLess, + Le: NonZero, { #[inline] fn write(&mut self, buf: &[u8]) -> std::io::Result { diff --git a/digest/src/core_api/wrapper.rs b/digest/src/core_api/wrapper.rs new file mode 100644 index 00000000..f600642c --- /dev/null +++ b/digest/src/core_api/wrapper.rs @@ -0,0 +1,275 @@ +use super::{ + AlgorithmName, Buffer, BufferKindUser, ExtendableOutputCore, FixedOutputCore, OutputSizeUser, + Reset, UpdateCore, XofReaderCoreWrapper, +}; +use crate::{ + ExtendableOutput, ExtendableOutputReset, FixedOutput, FixedOutputReset, HashMarker, Update, +}; +use block_buffer::BlockBuffer; +use core::fmt; +use crypto_common::{BlockSizeUser, InvalidLength, Key, KeyInit, KeySizeUser, Output}; +use generic_array::typenum::{IsLess, Le, NonZero, U256}; + +#[cfg(feature = "mac")] +use crate::MacMarker; + +/// Wrapper around [`BufferKindUser`]. +/// +/// It handles data buffering and implements the slice-based traits. +#[derive(Clone, Default)] +pub struct CoreWrapper +where + T: BufferKindUser, + T::BlockSize: IsLess, + Le: NonZero, +{ + core: T, + buffer: BlockBuffer, +} + +impl HashMarker for CoreWrapper +where + T: BufferKindUser + HashMarker, + T::BlockSize: IsLess, + Le: NonZero, +{ +} + +#[cfg(feature = "mac")] +#[cfg_attr(docsrs, doc(cfg(feature = "mac")))] +impl MacMarker for CoreWrapper +where + T: BufferKindUser + MacMarker, + T::BlockSize: IsLess, + Le: NonZero, +{ +} + +// this blanket impl is needed for HMAC +impl BlockSizeUser for CoreWrapper +where + T: BufferKindUser + HashMarker, + T::BlockSize: IsLess, + Le: NonZero, +{ + type BlockSize = T::BlockSize; +} + +impl CoreWrapper +where + T: BufferKindUser, + T::BlockSize: IsLess, + Le: NonZero, +{ + /// Create new wrapper from `core`. + #[inline] + pub fn from_core(core: T) -> Self { + let buffer = Default::default(); + Self { core, buffer } + } + + /// Decompose wrapper into inner parts. + #[inline] + pub fn decompose(self) -> (T, Buffer) { + let Self { core, buffer } = self; + (core, buffer) + } +} + +impl KeySizeUser for CoreWrapper +where + T: BufferKindUser + KeySizeUser, + T::BlockSize: IsLess, + Le: NonZero, +{ + type KeySize = T::KeySize; +} + +impl KeyInit for CoreWrapper +where + T: BufferKindUser + KeyInit, + T::BlockSize: IsLess, + Le: NonZero, +{ + #[inline] + fn new(key: &Key) -> Self { + Self { + core: T::new(key), + buffer: Default::default(), + } + } + + #[inline] + fn new_from_slice(key: &[u8]) -> Result { + Ok(Self { + core: T::new_from_slice(key)?, + buffer: Default::default(), + }) + } +} + +impl fmt::Debug for CoreWrapper +where + T: BufferKindUser + AlgorithmName, + T::BlockSize: IsLess, + Le: NonZero, +{ + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + T::write_alg_name(f)?; + f.write_str(" { .. }") + } +} + +impl Reset for CoreWrapper +where + T: BufferKindUser + Reset, + T::BlockSize: IsLess, + Le: NonZero, +{ + #[inline] + fn reset(&mut self) { + self.core.reset(); + self.buffer.reset(); + } +} + +impl Update for CoreWrapper +where + T: BufferKindUser + UpdateCore, + T::BlockSize: IsLess, + Le: NonZero, +{ + #[inline] + fn update(&mut self, input: &[u8]) { + let Self { core, buffer } = self; + buffer.digest_blocks(input, |blocks| core.update_blocks(blocks)); + } +} + +impl OutputSizeUser for CoreWrapper +where + T: BufferKindUser + OutputSizeUser, + T::BlockSize: IsLess, + Le: NonZero, +{ + type OutputSize = T::OutputSize; +} + +impl FixedOutput for CoreWrapper +where + T: FixedOutputCore, + T::BlockSize: IsLess, + Le: NonZero, +{ + #[inline] + fn finalize_into(mut self, out: &mut Output) { + let Self { core, buffer } = &mut self; + core.finalize_fixed_core(buffer, out); + } +} + +impl FixedOutputReset for CoreWrapper +where + T: FixedOutputCore + Reset, + T::BlockSize: IsLess, + Le: NonZero, +{ + #[inline] + fn finalize_into_reset(&mut self, out: &mut Output) { + let Self { core, buffer } = self; + core.finalize_fixed_core(buffer, out); + core.reset(); + buffer.reset(); + } +} + +impl ExtendableOutput for CoreWrapper +where + T: ExtendableOutputCore, + T::BlockSize: IsLess, + Le: NonZero, + ::BlockSize: IsLess, + Le<::BlockSize, U256>: NonZero, +{ + type Reader = XofReaderCoreWrapper; + + #[inline] + fn finalize_xof(self) -> Self::Reader { + let (mut core, mut buffer) = self.decompose(); + let core = core.finalize_xof_core(&mut buffer); + let buffer = Default::default(); + Self::Reader { core, buffer } + } +} + +impl ExtendableOutputReset for CoreWrapper +where + T: ExtendableOutputCore + Reset, + T::BlockSize: IsLess, + Le: NonZero, + ::BlockSize: IsLess, + Le<::BlockSize, U256>: NonZero, +{ + #[inline] + fn finalize_xof_reset(&mut self) -> Self::Reader { + let Self { core, buffer } = self; + let reader_core = core.finalize_xof_core(buffer); + core.reset(); + buffer.reset(); + let buffer = Default::default(); + Self::Reader { + core: reader_core, + buffer, + } + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl std::io::Write for CoreWrapper +where + T: BufferKindUser + UpdateCore, + T::BlockSize: IsLess, + Le: NonZero, +{ + #[inline] + fn write(&mut self, buf: &[u8]) -> std::io::Result { + Update::update(self, buf); + Ok(buf.len()) + } + + #[inline] + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +/// A proxy trait to a core type implemented by [`CoreWrapper`] +// TODO: replace with an inherent associated type on stabilization: +// https://github.com/rust-lang/rust/issues/8995 +pub trait CoreProxy: sealed::Sealed { + /// Type wrapped by [`CoreWrapper`]. + type Core; +} + +mod sealed { + pub trait Sealed {} +} + +impl sealed::Sealed for CoreWrapper +where + T: BufferKindUser, + T::BlockSize: IsLess, + Le: NonZero, +{ +} + +impl CoreProxy for CoreWrapper +where + T: BufferKindUser, + T::BlockSize: IsLess, + Le: NonZero, +{ + type Core = T; +} diff --git a/digest/src/core_api/xof_reader.rs b/digest/src/core_api/xof_reader.rs index 5d7b8352..5b41479b 100644 --- a/digest/src/core_api/xof_reader.rs +++ b/digest/src/core_api/xof_reader.rs @@ -1,35 +1,60 @@ use super::{AlgorithmName, XofReaderCore}; use crate::XofReader; +use block_buffer::EagerBuffer; use core::fmt; -use crypto_common::block_buffer::BlockBuffer; +use generic_array::typenum::{IsLess, Le, NonZero, U256}; /// Wrapper around [`XofReaderCore`] implementations. /// /// It handles data buffering and implements the mid-level traits. #[derive(Clone, Default)] -pub struct XofReaderCoreWrapper { +pub struct XofReaderCoreWrapper +where + T: XofReaderCore, + T::BlockSize: IsLess, + Le: NonZero, +{ pub(super) core: T, - pub(super) buffer: BlockBuffer, + pub(super) buffer: EagerBuffer, } -impl fmt::Debug for XofReaderCoreWrapper { +impl fmt::Debug for XofReaderCoreWrapper +where + T: XofReaderCore + AlgorithmName, + T::BlockSize: IsLess, + Le: NonZero, +{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { T::write_alg_name(f)?; f.write_str(" { .. }") } } -impl XofReader for XofReaderCoreWrapper { +impl XofReader for XofReaderCoreWrapper +where + T: XofReaderCore, + T::BlockSize: IsLess, + Le: NonZero, +{ #[inline] fn read(&mut self, buffer: &mut [u8]) { let Self { core, buffer: buf } = self; - buf.set_data(buffer, || core.read_block()); + buf.set_data(buffer, |blocks| { + for block in blocks { + *block = core.read_block(); + } + }); } } #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] -impl std::io::Read for XofReaderCoreWrapper { +impl std::io::Read for XofReaderCoreWrapper +where + T: XofReaderCore, + T::BlockSize: IsLess, + Le: NonZero, +{ #[inline] fn read(&mut self, buf: &mut [u8]) -> std::io::Result { XofReader::read(self, buf); diff --git a/digest/src/dev.rs b/digest/src/dev.rs index 926ebbd5..2b68bdd4 100644 --- a/digest/src/dev.rs +++ b/digest/src/dev.rs @@ -2,14 +2,22 @@ pub use blobby; -use super::{ExtendableOutput, Reset, Update, VariableOutput, XofReader}; -use core::fmt::Debug; - -/// Define test +mod fixed; +mod mac; +mod rng; +mod variable; +mod xof; + +pub use fixed::*; +pub use mac::*; +pub use variable::*; +pub use xof::*; + +/// Define hash function test #[macro_export] #[cfg_attr(docsrs, doc(cfg(feature = "dev")))] macro_rules! new_test { - ($name:ident, $test_name:expr, $hasher:ty, $test_func:ident) => { + ($name:ident, $test_name:expr, $hasher:ty, $test_func:ident $(,)?) => { #[test] fn $name() { use digest::dev::blobby::Blob2Iterator; @@ -31,216 +39,40 @@ macro_rules! new_test { }; } -/// Module to separate Digest from other traits -mod foo { - use super::super::{Digest, Reset}; - use core::fmt::Debug; - - /// Digest test - pub fn digest_test(input: &[u8], output: &[u8]) -> Option<&'static str> - where - D: Digest + Reset + Debug + Clone, - { - let mut hasher = D::new(); - // Test that it works when accepting the message all at once - hasher.update(input); - let mut hasher2 = hasher.clone(); - if hasher.finalize().as_slice() != output { - return Some("whole message"); - } - - // Test if reset works correctly - hasher2.reset(); - hasher2.update(input); - if hasher2.finalize().as_slice() != output { - return Some("whole message after reset"); - } - - // Test that it works when accepting the message in pieces - let mut hasher = D::new(); - let len = input.len(); - let mut left = len; - while left > 0 { - let take = (left + 1) / 2; - hasher.update(&input[len - left..take + len - left]); - left -= take; - } - if hasher.finalize().as_slice() != output { - return Some("message in pieces"); - } - - // Test processing byte-by-byte - let mut hasher = D::new(); - for chunk in input.chunks(1) { - hasher.update(chunk) - } - if hasher.finalize().as_slice() != output { - return Some("message byte-by-byte"); - } - None - } - - /// Compute digest of one million `a` bytes - pub fn one_million_a(expected: &[u8]) - where - D: Digest + Debug + Clone, - { - let mut sh = D::new(); - for _ in 0..50_000 { - sh.update(&[b'a'; 10]); - } - sh.update(&[b'a'; 500_000][..]); - let out = sh.finalize(); - assert_eq!(out[..], expected[..]); - } -} - -pub use self::foo::{digest_test, one_million_a}; - -/// XOF test -pub fn xof_test(input: &[u8], output: &[u8]) -> Option<&'static str> -where - D: Update + ExtendableOutput + Default + Debug + Reset + Clone, -{ - let mut hasher = D::default(); - let mut buf = [0u8; 1024]; - // Test that it works when accepting the message all at once - hasher.update(input); - - let mut hasher2 = hasher.clone(); - { - let out = &mut buf[..output.len()]; - hasher.finalize_xof().read(out); - - if out != output { - return Some("whole message"); - } - } - - // Test if hasher resets correctly - hasher2.reset(); - hasher2.update(input); - - { - let out = &mut buf[..output.len()]; - hasher2.finalize_xof().read(out); - - if out != output { - return Some("whole message after reset"); - } - } - - // Test if hasher accepts message in pieces correctly - let mut hasher = D::default(); - let len = input.len(); - let mut left = len; - while left > 0 { - let take = (left + 1) / 2; - hasher.update(&input[len - left..take + len - left]); - left -= take; - } - - { - let out = &mut buf[..output.len()]; - hasher.finalize_xof().read(out); - if out != output { - return Some("message in pieces"); - } - } - - // Test reading from reader byte by byte - let mut hasher = D::default(); - hasher.update(input); - - let mut reader = hasher.finalize_xof(); - let out = &mut buf[..output.len()]; - for chunk in out.chunks_mut(1) { - reader.read(chunk); - } - - if out != output { - return Some("message in pieces"); - } - None -} - -/// Variable-output digest test -pub fn variable_test(input: &[u8], output: &[u8]) -> Option<&'static str> -where - D: Update + VariableOutput + Reset + Debug + Clone, -{ - let mut hasher = D::new(output.len()).unwrap(); - let mut buf = [0u8; 128]; - let buf = &mut buf[..output.len()]; - // Test that it works when accepting the message all at once - hasher.update(input); - let mut hasher2 = hasher.clone(); - hasher.finalize_variable(|res| buf.copy_from_slice(res)); - if buf != output { - return Some("whole message"); - } - - // Test if reset works correctly - hasher2.reset(); - hasher2.update(input); - hasher2.finalize_variable(|res| buf.copy_from_slice(res)); - if buf != output { - return Some("whole message after reset"); - } - - // Test that it works when accepting the message in pieces - let mut hasher = D::new(output.len()).unwrap(); - let len = input.len(); - let mut left = len; - while left > 0 { - let take = (left + 1) / 2; - hasher.update(&input[len - left..take + len - left]); - left -= take; - } - hasher.finalize_variable(|res| buf.copy_from_slice(res)); - if buf != output { - return Some("message in pieces"); - } - - // Test processing byte-by-byte - let mut hasher = D::new(output.len()).unwrap(); - for chunk in input.chunks(1) { - hasher.update(chunk) - } - hasher.finalize_variable(|res| buf.copy_from_slice(res)); - if buf != output { - return Some("message byte-by-byte"); - } - None -} - -/// Define benchmark +/// Define [`Update`][crate::Update] impl benchmark #[macro_export] #[cfg_attr(docsrs, doc(cfg(feature = "dev")))] -macro_rules! bench { - ($name:ident, $engine:path, $bs:expr) => { - #[bench] - fn $name(b: &mut Bencher) { - let mut d = <$engine>::default(); - let data = [0; $bs]; - - b.iter(|| { - d.update(&data[..]); - }); - - b.bytes = $bs; - } +macro_rules! bench_update { + ( + $init:expr; + $($name:ident $bs:expr;)* + ) => { + $( + #[bench] + fn $name(b: &mut Bencher) { + let mut d = $init; + let data = [0; $bs]; + + b.iter(|| { + digest::Update::update(&mut d, &data[..]); + }); + + b.bytes = $bs; + } + )* }; +} - ($engine:path) => { - extern crate test; - - use digest::Digest; - use test::Bencher; - - $crate::bench!(bench1_10, $engine, 10); - $crate::bench!(bench2_100, $engine, 100); - $crate::bench!(bench3_1000, $engine, 1000); - $crate::bench!(bench4_10000, $engine, 10000); - }; +/// Feed ~1 MiB of pseudorandom data to an updatable state. +pub fn feed_rand_16mib(d: &mut D) { + let buf = &mut [0u8; 1024]; + let mut rng = rng::RNG; + let n = 16 * (1 << 20) / buf.len(); + for _ in 0..n { + rng.fill(buf); + d.update(buf); + // additional byte, so size of feeded data + // will not be multiple of block size + d.update(&[42]); + } } diff --git a/digest/src/dev/fixed.rs b/digest/src/dev/fixed.rs new file mode 100644 index 00000000..24f38011 --- /dev/null +++ b/digest/src/dev/fixed.rs @@ -0,0 +1,65 @@ +use crate::{Digest, FixedOutput, FixedOutputReset, HashMarker, Update}; +use core::fmt::Debug; + +/// Fixed-output resettable digest test via the `Digest` trait +pub fn fixed_reset_test(input: &[u8], output: &[u8]) -> Option<&'static str> +where + D: FixedOutputReset + Debug + Clone + Default + Update + HashMarker, +{ + let mut hasher = D::new(); + // Test that it works when accepting the message all at once + hasher.update(input); + let mut hasher2 = hasher.clone(); + if hasher.finalize()[..] != output[..] { + return Some("whole message"); + } + + // Test if reset works correctly + hasher2.reset(); + hasher2.update(input); + if hasher2.finalize_reset()[..] != output[..] { + return Some("whole message after reset"); + } + + // Test that it works when accepting the message in chunks + for n in 1..core::cmp::min(17, input.len()) { + let mut hasher = D::new(); + for chunk in input.chunks(n) { + hasher.update(chunk); + hasher2.update(chunk); + } + if hasher.finalize()[..] != output[..] { + return Some("message in chunks"); + } + if hasher2.finalize_reset()[..] != output[..] { + return Some("message in chunks"); + } + } + + None +} + +/// Variable-output resettable digest test +pub fn fixed_test(input: &[u8], output: &[u8]) -> Option<&'static str> +where + D: FixedOutput + Default + Debug + Clone, +{ + let mut hasher = D::default(); + // Test that it works when accepting the message all at once + hasher.update(input); + if hasher.finalize_fixed()[..] != output[..] { + return Some("whole message"); + } + + // Test that it works when accepting the message in chunks + for n in 1..core::cmp::min(17, input.len()) { + let mut hasher = D::default(); + for chunk in input.chunks(n) { + hasher.update(chunk); + } + if hasher.finalize_fixed()[..] != output[..] { + return Some("message in chunks"); + } + } + None +} diff --git a/digest/src/dev/mac.rs b/digest/src/dev/mac.rs new file mode 100644 index 00000000..0d4a37df --- /dev/null +++ b/digest/src/dev/mac.rs @@ -0,0 +1,159 @@ +/// Define MAC test +#[macro_export] +#[cfg(feature = "mac")] +#[cfg_attr(docsrs, doc(cfg(all(feature = "dev", feature = "mac"))))] +macro_rules! new_mac_test { + ($name:ident, $test_name:expr, $mac:ty $(,)?) => { + digest::new_mac_test!($name, $test_name, $mac, ""); + }; + ($name:ident, $test_name:expr, $mac:ty, trunc_left $(,)?) => { + digest::new_mac_test!($name, $test_name, $mac, "left"); + }; + ($name:ident, $test_name:expr, $mac:ty, trunc_right $(,)?) => { + digest::new_mac_test!($name, $test_name, $mac, "right"); + }; + ($name:ident, $test_name:expr, $mac:ty, $trunc:expr $(,)?) => { + #[test] + fn $name() { + use core::cmp::min; + use digest::dev::blobby::Blob3Iterator; + use digest::Mac; + + fn run_test(key: &[u8], input: &[u8], tag: &[u8]) -> Option<&'static str> { + let mac0 = <$mac as Mac>::new_from_slice(key).unwrap(); + + let mut mac = mac0.clone(); + mac.update(input); + let result = mac.finalize().into_bytes(); + let n = tag.len(); + let result_bytes = match $trunc { + "left" => &result[..n], + "right" => &result[result.len() - n..], + _ => &result[..], + }; + if result_bytes != tag { + return Some("whole message"); + } + + // test reading different chunk sizes + for chunk_size in 1..min(64, input.len()) { + let mut mac = mac0.clone(); + for chunk in input.chunks(chunk_size) { + mac.update(chunk); + } + let res = match $trunc { + "left" => mac.verify_truncated_left(tag), + "right" => mac.verify_truncated_right(tag), + _ => mac.verify_slice(tag), + }; + if res.is_err() { + return Some("chunked message"); + } + } + + None + } + + let data = include_bytes!(concat!("data/", $test_name, ".blb")); + + for (i, row) in Blob3Iterator::new(data).unwrap().enumerate() { + let [key, input, tag] = row.unwrap(); + if let Some(desc) = run_test(key, input, tag) { + panic!( + "\n\ + Failed test №{}: {}\n\ + key:\t{:?}\n\ + input:\t{:?}\n\ + tag:\t{:?}\n", + i, desc, key, input, tag, + ); + } + } + } + }; +} + +/// Define resettable MAC test +#[macro_export] +#[cfg(feature = "mac")] +#[cfg_attr(docsrs, doc(cfg(all(feature = "dev", feature = "mac"))))] +macro_rules! new_resettable_mac_test { + ($name:ident, $test_name:expr, $mac:ty $(,)?) => { + digest::new_resettable_mac_test!($name, $test_name, $mac, ""); + }; + ($name:ident, $test_name:expr, $mac:ty, trunc_left $(,)?) => { + digest::new_resettable_mac_test!($name, $test_name, $mac, "left"); + }; + ($name:ident, $test_name:expr, $mac:ty, trunc_right $(,)?) => { + digest::new_resettable_mac_test!($name, $test_name, $mac, "right"); + }; + ($name:ident, $test_name:expr, $mac:ty, $trunc:expr $(,)?) => { + #[test] + fn $name() { + use core::cmp::min; + use digest::dev::blobby::Blob3Iterator; + use digest::Mac; + + fn run_test(key: &[u8], input: &[u8], tag: &[u8]) -> Option<&'static str> { + let mac0 = <$mac as Mac>::new_from_slice(key).unwrap(); + + let mut mac = mac0.clone(); + mac.update(input); + let result = mac.finalize_reset().into_bytes(); + let n = tag.len(); + let result_bytes = match $trunc { + "left" => &result[..n], + "right" => &result[result.len() - n..], + _ => &result[..], + }; + if result_bytes != tag { + return Some("whole message"); + } + + // test if reset worked correctly + mac.update(input); + let res = match $trunc { + "left" => mac.verify_truncated_left(tag), + "right" => mac.verify_truncated_right(tag), + _ => mac.verify_slice(tag), + }; + if res.is_err() { + return Some("after reset"); + } + + // test reading different chunk sizes + for chunk_size in 1..min(64, input.len()) { + let mut mac = mac0.clone(); + for chunk in input.chunks(chunk_size) { + mac.update(chunk); + } + let res = match $trunc { + "left" => mac.verify_truncated_left(tag), + "right" => mac.verify_truncated_right(tag), + _ => mac.verify_slice(tag), + }; + if res.is_err() { + return Some("chunked message"); + } + } + None + } + + let data = include_bytes!(concat!("data/", $test_name, ".blb")); + + for (i, row) in Blob3Iterator::new(data).unwrap().enumerate() { + let [key, input, tag] = row.unwrap(); + if let Some(desc) = run_test(key, input, tag) { + panic!( + "\n\ + Failed test №{}: {}\n\ + key:\t{:?}\n\ + input:\t{:?}\n\ + tag:\t{:?}\n", + i, desc, key, input, tag, + ); + } + } + } + }; +} diff --git a/digest/src/dev/rng.rs b/digest/src/dev/rng.rs new file mode 100644 index 00000000..8b233aaf --- /dev/null +++ b/digest/src/dev/rng.rs @@ -0,0 +1,38 @@ +//! Xorshift RNG used for tests. Based on the `rand_xorshift` crate. +use core::num::Wrapping; + +/// Initial RNG state used in tests. +// choosen by fair dice roll. guaranteed to be random. +pub(crate) const RNG: XorShiftRng = XorShiftRng { + x: Wrapping(0x0787_3B4A), + y: Wrapping(0xFAAB_8FFE), + z: Wrapping(0x1745_980F), + w: Wrapping(0xB0AD_B4F3), +}; + +/// Xorshift RNG instance/ +pub(crate) struct XorShiftRng { + x: Wrapping, + y: Wrapping, + z: Wrapping, + w: Wrapping, +} + +impl XorShiftRng { + pub(crate) fn fill(&mut self, buf: &mut [u8; 1024]) { + for chunk in buf.chunks_exact_mut(4) { + chunk.copy_from_slice(&self.next_u32().to_le_bytes()); + } + } + + fn next_u32(&mut self) -> u32 { + let x = self.x; + let t = x ^ (x << 11); + self.x = self.y; + self.y = self.z; + self.z = self.w; + let w = self.w; + self.w = w ^ (w >> 19) ^ (t ^ (t >> 8)); + self.w.0 + } +} diff --git a/digest/src/dev/variable.rs b/digest/src/dev/variable.rs new file mode 100644 index 00000000..ed8ff882 --- /dev/null +++ b/digest/src/dev/variable.rs @@ -0,0 +1,82 @@ +use crate::{VariableOutput, VariableOutputReset}; +use core::fmt::Debug; + +/// Variable-output resettable digest test +pub fn variable_reset_test(input: &[u8], output: &[u8]) -> Option<&'static str> +where + D: VariableOutputReset + Debug + Clone, +{ + let mut hasher = D::new(output.len()).unwrap(); + let mut buf = [0u8; 128]; + let buf = &mut buf[..output.len()]; + // Test that it works when accepting the message all at once + hasher.update(input); + let mut hasher2 = hasher.clone(); + hasher.finalize_variable(buf).unwrap(); + if buf != output { + return Some("whole message"); + } + buf.iter_mut().for_each(|b| *b = 0); + + // Test if reset works correctly + hasher2.reset(); + hasher2.update(input); + hasher2.finalize_variable_reset(buf).unwrap(); + if buf != output { + return Some("whole message after reset"); + } + buf.iter_mut().for_each(|b| *b = 0); + + // Test that it works when accepting the message in chunks + for n in 1..core::cmp::min(17, input.len()) { + let mut hasher = D::new(output.len()).unwrap(); + for chunk in input.chunks(n) { + hasher.update(chunk); + hasher2.update(chunk); + } + hasher.finalize_variable(buf).unwrap(); + if buf != output { + return Some("message in chunks"); + } + buf.iter_mut().for_each(|b| *b = 0); + + hasher2.finalize_variable_reset(buf).unwrap(); + if buf != output { + return Some("message in chunks"); + } + buf.iter_mut().for_each(|b| *b = 0); + } + + None +} + +/// Variable-output resettable digest test +pub fn variable_test(input: &[u8], output: &[u8]) -> Option<&'static str> +where + D: VariableOutput + Debug + Clone, +{ + let mut hasher = D::new(output.len()).unwrap(); + let mut buf = [0u8; 128]; + let buf = &mut buf[..output.len()]; + // Test that it works when accepting the message all at once + hasher.update(input); + hasher.finalize_variable(buf).unwrap(); + if buf != output { + return Some("whole message"); + } + buf.iter_mut().for_each(|b| *b = 0); + + // Test that it works when accepting the message in chunks + for n in 1..core::cmp::min(17, input.len()) { + let mut hasher = D::new(output.len()).unwrap(); + for chunk in input.chunks(n) { + hasher.update(chunk); + } + hasher.finalize_variable(buf).unwrap(); + if buf != output { + return Some("message in chunks"); + } + buf.iter_mut().for_each(|b| *b = 0); + } + None +} diff --git a/digest/src/dev/xof.rs b/digest/src/dev/xof.rs new file mode 100644 index 00000000..9e5d07a0 --- /dev/null +++ b/digest/src/dev/xof.rs @@ -0,0 +1,51 @@ +use crate::ExtendableOutputReset; +use core::fmt::Debug; + +/// Resettable XOF test +pub fn xof_reset_test(input: &[u8], output: &[u8]) -> Option<&'static str> +where + D: ExtendableOutputReset + Default + Debug + Clone, +{ + let mut hasher = D::default(); + let mut buf = [0u8; 1024]; + let buf = &mut buf[..output.len()]; + // Test that it works when accepting the message all at once + hasher.update(input); + let mut hasher2 = hasher.clone(); + hasher.finalize_xof_into(buf); + if buf != output { + return Some("whole message"); + } + buf.iter_mut().for_each(|b| *b = 0); + + // Test if reset works correctly + hasher2.reset(); + hasher2.update(input); + hasher2.finalize_xof_reset_into(buf); + if buf != output { + return Some("whole message after reset"); + } + buf.iter_mut().for_each(|b| *b = 0); + + // Test that it works when accepting the message in chunks + for n in 1..core::cmp::min(17, input.len()) { + let mut hasher = D::default(); + for chunk in input.chunks(n) { + hasher.update(chunk); + hasher2.update(chunk); + } + hasher.finalize_xof_into(buf); + if buf != output { + return Some("message in chunks"); + } + buf.iter_mut().for_each(|b| *b = 0); + + hasher2.finalize_xof_reset_into(buf); + if buf != output { + return Some("message in chunks"); + } + buf.iter_mut().for_each(|b| *b = 0); + } + + None +} diff --git a/digest/src/digest.rs b/digest/src/digest.rs index 7c277c9a..0e974b32 100644 --- a/digest/src/digest.rs +++ b/digest/src/digest.rs @@ -1,17 +1,19 @@ -use super::{FixedOutput, FixedOutputReset, Update}; +use super::{FixedOutput, FixedOutputReset, InvalidBufferSize, Reset, Update}; +use crypto_common::{Output, OutputSizeUser}; use generic_array::typenum::Unsigned; -use generic_array::{ArrayLength, GenericArray}; -/// The `Digest` trait specifies an interface common for digest functions. -/// -/// It's a convenience wrapper around [`Update`], [`FixedOutput`], -/// [`Reset`][`crate::Reset`], [`Clone`], and [`Default`] traits. -/// -/// It also provides additional convenience methods. -pub trait Digest { - /// Output size for `Digest` - type OutputSize: ArrayLength; +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +/// Marker trait for cryptographic hash functions. +pub trait HashMarker {} +/// Convinience wrapper trait covering functionality of cryptographic hash +/// functions with fixed output size. +/// +/// This trait wraps [`Update`], [`FixedOutput`], [`Default`], and +/// [`HashMarker`] traits and provides additional convenience methods. +pub trait Digest: OutputSizeUser { /// Create new hasher instance fn new() -> Self; @@ -37,6 +39,11 @@ pub trait Digest { where Self: FixedOutputReset; + /// Reset hasher instance to its initial state. + fn reset(&mut self) + where + Self: Reset; + /// Get output size of the hasher fn output_size() -> usize; @@ -44,9 +51,7 @@ pub trait Digest { fn digest(data: impl AsRef<[u8]>) -> Output; } -impl Digest for D { - type OutputSize = ::OutputSize; - +impl Digest for D { #[inline] fn new() -> Self { Self::default() @@ -89,6 +94,14 @@ impl Digest for D { FixedOutputReset::finalize_into_reset(self, out); } + #[inline] + fn reset(&mut self) + where + Self: Reset, + { + Reset::reset(self) + } + #[inline] fn output_size() -> usize { Self::OutputSize::to_usize() @@ -102,5 +115,109 @@ impl Digest for D { } } -/// Fixed of fixed-sized hash-function used by [`Digest`] methods. -pub type Output = GenericArray::OutputSize>; +/// Modification of the [`Digest`] trait suitable for trait objects. +pub trait DynDigest { + /// Digest input data. + /// + /// This method can be called repeatedly for use with streaming messages. + fn update(&mut self, data: &[u8]); + + /// Retrieve result and reset hasher instance + #[cfg(feature = "alloc")] + #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] + fn finalize_reset(&mut self) -> Box<[u8]> { + let mut result = vec![0; self.output_size()]; + self.finalize_into_reset(&mut result).unwrap(); + result.into_boxed_slice() + } + + /// Retrieve result and consume boxed hasher instance + #[cfg(feature = "alloc")] + #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] + #[allow(clippy::boxed_local)] + fn finalize(mut self: Box) -> Box<[u8]> { + let mut result = vec![0; self.output_size()]; + self.finalize_into_reset(&mut result).unwrap(); + result.into_boxed_slice() + } + + /// Write result into provided array and consume the hasher instance. + /// + /// Returns error if buffer length is not equal to `output_size`. + fn finalize_into(self, buf: &mut [u8]) -> Result<(), InvalidBufferSize>; + + /// Write result into provided array and reset the hasher instance. + /// + /// Returns error if buffer length is not equal to `output_size`. + fn finalize_into_reset(&mut self, out: &mut [u8]) -> Result<(), InvalidBufferSize>; + + /// Reset hasher instance to its initial state. + fn reset(&mut self); + + /// Get output size of the hasher + fn output_size(&self) -> usize; + + /// Clone hasher state into a boxed trait object + #[cfg(feature = "alloc")] + #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] + fn box_clone(&self) -> Box; +} + +impl DynDigest for D { + fn update(&mut self, data: &[u8]) { + Update::update(self, data); + } + + #[cfg(feature = "alloc")] + fn finalize_reset(&mut self) -> Box<[u8]> { + FixedOutputReset::finalize_fixed_reset(self) + .to_vec() + .into_boxed_slice() + } + + #[cfg(feature = "alloc")] + fn finalize(self: Box) -> Box<[u8]> { + FixedOutput::finalize_fixed(*self) + .to_vec() + .into_boxed_slice() + } + + fn finalize_into(self, buf: &mut [u8]) -> Result<(), InvalidBufferSize> { + if buf.len() == self.output_size() { + FixedOutput::finalize_into(self, Output::::from_mut_slice(buf)); + Ok(()) + } else { + Err(InvalidBufferSize) + } + } + + fn finalize_into_reset(&mut self, buf: &mut [u8]) -> Result<(), InvalidBufferSize> { + if buf.len() == self.output_size() { + FixedOutputReset::finalize_into_reset(self, Output::::from_mut_slice(buf)); + Ok(()) + } else { + Err(InvalidBufferSize) + } + } + + fn reset(&mut self) { + Reset::reset(self); + } + + fn output_size(&self) -> usize { + ::OutputSize::to_usize() + } + + #[cfg(feature = "alloc")] + fn box_clone(&self) -> Box { + Box::new(self.clone()) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl Clone for Box { + fn clone(&self) -> Self { + self.box_clone() + } +} diff --git a/digest/src/dyn_digest.rs b/digest/src/dyn_digest.rs deleted file mode 100644 index ecafe794..00000000 --- a/digest/src/dyn_digest.rs +++ /dev/null @@ -1,124 +0,0 @@ -use super::{FixedOutput, FixedOutputReset, Reset, Update}; -use core::fmt; -use generic_array::{typenum::Unsigned, GenericArray}; - -#[cfg(feature = "alloc")] -use alloc::boxed::Box; - -/// The `DynDigest` trait is a modification of `Digest` trait suitable -/// for trait objects. -pub trait DynDigest { - /// Digest input data. - /// - /// This method can be called repeatedly for use with streaming messages. - fn update(&mut self, data: &[u8]); - - /// Retrieve result and reset hasher instance - #[cfg(feature = "alloc")] - #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - fn finalize_reset(&mut self) -> Box<[u8]> { - let mut result = vec![0; self.output_size()]; - self.finalize_into_reset(&mut result).unwrap(); - result.into_boxed_slice() - } - - /// Retrieve result and consume boxed hasher instance - #[cfg(feature = "alloc")] - #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - #[allow(clippy::boxed_local)] - fn finalize(mut self: Box) -> Box<[u8]> { - let mut result = vec![0; self.output_size()]; - self.finalize_into_reset(&mut result).unwrap(); - result.into_boxed_slice() - } - - /// Write result into provided array and consume the hasher instance. - /// - /// Returns error if buffer length is not equal to `output_size`. - fn finalize_into(self, buf: &mut [u8]) -> Result<(), InvalidBufferLength>; - - /// Write result into provided array and reset the hasher instance. - /// - /// Returns error if buffer length is not equal to `output_size`. - fn finalize_into_reset(&mut self, out: &mut [u8]) -> Result<(), InvalidBufferLength>; - - /// Reset hasher instance to its initial state. - fn reset(&mut self); - - /// Get output size of the hasher - fn output_size(&self) -> usize; - - /// Clone hasher state into a boxed trait object - #[cfg(feature = "alloc")] - #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - fn box_clone(&self) -> Box; -} - -impl DynDigest for D { - fn update(&mut self, data: &[u8]) { - Update::update(self, data); - } - - #[cfg(feature = "alloc")] - fn finalize_reset(&mut self) -> Box<[u8]> { - self.finalize_fixed_reset().to_vec().into_boxed_slice() - } - - #[cfg(feature = "alloc")] - fn finalize(self: Box) -> Box<[u8]> { - self.finalize_fixed().to_vec().into_boxed_slice() - } - - fn finalize_into(self, buf: &mut [u8]) -> Result<(), InvalidBufferLength> { - if buf.len() == self.output_size() { - self.finalize_into(GenericArray::from_mut_slice(buf)); - Ok(()) - } else { - Err(InvalidBufferLength) - } - } - - fn finalize_into_reset(&mut self, buf: &mut [u8]) -> Result<(), InvalidBufferLength> { - if buf.len() == self.output_size() { - self.finalize_into_reset(GenericArray::from_mut_slice(buf)); - Ok(()) - } else { - Err(InvalidBufferLength) - } - } - - fn reset(&mut self) { - Reset::reset(self); - } - - fn output_size(&self) -> usize { - ::OutputSize::to_usize() - } - - #[cfg(feature = "alloc")] - fn box_clone(&self) -> Box { - Box::new(self.clone()) - } -} - -#[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -impl Clone for Box { - fn clone(&self) -> Self { - self.box_clone() - } -} - -/// Buffer length is not equal to the hash output size. -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] -pub struct InvalidBufferLength; - -impl fmt::Display for InvalidBufferLength { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("invalid buffer length") - } -} - -#[cfg(feature = "std")] -impl std::error::Error for InvalidBufferLength {} diff --git a/digest/src/lib.rs b/digest/src/lib.rs index 2991ded6..3938c54c 100644 --- a/digest/src/lib.rs +++ b/digest/src/lib.rs @@ -1,27 +1,27 @@ //! This crate provides traits which describe functionality of cryptographic hash -//! functions. +//! functions and Message Authentication algorithms. //! -//! Traits in this repository are organized into high-level convenience traits, -//! mid-level traits which expose more fine-grained functionality, and -//! low-level traits intended to only be used by algorithm implementations: +//! Traits in this repository are organized into the following levels: //! -//! - **High-level convenience traits**: [`Digest`], [`DynDigest`]. They are wrappers -//! around lower-level traits for most common hash-function use-cases. -//! - **Mid-level traits**: [`Update`], [`FixedOutput`], [`ExtendableOutput`], [`Reset`]. -//! These traits atomically describe available functionality of hash function -//! implementations. +//! - **High-level convenience traits**: [`Digest`], [`DynDigest`], [`Mac`]. +//! Wrappers around lower-level traits for most common use-cases. Users should +//! usually prefer using these traits. +//! - **Mid-level traits**: [`Update`], [`FixedOutput`], [`FixedOutputReset`], +//! [`ExtendableOutput`], [`ExtendableOutputReset`], [`XofReader`], +//! [`VariableOutput`], [`VariableOutput`], [`Reset`], [`KeyInit`], and +//! [`InnerInit`]. These traits atomically describe available functionality +//! of an algorithm. +//! - **Marker traits**: [`HashMarker`], [`MacMarker`]. Used to distinguish +//! different algorithm classes. //! - **Low-level traits** defined in the [`core_api`] module. These traits //! operate at a block-level and do not contain any built-in buffering. -//! They are intended to be implemented by low-level algorithm providers only -//! and simplify the amount of work implementers need to do and therefore -//! usually shouldn't be used in application-level code. +//! They are intended to be implemented by low-level algorithm providers only. +//! Usually they should not be used in application-level code. //! //! Additionally hash functions implement traits from the standard library: //! [`Default`], [`Clone`], [`Write`][std::io::Write]. The latter is //! feature-gated behind `std` feature, which is usually enabled by default //! by hash implementation crates. -//! -//! The [`Digest`] trait is the most commonly used trait. #![no_std] #![cfg_attr(docsrs, feature(doc_cfg))] @@ -50,20 +50,60 @@ pub mod dev; #[cfg_attr(docsrs, doc(cfg(feature = "core-api")))] pub mod core_api; mod digest; -mod dyn_digest; +#[cfg(feature = "mac")] +mod mac; -pub use crate::digest::{Digest, Output}; -use core::fmt; #[cfg(feature = "core-api")] #[cfg_attr(docsrs, doc(cfg(feature = "core-api")))] -pub use crypto_common::block_buffer; -pub use dyn_digest::{DynDigest, InvalidBufferLength}; -pub use generic_array::{self, typenum::consts, GenericArray}; +pub use block_buffer; +pub use crypto_common; + +pub use crate::digest::{Digest, DynDigest, HashMarker}; +#[cfg(feature = "mac")] +pub use crypto_common::{InnerInit, InvalidLength, Key, KeyInit}; +pub use crypto_common::{Output, OutputSizeUser, Reset}; +pub use generic_array::{self, typenum::consts}; +#[cfg(feature = "mac")] +pub use mac::{CtOutput, Mac, MacError, MacMarker}; + +use core::fmt; + +/// Types which consume data with byte granularity. +pub trait Update { + /// Update state using the provided data. + fn update(&mut self, data: &[u8]); +} + +/// Trait for hash functions with fixed-size output. +pub trait FixedOutput: Update + OutputSizeUser + Sized { + /// Consume value and write result into provided array. + fn finalize_into(self, out: &mut Output); + + /// Retrieve result and consume the hasher instance. + #[inline] + fn finalize_fixed(self) -> Output { + let mut out = Default::default(); + self.finalize_into(&mut out); + out + } +} + +/// Trait for hash functions with fixed-size output able to reset themselves. +pub trait FixedOutputReset: FixedOutput + Reset { + /// Write result into provided array and reset the hasher state. + fn finalize_into_reset(&mut self, out: &mut Output); -pub use crypto_common::{FixedOutput, FixedOutputReset, Reset, Update}; + /// Retrieve result and reset the hasher state. + #[inline] + fn finalize_fixed_reset(&mut self) -> Output { + let mut out = Default::default(); + self.finalize_into_reset(&mut out); + out + } +} -/// Trait for describing readers which are used to extract extendable output -/// from XOF (extendable-output function) result. +/// Trait for reader types which are used to extract extendable output +/// from a XOF (extendable-output function) result. pub trait XofReader { /// Read output into the `buffer`. Can be called an unlimited number of times. fn read(&mut self, buffer: &mut [u8]); @@ -83,19 +123,24 @@ pub trait XofReader { } } -/// Trait which describes extendable-output functions (XOF). -pub trait ExtendableOutput: Sized + Update + Default + Reset { +/// Trait for hash functions with extendable-output (XOF). +pub trait ExtendableOutput: Sized + Update { /// Reader type Reader: XofReader; /// Retrieve XOF reader and consume hasher instance. fn finalize_xof(self) -> Self::Reader; - /// Retrieve XOF reader and reset hasher instance state. - fn finalize_xof_reset(&mut self) -> Self::Reader; + /// Finalize XOF and write result into `out`. + fn finalize_xof_into(self, out: &mut [u8]) { + self.finalize_xof().read(out); + } - /// Compute hash of `data` and write it to `output`. - fn digest_xof(input: impl AsRef<[u8]>, output: &mut [u8]) { + /// Compute hash of `data` and write it into `output`. + fn digest_xof(input: impl AsRef<[u8]>, output: &mut [u8]) + where + Self: Default, + { let mut hasher = Self::default(); hasher.update(input.as_ref()); hasher.finalize_xof().read(output); @@ -113,9 +158,20 @@ pub trait ExtendableOutput: Sized + Update + Default + Reset { self.finalize_xof().read(&mut buf); buf } +} + +/// Trait for hash functions with extendable-output (XOF) able to reset themselves. +pub trait ExtendableOutputReset: ExtendableOutput + Reset { + /// Retrieve XOF reader and reset hasher instance state. + fn finalize_xof_reset(&mut self) -> Self::Reader; + + /// Finalize XOF, write result into `out`, and reset the hasher state. + fn finalize_xof_reset_into(&mut self, out: &mut [u8]) { + self.finalize_xof_reset().read(out); + } /// Retrieve result into a boxed slice of the specified size and reset - /// the hasher's state. + /// the hasher state. /// /// `Box<[u8]>` is used instead of `Vec` to save stack space, since /// they have size of 2 and 3 words respectively. @@ -128,8 +184,8 @@ pub trait ExtendableOutput: Sized + Update + Default + Reset { } } -/// Trait for variable output size hash functions. -pub trait VariableOutput: Sized + Update + Reset { +/// Trait for hash functions with variable-size output. +pub trait VariableOutput: Sized + Update { /// Maximum size of output hash. const MAX_OUTPUT_SIZE: usize; @@ -142,17 +198,11 @@ pub trait VariableOutput: Sized + Update + Reset { /// Get output size of the hasher instance provided to the `new` method fn output_size(&self) -> usize; - /// Retrieve result via closure and consume hasher. - /// - /// Closure is guaranteed to be called, length of the buffer passed to it - /// will be equal to `output_size`. - fn finalize_variable(self, f: impl FnOnce(&[u8])); - - /// Retrieve result via closure and reset the hasher state. + /// Write result into the output buffer. /// - /// Closure is guaranteed to be called, length of the buffer passed to it - /// will be equal to `output_size`. - fn finalize_variable_reset(&mut self, f: impl FnOnce(&[u8])); + /// Returns `Err(InvalidOutputSize)` if `out` size is not equal to + /// `self.output_size()`. + fn finalize_variable(self, out: &mut [u8]) -> Result<(), InvalidBufferSize>; /// Compute hash of `data` and write it to `output`. /// @@ -165,8 +215,9 @@ pub trait VariableOutput: Sized + Update + Reset { ) -> Result<(), InvalidOutputSize> { let mut hasher = Self::new(output.len())?; hasher.update(input.as_ref()); - hasher.finalize_variable(|out| output.copy_from_slice(out)); - Ok(()) + hasher + .finalize_variable(output) + .map_err(|_| InvalidOutputSize) } /// Retrieve result into a boxed slice and consume hasher. @@ -178,11 +229,21 @@ pub trait VariableOutput: Sized + Update + Reset { fn finalize_boxed(self) -> Box<[u8]> { let n = self.output_size(); let mut buf = vec![0u8; n].into_boxed_slice(); - self.finalize_variable(|res| buf.copy_from_slice(res)); + self.finalize_variable(&mut buf) + .expect("buf length is equal to output_size"); buf } +} - /// Retrieve result into a boxed slice and reset hasher state. +/// Trait for hash functions with variable-size output able to reset themselves. +pub trait VariableOutputReset: VariableOutput + Reset { + /// Write result into the output buffer and reset the hasher state. + /// + /// Returns `Err(InvalidOutputSize)` if `out` size is not equal to + /// `self.output_size()`. + fn finalize_variable_reset(&mut self, out: &mut [u8]) -> Result<(), InvalidBufferSize>; + + /// Retrieve result into a boxed slice and reset the hasher state. /// /// `Box<[u8]>` is used instead of `Vec` to save stack space, since /// they have size of 2 and 3 words respectively. @@ -191,12 +252,13 @@ pub trait VariableOutput: Sized + Update + Reset { fn finalize_boxed_reset(&mut self) -> Box<[u8]> { let n = self.output_size(); let mut buf = vec![0u8; n].into_boxed_slice(); - self.finalize_variable_reset(|res| buf.copy_from_slice(res)); + self.finalize_variable_reset(&mut buf) + .expect("buf length is equal to output_size"); buf } } -/// The error type for variable hasher initialization. +/// The error type used in variable hash traits. #[derive(Clone, Copy, Debug, Default)] pub struct InvalidOutputSize; @@ -209,3 +271,16 @@ impl fmt::Display for InvalidOutputSize { #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl std::error::Error for InvalidOutputSize {} + +/// Buffer length is not equal to hash output size. +#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] +pub struct InvalidBufferSize; + +impl fmt::Display for InvalidBufferSize { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("invalid buffer length") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for InvalidBufferSize {} diff --git a/digest/src/mac.rs b/digest/src/mac.rs new file mode 100644 index 00000000..efebe1dc --- /dev/null +++ b/digest/src/mac.rs @@ -0,0 +1,221 @@ +use crate::{FixedOutput, FixedOutputReset, Update}; +use crypto_common::{InvalidLength, Key, KeyInit, KeySizeUser, Output, OutputSizeUser, Reset}; + +use core::fmt; +use generic_array::typenum::Unsigned; +use subtle::{Choice, ConstantTimeEq}; + +/// Marker trait for Message Authentication algorithms. +#[cfg_attr(docsrs, doc(cfg(feature = "mac")))] +pub trait MacMarker {} + +/// Convinience wrapper trait covering functionality of Message Authentication algorithms. +/// +/// This trait wraps [`KeyInit`], [`Update`], [`FixedOutput`], and [`MacMarker`] +/// traits and provides additional convenience methods. +#[cfg_attr(docsrs, doc(cfg(feature = "mac")))] +pub trait Mac: KeySizeUser + OutputSizeUser + Sized { + /// Create new value from fixed size key. + fn new(key: &Key) -> Self; + + /// Create new value from variable size key. + fn new_from_slice(key: &[u8]) -> Result; + + /// Update state using the provided data. + fn update(&mut self, data: &[u8]); + + /// Obtain the result of a [`Mac`] computation as a [`CtOutput`] and consume + /// [`Mac`] instance. + fn finalize(self) -> CtOutput; + + /// Obtain the result of a [`Mac`] computation as a [`CtOutput`] and reset + /// [`Mac`] instance. + fn finalize_reset(&mut self) -> CtOutput + where + Self: FixedOutputReset; + + /// Reset MAC instance to its initial state. + fn reset(&mut self) + where + Self: Reset; + + /// Check if tag/code value is correct for the processed input. + fn verify(self, tag: &Output) -> Result<(), MacError>; + + /// Check truncated tag correctness using all bytes + /// of calculated tag. + /// + /// Returns `Error` if `tag` is not valid or not equal in length + /// to MAC's output. + fn verify_slice(self, tag: &[u8]) -> Result<(), MacError>; + + /// Check truncated tag correctness using left side bytes + /// (i.e. `tag[..n]`) of calculated tag. + /// + /// Returns `Error` if `tag` is not valid or empty. + fn verify_truncated_left(self, tag: &[u8]) -> Result<(), MacError>; + + /// Check truncated tag correctness using right side bytes + /// (i.e. `tag[n..]`) of calculated tag. + /// + /// Returns `Error` if `tag` is not valid or empty. + fn verify_truncated_right(self, tag: &[u8]) -> Result<(), MacError>; +} + +impl Mac for T { + #[inline(always)] + fn new(key: &Key) -> Self { + KeyInit::new(key) + } + + #[inline(always)] + fn new_from_slice(key: &[u8]) -> Result { + KeyInit::new_from_slice(key) + } + + #[inline] + fn update(&mut self, data: &[u8]) { + Update::update(self, data); + } + + #[inline] + fn finalize(self) -> CtOutput { + CtOutput::new(self.finalize_fixed()) + } + + #[inline(always)] + fn finalize_reset(&mut self) -> CtOutput + where + Self: FixedOutputReset, + { + CtOutput::new(self.finalize_fixed_reset()) + } + + #[inline] + fn reset(&mut self) + where + Self: Reset, + { + Reset::reset(self) + } + + #[inline] + fn verify(self, tag: &Output) -> Result<(), MacError> { + if self.finalize() == tag.into() { + Ok(()) + } else { + Err(MacError) + } + } + + #[inline] + fn verify_slice(self, tag: &[u8]) -> Result<(), MacError> { + let n = tag.len(); + if n != Self::OutputSize::USIZE { + return Err(MacError); + } + let choice = self.finalize_fixed().ct_eq(tag); + if choice.unwrap_u8() == 1 { + Ok(()) + } else { + Err(MacError) + } + } + + fn verify_truncated_left(self, tag: &[u8]) -> Result<(), MacError> { + let n = tag.len(); + if n == 0 || n > Self::OutputSize::USIZE { + return Err(MacError); + } + let choice = self.finalize_fixed()[..n].ct_eq(tag); + + if choice.unwrap_u8() == 1 { + Ok(()) + } else { + Err(MacError) + } + } + + fn verify_truncated_right(self, tag: &[u8]) -> Result<(), MacError> { + let n = tag.len(); + if n == 0 || n > Self::OutputSize::USIZE { + return Err(MacError); + } + let m = Self::OutputSize::USIZE - n; + let choice = self.finalize_fixed()[m..].ct_eq(tag); + + if choice.unwrap_u8() == 1 { + Ok(()) + } else { + Err(MacError) + } + } +} + +/// Fixed size output value which provides a safe [`Eq`] implementation that +/// runs in constant time. +/// +/// It is useful for implementing Message Authentication Codes (MACs). +#[derive(Clone)] +#[cfg_attr(docsrs, doc(cfg(feature = "subtle")))] +pub struct CtOutput { + bytes: Output, +} + +impl CtOutput { + /// Create a new [`CtOutput`] value. + #[inline(always)] + pub fn new(bytes: Output) -> Self { + Self { bytes } + } + + /// Get the inner [`Output`] array this type wraps. + #[inline(always)] + pub fn into_bytes(self) -> Output { + self.bytes + } +} + +impl From> for CtOutput { + #[inline(always)] + fn from(bytes: Output) -> Self { + Self { bytes } + } +} + +impl<'a, T: OutputSizeUser> From<&'a Output> for CtOutput { + #[inline(always)] + fn from(bytes: &'a Output) -> Self { + bytes.clone().into() + } +} + +impl ConstantTimeEq for CtOutput { + #[inline(always)] + fn ct_eq(&self, other: &Self) -> Choice { + self.bytes.ct_eq(&other.bytes) + } +} + +impl PartialEq for CtOutput { + #[inline(always)] + fn eq(&self, x: &CtOutput) -> bool { + self.ct_eq(x).unwrap_u8() == 1 + } +} + +impl Eq for CtOutput {} + +/// Error type for when the [`Output`] of a [`Mac`] +/// is not equal to the expected value. +#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] +pub struct MacError; + +impl fmt::Display for MacError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("MAC tag mismatch") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for MacError {}