diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b59ee19d5..279d5796a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,7 +27,7 @@ jobs: - stable - beta - nightly - - 1.86.0 # MSRV + - 1.87.0 # MSRV os: - ubuntu-latest - macos-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index f5d820e88..b1b228d46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ +### 0.18.0 + +**Data:** TBD. + +**Changelog:** +- [Breaking change] Rust Edition moved from `2021` to `2024`. +- [Breaking change] `auth::SecretKey` has been changed to reference directly the hazardous type instead: `crate::hazardous::mac::blake2b::SecretKey`. +- [Breaking change] `T::generate() -> Self` -> `T::generate() -> Result` where this was not already the case. All `T::generate()` calls are now fallible and return `UnknownCryptoError` so all OS-related errors can be handled. +- [Breaking change] `T::from_slice()` is no longer provided. Instead, all types provide: `TryFrom<&[u8]>`, `TryFrom<&Vec>` and `TryFrom<&[u8; N]>` + `From<[N]>` where applicable. +- [Breaking change] `T::unprotected_as_bytes()` -> `T::unprotected_as_ref()`. +- [Breaking change] High-level types used in non-`hazardous` API no longer implement `Default` with a panicking CSPRNG call. Instead `generate() -> Result` is provided exclusively. +- [Breaking change] High-level types used in non-`hazardous` API no longer implement `T::generate(length: usize)`. +- [Breaking change] ML-KEM and X-Wing API have undergone large re-design: + - [Breaking change] `mlkem*:MlKem*` struct no longer exists, and all functionality has been moved to the respective `KeyPair`, `EncapsulationKey` and `DecapsulationKey` types. + - [Breaking change] ML-KEM `DecapsulatoinKeys` no longer themself perform key-caching. This has been moved to `KeyPair`. `KeyPair` therefor offers important performance benefits when decapsulating with the same secret more than once. + - [Breaking change] Constants previously associated with the zero-sized structs are now in `mlkem*::` modules. + - [Breaking change] `ML-KEM` `DecapuslationKey`s can now return the raw, encoded bytes. +- [Breaking change] `orion::kdf::Password` and `orion::pwhash::Password` no longer has `generate()` since it is meant to represent a user-supplied password (one of the many drawbacks of the older macro-based approach). +- [Breaking change] Types that previously implemented `Copy` do not anymore. `Copy` in all cases requires copying a lot of bytes and could hide a performance penalty, so now only `Clone` is available for `Public`. +- [Breaking change] `orion::hazardous::ecc::x25519::PublicKey` no longer stores the u-coordinate in masked form, but original byte slice. The `PartialEq` still respects (applies masking) the u-coordinate condition. Masking is applied before Montgomery ladder. +- [Breaking change] `orion::hazardous::ecc::x25519::SecretKey` no longer stores the clamped scalar, but the original byte slice. This changes the inherited `PartialEq`, which now operates on the original bytes, not the clamped. Clamping is applied before Montgomery ladder. +- [Breaking change] `orion::hazardous::ecc::x25519::SharedSecret` now respects (applies masking) the u-coordinate condition for `PartialEq`. + +- MSRV bumped to `1.87` + ### 0.17.14 **Date:** April 25, 2026. diff --git a/Cargo.toml b/Cargo.toml index 1a385df8c..44864e7d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "orion" -version = "0.17.14" +version = "0.18.0" authors = ["brycx "] description = "Usable, easy and safe pure-Rust crypto" keywords = ["cryptography", "crypto", "aead", "pqc", "kem"] categories = ["cryptography", "no-std"] -edition = "2021" -rust-version = "1.86" # Update CI (MSRV) test + README along with this. +edition = "2024" +rust-version = "1.87" # Update CI (MSRV) test + README along with this. readme = "README.md" repository = "https://github.com/orion-rs/orion" documentation = "https://docs.rs/orion" diff --git a/README.md b/README.md index dedec4d96..86b6675ea 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # orion -[![Daily tests](https://github.com/orion-rs/orion/actions/workflows/daily_tests.yml/badge.svg)](https://github.com/orion-rs/orion/actions/workflows/daily_tests.yml) [![dudect](https://github.com/orion-rs/orion-dudect/actions/workflows/weekly.yml/badge.svg)](https://github.com/orion-rs/orion-dudect/actions/workflows/weekly.yml) [![Audit](https://github.com/orion-rs/orion/actions/workflows/audit_check.yml/badge.svg)](https://github.com/orion-rs/orion/actions/workflows/audit_check.yml) [![codecov](https://codecov.io/gh/orion-rs/orion/branch/master/graph/badge.svg)](https://codecov.io/gh/orion-rs/orion) [![Documentation](https://docs.rs/orion/badge.svg)](https://docs.rs/orion/) [![Crates.io](https://img.shields.io/crates/v/orion.svg)](https://crates.io/crates/orion) [![Safety Dance](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) [![MSRV](https://img.shields.io/badge/MSRV-1.86-informational.svg)](https://img.shields.io/badge/MSRV-1.86-informational) [![Matrix](https://img.shields.io/matrix/orion-rs:matrix.org.svg?logo=matrix)](https://matrix.to/#/#orion-rs:matrix.org) +[![Daily tests](https://github.com/orion-rs/orion/actions/workflows/daily_tests.yml/badge.svg)](https://github.com/orion-rs/orion/actions/workflows/daily_tests.yml) [![dudect](https://github.com/orion-rs/orion-dudect/actions/workflows/weekly.yml/badge.svg)](https://github.com/orion-rs/orion-dudect/actions/workflows/weekly.yml) [![Audit](https://github.com/orion-rs/orion/actions/workflows/audit_check.yml/badge.svg)](https://github.com/orion-rs/orion/actions/workflows/audit_check.yml) [![codecov](https://codecov.io/gh/orion-rs/orion/branch/master/graph/badge.svg)](https://codecov.io/gh/orion-rs/orion) [![Documentation](https://docs.rs/orion/badge.svg)](https://docs.rs/orion/) [![Crates.io](https://img.shields.io/crates/v/orion.svg)](https://crates.io/crates/orion) [![Safety Dance](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) [![MSRV](https://img.shields.io/badge/MSRV-1.87-informational.svg)](https://img.shields.io/badge/MSRV-1.87-informational) [![Matrix](https://img.shields.io/matrix/orion-rs:matrix.org.svg?logo=matrix)](https://matrix.to/#/#orion-rs:matrix.org) ### About Orion is a cryptography library written in pure Rust. It aims to provide easy and usable crypto while trying to minimize the use of unsafe code. You can read more about Orion in the [wiki](https://github.com/orion-rs/orion/wiki). @@ -27,7 +27,7 @@ Orion uses formally verified arithmetic, generated by Fiat Crypto, for the X2551 See the [SECURITY.md](https://github.com/orion-rs/orion/blob/master/SECURITY.md) regarding recommendations on correct use, reporting security issues and more. Additional information about security regarding Orion is available in the [wiki](https://github.com/orion-rs/orion/wiki/Security). ### Minimum Supported Rust Version -Rust 1.86 or later is supported however, the majority of testing happens with latest stable Rust. +Rust 1.87 or later is supported however, the majority of testing happens with latest stable Rust. MSRV may be changed at any point and will not be considered a SemVer breaking change. diff --git a/benches/bench.rs b/benches/bench.rs index 9d899cd5d..97519eec1 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -25,6 +25,7 @@ extern crate orion; use criterion::*; +use orion::KP; use orion::hazardous::{ aead::{chacha20poly1305, xchacha20poly1305}, hash::*, @@ -40,7 +41,7 @@ mod mac { pub fn bench_poly1305(c: &mut Criterion) { let mut group = c.benchmark_group("Poly1305"); - let key = poly1305::OneTimeKey::generate(); + let key = poly1305::OneTimeKey::generate().unwrap(); for size in INPUT_SIZES.iter() { let input = vec![0u8; *size]; @@ -60,7 +61,7 @@ mod mac { let mut group = c.benchmark_group("HMAC-SHA256"); // NOTE: Setting the key like this will pad it for HMAC. // Padding is therefore not included in benchmarks. - let key = hmac::sha256::SecretKey::generate(); + let key = hmac::sha256::SecretKey::generate().unwrap(); for size in INPUT_SIZES.iter() { let input = vec![0u8; *size]; @@ -80,7 +81,7 @@ mod mac { let mut group = c.benchmark_group("HMAC-SHA512"); // NOTE: Setting the key like this will pad it for HMAC. // Padding is therefore not included in benchmarks. - let key = hmac::sha512::SecretKey::generate(); + let key = hmac::sha512::SecretKey::generate().unwrap(); for size in INPUT_SIZES.iter() { let input = vec![0u8; *size]; @@ -111,8 +112,8 @@ mod aead { pub fn bench_chacha20poly1305(c: &mut Criterion) { let mut group = c.benchmark_group("ChaCha20-Poly1305"); - let key = chacha20poly1305::SecretKey::generate(); - let nonce = chacha20poly1305::Nonce::from_slice(&[0u8; 12]).unwrap(); + let key = chacha20poly1305::SecretKey::generate().unwrap(); + let nonce = chacha20poly1305::Nonce::try_from(&[0u8; 12]).unwrap(); for size in INPUT_SIZES.iter() { let input = vec![0u8; *size]; @@ -134,8 +135,8 @@ mod aead { pub fn bench_xchacha20poly1305(c: &mut Criterion) { let mut group = c.benchmark_group("XChaCha20-Poly1305"); - let key = xchacha20poly1305::SecretKey::generate(); - let nonce = xchacha20poly1305::Nonce::generate(); + let key = xchacha20poly1305::SecretKey::generate().unwrap(); + let nonce = xchacha20poly1305::Nonce::generate().unwrap(); for size in INPUT_SIZES.iter() { let input = vec![0u8; *size]; @@ -249,7 +250,7 @@ mod stream { pub fn bench_chacha20(c: &mut Criterion) { let mut group = c.benchmark_group("ChaCha20"); - let key = chacha20poly1305::SecretKey::generate(); + let key = chacha20poly1305::SecretKey::generate().unwrap(); let nonce = chacha20poly1305::Nonce::from([0u8; 12]); for size in INPUT_SIZES.iter() { @@ -269,8 +270,8 @@ mod stream { pub fn bench_xchacha20(c: &mut Criterion) { let mut group = c.benchmark_group("XChaCha20"); - let key = xchacha20::SecretKey::generate(); - let nonce = xchacha20::Nonce::generate(); + let key = xchacha20::SecretKey::generate().unwrap(); + let nonce = xchacha20::Nonce::generate().unwrap(); for size in INPUT_SIZES.iter() { let input = vec![0u8; *size]; @@ -368,7 +369,7 @@ mod kdf { |b, iter_count| { b.iter(|| { pbkdf2::sha256::derive_key( - &pbkdf2::sha256::Password::from_slice(&salt).unwrap(), + &pbkdf2::sha256::Password::try_from(&salt).unwrap(), &ikm, **iter_count, &mut dk_out, @@ -400,7 +401,7 @@ mod kdf { |b, iter_count| { b.iter(|| { pbkdf2::sha512::derive_key( - &pbkdf2::sha512::Password::from_slice(&salt).unwrap(), + &pbkdf2::sha512::Password::try_from(&salt).unwrap(), &ikm, **iter_count, &mut dk_out, @@ -458,13 +459,13 @@ mod ecc { pub fn bench_x25519(c: &mut Criterion) { let mut group = c.benchmark_group("X25519"); - let alice_sk = x25519::PrivateKey::generate(); + let alice_sk = x25519::PrivateKey::generate().unwrap(); let alice_pk = x25519::PublicKey::try_from(&alice_sk).unwrap(); group.sample_size(100); group.bench_function("key_agreement", move |b| { b.iter_with_setup( - || x25519::PrivateKey::generate(), + || x25519::PrivateKey::generate().unwrap(), |bob_sk| x25519::key_agreement(&bob_sk, &alice_pk).unwrap(), ) }); @@ -480,7 +481,7 @@ mod ecc { mod kem { use super::*; - use orion::hazardous::kem::{mlkem1024, mlkem512, mlkem768}; + use orion::hazardous::kem::{mlkem512, mlkem768, mlkem1024}; pub fn bench_mlkem512(c: &mut Criterion) { let mut group = c.benchmark_group("ML-KEM-512"); diff --git a/src/generics/data.rs b/src/generics/data.rs new file mode 100644 index 000000000..77400cc72 --- /dev/null +++ b/src/generics/data.rs @@ -0,0 +1,421 @@ +// MIT License + +// Copyright (c) 2026 The orion Developers + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use crate::{ + errors::UnknownCryptoError, + generics::data::sealed::{Data, TryFromBytes}, +}; + +#[cfg(feature = "alloc")] +use alloc::vec; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; +#[cfg(feature = "zeroize")] +use zeroize::Zeroize; + +pub(crate) mod sealed { + use crate::errors::UnknownCryptoError; + + /// Sealing marker trait for any public/private newtypes based on const-generics + /// such that they cannot be implemented outside the library, given that [`Context`] + /// requires [`Sealed`]. + pub trait Sealed {} + + pub trait TryFromBytes: Sized { + fn try_from_bytes(bytes: &[u8]) -> Result; + } + + /// A datatype that can be used as the internal backing of a newtype. + pub trait Data: AsRef<[u8]> + AsMut<[u8]> + TryFromBytes + Sealed { + /// Return the amount of data stored. + fn len(&self) -> usize; + + /// Return `true` if no data is stored, `false` otherwise. + fn is_empty(&self) -> bool; + + /// Create a new instance with `size`. + fn new(size: usize) -> Result; + + #[cfg(feature = "zeroize")] + /// Zero our the data stored. + fn memzero(&mut self); + } +} + +#[derive(Debug, Clone)] +/// A newtype data-type that represents static constant size of [`u8`]s. +/// This is for types that only have _one_ valid size. +/// +/// SECURITY: +/// While these types do derive non-constant time or plain debug impls, these +/// should *NEVER* be exposed publicly anyway. The implementations of [`crate::generics::Public`]/[`crate::generics::Secret`] +/// will define protections for their type. +pub struct ByteArrayData { + pub(crate) bytes: [u8; LEN], +} + +impl sealed::Sealed for ByteArrayData {} + +impl AsRef<[u8]> for ByteArrayData { + fn as_ref(&self) -> &[u8] { + &self.bytes + } +} + +impl AsRef<[u8; LEN]> for ByteArrayData { + fn as_ref(&self) -> &[u8; LEN] { + &self.bytes + } +} + +impl AsMut<[u8]> for ByteArrayData { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.bytes + } +} + +impl AsMut<[u8; LEN]> for ByteArrayData { + fn as_mut(&mut self) -> &mut [u8; LEN] { + &mut self.bytes + } +} + +impl From<[u8; LEN]> for ByteArrayData { + fn from(value: [u8; LEN]) -> Self { + Self { bytes: value } + } +} + +impl TryFromBytes for ByteArrayData { + fn try_from_bytes(bytes: &[u8]) -> Result { + Ok(Self { + // NOTE: Length check here is implicit to const-def of LEN. + bytes: bytes.try_into().map_err(|_| UnknownCryptoError)?, + }) + } +} + +impl Data for ByteArrayData { + fn len(&self) -> usize { + debug_assert_eq!(self.bytes.len(), LEN); + LEN + } + + fn is_empty(&self) -> bool { + LEN == 0 + } + + fn new(_size: usize) -> Result { + debug_assert_eq!(_size, LEN); + Ok(Self { bytes: [0u8; LEN] }) + } + + #[cfg(feature = "zeroize")] + fn memzero(&mut self) { + self.bytes.iter_mut().zeroize(); + } +} + +#[derive(Debug, Clone)] +/// A newtype data-type that represents array of bytes within a `MAX` known range. +/// This is for types that have a valid size within a stack-allocatable range, e.g. BLAKE2b secret keys. +/// +/// It always allocates `MAX`, but keeps an extra length field to keep track of what the original value was. +/// If the provided byte slice was less than `MAX`, then the required bytes from left to right are returned. +/// +/// SECURITY: +/// While these types do derive non-constant time or omitted debug impls, these +/// should *NEVER* be exposed publicly anyway. The implementations of [`crate::generics::Public`]/[`crate::generics::Secret`] +/// will define protections for their type. +pub struct ByteArrayVecData { + pub(crate) bytes: [u8; MAX], + pub(crate) len: usize, +} + +impl sealed::Sealed for ByteArrayVecData {} + +impl AsRef<[u8]> for ByteArrayVecData { + fn as_ref(&self) -> &[u8] { + self.bytes[..self.len].as_ref() + } +} + +impl AsMut<[u8]> for ByteArrayVecData { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.bytes[..self.len] + } +} + +impl TryFromBytes for ByteArrayVecData { + fn try_from_bytes(bytes: &[u8]) -> Result { + if !(MIN..=MAX).contains(&bytes.len()) { + return Err(UnknownCryptoError); + } + + let mut ret = Self { + bytes: [0u8; MAX], + len: bytes.len(), + }; + + ret.bytes[..bytes.len()].copy_from_slice(bytes); + + Ok(ret) + } +} + +impl Data for ByteArrayVecData { + fn len(&self) -> usize { + self.len + } + + fn is_empty(&self) -> bool { + self.len == 0 + } + + fn new(size: usize) -> Result { + if !(MIN..=MAX).contains(&size) { + return Err(UnknownCryptoError); + } + + Ok(Self { + bytes: [0u8; MAX], + len: size, + }) + } + + #[cfg(feature = "zeroize")] + fn memzero(&mut self) { + self.bytes.iter_mut().zeroize(); + } +} + +#[derive(Debug, Clone)] +#[cfg(any(feature = "safe_api", feature = "alloc"))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "safe_api", feature = "alloc"))))] +/// A newtype data-type that represents heap-allocated [`Vec`]. +/// +/// The maximum size for [`ByteVecData`] is [`isize::MAX`]. +/// +/// SECURITY: +/// While these types do derive non-constant time or omitted debug impls, these +/// should *NEVER* be exposed publicly anyway. The implementations of [`crate::generics::Public`]/[`crate::generics::Secret`] +/// will define protections for their type. +pub struct ByteVecData { + pub(crate) bytes: Vec, +} + +#[cfg(any(feature = "safe_api", feature = "alloc"))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "safe_api", feature = "alloc"))))] +impl sealed::Sealed for ByteVecData {} + +#[cfg(any(feature = "safe_api", feature = "alloc"))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "safe_api", feature = "alloc"))))] +impl AsRef<[u8]> for ByteVecData { + fn as_ref(&self) -> &[u8] { + self.bytes.as_slice() + } +} + +#[cfg(any(feature = "safe_api", feature = "alloc"))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "safe_api", feature = "alloc"))))] +impl AsMut<[u8]> for ByteVecData { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.bytes + } +} + +#[cfg(any(feature = "safe_api", feature = "alloc"))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "safe_api", feature = "alloc"))))] +impl TryFromBytes for ByteVecData { + fn try_from_bytes(bytes: &[u8]) -> Result { + // See issue on `isize` limit: https://github.com/orion-rs/orion/issues/130 + if bytes.is_empty() || bytes.len() > (isize::MAX as usize) { + return Err(UnknownCryptoError); + } + + Ok(Self { + bytes: Vec::from(bytes), + }) + } +} + +#[cfg(any(feature = "safe_api", feature = "alloc"))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "safe_api", feature = "alloc"))))] +impl Data for ByteVecData { + fn len(&self) -> usize { + self.bytes.len() + } + + fn is_empty(&self) -> bool { + self.bytes.is_empty() + } + + fn new(size: usize) -> Result { + // See issue on `isize` limit: https://github.com/orion-rs/orion/issues/130 + if size == 0 || size > (isize::MAX as usize) { + return Err(UnknownCryptoError); + } + + Ok(Self { + bytes: vec![0u8; size], + }) + } + + #[cfg(feature = "zeroize")] + fn memzero(&mut self) { + self.bytes.iter_mut().zeroize(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[should_panic] + #[cfg(debug_assertions)] // panics with debug_assertions. + fn force_internal_new_size_contract() { + // ByteArrayData::new() does not use the size argument. It uses LEN + // directly. But if this should ever change or simply for the reason + // of keeping things sane, we require the size argument then be LEN. + assert!(ByteArrayData::<4>::new(3).is_ok()); + } + + #[test] + fn test_arraydata_u8() { + assert!(ByteArrayData::<4>::try_from_bytes(&[0u8; 4]).is_ok()); + assert!(ByteArrayData::<4>::try_from_bytes(&[0u8; 3]).is_err()); + assert!(ByteArrayData::<4>::try_from_bytes(&[0u8; 5]).is_err()); + assert!(ByteArrayData::<4>::new(4).is_ok()); + assert_eq!(&ByteArrayData::<4>::new(4).unwrap().as_ref(), &[0u8; 4]); + + let mut data = ByteArrayData::<4>::try_from_bytes(&[1u8; 4]).unwrap(); + assert_eq!(data.bytes, [1u8; 4]); + assert_eq!(data.as_ref(), [1u8; 4]); // AsRef<[u8]> + assert_eq!(data.as_mut(), [1u8; 4]); // AsMut<[u8]> + assert_eq!( + as AsRef<[u8; 4]>>::as_ref(&data), + &[1u8; 4] + ); // AsRef<[; N]> + assert_eq!( + as AsMut<[u8; 4]>>::as_mut(&mut data), + &[1u8; 4] + ); // AsRef<[; N]> + assert_eq!(data.len(), 4); + assert!(!data.is_empty()); + + #[cfg(feature = "zeroize")] + { + data.memzero(); + assert_eq!(data.bytes, [0u8; 4]); + } + } + + #[test] + fn test_arrayvecdata_u8() { + assert!(ByteArrayVecData::<1, 4>::try_from_bytes(&[0u8; 0]).is_err()); + assert!(ByteArrayVecData::<1, 4>::try_from_bytes(&[0u8; 1]).is_ok()); + assert!(ByteArrayVecData::<1, 4>::try_from_bytes(&[0u8; 4]).is_ok()); + assert!(ByteArrayVecData::<1, 4>::try_from_bytes(&[0u8; 3]).is_ok()); + assert!(ByteArrayVecData::<1, 4>::try_from_bytes(&[0u8; 5]).is_err()); + assert!(ByteArrayVecData::<1, 4>::new(0).is_err()); + assert!(ByteArrayVecData::<1, 4>::new(1).is_ok()); + assert!(ByteArrayVecData::<1, 4>::new(4).is_ok()); + assert!(ByteArrayVecData::<1, 4>::new(3).is_ok()); + assert!(ByteArrayVecData::<1, 4>::new(5).is_err()); + + assert_eq!( + ByteArrayVecData::<1, 4>::new(4).unwrap().as_ref(), + &[0u8; 4] + ); + + let mut data = ByteArrayVecData::<1, 4>::try_from_bytes(&[1u8; 4]).unwrap(); + assert_eq!(data.bytes, [1u8; 4]); + assert_eq!(data.as_ref(), &[1u8; 4]); + assert_eq!(data.as_mut(), &mut [1u8; 4]); + assert_eq!(data.len(), 4); + assert!(!data.is_empty()); + + #[cfg(feature = "zeroize")] + { + data.memzero(); + assert_eq!(data.bytes, [0u8; 4]); + } + + let mut data = ByteArrayVecData::<1, 4>::try_from_bytes(&[1u8; 3]).unwrap(); + assert_eq!(&data.bytes[..3], &[1u8; 3]); + assert_eq!(&data.bytes[3], &0u8); + assert_eq!(data.as_ref(), &[1u8; 3]); + assert_eq!(data.as_mut(), &mut [1u8; 3]); + assert_eq!(data.len(), 3); + assert!(!data.is_empty()); + + #[cfg(feature = "zeroize")] + { + // memzero() zeroes all bytes, even if the full range wasn't used. + data.memzero(); + assert_eq!(data.bytes, [0u8; 4]); + } + } + + #[test] + #[cfg(any(feature = "safe_api", feature = "alloc"))] + fn test_vecdata_u8() { + assert!(ByteVecData::try_from_bytes(&[0u8; 0]).is_err()); + assert!(ByteVecData::try_from_bytes(&[0u8; 4]).is_ok()); + assert!(ByteVecData::try_from_bytes(&[0u8; 3]).is_ok()); + assert!(ByteVecData::try_from_bytes(&[0u8; 5]).is_ok()); + assert!(ByteVecData::new(0).is_err()); + assert!(ByteVecData::new(4).is_ok()); + assert!(ByteVecData::new(3).is_ok()); + assert!(ByteVecData::new(5).is_ok()); + + assert_eq!(ByteVecData::new(4).unwrap().as_ref(), &[0u8; 4]); + + let mut data = ByteVecData::try_from_bytes(&[1u8; 4]).unwrap(); + assert_eq!(data.bytes, [1u8; 4]); + assert_eq!(data.as_ref(), &[1u8; 4]); + assert_eq!(data.as_mut(), &mut [1u8; 4]); + assert_eq!(data.len(), 4); + assert!(!data.is_empty()); + + #[cfg(feature = "zeroize")] + { + data.memzero(); + assert_eq!(data.bytes, [0u8; 4]); + } + + let mut data = ByteVecData::try_from_bytes(&[1u8; 3]).unwrap(); + assert_eq!(data.bytes, [1u8; 3]); + assert_eq!(data.as_ref(), &[1u8; 3]); + assert_eq!(data.as_mut(), &mut [1u8; 3]); + assert_eq!(data.len(), 3); + assert!(!data.is_empty()); + + #[cfg(feature = "zeroize")] + { + // memzero() zeroes all bytes, even if the full range wasn't used. + data.memzero(); + assert_eq!(data.bytes, [0u8; 3]); + } + } +} diff --git a/src/generics/keytype.rs b/src/generics/keytype.rs new file mode 100644 index 000000000..0f042ea30 --- /dev/null +++ b/src/generics/keytype.rs @@ -0,0 +1,386 @@ +// MIT License + +// Copyright (c) 2026 The orion Developers + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use crate::errors::UnknownCryptoError; +use crate::generics::data::sealed::{Data, Sealed, TryFromBytes}; +use core::fmt::Debug; +use core::marker::PhantomData; +use subtle::ConstantTimeEq; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +/// A sealed trait covering a generic newtype backed by a `Data`-impl over some `Primitive`. +pub trait TypeSpec: Sealed + Sized { + #[doc(hidden)] + /// Name to use in [`Debug`] impl. + const NAME: &'static str; + + /// Data-backend, such as statically sized array, MAX-bound allocation with + /// data on some range up to (incl.), [`Vec`]-based, etc. + type TypeData: Data + TryFromBytes; + + #[doc(hidden)] + /// The logic used to create an instance of [`Self::TypeData`]. Default logic + /// are located within a specific [`Data`]-implementing struct, but can be overridden, + /// if additional pre-processing is required. + fn parse_bytes(bytes: &[u8]) -> Result { + Self::TypeData::try_from_bytes(bytes) + } + + #[doc(hidden)] + /// Default logic to get the amount of data stored. Can be overwritten if + /// the internal representation does not match [`AsRef<[u8]>`] for example. + /// + /// NOTE: If overwritten, make sure it matches with [`Self::is_empty`] behavior. + fn len(datatype: &Self::TypeData) -> usize { + datatype.len() + } + + #[doc(hidden)] + /// Default logic to get the amount of data stored. Can be overwritten if + /// the internal representation does not match [`AsRef<[u8]>`] for example. + /// + /// NOTE: If overwritten, make sure it matches with [`Self::len`] behavior. + fn is_empty(datatype: &Self::TypeData) -> bool { + datatype.len() == 0 + } + + #[doc(hidden)] + /// Default constant-time `PartialEq` impl. Can be overwritten if: + /// - internal representation has additional requirements or + /// the bit-for-bit representation does not imply equality. + /// + /// [`Secret`] implements this as default internally. + fn ct_partial_eq(lhs: &Self::TypeData, rhs: &[u8]) -> bool { + lhs.as_ref().ct_eq(rhs).into() + } + + #[doc(hidden)] + /// Default variable-time `PartialEq` impl.Can be overwritten if: + /// - internal representation has additional requirements or + /// the bit-for-bit representation does not imply equality. + /// + /// [`Public`] implements this as default internally. + fn vartime_partial_eq(lhs: &Self::TypeData, rhs: &[u8]) -> bool { + lhs.as_ref() == rhs + } +} + +/// Trait for a [`Secret`] type that may be instantiated using calls to a CSPRNG. +pub trait GenerateSecret: TypeSpec { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + /// Use a CSPRNG to fill a new instance of this type with secure random bytes. + /// + /// # Errors: + /// - Failure during the call to the OS CSPRNG. + fn generate() -> Result, UnknownCryptoError>; +} + +impl Secret { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + /// See [`GenerateSecret::generate`]. + pub fn generate() -> Result { + T::generate() + } +} + +/// Trait for a [`Public`] type that may be instantiated using calls to a CSPRNG. +pub trait GeneratePublic: TypeSpec { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + /// Use a CSPRNG to fill a new instance of this type with secure random bytes. + /// + /// # Errors: + /// - Failure during the call to the OS CSPRNG. + fn generate() -> Result, UnknownCryptoError>; +} + +impl Public { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + /// See [`GeneratePublic::generate`]. + pub fn generate() -> Result { + T::generate() + } +} + +/// A key-pair trait, implemented over types holding a [`Secret`] key and its [`Public`] counterpart. +pub trait KP: Debug { + // NOTE(brycx): This trait is not implemented over a generalized keytype, like [`Secret`] and [`Public`] are. + // This is intentional, in the way that this should simply provide a basic API contract for a given keypair. + // The purpose of a keypair, such as ML-KEM, is more specialized operations like key-caching that are not + // as easily generalized over the two other generic keytypes. As such, keeping this as a trait only + // is the intended way, to allow more specialized implementations and struct fields where needed. + + /// Return a reference to this key-pairs [`Secret`] key. + fn private(&self) -> &Secret; + + /// Return a reference to this key-pairs [`Public`] key. + fn public(&self) -> &Public; +} + +#[derive(Clone)] +/// A [`Public`] datatype. +/// +/// # SECURITY: +/// - Provides a variable-time implementation of [`PartialEq`]. +/// - Provides non-omitted implementation of [`Debug`]. +pub struct Public { + pub(crate) data: T::TypeData, + _spec: PhantomData, +} + +#[derive(Clone)] // SECURITY: Requires T: Clone which should only be enabled selectively. +/// A [`Secret`] datatype. +/// +/// # SECURITY: +/// - Provides a constant-time implementation of [`PartialEq`]. +/// - Provides omitted implementation of [`Debug`]. +/// - Provides a zeroizing [`Drop`] implementation if `"zeroize"` feature is enabled. +pub struct Secret { + pub(crate) data: T::TypeData, + _spec: PhantomData, +} + +impl Public { + pub(crate) fn from_data(data: T::TypeData) -> Self { + Public { + data, + _spec: PhantomData, + } + } + + /// Return the length of the object. + pub fn len(&self) -> usize { + T::len(&self.data) + } + + /// Return true if this object does not hold any data, false otherwise. + /// + /// # NOTE: + /// This method should always return false, since there shouldn't be a way to create + /// an empty instance of this object. + pub fn is_empty(&self) -> bool { + T::is_empty(&self.data) + } +} + +impl Secret { + pub(crate) fn from_data(data: T::TypeData) -> Self { + Secret { + data, + _spec: PhantomData, + } + } + + /// Return the length of the object. + pub fn len(&self) -> usize { + T::len(&self.data) + } + + /// Return true if this object does not hold any data, false otherwise. + /// + /// # NOTE: This method should always return false, since there shouldn't be a way to create + /// an empty instance of this object. + pub fn is_empty(&self) -> bool { + T::is_empty(&self.data) + } +} + +// ------ Public trait impls + +impl PartialEq for Public { + fn eq(&self, other: &Self) -> bool { + T::vartime_partial_eq(&self.data, other.data.as_ref()) + } +} + +impl Eq for Public {} + +impl PartialEq<[u8]> for Public { + fn eq(&self, other: &[u8]) -> bool { + T::vartime_partial_eq(&self.data, other) + } +} + +impl PartialEq<&[u8]> for Public { + fn eq(&self, other: &&[u8]) -> bool { + T::vartime_partial_eq(&self.data, other) + } +} + +impl PartialEq<&[u8; N]> for Public { + fn eq(&self, other: &&[u8; N]) -> bool { + T::vartime_partial_eq(&self.data, *other) + } +} + +impl Debug for Public { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{} {:?}", T::NAME, self.data.as_ref()) + } +} + +impl AsRef<[u8]> for Public { + fn as_ref(&self) -> &[u8] { + self.data.as_ref() + } +} + +impl TryFrom<&[u8]> for Public { + type Error = UnknownCryptoError; + + fn try_from(bytes: &[u8]) -> Result { + Ok(Public::from_data(T::parse_bytes(bytes)?)) + } +} + +impl TryFrom<&[u8; N]> for Public { + type Error = UnknownCryptoError; + + fn try_from(bytes: &[u8; N]) -> Result { + Ok(Public::from_data(T::parse_bytes(bytes)?)) + } +} + +#[cfg(any(feature = "safe_api", feature = "alloc"))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "safe_api", feature = "alloc"))))] +impl TryFrom<&Vec> for Public { + type Error = UnknownCryptoError; + + fn try_from(bytes: &Vec) -> Result { + Self::try_from(bytes.as_slice()) + } +} + +#[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] +/// This type tries to serialize as a `&[u8]` would. Note that the serialized +/// type likely does not have the same protections that Orion provides, such +/// as constant-time operations. A good rule of thumb is to only serialize +/// these types for storage. Don't operate on the serialized types. +impl serde::Serialize for Public { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + let bytes: &[u8] = &self.data.as_ref(); + bytes.serialize(serializer) + } +} + +#[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] +/// This type tries to deserialize as a `Vec` would. If it succeeds, the public data +/// will be built using `Self::try_from`. +/// +/// Note that **this allocates** once to store the referenced bytes on the heap. +impl<'de, T: TypeSpec> serde::Deserialize<'de> for Public { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + let bytes = Vec::::deserialize(deserializer)?; + TryFrom::try_from(bytes.as_slice()).map_err(serde::de::Error::custom) + } +} + +// ------ Secret trait impls + +impl Drop for Secret { + fn drop(&mut self) { + #[cfg(feature = "zeroize")] + { + self.data.memzero(); + } + } +} + +impl PartialEq for Secret { + fn eq(&self, other: &Self) -> bool { + T::ct_partial_eq(&self.data, other.data.as_ref()) + } +} + +impl Eq for Secret {} + +impl PartialEq<[u8]> for Secret { + fn eq(&self, other: &[u8]) -> bool { + T::ct_partial_eq(&self.data, other) + } +} + +impl PartialEq<&[u8]> for Secret { + fn eq(&self, other: &&[u8]) -> bool { + T::ct_partial_eq(&self.data, other) + } +} + +impl PartialEq<&[u8; N]> for Secret { + fn eq(&self, other: &&[u8; N]) -> bool { + T::ct_partial_eq(&self.data, *other) + } +} + +impl Debug for Secret { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{} {{***OMITTED***}}", T::NAME) + } +} + +impl TryFrom<&[u8]> for Secret { + type Error = UnknownCryptoError; + + fn try_from(bytes: &[u8]) -> Result { + Ok(Secret::from_data(T::parse_bytes(bytes)?)) + } +} + +impl TryFrom<&[u8; N]> for Secret { + type Error = UnknownCryptoError; + + fn try_from(bytes: &[u8; N]) -> Result { + Ok(Secret::from_data(T::parse_bytes(bytes)?)) + } +} + +#[cfg(any(feature = "safe_api", feature = "alloc"))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "safe_api", feature = "alloc"))))] +impl TryFrom<&Vec> for Secret { + type Error = UnknownCryptoError; + + fn try_from(bytes: &Vec) -> Result { + Self::try_from(bytes.as_slice()) + } +} + +impl Secret { + #[inline] + /// Return the object as byte slice. **Warning**: Should not be used unless strictly + /// needed. This **breaks protections** that the type implements. + pub fn unprotected_as_ref(&self) -> &[u8] { + self.data.as_ref() + } +} diff --git a/src/generics/mod.rs b/src/generics/mod.rs new file mode 100644 index 000000000..2cc5a912a --- /dev/null +++ b/src/generics/mod.rs @@ -0,0 +1,56 @@ +// MIT License + +// Copyright (c) 2026 The orion Developers + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +mod data; +mod keytype; + +pub use data::*; +pub use keytype::*; + +/// Wrap a value in `Zeroizing` if the `zeroize` feature is enabled, +/// otherwise return the value as-is. +macro_rules! zeroize_wrap { + ($val:expr) => {{ + #[cfg(feature = "zeroize")] + { + zeroize::Zeroizing::new($val) + } + #[cfg(not(feature = "zeroize"))] + { + $val + } + }}; +} + +#[cfg(feature = "zeroize")] +pub(crate) type ZeroizeWrap = zeroize::Zeroizing; +#[cfg(not(feature = "zeroize"))] +pub(crate) type ZeroizeWrap = T; + +macro_rules! zeroize_call { + ($val:expr) => {{ + #[cfg(feature = "zeroize")] + { + zeroize::Zeroize::zeroize(&mut $val); + } + }}; +} diff --git a/src/hazardous/aead/chacha20poly1305.rs b/src/hazardous/aead/chacha20poly1305.rs index 5140b2952..f42e30727 100644 --- a/src/hazardous/aead/chacha20poly1305.rs +++ b/src/hazardous/aead/chacha20poly1305.rs @@ -77,7 +77,7 @@ //! # #[cfg(feature = "safe_api")] { //! use orion::hazardous::aead; //! -//! let secret_key = aead::chacha20poly1305::SecretKey::generate(); +//! let secret_key = aead::chacha20poly1305::SecretKey::generate()?; //! //! // WARNING: This nonce is only meant for demonstration and should not //! // be repeated. Please read the security section. @@ -110,13 +110,13 @@ //! [`A_MAX`]: chacha20poly1305::A_MAX //! [`C_MAX`]: chacha20poly1305::C_MAX -pub use crate::hazardous::stream::chacha20::{Nonce, SecretKey}; use crate::ZeroizeWrap; +pub use crate::hazardous::stream::chacha20::{Nonce, SecretKey}; use crate::{ errors::UnknownCryptoError, hazardous::{ - mac::poly1305::{OneTimeKey, Poly1305, POLY1305_KEYSIZE, POLY1305_OUTSIZE}, - stream::chacha20::{self, ChaCha20, CHACHA_BLOCKSIZE}, + mac::poly1305::{OneTimeKey, POLY1305_KEYSIZE, POLY1305_OUTSIZE, Poly1305}, + stream::chacha20::{self, CHACHA_BLOCKSIZE, ChaCha20}, }, util, }; @@ -141,9 +141,9 @@ pub const A_MAX: u64 = u64::MAX; pub(crate) fn poly1305_key_gen( ctx: &mut ChaCha20, tmp_buffer: &mut ZeroizeWrap<[u8; CHACHA_BLOCKSIZE]>, -) -> OneTimeKey { +) -> Result { ctx.keystream_block(AUTH_CTR, tmp_buffer.as_mut()); - OneTimeKey::from_slice(&tmp_buffer[..POLY1305_KEYSIZE]).unwrap() + OneTimeKey::try_from(&tmp_buffer[..POLY1305_KEYSIZE]) } /// Authenticates the ciphertext, ad and their lengths. @@ -194,11 +194,10 @@ pub fn seal( None => return Err(UnknownCryptoError), }; - let mut stream = - ChaCha20::new(secret_key.unprotected_as_bytes(), nonce.as_ref(), true).unwrap(); + let mut stream = ChaCha20::new(secret_key.unprotected_as_ref(), nonce.as_ref(), true)?; let mut tmp = zeroize_wrap!([0u8; CHACHA_BLOCKSIZE]); - let mut auth_ctx = Poly1305::new(&poly1305_key_gen(&mut stream, &mut tmp)); + let mut auth_ctx = Poly1305::new(&poly1305_key_gen(&mut stream, &mut tmp)?); let ad_len = ad.len(); let ct_len = plaintext.len(); @@ -254,7 +253,7 @@ pub fn seal( auth_ctx.update(tmp_pad.as_ref())?; dst_out[ct_len..(ct_len + POLY1305_OUTSIZE)] - .copy_from_slice(auth_ctx.finalize()?.unprotected_as_bytes()); + .copy_from_slice(auth_ctx.finalize()?.unprotected_as_ref()); Ok(()) } @@ -283,15 +282,14 @@ pub fn open( return Err(UnknownCryptoError); } - let mut dec_ctx = - ChaCha20::new(secret_key.unprotected_as_bytes(), nonce.as_ref(), true).unwrap(); + let mut dec_ctx = ChaCha20::new(secret_key.unprotected_as_ref(), nonce.as_ref(), true)?; let mut tmp = zeroize_wrap!([0u8; CHACHA_BLOCKSIZE]); - let mut auth_ctx = Poly1305::new(&poly1305_key_gen(&mut dec_ctx, &mut tmp)); + let mut auth_ctx = Poly1305::new(&poly1305_key_gen(&mut dec_ctx, &mut tmp)?); let ciphertext_len = ciphertext_with_tag.len() - POLY1305_OUTSIZE; process_authentication(&mut auth_ctx, ad, &ciphertext_with_tag[..ciphertext_len])?; util::secure_cmp( - auth_ctx.finalize()?.unprotected_as_bytes(), + auth_ctx.finalize()?.unprotected_as_ref(), &ciphertext_with_tag[ciphertext_len..], )?; @@ -313,13 +311,13 @@ pub fn open( #[cfg(feature = "safe_api")] mod public { use super::*; - use crate::test_framework::aead_interface::{test_diff_params_err, AeadTestRunner}; + use crate::test_framework::aead_interface::{AeadTestRunner, test_diff_params_err}; #[quickcheck] #[cfg(feature = "safe_api")] fn prop_aead_interface(input: Vec, ad: Vec) -> bool { - let secret_key = SecretKey::generate(); - let nonce = Nonce::from_slice(&[0u8; chacha20::IETF_CHACHA_NONCESIZE]).unwrap(); + let secret_key = SecretKey::generate().unwrap(); + let nonce = Nonce::try_from(&[0u8; chacha20::IETF_CHACHA_NONCESIZE]).unwrap(); AeadTestRunner( seal, open, @@ -342,8 +340,8 @@ mod test_vectors { #[test] fn rfc8439_poly1305_key_gen_1() { - let key = SecretKey::from_slice(&[0u8; 32]).unwrap(); - let nonce = Nonce::from_slice(&[ + let key = SecretKey::try_from(&[0u8; 32]).unwrap(); + let nonce = Nonce::try_from(&[ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]) .unwrap(); @@ -354,24 +352,26 @@ mod test_vectors { ]; let mut chacha20_ctx = - ChaCha20::new(key.unprotected_as_bytes(), nonce.as_ref(), true).unwrap(); + ChaCha20::new(key.unprotected_as_ref(), nonce.as_ref(), true).unwrap(); let mut tmp_block = zeroize_wrap!([0u8; CHACHA_BLOCKSIZE]); assert_eq!( - poly1305_key_gen(&mut chacha20_ctx, &mut tmp_block).unprotected_as_bytes(), + poly1305_key_gen(&mut chacha20_ctx, &mut tmp_block) + .unwrap() + .unprotected_as_ref(), expected.as_ref() ); } #[test] fn rfc8439_poly1305_key_gen_2() { - let key = SecretKey::from_slice(&[ + let key = SecretKey::try_from(&[ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, ]) .unwrap(); - let nonce = Nonce::from_slice(&[ + let nonce = Nonce::try_from(&[ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, ]) .unwrap(); @@ -382,24 +382,26 @@ mod test_vectors { ]; let mut chacha20_ctx = - ChaCha20::new(key.unprotected_as_bytes(), nonce.as_ref(), true).unwrap(); + ChaCha20::new(key.unprotected_as_ref(), nonce.as_ref(), true).unwrap(); let mut tmp_block = zeroize_wrap!([0u8; CHACHA_BLOCKSIZE]); assert_eq!( - poly1305_key_gen(&mut chacha20_ctx, &mut tmp_block).unprotected_as_bytes(), + poly1305_key_gen(&mut chacha20_ctx, &mut tmp_block) + .unwrap() + .unprotected_as_ref(), expected.as_ref() ); } #[test] fn rfc8439_poly1305_key_gen_3() { - let key = SecretKey::from_slice(&[ + let key = SecretKey::try_from(&[ 0x1c, 0x92, 0x40, 0xa5, 0xeb, 0x55, 0xd3, 0x8a, 0xf3, 0x33, 0x88, 0x86, 0x04, 0xf6, 0xb5, 0xf0, 0x47, 0x39, 0x17, 0xc1, 0x40, 0x2b, 0x80, 0x09, 0x9d, 0xca, 0x5c, 0xbc, 0x20, 0x70, 0x75, 0xc0, ]) .unwrap(); - let nonce = Nonce::from_slice(&[ + let nonce = Nonce::try_from(&[ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, ]) .unwrap(); @@ -410,11 +412,13 @@ mod test_vectors { ]; let mut chacha20_ctx = - ChaCha20::new(key.unprotected_as_bytes(), nonce.as_ref(), true).unwrap(); + ChaCha20::new(key.unprotected_as_ref(), nonce.as_ref(), true).unwrap(); let mut tmp_block = zeroize_wrap!([0u8; CHACHA_BLOCKSIZE]); assert_eq!( - poly1305_key_gen(&mut chacha20_ctx, &mut tmp_block).unprotected_as_bytes(), + poly1305_key_gen(&mut chacha20_ctx, &mut tmp_block) + .unwrap() + .unprotected_as_ref(), expected.as_ref() ); } diff --git a/src/hazardous/aead/streaming.rs b/src/hazardous/aead/streaming.rs index d9f1f77f2..2f54ded41 100644 --- a/src/hazardous/aead/streaming.rs +++ b/src/hazardous/aead/streaming.rs @@ -68,8 +68,8 @@ //! # #[cfg(feature = "safe_api")] { //! use orion::hazardous::aead::streaming::*; //! -//! let secret_key = SecretKey::generate(); -//! let nonce = Nonce::generate(); +//! let secret_key = SecretKey::generate()?; +//! let nonce = Nonce::generate()?; //! let ad = "Additional data".as_bytes(); //! let message = "Data to protect".as_bytes(); //! @@ -104,14 +104,14 @@ use crate::errors::UnknownCryptoError; use crate::hazardous::aead::chacha20poly1305::poly1305_key_gen; -use crate::hazardous::mac::poly1305::{Poly1305, Tag as Poly1305Tag, POLY1305_OUTSIZE}; +use crate::hazardous::mac::poly1305::{POLY1305_OUTSIZE, Poly1305, Tag as Poly1305Tag}; pub use crate::hazardous::stream::chacha20::SecretKey; use crate::hazardous::stream::chacha20::{ - encrypt as chacha20_enc, encrypt_in_place as chacha20_xor_stream, ChaCha20, Nonce as IETFNonce, - CHACHA_BLOCKSIZE, CHACHA_KEYSIZE, HCHACHA_NONCESIZE, IETF_CHACHA_NONCESIZE, + CHACHA_BLOCKSIZE, CHACHA_KEYSIZE, ChaCha20, HCHACHA_NONCESIZE, IETF_CHACHA_NONCESIZE, + Nonce as IETFNonce, encrypt as chacha20_enc, encrypt_in_place as chacha20_xor_stream, }; -use crate::hazardous::stream::xchacha20::subkey_and_nonce; pub use crate::hazardous::stream::xchacha20::Nonce; +use crate::hazardous::stream::xchacha20::subkey_and_nonce; use core::convert::TryFrom; use subtle::ConstantTimeEq; @@ -181,11 +181,7 @@ fn padding(input: usize) -> usize { let rem = input % 16; - if rem != 0 { - 16 - rem - } else { - 0 - } + if rem != 0 { 16 - rem } else { 0 } } /// Streaming XChaCha20Poly1305 state. @@ -227,15 +223,14 @@ impl StreamXChaCha20Poly1305 { debug_assert!(text.len() >= textpos + msglen); let mut chacha20_ctx = ChaCha20::new( - self.key.unprotected_as_bytes(), + self.key.unprotected_as_ref(), self.get_nonce().as_ref(), true, - ) - .unwrap(); + )?; let mut tmp_block = zeroize_wrap!([0u8; CHACHA_BLOCKSIZE]); let mut pad = [0u8; 16]; - let mut poly = Poly1305::new(&poly1305_key_gen(&mut chacha20_ctx, &mut tmp_block)); + let mut poly = Poly1305::new(&poly1305_key_gen(&mut chacha20_ctx, &mut tmp_block)?); poly.process_pad_to_blocksize(ad)?; poly.update(block)?; @@ -260,7 +255,7 @@ impl StreamXChaCha20Poly1305 { mac: &Poly1305Tag, tag: &StreamTag, ) -> Result<(), UnknownCryptoError> { - xor_slices!(mac.unprotected_as_bytes()[..INONCEBYTES], self.inonce); + xor_slices!(mac.unprotected_as_ref()[..INONCEBYTES], self.inonce); self.counter = self.counter.wrapping_add(1); if bool::from( !(tag.as_byte() & StreamTag::Rekey.as_byte()).ct_eq(&0u8) | self.counter.ct_eq(&0u32), @@ -287,12 +282,12 @@ impl StreamXChaCha20Poly1305 { /// Derives a new secret key used for encryption and decryption. pub fn rekey(&mut self) -> Result<(), UnknownCryptoError> { let mut new_key_and_inonce = [0u8; CHACHA_KEYSIZE + INONCEBYTES]; - new_key_and_inonce[..CHACHA_KEYSIZE].copy_from_slice(self.key.unprotected_as_bytes()); + new_key_and_inonce[..CHACHA_KEYSIZE].copy_from_slice(self.key.unprotected_as_ref()); new_key_and_inonce[CHACHA_KEYSIZE..].copy_from_slice(&self.inonce); chacha20_xor_stream(&self.key, &self.get_nonce(), 0, &mut new_key_and_inonce)?; - self.key = SecretKey::from_slice(&new_key_and_inonce[..CHACHA_KEYSIZE]).unwrap(); + self.key = SecretKey::try_from(&new_key_and_inonce[..CHACHA_KEYSIZE])?; self.inonce .copy_from_slice(&new_key_and_inonce[CHACHA_KEYSIZE..]); self.counter = 1; @@ -335,7 +330,7 @@ impl StreamXChaCha20Poly1305 { } let mac = self.generate_auth_tag(dst_out, ad, msglen, &block, TAG_SIZE)?; - dst_out[macpos..(macpos + POLY1305_OUTSIZE)].copy_from_slice(mac.unprotected_as_bytes()); + dst_out[macpos..(macpos + POLY1305_OUTSIZE)].copy_from_slice(mac.unprotected_as_ref()); self.advance_state(&mac, tag) } @@ -369,7 +364,7 @@ impl StreamXChaCha20Poly1305 { let tag = StreamTag::try_from(block[0])?; block[0] = ciphertext[0]; let mac = self.generate_auth_tag(ciphertext, ad, msglen, &block, TAG_SIZE)?; - if !(mac == &ciphertext[macpos..macpos + mac.len()]) { + if !(mac == ciphertext[macpos..macpos + mac.len()]) { return Err(UnknownCryptoError); } if msglen != 0 { @@ -395,8 +390,8 @@ mod public { #[test] #[cfg(feature = "safe_api")] fn test_debug_impl() { - let secret_key = SecretKey::generate(); - let nonce = Nonce::generate(); + let secret_key = SecretKey::generate().unwrap(); + let nonce = Nonce::generate().unwrap(); let initial_state = StreamXChaCha20Poly1305::new(&secret_key, &nonce); let debug = format!("{initial_state:?}"); let expected = "StreamXChaCha20Poly1305 { key: [***OMITTED***], counter: [***OMITTED***], inonce: [***OMITTED***]"; @@ -407,10 +402,9 @@ mod public { mod proptest { use crate::errors::UnknownCryptoError; use crate::hazardous::aead::streaming::{ - Nonce, SecretKey, StreamTag, StreamXChaCha20Poly1305, ABYTES, + ABYTES, Nonce, SecretKey, StreamTag, StreamXChaCha20Poly1305, }; use crate::test_framework::aead_interface::*; - use core::convert::TryFrom; fn seal( sk: &SecretKey, @@ -438,8 +432,8 @@ mod public { #[quickcheck] fn prop_aead_interface(input: Vec, ad: Vec) -> bool { - let secret_key = SecretKey::generate(); - let nonce = Nonce::generate(); + let secret_key = SecretKey::generate().unwrap(); + let nonce = Nonce::generate().unwrap(); AeadTestRunner(seal, open, secret_key, nonce, &input, None, ABYTES, &ad); true @@ -447,7 +441,10 @@ mod public { #[quickcheck] fn prop_same_input_twice_diff_output(input: Vec, ad: Vec) -> bool { - let mut ctx = StreamXChaCha20Poly1305::new(&SecretKey::generate(), &Nonce::generate()); + let mut ctx = StreamXChaCha20Poly1305::new( + &SecretKey::generate().unwrap(), + &Nonce::generate().unwrap(), + ); let mut ct1 = vec![0u8; input.len() + ABYTES]; let mut ct2 = ct1.clone(); @@ -459,17 +456,6 @@ mod public { ct1 != ct2 } - - #[quickcheck] - fn prop_tag(byte: u8) -> bool { - match byte { - 0u8 => StreamTag::try_from(byte).unwrap() == StreamTag::Message, - 1u8 => StreamTag::try_from(byte).unwrap() == StreamTag::Push, - 2u8 => StreamTag::try_from(byte).unwrap() == StreamTag::Rekey, - 3u8 => StreamTag::try_from(byte).unwrap() == StreamTag::Finish, - _ => StreamTag::try_from(byte).is_err(), - } - } } } @@ -568,8 +554,8 @@ mod private { 75u8, 188u8, 65u8, 41u8, 75u8, ]; - let secret_key = SecretKey::from_slice(&sk).unwrap(); - let nonce = Nonce::from_slice(&nonce).unwrap(); + let secret_key = SecretKey::try_from(&sk).unwrap(); + let nonce = Nonce::try_from(&nonce).unwrap(); let mut state = StreamXChaCha20Poly1305::new(&secret_key, &nonce); let mut dst_out = [0u8; 96]; @@ -610,6 +596,11 @@ mod private { assert_eq!(StreamTag::Rekey.as_byte(), 2u8); assert_eq!(StreamTag::Finish.as_byte(), 3u8); assert!(StreamTag::try_from(4u8).is_err()); + + assert_eq!(StreamTag::try_from(0u8).unwrap(), StreamTag::Message); + assert_eq!(StreamTag::try_from(1u8).unwrap(), StreamTag::Push); + assert_eq!(StreamTag::try_from(2u8).unwrap(), StreamTag::Rekey); + assert_eq!(StreamTag::try_from(3u8).unwrap(), StreamTag::Finish); } #[test] @@ -627,7 +618,10 @@ mod private { ); assert_eq!( s.get_nonce(), - [1u8, 0u8, 0u8, 0u8, 97u8, 98u8, 97u8, 97u8, 98u8, 97u8, 98u8, 0u8,].as_ref() + [ + 1u8, 0u8, 0u8, 0u8, 97u8, 98u8, 97u8, 97u8, 98u8, 97u8, 98u8, 0u8, + ] + .as_ref() ); // 1st StreamTag::Message @@ -646,7 +640,10 @@ mod private { ); assert_eq!( s.get_nonce(), - [2u8, 0u8, 0u8, 0u8, 88u8, 186u8, 23u8, 231u8, 10u8, 253u8, 79u8, 71u8,].as_ref() + [ + 2u8, 0u8, 0u8, 0u8, 88u8, 186u8, 23u8, 231u8, 10u8, 253u8, 79u8, 71u8, + ] + .as_ref() ); assert_eq!( out1, @@ -675,7 +672,10 @@ mod private { ); assert_eq!( s.get_nonce(), - [3u8, 0u8, 0u8, 0u8, 73u8, 199u8, 255u8, 159u8, 213u8, 205u8, 201u8, 51u8,].as_ref() + [ + 3u8, 0u8, 0u8, 0u8, 73u8, 199u8, 255u8, 159u8, 213u8, 205u8, 201u8, 51u8, + ] + .as_ref() ); assert_eq!( out2.as_ref(), @@ -704,7 +704,10 @@ mod private { ); assert_eq!( s.get_nonce(), - [4u8, 0u8, 0u8, 0u8, 229u8, 134u8, 216u8, 143u8, 117u8, 43u8, 216u8, 142u8,].as_ref() + [ + 4u8, 0u8, 0u8, 0u8, 229u8, 134u8, 216u8, 143u8, 117u8, 43u8, 216u8, 142u8, + ] + .as_ref() ); assert_eq!( out3.as_ref(), @@ -728,7 +731,10 @@ mod private { ); assert_eq!( s.get_nonce(), - [1u8, 0u8, 0u8, 0u8, 250u8, 25u8, 191u8, 166u8, 103u8, 98u8, 187u8, 196u8,].as_ref() + [ + 1u8, 0u8, 0u8, 0u8, 250u8, 25u8, 191u8, 166u8, 103u8, 98u8, 187u8, 196u8, + ] + .as_ref() ); // 4th StreamTag::Message @@ -750,7 +756,10 @@ mod private { ); assert_eq!( s.get_nonce(), - [2u8, 0u8, 0u8, 0u8, 70u8, 193u8, 51u8, 16u8, 173u8, 151u8, 68u8, 48u8,].as_ref() + [ + 2u8, 0u8, 0u8, 0u8, 70u8, 193u8, 51u8, 16u8, 173u8, 151u8, 68u8, 48u8, + ] + .as_ref() ); assert_eq!( out4.as_ref(), @@ -783,7 +792,10 @@ mod private { ); assert_eq!( s.get_nonce(), - [3u8, 0u8, 0u8, 0u8, 119u8, 231u8, 54u8, 137u8, 64u8, 159u8, 87u8, 77u8,].as_ref() + [ + 3u8, 0u8, 0u8, 0u8, 119u8, 231u8, 54u8, 137u8, 64u8, 159u8, 87u8, 77u8, + ] + .as_ref() ); assert_eq!( out5.as_ref(), @@ -1029,9 +1041,10 @@ mod private { // Reset state let mut s = StreamXChaCha20Poly1305::new(&SecretKey::from(KEY), &Nonce::from(NONCE)); - assert!(s - .open_chunk(&cipher1, Some(&[1u8; 1]), &mut plain_out1) - .is_err()); + assert!( + s.open_chunk(&cipher1, Some(&[1u8; 1]), &mut plain_out1) + .is_err() + ); } #[test] @@ -1138,7 +1151,7 @@ mod private { ]; let mut ctx = StreamXChaCha20Poly1305::new(&SecretKey::from(KEY), &Nonce::from(NONCE)); - assert_eq!(ctx.key.unprotected_as_bytes(), before_internal_key.as_ref()); + assert_eq!(ctx.key.unprotected_as_ref(), before_internal_key.as_ref()); assert_eq!(ctx.get_nonce(), before_internal_nonce.as_ref()); assert_eq!(ctx.counter, u32::from_le_bytes(before_internal_counter)); @@ -1146,7 +1159,7 @@ mod private { ctx.seal_chunk(DEFAULT_MSG.as_ref(), None, &mut actual, &StreamTag::Finish) .unwrap(); - assert_eq!(ctx.key.unprotected_as_bytes(), after_internal_key.as_ref()); + assert_eq!(ctx.key.unprotected_as_ref(), after_internal_key.as_ref()); assert_eq!(ctx.get_nonce(), after_internal_nonce.as_ref()); assert_eq!(ctx.counter, u32::from_le_bytes(after_internal_counter)); assert_eq!(actual.as_ref(), out.as_ref()); @@ -1182,7 +1195,7 @@ mod private { ]; let mut ctx = StreamXChaCha20Poly1305::new(&SecretKey::from(KEY), &Nonce::from(NONCE)); - assert_eq!(ctx.key.unprotected_as_bytes(), before_internal_key.as_ref()); + assert_eq!(ctx.key.unprotected_as_ref(), before_internal_key.as_ref()); assert_eq!(ctx.get_nonce(), before_internal_nonce.as_ref()); assert_eq!(ctx.counter, u32::from_le_bytes(before_internal_counter)); @@ -1190,7 +1203,7 @@ mod private { ctx.seal_chunk(DEFAULT_MSG.as_ref(), None, &mut actual, &StreamTag::Rekey) .unwrap(); - assert_eq!(ctx.key.unprotected_as_bytes(), after_internal_key.as_ref()); + assert_eq!(ctx.key.unprotected_as_ref(), after_internal_key.as_ref()); assert_eq!(ctx.get_nonce(), after_internal_nonce.as_ref()); assert_eq!(ctx.counter, u32::from_le_bytes(after_internal_counter)); assert_eq!(actual.as_ref(), out.as_ref()); @@ -1226,7 +1239,7 @@ mod private { ]; let mut ctx = StreamXChaCha20Poly1305::new(&SecretKey::from(KEY), &Nonce::from(NONCE)); - assert_eq!(ctx.key.unprotected_as_bytes(), before_internal_key.as_ref()); + assert_eq!(ctx.key.unprotected_as_ref(), before_internal_key.as_ref()); assert_eq!(ctx.get_nonce(), before_internal_nonce.as_ref()); assert_eq!(ctx.counter, u32::from_le_bytes(before_internal_counter)); @@ -1236,7 +1249,7 @@ mod private { ctx.seal_chunk(DEFAULT_MSG.as_ref(), None, &mut actual, &StreamTag::Finish) .unwrap(); - assert_eq!(ctx.key.unprotected_as_bytes(), after_internal_key.as_ref()); + assert_eq!(ctx.key.unprotected_as_ref(), after_internal_key.as_ref()); assert_eq!(ctx.get_nonce(), after_internal_nonce.as_ref()); assert_eq!(ctx.counter, u32::from_le_bytes(after_internal_counter)); assert_eq!(actual.as_ref(), out.as_ref()); @@ -1272,7 +1285,7 @@ mod private { ]; let mut ctx = StreamXChaCha20Poly1305::new(&SecretKey::from(KEY), &Nonce::from(NONCE)); - assert_eq!(ctx.key.unprotected_as_bytes(), before_internal_key.as_ref()); + assert_eq!(ctx.key.unprotected_as_ref(), before_internal_key.as_ref()); assert_eq!(ctx.get_nonce(), before_internal_nonce.as_ref()); assert_eq!(ctx.counter, u32::from_le_bytes(before_internal_counter)); @@ -1282,7 +1295,7 @@ mod private { ctx.seal_chunk(DEFAULT_MSG.as_ref(), None, &mut actual, &StreamTag::Rekey) .unwrap(); - assert_eq!(ctx.key.unprotected_as_bytes(), after_internal_key.as_ref()); + assert_eq!(ctx.key.unprotected_as_ref(), after_internal_key.as_ref()); assert_eq!(ctx.get_nonce(), after_internal_nonce.as_ref()); assert_eq!(ctx.counter, u32::from_le_bytes(after_internal_counter)); assert_eq!(actual.as_ref(), out.as_ref()); @@ -1408,7 +1421,7 @@ mod private { let mut ctx = StreamXChaCha20Poly1305::new(&SecretKey::from(KEY), &Nonce::from(NONCE)); ctx.counter = u32::MAX; - assert_eq!(ctx.key.unprotected_as_bytes(), before_internal_key.as_ref()); + assert_eq!(ctx.key.unprotected_as_ref(), before_internal_key.as_ref()); assert_eq!(ctx.get_nonce(), before_internal_nonce.as_ref()); assert_eq!(ctx.counter, u32::from_le_bytes(before_internal_counter)); @@ -1416,7 +1429,7 @@ mod private { ctx.seal_chunk(DEFAULT_MSG.as_ref(), None, &mut actual, &StreamTag::Rekey) .unwrap(); - assert_eq!(ctx.key.unprotected_as_bytes(), after_internal_key.as_ref()); + assert_eq!(ctx.key.unprotected_as_ref(), after_internal_key.as_ref()); assert_eq!(ctx.get_nonce(), after_internal_nonce.as_ref()); assert_eq!(ctx.counter, u32::from_le_bytes(after_internal_counter)); assert_eq!(actual.as_ref(), out.as_ref()); diff --git a/src/hazardous/aead/xchacha20poly1305.rs b/src/hazardous/aead/xchacha20poly1305.rs index 94afc7c1f..93422dbd0 100644 --- a/src/hazardous/aead/xchacha20poly1305.rs +++ b/src/hazardous/aead/xchacha20poly1305.rs @@ -68,8 +68,8 @@ //! # #[cfg(feature = "safe_api")] { //! use orion::hazardous::aead; //! -//! let secret_key = aead::xchacha20poly1305::SecretKey::generate(); -//! let nonce = aead::xchacha20poly1305::Nonce::generate(); +//! let secret_key = aead::xchacha20poly1305::SecretKey::generate()?; +//! let nonce = aead::xchacha20poly1305::Nonce::generate()?; //! let ad = "Additional data".as_bytes(); //! let message = "Data to protect".as_bytes(); //! @@ -130,13 +130,13 @@ pub fn open( mod public { use super::*; use crate::hazardous::mac::poly1305::POLY1305_OUTSIZE; - use crate::test_framework::aead_interface::{test_diff_params_err, AeadTestRunner}; + use crate::test_framework::aead_interface::{AeadTestRunner, test_diff_params_err}; #[quickcheck] #[cfg(feature = "safe_api")] fn prop_aead_interface(input: Vec, ad: Vec) -> bool { - let secret_key = SecretKey::generate(); - let nonce = Nonce::generate(); + let secret_key = SecretKey::generate().unwrap(); + let nonce = Nonce::generate().unwrap(); AeadTestRunner( seal, open, diff --git a/src/hazardous/cae/chacha20poly1305blake2b.rs b/src/hazardous/cae/chacha20poly1305blake2b.rs index a2cf5d470..c87fea297 100644 --- a/src/hazardous/cae/chacha20poly1305blake2b.rs +++ b/src/hazardous/cae/chacha20poly1305blake2b.rs @@ -83,7 +83,7 @@ //! # #[cfg(feature = "safe_api")] { //! use orion::hazardous::cae; //! -//! let secret_key = cae::chacha20poly1305blake2b::SecretKey::generate(); +//! let secret_key = cae::chacha20poly1305blake2b::SecretKey::generate()?; //! //! // WARNING: This nonce is only meant for demonstration and should not //! // be repeated. Please read the security section. @@ -120,11 +120,11 @@ use crate::errors::UnknownCryptoError; use crate::hazardous::aead; -use crate::hazardous::aead::chacha20poly1305::{poly1305_key_gen, process_authentication, ENC_CTR}; +use crate::hazardous::aead::chacha20poly1305::{ENC_CTR, poly1305_key_gen, process_authentication}; use crate::hazardous::hash::blake2::blake2b::Blake2b; -use crate::hazardous::mac::poly1305::Poly1305; use crate::hazardous::mac::poly1305::POLY1305_OUTSIZE; -use crate::hazardous::stream::chacha20::{self, ChaCha20, CHACHA_BLOCKSIZE}; +use crate::hazardous::mac::poly1305::Poly1305; +use crate::hazardous::stream::chacha20::{self, CHACHA_BLOCKSIZE, ChaCha20}; use crate::util; pub use crate::hazardous::aead::chacha20poly1305::A_MAX; @@ -174,7 +174,7 @@ pub fn seal( )?; let mut blake2b = Blake2b::new(32)?; - blake2b.update(secret_key.unprotected_as_bytes())?; + blake2b.update(secret_key.unprotected_as_ref())?; blake2b.update(nonce.as_ref())?; blake2b.update(ad)?; blake2b.update(&dst_out[plaintext.len()..plaintext.len() + POLY1305_OUTSIZE])?; @@ -210,19 +210,18 @@ pub fn open( } let mut blake2b = Blake2b::new(32)?; - blake2b.update(secret_key.unprotected_as_bytes())?; + blake2b.update(secret_key.unprotected_as_ref())?; blake2b.update(nonce.as_ref())?; blake2b.update(ad)?; - let mut dec_ctx = - ChaCha20::new(secret_key.unprotected_as_bytes(), nonce.as_ref(), true).unwrap(); + let mut dec_ctx = ChaCha20::new(secret_key.unprotected_as_ref(), nonce.as_ref(), true)?; let mut tmp = zeroize_wrap!([0u8; CHACHA_BLOCKSIZE]); - let mut auth_ctx = Poly1305::new(&poly1305_key_gen(&mut dec_ctx, &mut tmp)); + let mut auth_ctx = Poly1305::new(&poly1305_key_gen(&mut dec_ctx, &mut tmp)?); let ciphertext_len = ciphertext_with_tag.len() - TAG_SIZE; process_authentication(&mut auth_ctx, ad, &ciphertext_with_tag[..ciphertext_len])?; - blake2b.update(auth_ctx.finalize()?.unprotected_as_bytes())?; + blake2b.update(auth_ctx.finalize()?.unprotected_as_ref())?; util::secure_cmp( blake2b.finalize()?.as_ref(), @@ -247,13 +246,13 @@ pub fn open( #[cfg(feature = "safe_api")] mod public { use super::*; - use crate::test_framework::aead_interface::{test_diff_params_err, AeadTestRunner}; + use crate::test_framework::aead_interface::{AeadTestRunner, test_diff_params_err}; #[quickcheck] #[cfg(feature = "safe_api")] fn prop_aead_interface(input: Vec, ad: Vec) -> bool { - let secret_key = SecretKey::generate(); - let nonce = Nonce::from_slice(&[0u8; chacha20::IETF_CHACHA_NONCESIZE]).unwrap(); + let secret_key = SecretKey::generate().unwrap(); + let nonce = Nonce::try_from(&[0u8; chacha20::IETF_CHACHA_NONCESIZE]).unwrap(); AeadTestRunner(seal, open, secret_key, nonce, &input, None, TAG_SIZE, &ad); test_diff_params_err(&seal, &open, &input, TAG_SIZE); true diff --git a/src/hazardous/cae/mod.rs b/src/hazardous/cae/mod.rs index 8b93ba7dd..1a10f6351 100644 --- a/src/hazardous/cae/mod.rs +++ b/src/hazardous/cae/mod.rs @@ -24,10 +24,10 @@ /// Fully-committing ChaCha20-Poly1305 with BLAKE2b based on the [CTX] construction by John Chan & Phillip Rogaway. /// -/// [CTX]: https://eprint.iacr.org/2022/1260 +/// [CTX]: pub mod chacha20poly1305blake2b; /// Fully-committing XChaCha20-Poly1305 with BLAKE2b based on the [CTX] construction by John Chan & Phillip Rogaway. /// -/// [CTX]: https://eprint.iacr.org/2022/1260 +/// [CTX]: pub mod xchacha20poly1305blake2b; diff --git a/src/hazardous/cae/xchacha20poly1305blake2b.rs b/src/hazardous/cae/xchacha20poly1305blake2b.rs index 4d76a8cec..2755ff74e 100644 --- a/src/hazardous/cae/xchacha20poly1305blake2b.rs +++ b/src/hazardous/cae/xchacha20poly1305blake2b.rs @@ -74,8 +74,8 @@ //! # #[cfg(feature = "safe_api")] { //! use orion::hazardous::cae; //! -//! let secret_key = cae::xchacha20poly1305blake2b::SecretKey::generate(); -//! let nonce = cae::xchacha20poly1305blake2b::Nonce::generate(); +//! let secret_key = cae::xchacha20poly1305blake2b::SecretKey::generate()?; +//! let nonce = cae::xchacha20poly1305blake2b::Nonce::generate()?; //! let ad = "Additional data".as_bytes(); //! let message = "Data to protect".as_bytes(); //! @@ -110,11 +110,11 @@ use crate::errors::UnknownCryptoError; use crate::hazardous::aead; pub use crate::hazardous::aead::chacha20poly1305::A_MAX; pub use crate::hazardous::aead::chacha20poly1305::P_MAX; -use crate::hazardous::aead::chacha20poly1305::{poly1305_key_gen, process_authentication, ENC_CTR}; +use crate::hazardous::aead::chacha20poly1305::{ENC_CTR, poly1305_key_gen, process_authentication}; pub use crate::hazardous::cae::chacha20poly1305blake2b::{C_MAX, TAG_SIZE}; use crate::hazardous::hash::blake2::blake2b::Blake2b; -use crate::hazardous::mac::poly1305::{Poly1305, POLY1305_OUTSIZE}; -use crate::hazardous::stream::chacha20::{self, ChaCha20, CHACHA_BLOCKSIZE}; +use crate::hazardous::mac::poly1305::{POLY1305_OUTSIZE, Poly1305}; +use crate::hazardous::stream::chacha20::{self, CHACHA_BLOCKSIZE, ChaCha20}; use crate::hazardous::stream::xchacha20::subkey_and_nonce; pub use crate::hazardous::stream::{chacha20::SecretKey, xchacha20::Nonce}; use crate::util; @@ -157,7 +157,7 @@ pub fn seal( )?; let mut blake2b = Blake2b::new(32)?; - blake2b.update(secret_key.unprotected_as_bytes())?; + blake2b.update(secret_key.unprotected_as_ref())?; blake2b.update(nonce.as_ref())?; blake2b.update(ad)?; blake2b.update(&dst_out[plaintext.len()..plaintext.len() + POLY1305_OUTSIZE])?; @@ -193,20 +193,19 @@ pub fn open( } let mut blake2b = Blake2b::new(32)?; - blake2b.update(secret_key.unprotected_as_bytes())?; + blake2b.update(secret_key.unprotected_as_ref())?; blake2b.update(nonce.as_ref())?; blake2b.update(ad)?; let (subkey, ietf_nonce) = subkey_and_nonce(secret_key, nonce); - let mut dec_ctx = - ChaCha20::new(subkey.unprotected_as_bytes(), ietf_nonce.as_ref(), true).unwrap(); + let mut dec_ctx = ChaCha20::new(subkey.unprotected_as_ref(), ietf_nonce.as_ref(), true)?; let mut tmp = zeroize_wrap!([0u8; CHACHA_BLOCKSIZE]); - let mut auth_ctx = Poly1305::new(&poly1305_key_gen(&mut dec_ctx, &mut tmp)); + let mut auth_ctx = Poly1305::new(&poly1305_key_gen(&mut dec_ctx, &mut tmp)?); let ciphertext_len = ciphertext_with_tag.len() - TAG_SIZE; process_authentication(&mut auth_ctx, ad, &ciphertext_with_tag[..ciphertext_len])?; - blake2b.update(auth_ctx.finalize()?.unprotected_as_bytes())?; + blake2b.update(auth_ctx.finalize()?.unprotected_as_ref())?; util::secure_cmp( blake2b.finalize()?.as_ref(), @@ -231,13 +230,13 @@ pub fn open( #[cfg(feature = "safe_api")] mod public { use super::*; - use crate::test_framework::aead_interface::{test_diff_params_err, AeadTestRunner}; + use crate::test_framework::aead_interface::{AeadTestRunner, test_diff_params_err}; #[quickcheck] #[cfg(feature = "safe_api")] fn prop_aead_interface(input: Vec, ad: Vec) -> bool { - let secret_key = SecretKey::generate(); - let nonce = Nonce::generate(); + let secret_key = SecretKey::generate().unwrap(); + let nonce = Nonce::generate().unwrap(); AeadTestRunner(seal, open, secret_key, nonce, &input, None, TAG_SIZE, &ad); test_diff_params_err(&seal, &open, &input, TAG_SIZE); true diff --git a/src/hazardous/ecc/x25519.rs b/src/hazardous/ecc/x25519.rs index 90a886ae9..d3108ff12 100644 --- a/src/hazardous/ecc/x25519.rs +++ b/src/hazardous/ecc/x25519.rs @@ -26,11 +26,11 @@ //! //! # Errors: //! An error will be returned if: -//! - The `key_agreement()` operation results in an all-zero output. +//! - The [`key_agreement()`] operation results in an all-zero output. //! //! # Security: //! - Multiple different `private_key`/`public_key` pairs can produce the same shared key. Therefore, -//! using the resulting `SharedKey`, directly from `key_agreement()`, is not recommended. This is handled +//! using the resulting [`SharedKey`], directly from [`key_agreement()`], is not recommended. This is handled //! automatically in [`orion::kex`]. //! - To securely generate a strong key, use [`PrivateKey::generate()`]. //! @@ -41,14 +41,13 @@ //! ```rust //! # #[cfg(feature = "safe_api")] { //! use orion::hazardous::ecc::x25519::{PrivateKey, PublicKey, SharedKey, key_agreement}; -//! use core::convert::TryFrom; //! //! // Alice generates a private key and computes the corresponding public key -//! let alice_sk = PrivateKey::generate(); +//! let alice_sk = PrivateKey::generate()?; //! let alice_pk = PublicKey::try_from(&alice_sk)?; //! //! // Bob does the same -//! let bob_sk = PrivateKey::generate(); +//! let bob_sk = PrivateKey::generate()?; //! let bob_pk = PublicKey::try_from(&bob_sk)?; //! //! // They both compute a shared key using the others public key @@ -61,11 +60,22 @@ //! ``` //! [`PrivateKey::generate()`]: crate::hazardous::ecc::x25519::PrivateKey::generate //! [`orion::kex`]: crate::kex +//! [`key_agreement()`]: crate::hazardous::ecc::x25519::key_agreement +//! [`SharedKey`]: crate::hazardous::ecc::x25519::SharedKey use crate::errors::UnknownCryptoError; -use crate::util::secure_cmp; +use crate::generics::GenerateSecret; +use crate::generics::{ByteArrayData, TypeSpec}; use core::ops::{Add, Mul, Sub}; +pub use crate::generics::Public; +pub use crate::generics::Secret; + +#[cfg(feature = "safe_api")] +use crate::generics::sealed::Data; + +use crate::generics::sealed::Sealed; + /// Formally verified Curve25519 field arithmetic from: . use fiat_crypto::curve25519_64 as fiat_curve25519_u64; use fiat_curve25519_u64::{ @@ -84,9 +94,9 @@ pub const PRIVATE_KEY_SIZE: usize = 32; pub const SHARED_KEY_SIZE: usize = 32; /// u-coordinate of the base point. -const BASEPOINT: [u8; 32] = [ +const BASEPOINT: FieldElement = FieldElement::from_bytes(&[ 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -]; +]); /// The result of computing a shared secret with a low order point. const LOW_ORDER_POINT_RESULT: [u8; 32] = [0u8; 32]; @@ -95,18 +105,9 @@ const LOW_ORDER_POINT_RESULT: [u8; 32] = [0u8; 32]; /// Represent an element in the curve field. struct FieldElement(fiat_25519_tight_field_element); -impl Eq for FieldElement {} - -impl PartialEq for FieldElement { - fn eq(&self, other: &Self) -> bool { - use subtle::ConstantTimeEq; - self.as_bytes().ct_eq(&other.as_bytes()).into() - } -} - impl core::fmt::Debug for FieldElement { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "FieldElement({:?})", &self.0 .0) + write!(f, "FieldElement({:?})", &self.0.0) } } @@ -187,14 +188,14 @@ impl FieldElement { /// Deserialize the `FieldElement` from a byte-array in little-endian. /// /// Masks the MSB in the final byte of the input bytes. - fn from_bytes(bytes: &[u8; 32]) -> Self { + const fn from_bytes(bytes: &[u8; 32]) -> Self { // The function fiat_25519_from_bytes deserializes a field element from bytes in little-endian order use fiat_curve25519_u64::fiat_25519_from_bytes; let mut temp = [0u8; 32]; temp.copy_from_slice(bytes); temp[31] &= 127u8; // See RFC: "When receiving such an array, implementations of X25519 - // (but not X448) MUST mask the most significant bit in the final byte." + // (but not X448) MUST mask the most significant bit in the final byte." let mut ret = fiat_25519_tight_field_element([0u64; 5]); fiat_25519_from_bytes(&mut ret, &temp); @@ -213,8 +214,8 @@ impl FieldElement { let tmp_a = *a; let tmp_b = *b; - fiat_25519_selectznz(&mut a.0 .0, swap, &tmp_a.0 .0, &tmp_b.0 .0); - fiat_25519_selectznz(&mut b.0 .0, swap, &tmp_b.0 .0, &tmp_a.0 .0); + fiat_25519_selectznz(&mut a.0.0, swap, &tmp_a.0.0, &tmp_b.0.0); + fiat_25519_selectznz(&mut b.0.0, swap, &tmp_b.0.0, &tmp_a.0.0); } /// Square the `FieldElement` and reduce the result. @@ -297,55 +298,20 @@ impl FieldElement { } } -#[derive(Clone)] -/// Represents a Scalar decoded from a byte array. -struct Scalar([u8; PRIVATE_KEY_SIZE]); - -#[cfg(feature = "zeroize")] -impl Drop for Scalar { - fn drop(&mut self) { - use zeroize::Zeroize; - self.0.iter_mut().zeroize(); - } -} - -impl PartialEq for Scalar { - fn eq(&self, other: &Self) -> bool { - use subtle::ConstantTimeEq; - self.0.ct_eq(&other.0).into() - } -} - -impl Eq for Scalar {} - -impl Scalar { - /// Create a scalar from some byte-array. - /// The scalar is clamped according to the RFC. - /// - /// Ref: https://www.ietf.org/rfc/rfc7748.html#section-5 - fn from_slice(slice: &[u8]) -> Result { - if slice.len() != PRIVATE_KEY_SIZE { - return Err(UnknownCryptoError); - } - - let mut ret = [0u8; PRIVATE_KEY_SIZE]; - ret.copy_from_slice(slice); - // Clamp - ret[0] &= 248; - ret[31] &= 127; - ret[31] |= 64; - - Ok(Self(ret)) - } -} - /// Scalar multiplication using the Montgomery Ladder (a.k.a "scalarmult") /// /// Refs: -/// - https://eprint.iacr.org/2020/956.pdf -/// - https://eprint.iacr.org/2017/212.pdf -/// - https://github.com/golang/crypto/blob/0c34fe9e7dc2486962ef9867e3edb3503537209f/curve25519/curve25519_generic.go#L779 -fn mont_ladder(scalar: &Scalar, point: FieldElement) -> FieldElement { +/// - +/// - +/// - +fn mont_ladder(scalar: &[u8; PRIVATE_KEY_SIZE], point: FieldElement) -> FieldElement { + debug_assert_eq!( + point.as_bytes()[31] & 0x80, + 0, + "FieldElement missing highbit mask!" + ); + debug_assert!(X25519PrivateKey::is_clamped(scalar)); + let x1 = point; let mut x2 = FieldElement::one(); let mut x3 = x1; @@ -357,7 +323,7 @@ fn mont_ladder(scalar: &Scalar, point: FieldElement) -> FieldElement { let mut swap: u8 = 0; for idx in (0..=254).rev() { - let bit = (scalar.0[idx >> 3] >> (idx & 7)) & 1; + let bit = (scalar[idx >> 3] >> (idx & 7)) & 1; swap ^= bit; FieldElement::conditional_swap(swap, &mut x2, &mut x3); FieldElement::conditional_swap(swap, &mut z2, &mut z3); @@ -392,295 +358,265 @@ fn mont_ladder(scalar: &Scalar, point: FieldElement) -> FieldElement { x2 } -#[allow(clippy::derive_partial_eq_without_eq)] -// NOTE: FieldElement contains a constant-time PartialEq impl. -/// A type that represents a `PublicKey` that X25519 uses. -/// -/// This type holds a field element and is used internally as the u-coordinate. -/// As the RFC mandates, the most significant bit of the last byte is masked. -/// -/// # Errors: -/// An error will be returned if: -/// - `slice` is not 32 bytes. -#[derive(PartialEq, Debug, Clone)] -pub struct PublicKey { - fe: FieldElement, -} +#[derive(Debug)] +/// X25519 private key implementation. See [`PrivateKey`] type for convenience. +pub struct X25519PrivateKey {} +impl Sealed for X25519PrivateKey {} -impl PartialEq<&[u8]> for PublicKey { - fn eq(&self, other: &&[u8]) -> bool { - if other.len() != PUBLIC_KEY_SIZE { - return false; - } - let other: [u8; 32] = (*other).try_into().unwrap(); +impl X25519PrivateKey { + /// Ref: + const fn clamp(k: &[u8; PRIVATE_KEY_SIZE]) -> [u8; PRIVATE_KEY_SIZE] { + let mut scalar = *k; + // Clamp + scalar[0] &= 248; + scalar[31] &= 127; + scalar[31] |= 64; - self.fe == FieldElement::from_bytes(&other) + scalar } -} -impl From<[u8; PUBLIC_KEY_SIZE]> for PublicKey { - #[inline] - fn from(bytes: [u8; PUBLIC_KEY_SIZE]) -> Self { - Self { - fe: FieldElement::from_bytes(&bytes), - } + fn is_clamped(scalar: &[u8; PRIVATE_KEY_SIZE]) -> bool { + use subtle::ConstantTimeEq; + ((scalar[0] & 7).ct_eq(&0) & ((scalar[31] & 0xC0).ct_eq(&0x40))).into() } } -impl_try_from_trait!(PublicKey); -#[cfg(feature = "serde")] -impl_serde_traits!(PublicKey, to_bytes); +impl TypeSpec for X25519PrivateKey { + const NAME: &'static str = stringify!(PrivateKey); + type TypeData = ByteArrayData; +} -impl TryFrom<&PrivateKey> for PublicKey { - type Error = UnknownCryptoError; +impl From<[u8; PRIVATE_KEY_SIZE]> for Secret { + fn from(value: [u8; PRIVATE_KEY_SIZE]) -> Self { + Self::from_data(::TypeData::from(value)) + } +} - fn try_from(private_key: &PrivateKey) -> Result { - // NOTE: This implementation should be identical to key_agreement() except - // for the check of a resulting low order point result. - let scalar = Scalar::from_slice(private_key.unprotected_as_bytes())?; +impl GenerateSecret for X25519PrivateKey { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + fn generate() -> Result, UnknownCryptoError> { + let mut data = Self::TypeData::new(PRIVATE_KEY_SIZE)?; + crate::util::secure_rand_bytes(&mut data.bytes)?; - Ok(PublicKey::from( - mont_ladder(&scalar, FieldElement::from_bytes(&BASEPOINT)).as_bytes(), - )) + Ok(Secret::from_data(data)) } } -impl PublicKey { - #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."] - /// Construct from a given byte slice. - pub fn from_slice(slice: &[u8]) -> Result { - let slice_len = slice.len(); +/// X25519 private key. +pub type PrivateKey = Secret; - if slice_len != PUBLIC_KEY_SIZE { - return Err(UnknownCryptoError); - } +#[derive(Debug, Clone, Copy)] +/// X25519 public key implementation. See [`PublicKey`] type for convenience. +pub struct X25519PublicKey {} +impl Sealed for X25519PublicKey {} - Ok(Self { - fe: FieldElement::from_bytes(slice.try_into().unwrap()), - }) - } +impl TypeSpec for X25519PublicKey { + const NAME: &'static str = stringify!(PublicKey); + type TypeData = ByteArrayData; - #[inline] - /// Return the length of the object. - pub fn len(&self) -> usize { - PUBLIC_KEY_SIZE - } + /// SECURITY: This overrides the default variable-time [`PartialEq`] with + /// a variable time one, in order to include high-bit masking. + fn vartime_partial_eq(lhs: &Self::TypeData, rhs: &[u8]) -> bool { + debug_assert_eq!(lhs.bytes.len(), PUBLIC_KEY_SIZE); + if lhs.bytes.len() != rhs.len() { + return false; + } - #[inline] - /// Return `true` if this object does not hold any data, `false` otherwise. - /// - /// __NOTE__: This method should always return `false`, since there shouldn't be a way - /// to create an empty instance of this object. - pub fn is_empty(&self) -> bool { - PUBLIC_KEY_SIZE == 0 + lhs.bytes[..31] == rhs[..31] && (lhs.bytes[31] & 127u8) == (rhs[31] & 127u8) } +} - #[inline] - /// Convert this PublicKey to its byte-representation. - pub fn to_bytes(&self) -> [u8; 32] { - self.fe.as_bytes() +impl From<[u8; PUBLIC_KEY_SIZE]> for Public { + fn from(value: [u8; PUBLIC_KEY_SIZE]) -> Self { + Self::from_data(::TypeData::from(value)) } } -#[allow(clippy::derive_partial_eq_without_eq)] -// NOTE: Scalar contains a constant-time PartialEq impl. -// NOTE: All newtypes impl Drop by default and Scalar has zeroizing Drop -/// A type to represent the `PrivateKey` that X25519 uses. -/// -/// This type holds a scalar and is used internally as such. The scalar held is decoded -/// (a.k.a "clamped") as mandated in the [RFC](https://datatracker.ietf.org/doc/html/rfc7748#section-5). -/// -/// # Errors: -/// An error will be returned if: -/// - `slice` is not 32 bytes. -/// -/// # Panics: -/// A panic will occur if: -/// - Failure to generate random bytes securely. -/// -/// -/// # Security: -/// - __**Avoid using**__ `unprotected_as_bytes()` whenever possible, as it breaks all protections -/// that the type implements. -/// -/// - The trait `PartialEq<&'_ [u8]>` is implemented for this type so that users are not tempted -/// to call `unprotected_as_bytes` to compare this sensitive value to a byte slice. The trait -/// is implemented in such a way that the comparison happens in constant time. Thus, users should -/// prefer `SecretType == &[u8]` over `SecretType.unprotected_as_bytes() == &[u8]`. -/// -/// Examples are shown below. The examples apply to any type that implements `PartialEq<&'_ [u8]>`. -/// ```rust -/// # #[cfg(feature = "safe_api")] { -/// use orion::hazardous::ecc::x25519::PrivateKey; -/// -/// // Initialize a secret key with random bytes. -/// let secret_key = PrivateKey::generate(); -/// -/// // Secure, constant-time comparison with a byte slice -/// assert_ne!(secret_key, &[0; 32][..]); -/// -/// // Secure, constant-time comparison with another SecretKey -/// assert_ne!(secret_key, PrivateKey::generate()); -/// # } -/// # Ok::<(), orion::errors::UnknownCryptoError>(()) -/// ``` -#[derive(PartialEq)] -pub struct PrivateKey { - scalar: Scalar, -} +impl TryFrom<&PrivateKey> for Public { + type Error = UnknownCryptoError; -impl PartialEq<&[u8]> for PrivateKey { - fn eq(&self, other: &&[u8]) -> bool { - match Scalar::from_slice(other) { - Ok(other_scalar) => self.scalar == other_scalar, - Err(_) => false, - } + fn try_from(private_key: &PrivateKey) -> Result { + // NOTE: This implementation should be identical to key_agreement() except + // for the check of a resulting low order point result. + Ok(PublicKey::from( + mont_ladder(&X25519PrivateKey::clamp(&private_key.data.bytes), BASEPOINT).as_bytes(), + )) } } -impl core::fmt::Debug for PrivateKey { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{} {{***OMITTED***}}", stringify!(PrivateKey)) +impl From<&Public> for FieldElement { + fn from(value: &Public) -> Self { + FieldElement::from_bytes(&value.data.bytes) } } -impl From<[u8; PRIVATE_KEY_SIZE]> for PrivateKey { - #[inline] - fn from(bytes: [u8; PRIVATE_KEY_SIZE]) -> Self { - PrivateKey { - // unwrap OK due to valid len - scalar: Scalar::from_slice(bytes.as_ref()).unwrap(), - } +impl From<&FieldElement> for Public { + fn from(value: &FieldElement) -> Self { + let bytes = value.as_bytes(); + debug_assert_eq!( + bytes[31] & 0x80, + 0, + "parsed FieldElement for PublicKey with missing highbit mask!" + ); + + Self::from(bytes) } } -impl PrivateKey { - #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."] - /// Construct from a given byte slice. - pub fn from_slice(slice: &[u8]) -> Result { - Ok(Self { - scalar: Scalar::from_slice(slice)?, - }) - } +/// X25519 public key. +/// +/// The most significant bit of the last byte is masked when comparing with [`PartialEq`]. +/// See [RFC](https://www.ietf.org/rfc/rfc7748.html#section-5). +/// +/// This means if two instances differ in the highest bit, then comparing [`PartialEq`] +/// will return true, directly on this type. If comparing with [`Self::as_ref()`] +/// then they will differ. +pub type PublicKey = Public; + +#[derive(Debug)] +/// X25519 shared key implementation. See [`SharedKey`] type for convenience. +pub struct X25519SharedKey {} +impl Sealed for X25519SharedKey {} + +impl TypeSpec for X25519SharedKey { + const NAME: &'static str = stringify!(SharedKey); + type TypeData = ByteArrayData; + + /// SECURITY: This overrides the default constant-time [`PartialEq`] with + /// a constant time one, in order to include high-bit masking. + fn ct_partial_eq(lhs: &Self::TypeData, rhs: &[u8]) -> bool { + use subtle::ConstantTimeEq; + debug_assert_eq!(lhs.bytes.len(), SHARED_KEY_SIZE); + if lhs.bytes.len() != rhs.len() { + return false; + } - #[inline] - /// Return the length of the object. - pub fn len(&self) -> usize { - PRIVATE_KEY_SIZE + (lhs.bytes[..31].ct_eq(&rhs[..31]) & (lhs.bytes[31] & 127u8).ct_eq(&(rhs[31] & 127u8))) + .into() } +} - #[inline] - /// Return `true` if this object does not hold any data, `false` otherwise. - /// - /// __NOTE__: This method should always return `false`, since there shouldn't be a way - /// to create an empty instance of this object. - pub fn is_empty(&self) -> bool { - PRIVATE_KEY_SIZE == 0 +impl From<[u8; SHARED_KEY_SIZE]> for Secret { + fn from(value: [u8; SHARED_KEY_SIZE]) -> Self { + Self::from_data(::TypeData::from(value)) } +} - #[inline] - /// Return the object as byte slice. __**Warning**__: Should not be used unless strictly - /// needed. This __**breaks protections**__ that the type implements. - pub fn unprotected_as_bytes(&self) -> &[u8] { - self.scalar.0.as_ref() - } +impl From for Secret { + fn from(value: FieldElement) -> Self { + let bytes = value.as_bytes(); + debug_assert_eq!( + bytes[31] & 0x80, + 0, + "parsed FieldElement for SharedSecret with missing highbit mask!" + ); - #[cfg(feature = "safe_api")] - #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] - /// Randomly generate using a CSPRNG. Not available in `no_std` context. - pub fn generate() -> PrivateKey { - let mut value = [0u8; PRIVATE_KEY_SIZE]; - crate::util::secure_rand_bytes(&mut value).unwrap(); - - Self { - // unwrap OK due to valid len - scalar: Scalar::from_slice(&value).unwrap(), - } + Self::from(bytes) } } -construct_secret_key! { - /// A type to represent the `SharedKey` that X25519 produces. - /// - /// This type simply holds bytes. Creating an instance from slices or similar, - /// performs no checks whatsoever. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 32 bytes. - (SharedKey, test_shared_key, SHARED_KEY_SIZE, SHARED_KEY_SIZE) -} - -impl_from_trait!(SharedKey, SHARED_KEY_SIZE); +/// X25519 shared key. +/// +/// The most significant bit of the last byte is masked when comparing with [`PartialEq`]. +/// See [RFC](https://www.ietf.org/rfc/rfc7748.html#section-5). +/// +/// This means if two instances differ in the highest bit, then comparing [`PartialEq`] +/// will return true, directly on this type. If comparing with [`Self::unprotected_as_ref()`] +/// then they will differ. +pub type SharedKey = Secret; /// X25519 (Diffie-Hellman with Montgomery form of Curve25519). pub fn key_agreement( private_key: &PrivateKey, public_key: &PublicKey, ) -> Result { - let u_coord = public_key.fe; - let field_element = mont_ladder(&private_key.scalar, u_coord).as_bytes(); + let u_coord = FieldElement::from(public_key); + let shared_key = SharedKey::from(mont_ladder( + &X25519PrivateKey::clamp(&private_key.data.bytes), + u_coord, + )); + // High bit should be zero. - debug_assert_eq!(field_element[31] & 0b1000_0000u8, 0u8); - if secure_cmp(&field_element, &LOW_ORDER_POINT_RESULT).is_ok() { + debug_assert_eq!(shared_key.data.bytes[31] & 0b1000_0000u8, 0u8); + if shared_key == &LOW_ORDER_POINT_RESULT { return Err(UnknownCryptoError); } - Ok(SharedKey::from(field_element)) + Ok(shared_key) } #[cfg(test)] mod public { use crate::hazardous::ecc::x25519::{ - key_agreement, PrivateKey, PublicKey, Scalar, SharedKey, BASEPOINT, PRIVATE_KEY_SIZE, - PUBLIC_KEY_SIZE, + BASEPOINT, FieldElement, PRIVATE_KEY_SIZE, PUBLIC_KEY_SIZE, PrivateKey, PublicKey, + SHARED_KEY_SIZE, SharedKey, X25519PrivateKey, X25519PublicKey, X25519SharedKey, + key_agreement, }; - // NOTE(brycx): PrivateKey/PublicKey in X25519 are manual impls of types that are normally tested as - // part of typedefs, so we have some extra test code that here, that normally - // would be part of the macros. + const BASEPOINT_BYTES: [u8; 32] = [ + 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, + ]; #[test] - #[cfg(feature = "safe_api")] - fn testpublickey_partialeq_bytes() { - let k = PrivateKey::generate(); - let pk = PublicKey::try_from(&k).unwrap(); - - assert_eq!(pk, pk.to_bytes().as_ref()); - assert_ne!(pk, [0u8; PUBLIC_KEY_SIZE].as_ref()); // not zero, because generate() - assert_ne!(pk, [0u8; PUBLIC_KEY_SIZE - 1].as_ref()); // early abort on length mismatch - assert_ne!(pk, [0u8; PUBLIC_KEY_SIZE + 1].as_ref()); + fn correctness_clamping() { + let mut preclamp = [0u8; PRIVATE_KEY_SIZE]; + + for b0 in 0u8..=u8::MAX { + preclamp[0] = b0; + + for b31 in 0u8..=u8::MAX { + preclamp[31] = b31; + if X25519PrivateKey::is_clamped(&preclamp) { + assert_eq!(&preclamp, &X25519PrivateKey::clamp(&preclamp)); + } + assert!(X25519PrivateKey::is_clamped(&X25519PrivateKey::clamp( + &preclamp + ))); + } + } } #[test] - #[cfg(feature = "safe_api")] - fn testprivate_partialeq_bytes() { - let k = PrivateKey::generate(); - assert!(!k.is_empty()); - assert_eq!(k.len(), PRIVATE_KEY_SIZE); - - assert_eq!(k, k.unprotected_as_bytes()); - assert_ne!(k, [0u8; PRIVATE_KEY_SIZE].as_ref()); // not zero, because generate() - assert_ne!(k, [0u8; PRIVATE_KEY_SIZE - 1].as_ref()); // early abort on length mismatch - assert_ne!(k, [0u8; PRIVATE_KEY_SIZE + 1].as_ref()); + fn test_shared_secret() { + use crate::test_framework::newtypes::secret::SecretNewtype; + SecretNewtype::test_no_generate::(); + // Test of From<[u8; N]> + assert_ne!( + SharedKey::from([0u8; SHARED_KEY_SIZE]), + SharedKey::from([1u8; SHARED_KEY_SIZE]) + ); } #[test] - fn test_scalar_length_from_slice() { - assert!(Scalar::from_slice(&[0u8; PRIVATE_KEY_SIZE]).is_ok()); - assert!(Scalar::from_slice(&[0u8; PRIVATE_KEY_SIZE - 1]).is_err()); - assert!(Scalar::from_slice(&[0u8; PRIVATE_KEY_SIZE + 1]).is_err()); + fn test_private_key() { + use crate::test_framework::newtypes::secret::SecretNewtype; + SecretNewtype::test_with_generate::< + PRIVATE_KEY_SIZE, + PRIVATE_KEY_SIZE, + PRIVATE_KEY_SIZE, + X25519PrivateKey, + >(); + // Test of From<[u8; N]> + assert_ne!( + PrivateKey::from([0u8; PRIVATE_KEY_SIZE]), + PrivateKey::from([1u8; PRIVATE_KEY_SIZE]) + ); } #[test] - fn test_publickey_length_from_slice() { - assert!(PublicKey::from_slice(&[0u8; PUBLIC_KEY_SIZE]).is_ok()); - assert!(PublicKey::from_slice(&[0u8; PUBLIC_KEY_SIZE - 1]).is_err()); - assert!(PublicKey::from_slice(&[0u8; PUBLIC_KEY_SIZE + 1]).is_err()); + fn test_public_key() { + use crate::test_framework::newtypes::public::PublicNewtype; + PublicNewtype::test_no_generate::(); + // Test of From<[u8; N]> + assert_ne!( + PublicKey::from([0u8; PUBLIC_KEY_SIZE]), + PublicKey::from([1u8; PUBLIC_KEY_SIZE]) + ); - let pk = PublicKey::from_slice(&[0u8; PUBLIC_KEY_SIZE]).unwrap(); - assert!(!pk.is_empty()); - assert_eq!(pk.len(), PUBLIC_KEY_SIZE); + #[cfg(feature = "serde")] + PublicNewtype::test_serialization::(); } #[test] @@ -688,7 +624,8 @@ mod public { // format! is only available with std fn test_field_element_debug_impl() { let secret = format!("{:?}", [1u8; 32].as_ref()); - let test_debug_contents = format!("{:?}", PrivateKey::from_slice(&[1u8; 32]).unwrap()); + let test_debug_contents = + format!("{:?}", PrivateKey::try_from([1u8; 32].as_slice()).unwrap()); assert!(!test_debug_contents.contains(&secret)); } @@ -712,12 +649,49 @@ mod public { msb_one[31] |= 128u8; // These should equal each other. The high bits differ, but should be ignored. - assert_eq!(PublicKey::from(msb_zero), msb_one.as_ref()); - assert_eq!(PublicKey::from(msb_zero), PublicKey::from(msb_one)); + let pk_msb_zero = PublicKey::from(msb_zero); + assert_eq!(pk_msb_zero, &msb_zero); + assert_eq!(pk_msb_zero, &msb_one); - let pk_zero = PublicKey::from_slice(&msb_zero).unwrap(); - let pk_one = PublicKey::from_slice(&msb_one).unwrap(); - assert_eq!(pk_zero.to_bytes(), pk_one.to_bytes()); + let pk_msb_one = PublicKey::from(msb_one); + assert_eq!(pk_msb_one, &msb_zero); + assert_eq!(pk_msb_one, &msb_one); + + assert_eq!(pk_msb_zero, pk_msb_one.as_ref()); + // We do not modify the stored value itself, because any impl MUST accept + // non-canonical and treat them as if they are. So in stead of modifying, + // we only handle it internally and on [`PartialEq`]. Preserve still original + // value. + assert_ne!(pk_msb_one.as_ref(), pk_msb_zero.as_ref()); + } + + #[test] + fn test_shared_key_ignores_highbit() { + let u = [0u8; 32]; + + let mut msb_zero = u; + msb_zero[31] &= 127u8; + let mut msb_one = u; + msb_one[31] |= 128u8; + + // These should equal each other. The high bits differ, but should be ignored. + let pk_msb_zero = SharedKey::from(msb_zero); + assert_eq!(pk_msb_zero, &msb_zero); + assert_eq!(pk_msb_zero, &msb_one); + + let pk_msb_one = SharedKey::from(msb_one); + assert_eq!(pk_msb_one, &msb_zero); + assert_eq!(pk_msb_one, &msb_one); + + assert_eq!(pk_msb_zero, pk_msb_one.unprotected_as_ref()); + // We do not modify the stored value itself, because any impl MUST accept + // non-canonical and treat them as if they are. So in stead of modifying, + // we only handle it internally and on [`PartialEq`]. Preserve still original + // value. + assert_ne!( + pk_msb_one.unprotected_as_ref(), + pk_msb_zero.unprotected_as_ref() + ); } #[test] @@ -726,7 +700,7 @@ mod public { // RFC 7748 dictates that the MSB of final byte must be masked when receiving a field element, // used for agreement (public key). We check that modifying it does not impact the result of // the agreement. - let k = PrivateKey::generate(); + let k = PrivateKey::generate().unwrap(); let mut u = [0u8; 32]; crate::util::secure_rand_bytes(&mut u).unwrap(); debug_assert_ne!(u[31] & 127u8, (u[31] & 127u8) | 128u8); @@ -743,6 +717,30 @@ mod public { assert_eq!(msb_zero, msb_one); } + #[test] + #[cfg(feature = "safe_api")] + fn test_scalar_clamp() { + // We test clamping happens on all three manual impls of parsing bytes + + // GenerateSecret + let private = PrivateKey::generate().unwrap(); + assert_eq!(X25519PrivateKey::clamp(&private.data.bytes)[0] & !248, 0); + assert_eq!(X25519PrivateKey::clamp(&private.data.bytes)[31] & !127, 0); + assert_eq!(X25519PrivateKey::clamp(&private.data.bytes)[31] & 64, 64); + + // TryFrom default impls defined by parse_bytes() + let private = PrivateKey::try_from(&[0xFFu8; PRIVATE_KEY_SIZE]).unwrap(); + assert_eq!(X25519PrivateKey::clamp(&private.data.bytes)[0] & !248, 0); + assert_eq!(X25519PrivateKey::clamp(&private.data.bytes)[31] & !127, 0); + assert_eq!(X25519PrivateKey::clamp(&private.data.bytes)[31] & 64, 64); + + // From<[u8; 32]> + let private = PrivateKey::from([0xFFu8; PRIVATE_KEY_SIZE]); + assert_eq!(X25519PrivateKey::clamp(&private.data.bytes)[0] & !248, 0); + assert_eq!(X25519PrivateKey::clamp(&private.data.bytes)[31] & !127, 0); + assert_eq!(X25519PrivateKey::clamp(&private.data.bytes)[31] & 64, 64); + } + #[test] /// Ref: https://www.ietf.org/rfc/rfc7748.html#section-5.2 fn test_rfc_section_5() { @@ -762,7 +760,7 @@ mod public { .unwrap(); hex::decode_to_slice( "c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552", - &mut expected.value, + expected.data.as_mut(), ) .unwrap(); @@ -781,7 +779,7 @@ mod public { .unwrap(); hex::decode_to_slice( "95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957", - &mut expected.value, + expected.data.as_mut(), ) .unwrap(); @@ -792,34 +790,34 @@ mod public { #[test] /// Ref: https://www.ietf.org/rfc/rfc7748.html#section-5.2 fn test_rfc_section_5_iter() { - let mut k = BASEPOINT; + let mut k = BASEPOINT_BYTES; let mut u = BASEPOINT; // 1 iter - let ret = key_agreement(&PrivateKey::from(k), &PublicKey::from(u)).unwrap(); - u = k; - k = ret.value; + let ret = key_agreement(&PrivateKey::from(k), &PublicKey::from(&u)).unwrap(); + u = FieldElement::from_bytes(&k); + k = ret.data.bytes; let mut expected = SharedKey::from([0u8; 32]); hex::decode_to_slice( "422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079", - &mut expected.value, + expected.data.as_mut(), ) .unwrap(); - assert_eq!(k, expected.value, "Failed after 1 iter"); + assert_eq!(k, expected.unprotected_as_ref(), "Failed after 1 iter"); for _ in 0..999 { - let ret = key_agreement(&PrivateKey::from(k), &PublicKey::from(u)).unwrap(); - u = k; - k = ret.value; + let ret = key_agreement(&PrivateKey::from(k), &PublicKey::from(&u)).unwrap(); + u = FieldElement::from_bytes(&k); + k = ret.data.bytes; } hex::decode_to_slice( "684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51", - &mut expected.value, + expected.data.as_mut(), ) .unwrap(); - assert_eq!(k, expected.value, "Failed after 1.000 iter"); + assert_eq!(k, expected.unprotected_as_ref(), "Failed after 1.000 iter"); /* Taking a decade... for num in 0..999000 { @@ -830,7 +828,7 @@ mod public { hex::decode_to_slice( "7c3911e0ab2586fd864497297e575e6f3bc601c0883c30df5f4dd2d24f665424", - &mut expected.value, + expected.data.as_mut(), ) .unwrap(); assert_eq!(k, expected.value, "Failed after 1.000.000 iter"); @@ -859,8 +857,13 @@ mod public { ) .unwrap(); assert_eq!( - key_agreement(&PrivateKey::from(alice_priv), &PublicKey::from(BASEPOINT)).unwrap(), - PublicKey::from(alice_pub).to_bytes().as_ref() + PublicKey::from(alice_pub), + key_agreement( + &PrivateKey::from(alice_priv), + &PublicKey::from(BASEPOINT_BYTES) + ) + .unwrap() + .unprotected_as_ref() ); hex::decode_to_slice( @@ -874,39 +877,27 @@ mod public { ) .unwrap(); assert_eq!( - key_agreement(&PrivateKey::from(bob_priv), &PublicKey::from(BASEPOINT)).unwrap(), - PublicKey::from(bob_pub).to_bytes().as_ref() + PublicKey::from(bob_pub), + key_agreement( + &PrivateKey::from(bob_priv), + &PublicKey::from(BASEPOINT_BYTES) + ) + .unwrap() + .unprotected_as_ref() ); hex::decode_to_slice( "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742", - &mut shared.value, + shared.data.as_mut(), ) .unwrap(); assert_eq!( key_agreement(&PrivateKey::from(alice_priv), &PublicKey::from(bob_pub)).unwrap(), - shared.value.as_ref() + shared ); assert_eq!( key_agreement(&PrivateKey::from(bob_priv), &PublicKey::from(alice_pub)).unwrap(), - shared.value.as_ref() + shared ); } - - #[test] - fn test_privatekey_edge_cases() { - assert!(PrivateKey::from_slice(&[0u8; PRIVATE_KEY_SIZE]).is_ok()); - - assert!(PrivateKey::from_slice(&[0xffu8; PRIVATE_KEY_SIZE]).is_ok()); - - assert!(PrivateKey::from_slice(&[0b10101010u8; PRIVATE_KEY_SIZE]).is_ok()); - - let mut first = [0u8; PRIVATE_KEY_SIZE]; - first[0] = 1; - assert!(PrivateKey::from_slice(&first).is_ok()); - - let mut last = [0u8; PRIVATE_KEY_SIZE]; - last[PRIVATE_KEY_SIZE - 1] = 1; - assert!(PrivateKey::from_slice(&last).is_ok()); - } } diff --git a/src/hazardous/hash/blake2/blake2b.rs b/src/hazardous/hash/blake2/blake2b.rs index 35ccb59ab..879049d46 100644 --- a/src/hazardous/hash/blake2/blake2b.rs +++ b/src/hazardous/hash/blake2/blake2b.rs @@ -62,21 +62,25 @@ //! [`mac::blake2b`]: crate::hazardous::mac::blake2b use crate::errors::UnknownCryptoError; +use crate::generics::sealed::Sealed; +use crate::generics::{ByteArrayVecData, Public, TypeSpec}; use crate::hazardous::hash::blake2::blake2b_core; use crate::hazardous::hash::blake2::blake2b_core::BLAKE2B_OUTSIZE; #[cfg(feature = "safe_api")] use std::io; -construct_public! { - /// A type to represent the `Digest` that BLAKE2b returns. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is empty. - /// - `slice` is greater than 64 bytes. - (Digest, test_digest, 1, BLAKE2B_OUTSIZE) +#[derive(Debug, Clone, Copy)] +/// Marker type for BLAKE2b digest. See [`Digest`] type for convenience. +pub struct Blake2bDigest {} +impl Sealed for Blake2bDigest {} + +impl TypeSpec for Blake2bDigest { + const NAME: &'static str = stringify!(Digest); + type TypeData = ByteArrayVecData<1, BLAKE2B_OUTSIZE>; } +/// A type to represent the [`Digest`]/hash-output that BLAKE2b returns. +pub type Digest = Public; #[derive(Debug, Clone)] /// BLAKE2b streaming state. @@ -111,7 +115,7 @@ impl Blake2b { let mut tmp = [0u8; BLAKE2B_OUTSIZE]; self._state._finalize(&mut tmp)?; - Digest::from_slice(&tmp[..self._state.size]) + Digest::try_from(&tmp[..self._state.size]) } } @@ -196,11 +200,22 @@ impl io::Write for Blake2b { #[cfg(test)] mod public { + + #[test] + fn test_blake2b_digest() { + use super::*; + use crate::test_framework::newtypes::public::PublicNewtype; + PublicNewtype::test_no_generate::<1, BLAKE2B_OUTSIZE, Blake2bDigest>(); + + #[cfg(feature = "serde")] + PublicNewtype::test_serialization::(); + } + mod test_streaming_interface_no_key { use crate::errors::UnknownCryptoError; use crate::hazardous::hash::blake2::blake2b::{Blake2b, Digest}; use crate::hazardous::hash::blake2::blake2b_core::{ - compare_blake2b_states, BLAKE2B_BLOCKSIZE, BLAKE2B_OUTSIZE, + BLAKE2B_BLOCKSIZE, BLAKE2B_OUTSIZE, compare_blake2b_states, }; use crate::test_framework::incremental_interface::{ StreamingContextConsistencyTester, TestableStreamingContext, diff --git a/src/hazardous/hash/blake2/mod.rs b/src/hazardous/hash/blake2/mod.rs index 08f331a22..7791fd5e7 100644 --- a/src/hazardous/hash/blake2/mod.rs +++ b/src/hazardous/hash/blake2/mod.rs @@ -408,7 +408,7 @@ mod private { } mod test_increment_offset { - use crate::hazardous::hash::blake2::blake2b_core::{State, BLAKE2B_BLOCKSIZE, IV}; + use crate::hazardous::hash::blake2::blake2b_core::{BLAKE2B_BLOCKSIZE, IV, State}; use crate::util::u64x4::U64x4; #[test] diff --git a/src/hazardous/hash/sha2/mod.rs b/src/hazardous/hash/sha2/mod.rs index e375de6f5..115a735d6 100644 --- a/src/hazardous/hash/sha2/mod.rs +++ b/src/hazardous/hash/sha2/mod.rs @@ -149,12 +149,12 @@ pub(crate) mod sha2_core { #[cfg(feature = "zeroize")] impl< - W: Word, - T: Variant, - const BLOCKSIZE: usize, - const OUTSIZE: usize, - const N_CONSTS: usize, - > Drop for State + W: Word, + T: Variant, + const BLOCKSIZE: usize, + const OUTSIZE: usize, + const N_CONSTS: usize, + > Drop for State { fn drop(&mut self) { self.working_state.iter_mut().zeroize(); @@ -166,12 +166,12 @@ pub(crate) mod sha2_core { } impl< - W: Word, - T: Variant, - const BLOCKSIZE: usize, - const OUTSIZE: usize, - const N_CONSTS: usize, - > Debug for State + W: Word, + T: Variant, + const BLOCKSIZE: usize, + const OUTSIZE: usize, + const N_CONSTS: usize, + > Debug for State { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!( @@ -184,12 +184,12 @@ pub(crate) mod sha2_core { } impl< - W: Word, - T: Variant, - const BLOCKSIZE: usize, - const OUTSIZE: usize, - const N_CONSTS: usize, - > State + W: Word, + T: Variant, + const BLOCKSIZE: usize, + const OUTSIZE: usize, + const N_CONSTS: usize, + > State { /// Increment the message length during processing of data. pub(crate) fn increment_mlen(&mut self, length: &W) { diff --git a/src/hazardous/hash/sha2/sha256.rs b/src/hazardous/hash/sha2/sha256.rs index 2b4f9139f..17392020e 100644 --- a/src/hazardous/hash/sha2/sha256.rs +++ b/src/hazardous/hash/sha2/sha256.rs @@ -60,10 +60,15 @@ //! [BLAKE2b]: super::blake2::blake2b use crate::errors::UnknownCryptoError; +use crate::generics::sealed::Sealed; +use crate::generics::{ByteArrayData, Public, TypeSpec}; #[cfg(feature = "safe_api")] use std::io; +use super::sha2_core::{State, Variant, Word}; +use super::w32::WordU32; + /// The blocksize for the hash function SHA256. pub const SHA256_BLOCKSIZE: usize = 64; /// The output size for the hash function SHA256. @@ -71,19 +76,24 @@ pub const SHA256_OUTSIZE: usize = 32; /// The number of constants for the hash function SHA256. const N_CONSTS: usize = 64; -construct_public! { - /// A type to represent the `Digest` that SHA256 returns. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 32 bytes. - (Digest, test_digest, SHA256_OUTSIZE, SHA256_OUTSIZE) +#[derive(Debug, Clone, Copy)] +/// Marker type for SHA256 digest. See [`Digest`] type for convenience. +pub struct Sha256Digest {} +impl Sealed for Sha256Digest {} + +impl TypeSpec for Sha256Digest { + const NAME: &'static str = stringify!(Digest); + type TypeData = ByteArrayData; } -impl_from_trait!(Digest, SHA256_OUTSIZE); +impl From<[u8; SHA256_OUTSIZE]> for Public { + fn from(value: [u8; SHA256_OUTSIZE]) -> Self { + Self::from_data(::TypeData::from(value)) + } +} -use super::sha2_core::{State, Variant, Word}; -use super::w32::WordU32; +/// A type to represent the [`Digest`]/hash-output that SHA256 returns. +pub type Digest = Public; #[derive(Clone)] /// SHA256 streaming state. @@ -276,6 +286,16 @@ impl io::Write for Sha256 { mod public { use super::*; + #[test] + fn test_sha256_digest() { + use super::*; + use crate::test_framework::newtypes::public::PublicNewtype; + PublicNewtype::test_no_generate::(); + + #[cfg(feature = "serde")] + PublicNewtype::test_serialization::(); + } + #[test] fn test_default_equals_new() { let new = Sha256::new(); diff --git a/src/hazardous/hash/sha2/sha384.rs b/src/hazardous/hash/sha2/sha384.rs index f6f898c00..748760452 100644 --- a/src/hazardous/hash/sha2/sha384.rs +++ b/src/hazardous/hash/sha2/sha384.rs @@ -59,22 +59,14 @@ //! [`finalize()`]: sha384::Sha384::finalize //! [BLAKE2b]: super::blake2::blake2b -use crate::errors::UnknownCryptoError; +use crate::{ + errors::UnknownCryptoError, + generics::{ByteArrayData, Public, TypeSpec, sealed::Sealed}, +}; #[cfg(feature = "safe_api")] use std::io; -construct_public! { - /// A type to represent the `Digest` that SHA384 returns. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 48 bytes. - (Digest, test_digest, SHA384_OUTSIZE, SHA384_OUTSIZE) -} - -impl_from_trait!(Digest, SHA384_OUTSIZE); - use super::sha2_core::{State, Variant}; use super::w64::WordU64; @@ -85,6 +77,25 @@ pub const SHA384_OUTSIZE: usize = 48; /// The number of constants for the hash function SHA384. const N_CONSTS: usize = 80; +#[derive(Debug, Clone, Copy)] +/// Marker type for SHA384 digest. See [`Digest`] type for convenience. +pub struct Sha384Digest {} +impl Sealed for Sha384Digest {} + +impl TypeSpec for Sha384Digest { + const NAME: &'static str = stringify!(Digest); + type TypeData = ByteArrayData; +} + +impl From<[u8; SHA384_OUTSIZE]> for Public { + fn from(value: [u8; SHA384_OUTSIZE]) -> Self { + Self::from_data(::TypeData::from(value)) + } +} + +/// A type to represent the [`Digest`]/hash-output that SHA384 returns. +pub type Digest = Public; + #[derive(Clone)] pub(crate) struct V384; @@ -256,6 +267,16 @@ impl io::Write for Sha384 { mod public { use super::*; + #[test] + fn test_sha384_digest() { + use super::*; + use crate::test_framework::newtypes::public::PublicNewtype; + PublicNewtype::test_no_generate::(); + + #[cfg(feature = "serde")] + PublicNewtype::test_serialization::(); + } + #[test] fn test_default_equals_new() { let new = Sha384::new(); diff --git a/src/hazardous/hash/sha2/sha512.rs b/src/hazardous/hash/sha2/sha512.rs index 3bbf45d4b..2513ea6a0 100644 --- a/src/hazardous/hash/sha2/sha512.rs +++ b/src/hazardous/hash/sha2/sha512.rs @@ -60,21 +60,12 @@ //! [BLAKE2b]: super::blake2::blake2b use crate::errors::UnknownCryptoError; +use crate::generics::sealed::Sealed; +use crate::generics::{ByteArrayData, Public, TypeSpec}; #[cfg(feature = "safe_api")] use std::io; -construct_public! { - /// A type to represent the `Digest` that SHA512 returns. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 64 bytes. - (Digest, test_digest, SHA512_OUTSIZE, SHA512_OUTSIZE) -} - -impl_from_trait!(Digest, SHA512_OUTSIZE); - use super::sha2_core::{State, Variant, Word}; use super::w64::WordU64; @@ -85,6 +76,25 @@ pub const SHA512_OUTSIZE: usize = 64; /// The number of constants for the hash function SHA512. const N_CONSTS: usize = 80; +#[derive(Debug, Clone, Copy)] +/// Marker type for SHA512 digest. See [`Digest`] type for convenience. +pub struct Sha512Digest {} +impl Sealed for Sha512Digest {} + +impl TypeSpec for Sha512Digest { + const NAME: &'static str = stringify!(Digest); + type TypeData = ByteArrayData; +} + +impl From<[u8; SHA512_OUTSIZE]> for Public { + fn from(value: [u8; SHA512_OUTSIZE]) -> Self { + Self::from_data(::TypeData::from(value)) + } +} + +/// A type to represent the [`Digest`]/hash-output that SHA512 returns. +pub type Digest = Public; + #[derive(Clone)] pub(crate) struct V512; @@ -279,6 +289,16 @@ impl io::Write for Sha512 { mod public { use super::*; + #[test] + fn test_sha512_digest() { + use super::*; + use crate::test_framework::newtypes::public::PublicNewtype; + PublicNewtype::test_no_generate::(); + + #[cfg(feature = "serde")] + PublicNewtype::test_serialization::(); + } + #[test] fn test_default_equals_new() { let new = Sha512::new(); diff --git a/src/hazardous/hash/sha3/sha3_224.rs b/src/hazardous/hash/sha3/sha3_224.rs index 4755c645f..54c1b8996 100644 --- a/src/hazardous/hash/sha3/sha3_224.rs +++ b/src/hazardous/hash/sha3/sha3_224.rs @@ -48,7 +48,11 @@ //! [`reset()`]: sha3_224::Sha3_224::reset //! [`finalize()`]: sha3_224::Sha3_224::finalize -use crate::errors::UnknownCryptoError; +use crate::{ + errors::UnknownCryptoError, + generics::{ByteArrayData, Public, TypeSpec, sealed::Sealed}, +}; + #[cfg(feature = "safe_api")] use std::io; @@ -60,16 +64,25 @@ pub const SHA3_224_RATE: usize = 144; /// Output size of SHA3-224 in bytes. pub const SHA3_224_OUTSIZE: usize = 28; -construct_public! { - /// A type to represent the `Digest` that SHA3-224 returns. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 28 bytes. - (Digest, test_digest, SHA3_224_OUTSIZE, SHA3_224_OUTSIZE) +#[derive(Debug, Clone, Copy)] +#[allow(non_camel_case_types)] +/// Marker type for SHA3_224 digest. See [`Digest`] type for convenience. +pub struct Sha3_224_Digest {} +impl Sealed for Sha3_224_Digest {} + +impl TypeSpec for Sha3_224_Digest { + const NAME: &'static str = stringify!(Digest); + type TypeData = ByteArrayData; +} + +impl From<[u8; SHA3_224_OUTSIZE]> for Public { + fn from(value: [u8; SHA3_224_OUTSIZE]) -> Self { + Self::from_data(::TypeData::from(value)) + } } -impl_from_trait!(Digest, SHA3_224_OUTSIZE); +/// A type to represent the [`Digest`]/hash-output that SHA3-224 returns. +pub type Digest = Public; #[derive(Clone, Debug)] /// SHA3-224 streaming state. @@ -171,6 +184,16 @@ impl Sha3_224 { mod public { use super::*; + #[test] + fn test_sha3_224_digest() { + use super::*; + use crate::test_framework::newtypes::public::PublicNewtype; + PublicNewtype::test_no_generate::(); + + #[cfg(feature = "serde")] + PublicNewtype::test_serialization::(); + } + #[test] fn test_default_equals_new() { let new = Sha3_224::new(); diff --git a/src/hazardous/hash/sha3/sha3_256.rs b/src/hazardous/hash/sha3/sha3_256.rs index e5f49b582..bee76a30b 100644 --- a/src/hazardous/hash/sha3/sha3_256.rs +++ b/src/hazardous/hash/sha3/sha3_256.rs @@ -48,7 +48,11 @@ //! [`reset()`]: sha3_256::Sha3_256::reset //! [`finalize()`]: sha3_256::Sha3_256::finalize -use crate::errors::UnknownCryptoError; +use crate::{ + errors::UnknownCryptoError, + generics::{ByteArrayData, Public, TypeSpec, sealed::Sealed}, +}; + #[cfg(feature = "safe_api")] use std::io; @@ -60,16 +64,25 @@ pub const SHA3_256_RATE: usize = 136; /// Output size of SHA3-256 in bytes. pub const SHA3_256_OUTSIZE: usize = 32; -construct_public! { - /// A type to represent the `Digest` that SHA3-256 returns. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 32 bytes. - (Digest, test_digest, SHA3_256_OUTSIZE, SHA3_256_OUTSIZE) +#[derive(Debug, Clone, Copy)] +#[allow(non_camel_case_types)] +/// Marker type for SHA3_256 digest. See [`Digest`] type for convenience. +pub struct Sha3_256_Digest {} +impl Sealed for Sha3_256_Digest {} + +impl TypeSpec for Sha3_256_Digest { + const NAME: &'static str = stringify!(Digest); + type TypeData = ByteArrayData; +} + +impl From<[u8; SHA3_256_OUTSIZE]> for Public { + fn from(value: [u8; SHA3_256_OUTSIZE]) -> Self { + Self::from_data(::TypeData::from(value)) + } } -impl_from_trait!(Digest, SHA3_256_OUTSIZE); +/// A type to represent the [`Digest`]/hash-output that SHA3-256 returns. +pub type Digest = Public; #[derive(Clone, Debug)] /// SHA3-256 streaming state. @@ -171,6 +184,16 @@ impl Sha3_256 { mod public { use super::*; + #[test] + fn test_sha3_256_digest() { + use super::*; + use crate::test_framework::newtypes::public::PublicNewtype; + PublicNewtype::test_no_generate::(); + + #[cfg(feature = "serde")] + PublicNewtype::test_serialization::(); + } + #[test] fn test_default_equals_new() { let new = Sha3_256::new(); diff --git a/src/hazardous/hash/sha3/sha3_384.rs b/src/hazardous/hash/sha3/sha3_384.rs index b525efcf9..a22ca6917 100644 --- a/src/hazardous/hash/sha3/sha3_384.rs +++ b/src/hazardous/hash/sha3/sha3_384.rs @@ -48,7 +48,11 @@ //! [`reset()`]: sha3_384::Sha3_384::reset //! [`finalize()`]: sha3_384::Sha3_384::finalize -use crate::errors::UnknownCryptoError; +use crate::{ + errors::UnknownCryptoError, + generics::{ByteArrayData, Public, TypeSpec, sealed::Sealed}, +}; + #[cfg(feature = "safe_api")] use std::io; @@ -60,16 +64,25 @@ pub const SHA3_384_RATE: usize = 104; /// Output size of SHA3-384 in bytes. pub const SHA3_384_OUTSIZE: usize = 48; -construct_public! { - /// A type to represent the `Digest` that SHA3-384 returns. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 48 bytes. - (Digest, test_digest, SHA3_384_OUTSIZE, SHA3_384_OUTSIZE) +#[derive(Debug, Clone, Copy)] +#[allow(non_camel_case_types)] +/// Marker type for SHA3_384 digest. See [`Digest`] type for convenience. +pub struct Sha3_384_Digest {} +impl Sealed for Sha3_384_Digest {} + +impl TypeSpec for Sha3_384_Digest { + const NAME: &'static str = stringify!(Digest); + type TypeData = ByteArrayData; +} + +impl From<[u8; SHA3_384_OUTSIZE]> for Public { + fn from(value: [u8; SHA3_384_OUTSIZE]) -> Self { + Self::from_data(::TypeData::from(value)) + } } -impl_from_trait!(Digest, SHA3_384_OUTSIZE); +/// A type to represent the [`Digest`]/hash-output that SHA3-384 returns. +pub type Digest = Public; #[derive(Clone, Debug)] /// SHA3-384 streaming state. @@ -171,6 +184,16 @@ impl Sha3_384 { mod public { use super::*; + #[test] + fn test_sha3_384_digest() { + use super::*; + use crate::test_framework::newtypes::public::PublicNewtype; + PublicNewtype::test_no_generate::(); + + #[cfg(feature = "serde")] + PublicNewtype::test_serialization::(); + } + #[test] fn test_default_equals_new() { let new = Sha3_384::new(); diff --git a/src/hazardous/hash/sha3/sha3_512.rs b/src/hazardous/hash/sha3/sha3_512.rs index 92975b49d..06740f304 100644 --- a/src/hazardous/hash/sha3/sha3_512.rs +++ b/src/hazardous/hash/sha3/sha3_512.rs @@ -48,7 +48,11 @@ //! [`reset()`]: sha3_512::Sha3_512::reset //! [`finalize()`]: sha3_512::Sha3_512::finalize -use crate::errors::UnknownCryptoError; +use crate::{ + errors::UnknownCryptoError, + generics::{ByteArrayData, Public, TypeSpec, sealed::Sealed}, +}; + #[cfg(feature = "safe_api")] use std::io; @@ -60,16 +64,25 @@ pub const SHA3_512_RATE: usize = 72; /// Output size of SHA3-512 in bytes. pub const SHA3_512_OUTSIZE: usize = 64; -construct_public! { - /// A type to represent the `Digest` that SHA3-512 returns. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 64 bytes. - (Digest, test_digest, SHA3_512_OUTSIZE, SHA3_512_OUTSIZE) +#[derive(Debug, Clone, Copy)] +#[allow(non_camel_case_types)] +/// Marker type for SHA3_512 digest. See [`Digest`] type for convenience. +pub struct Sha3_512_Digest {} +impl Sealed for Sha3_512_Digest {} + +impl TypeSpec for Sha3_512_Digest { + const NAME: &'static str = stringify!(Digest); + type TypeData = ByteArrayData; +} + +impl From<[u8; SHA3_512_OUTSIZE]> for Public { + fn from(value: [u8; SHA3_512_OUTSIZE]) -> Self { + Self::from_data(::TypeData::from(value)) + } } -impl_from_trait!(Digest, SHA3_512_OUTSIZE); +/// A type to represent the [`Digest`]/hash-output that SHA3-512 returns. +pub type Digest = Public; #[derive(Clone, Debug)] /// SHA3-512 streaming state. @@ -171,6 +184,16 @@ impl Sha3_512 { mod public { use super::*; + #[test] + fn test_sha3_512_digest() { + use super::*; + use crate::test_framework::newtypes::public::PublicNewtype; + PublicNewtype::test_no_generate::(); + + #[cfg(feature = "serde")] + PublicNewtype::test_serialization::(); + } + #[test] fn test_default_equals_new() { let new = Sha3_512::new(); diff --git a/src/hazardous/hpke/mode.rs b/src/hazardous/hpke/mode.rs index 36fadf557..eab837bb3 100644 --- a/src/hazardous/hpke/mode.rs +++ b/src/hazardous/hpke/mode.rs @@ -21,8 +21,8 @@ // SOFTWARE. use crate::errors::UnknownCryptoError; -use crate::hazardous::hpke::suite::private::*; use crate::hazardous::hpke::Role; +use crate::hazardous::hpke::suite::private::*; use private::*; pub(crate) mod private { @@ -40,6 +40,7 @@ pub(crate) mod private { /// Marker trait intended for a suite that implements this HPKE mode. pub trait AuthPsk {} + #[derive(Debug)] #[repr(u8)] /// HPKE modes utility. pub enum HpkeMode { @@ -107,15 +108,12 @@ pub(crate) mod private { /// - `out` buffer is longer than `S::EXPORT_SECRET_MAXLEN` when exporting secrets with [`Self::export_secret()`] /// - `exporter_context` is longer than 64 bytes /// - The internal counter reaches [`u64::MAX`] and a call to [`Self::seal()`]/[`Self::open()`] is made -/// - Calling [`Self::seal()`] when the role is `Role::Recipient` -/// - Calling [`Self::open()`] when the role is `Role::Sender` +/// - Calling [`Self::seal()`] when the role is [`Role::Recipient`] +/// - Calling [`Self::open()`] when the role is [`Role::Sender`] /// - Calling [`Self::open()`] on a set of messages that does not match the order of how they were [`Self::seal()`]'ed (re-ordering) /// - If a shared secret is all-zero. /// - If `ikm.len() < 32` when calling `derive_keypair()` on a suite's KEM. -/// -/// # Panics: -/// A panic will occur if: -/// - `getrandom` panics during [`Self::new_sender()`] or [`Self::base_seal()`]. +/// - `getrandom` errors during [`Self::new_sender()`] or [`Self::base_seal()`]. /// /// # Security: /// - When deriving a keypair deterministically instead of generating it randomly, the input `ikm` must have at least as much entropy @@ -308,18 +306,15 @@ impl ModeBase { /// - `out` buffer is longer than `S::EXPORT_SECRET_MAXLEN` when exporting secrets with [`Self::export_secret()`] /// - `exporter_context` is longer than 64 bytes /// - The internal counter reaches [`u64::MAX`] and a call to [`Self::seal()`]/[`Self::open()`] is made -/// - Calling [`Self::seal()`] when the role is `Role::Recipient` -/// - Calling [`Self::open()`] when the role is `Role::Sender` +/// - Calling [`Self::seal()`] when the role is [`Role::Recipient`] +/// - Calling [`Self::open()`] when the role is [`Role::Sender`] /// - Calling [`Self::open()`] on a set of messages that does not match the order of how they were [`Self::seal()`]'ed (re-ordering) /// - `psk` or `psk_id` are empty /// - `psk` is less than 32 bytes or more than 64 bytes /// - `psk_id` is more than 64 bytes /// - If a shared secret is all-zero. /// - If `ikm.len() < 32` when calling `derive_keypair()` on a suite's KEM. -/// -/// # Panics: -/// A panic will occur if: -/// - `getrandom` panics during [`Self::new_sender()`] or [`Self::psk_seal()`]. +/// - `getrandom` errors during [`Self::new_sender()`] or [`Self::psk_seal()`]. /// /// # Security: /// - When deriving a keypair deterministically instead of generating it randomly, the input `ikm` must have at least as much entropy @@ -525,15 +520,12 @@ impl ModePsk { /// - `out` buffer is longer than `S::EXPORT_SECRET_MAXLEN` when exporting secrets with [`Self::export_secret()`] /// - `exporter_context` is longer than 64 bytes /// - The internal counter reaches [`u64::MAX`] and a call to [`Self::seal()`]/[`Self::open()`] is made -/// - Calling [`Self::seal()`] when the role is `Role::Recipient` -/// - Calling [`Self::open()`] when the role is `Role::Sender` +/// - Calling [`Self::seal()`] when the role is [`Role::Recipient`] +/// - Calling [`Self::open()`] when the role is [`Role::Sender`] /// - Calling [`Self::open()`] on a set of messages that does not match the order of how they were [`Self::seal()`]'ed (re-ordering) /// - If a shared secret is all-zero. /// - If `ikm.len() < 32` when calling `derive_keypair()` on a suite's KEM. -/// -/// # Panics: -/// A panic will occur if: -/// - `getrandom` panics during [`Self::new_sender()`] or [`Self::auth_seal()`]. +/// - `getrandom` errors during [`Self::new_sender()`] or [`Self::auth_seal()`]. /// /// # Security: /// - When deriving a keypair deterministically instead of generating it randomly, the input `ikm` must have at least as much entropy @@ -733,18 +725,15 @@ impl ModeAuth { /// - `out` buffer is longer than `S::EXPORT_SECRET_MAXLEN` when exporting secrets with [`Self::export_secret()`] /// - `exporter_context` is longer than 64 bytes /// - The internal counter reaches [`u64::MAX`] and a call to [`Self::seal()`]/[`Self::open()`] is made -/// - Calling [`Self::seal()`] when the role is `Role::Recipient` -/// - Calling [`Self::open()`] when the role is `Role::Sender` +/// - Calling [`Self::seal()`] when the role is [`Role::Recipient`] +/// - Calling [`Self::open()`] when the role is [`Role::Sender`] /// - Calling [`Self::open()`] on a set of messages that does not match the order of how they were [`Self::seal()`]'ed (re-ordering) /// - `psk` or `psk_id` are empty /// - `psk` is less than 32 bytes or more than 64 bytes /// - `psk_id` is more than 64 bytes /// - If a shared secret is all-zero. /// - If `ikm.len() < 32` when calling `derive_keypair()` on a suite's KEM. -/// -/// # Panics: -/// A panic will occur if: -/// - `getrandom` panics during [`Self::new_sender()`] or [`Self::authpsk_seal()`]. +/// - `getrandom` errors during [`Self::new_sender()`] or [`Self::authpsk_seal()`]. /// /// # Security: /// - When deriving a keypair deterministically instead of generating it randomly, the input `ikm` must have at least as much entropy diff --git a/src/hazardous/hpke/suite.rs b/src/hazardous/hpke/suite.rs index 0f00fe668..063e680b8 100644 --- a/src/hazardous/hpke/suite.rs +++ b/src/hazardous/hpke/suite.rs @@ -201,6 +201,6 @@ pub(crate) mod private { /// fn export(&self, exporter_context: &[u8], out: &mut [u8]) - -> Result<(), UnknownCryptoError>; + -> Result<(), UnknownCryptoError>; } } diff --git a/src/hazardous/hpke/x25519_sha256_chacha20poly1305.rs b/src/hazardous/hpke/x25519_sha256_chacha20poly1305.rs index 172580d9e..07a4ca084 100644 --- a/src/hazardous/hpke/x25519_sha256_chacha20poly1305.rs +++ b/src/hazardous/hpke/x25519_sha256_chacha20poly1305.rs @@ -35,16 +35,16 @@ use crate::hazardous::kem::x25519_hkdf_sha256; /// # Note about serialized private keys for this suite /// RFC 9180 defines the format of X25519 serialized private keys as the clamped version. According to the standard, /// (de)serializing from/to a private key requires clamping input/output. This implementation adheres to this requirement, -/// and as such, calling [`unprotected_as_bytes()`] on the private key used with this suite will return its clamped version. +/// and as such, calling [`unprotected_as_ref()`] on the private key used with this suite will return its clamped version. /// /// The original RFC 9180 test vectors for this suite do on the contrary not include this clamping, so if someone were to compare -/// or otherwise use the output of [`unprotected_as_bytes()`], and expect it to be equal that of other implementations, it might not be. +/// or otherwise use the output of [`unprotected_as_ref()`], and expect it to be equal that of other implementations, it might not be. /// This does not affect interoperability in any other way, meaning HPKE data encrypted with Orion will still decrypt successfully with different /// HPKE implementations. /// /// The test-vector issues have been reported: , /// -/// [`unprotected_as_bytes()`]: crate::hazardous::kem::x25519_hkdf_sha256::PrivateKey::unprotected_as_bytes +/// [`unprotected_as_ref()`]: crate::hazardous::kem::x25519_hkdf_sha256::PrivateKey::unprotected_as_ref pub struct DHKEM_X25519_SHA256_CHACHA20 { key: [u8; 32], base_nonce: [u8; 12], @@ -68,8 +68,13 @@ impl Eq for DHKEM_X25519_SHA256_CHACHA20 {} impl core::fmt::Debug for DHKEM_X25519_SHA256_CHACHA20 { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{} key: {{***OMITTED***}}, base_nonce: {:?}, ctr: {:?}, exporter_secret: {{***OMITTED***}}", - stringify!(DHKEM_X25519_SHA256_CHACHA20), &self.base_nonce, self.ctr) + write!( + f, + "{} key: {{***OMITTED***}}, base_nonce: {:?}, ctr: {:?}, exporter_secret: {{***OMITTED***}}", + stringify!(DHKEM_X25519_SHA256_CHACHA20), + &self.base_nonce, + self.ctr + ) } } @@ -89,11 +94,6 @@ impl Psk for DHKEM_X25519_SHA256_CHACHA20 {} impl Auth for DHKEM_X25519_SHA256_CHACHA20 {} impl AuthPsk for DHKEM_X25519_SHA256_CHACHA20 {} -const fn key_schedule_ctx_size() -> usize { - // Two hashes and one mode id - (NK * 2) + 1 -} - impl DHKEM_X25519_SHA256_CHACHA20 { /// Size of the HPKE suite KEM ciphertext/encapsulated key. pub const KEM_CT_SIZE: usize = 32; // Equivalent to X25519 public key. @@ -202,7 +202,7 @@ impl Suite for DHKEM_X25519_SHA256_CHACHA20 { ], )?; - out[..SHA256_OUTSIZE].copy_from_slice(prk.unprotected_as_bytes()); + out[..SHA256_OUTSIZE].copy_from_slice(prk.unprotected_as_ref()); Ok(()) } @@ -245,7 +245,7 @@ impl Suite for DHKEM_X25519_SHA256_CHACHA20 { // NOTE: We hardcode NK here, is this an approach we want to keep? // key_schedule_context: [ mode || psk_id_hash || info_hash ] - let mut key_schedule_context = zeroize_wrap!([0u8; key_schedule_ctx_size::<32>()]); + let mut key_schedule_context = zeroize_wrap!([0u8; { (32 * 2) + 1 }]); key_schedule_context[0] = mode.mode_id(); Self::labeled_extract( b"", @@ -299,7 +299,7 @@ impl Suite for DHKEM_X25519_SHA256_CHACHA20 { Self::check_input_max_lengths(info)?; let (ss, enc) = x25519_hkdf_sha256::DhKem::encap(pubkey_r)?; - let ctx = Self::key_schedule(&HpkeMode::Base, ss.unprotected_as_bytes(), info, &[], &[])?; + let ctx = Self::key_schedule(&HpkeMode::Base, ss.unprotected_as_ref(), info, &[], &[])?; Ok((ctx, enc)) } @@ -312,7 +312,7 @@ impl Suite for DHKEM_X25519_SHA256_CHACHA20 { Self::check_input_max_lengths(info)?; let (ss, enc) = x25519_hkdf_sha256::DhKem::encap_deterministic(pubkey_r, secret_ephemeral)?; - let ctx = Self::key_schedule(&HpkeMode::Base, ss.unprotected_as_bytes(), info, &[], &[])?; + let ctx = Self::key_schedule(&HpkeMode::Base, ss.unprotected_as_ref(), info, &[], &[])?; Ok((ctx, enc)) } @@ -325,7 +325,7 @@ impl Suite for DHKEM_X25519_SHA256_CHACHA20 { Self::check_input_max_lengths(info)?; let ss = x25519_hkdf_sha256::DhKem::decap(enc, secret_key_r)?; - Self::key_schedule(&HpkeMode::Base, ss.unprotected_as_bytes(), info, &[], &[]) + Self::key_schedule(&HpkeMode::Base, ss.unprotected_as_ref(), info, &[], &[]) } #[cfg(feature = "safe_api")] @@ -339,7 +339,7 @@ impl Suite for DHKEM_X25519_SHA256_CHACHA20 { Self::check_input_max_lengths(info)?; let (ss, enc) = x25519_hkdf_sha256::DhKem::encap(pubkey_r)?; - let ctx = Self::key_schedule(&HpkeMode::Psk, ss.unprotected_as_bytes(), info, psk, psk_id)?; + let ctx = Self::key_schedule(&HpkeMode::Psk, ss.unprotected_as_ref(), info, psk, psk_id)?; Ok((ctx, enc)) } @@ -355,7 +355,7 @@ impl Suite for DHKEM_X25519_SHA256_CHACHA20 { Self::check_input_max_lengths(info)?; let (ss, enc) = x25519_hkdf_sha256::DhKem::encap_deterministic(pubkey_r, secret_ephemeral)?; - let ctx = Self::key_schedule(&HpkeMode::Psk, ss.unprotected_as_bytes(), info, psk, psk_id)?; + let ctx = Self::key_schedule(&HpkeMode::Psk, ss.unprotected_as_ref(), info, psk, psk_id)?; Ok((ctx, enc)) } @@ -371,7 +371,7 @@ impl Suite for DHKEM_X25519_SHA256_CHACHA20 { Self::check_input_max_lengths(info)?; let ss = x25519_hkdf_sha256::DhKem::decap(enc, secret_key_r)?; - Self::key_schedule(&HpkeMode::Psk, ss.unprotected_as_bytes(), info, psk, psk_id) + Self::key_schedule(&HpkeMode::Psk, ss.unprotected_as_ref(), info, psk, psk_id) } #[cfg(feature = "safe_api")] @@ -383,7 +383,7 @@ impl Suite for DHKEM_X25519_SHA256_CHACHA20 { Self::check_input_max_lengths(info)?; let (ss, enc) = x25519_hkdf_sha256::DhKem::auth_encap(pubkey_r, secrety_key_s)?; - let ctx = Self::key_schedule(&HpkeMode::Auth, ss.unprotected_as_bytes(), info, &[], &[])?; + let ctx = Self::key_schedule(&HpkeMode::Auth, ss.unprotected_as_ref(), info, &[], &[])?; Ok((ctx, enc)) } @@ -401,7 +401,7 @@ impl Suite for DHKEM_X25519_SHA256_CHACHA20 { secrety_key_s, secret_ephemeral, )?; - let ctx = Self::key_schedule(&HpkeMode::Auth, ss.unprotected_as_bytes(), info, &[], &[])?; + let ctx = Self::key_schedule(&HpkeMode::Auth, ss.unprotected_as_ref(), info, &[], &[])?; Ok((ctx, enc)) } @@ -415,7 +415,7 @@ impl Suite for DHKEM_X25519_SHA256_CHACHA20 { Self::check_input_max_lengths(info)?; let ss = x25519_hkdf_sha256::DhKem::auth_decap(enc, secret_key_r, pubkey_s)?; - Self::key_schedule(&HpkeMode::Auth, ss.unprotected_as_bytes(), info, &[], &[]) + Self::key_schedule(&HpkeMode::Auth, ss.unprotected_as_ref(), info, &[], &[]) } #[cfg(feature = "safe_api")] @@ -432,7 +432,7 @@ impl Suite for DHKEM_X25519_SHA256_CHACHA20 { let (ss, enc) = x25519_hkdf_sha256::DhKem::auth_encap(pubkey_r, secrety_key_s)?; let ctx = Self::key_schedule( &HpkeMode::AuthPsk, - ss.unprotected_as_bytes(), + ss.unprotected_as_ref(), info, psk, psk_id, @@ -459,7 +459,7 @@ impl Suite for DHKEM_X25519_SHA256_CHACHA20 { )?; let ctx = Self::key_schedule( &HpkeMode::AuthPsk, - ss.unprotected_as_bytes(), + ss.unprotected_as_ref(), info, psk, psk_id, @@ -482,7 +482,7 @@ impl Suite for DHKEM_X25519_SHA256_CHACHA20 { let ss = x25519_hkdf_sha256::DhKem::auth_decap(enc, secret_key_r, pubkey_s)?; Self::key_schedule( &HpkeMode::AuthPsk, - ss.unprotected_as_bytes(), + ss.unprotected_as_ref(), info, psk, psk_id, @@ -597,51 +597,59 @@ mod test { fn test_error_on_lengths_psk() { let (sk, pk) = DhKem::derive_keypair(&[0u8; 64]).unwrap(); // Info - assert!(DHKEM_X25519_SHA256_CHACHA20::setup_psk_sender( - &pk, &[0u8; 65], &[0u8; 64], b"psk_id" - ) - .is_err()); - assert!(DHKEM_X25519_SHA256_CHACHA20::setup_psk_sender( - &pk, &[0u8; 64], &[0u8; 64], b"psk_id" - ) - .is_ok()); + assert!( + DHKEM_X25519_SHA256_CHACHA20::setup_psk_sender(&pk, &[0u8; 65], &[0u8; 64], b"psk_id") + .is_err() + ); + assert!( + DHKEM_X25519_SHA256_CHACHA20::setup_psk_sender(&pk, &[0u8; 64], &[0u8; 64], b"psk_id") + .is_ok() + ); // PSK - assert!(DHKEM_X25519_SHA256_CHACHA20::setup_psk_sender( - &pk, &[0u8; 64], &[0u8; 65], b"psk_id" - ) - .is_err()); - assert!(DHKEM_X25519_SHA256_CHACHA20::setup_psk_sender( - &pk, &[0u8; 64], &[0u8; 31], b"psk_id" - ) - .is_err()); - assert!(DHKEM_X25519_SHA256_CHACHA20::setup_psk_sender( - &pk, &[0u8; 64], &[0u8; 32], b"psk_id" - ) - .is_ok()); - assert!(DHKEM_X25519_SHA256_CHACHA20::setup_psk_sender( - &pk, &[0u8; 64], &[0u8; 64], b"psk_id" - ) - .is_ok()); + assert!( + DHKEM_X25519_SHA256_CHACHA20::setup_psk_sender(&pk, &[0u8; 64], &[0u8; 65], b"psk_id") + .is_err() + ); + assert!( + DHKEM_X25519_SHA256_CHACHA20::setup_psk_sender(&pk, &[0u8; 64], &[0u8; 31], b"psk_id") + .is_err() + ); + assert!( + DHKEM_X25519_SHA256_CHACHA20::setup_psk_sender(&pk, &[0u8; 64], &[0u8; 32], b"psk_id") + .is_ok() + ); + assert!( + DHKEM_X25519_SHA256_CHACHA20::setup_psk_sender(&pk, &[0u8; 64], &[0u8; 64], b"psk_id") + .is_ok() + ); let (ctx, enc) = DHKEM_X25519_SHA256_CHACHA20::setup_psk_sender(&pk, &[0u8; 64], &[0u8; 64], b"psk_id") .unwrap(); - assert!(DHKEM_X25519_SHA256_CHACHA20::setup_psk_recipient( - &enc, &sk, &[0u8; 64], &[0u8; 31], b"psk_id" - ) - .is_err()); - assert!(DHKEM_X25519_SHA256_CHACHA20::setup_psk_recipient( - &enc, &sk, &[0u8; 64], &[0u8; 65], b"psk_id" - ) - .is_err()); - assert!(DHKEM_X25519_SHA256_CHACHA20::setup_psk_recipient( - &enc, &sk, &[0u8; 64], &[0u8; 32], b"psk_id" - ) - .is_ok()); - assert!(DHKEM_X25519_SHA256_CHACHA20::setup_psk_recipient( - &enc, &sk, &[0u8; 64], &[0u8; 64], b"psk_id" - ) - .is_ok()); + assert!( + DHKEM_X25519_SHA256_CHACHA20::setup_psk_recipient( + &enc, &sk, &[0u8; 64], &[0u8; 31], b"psk_id" + ) + .is_err() + ); + assert!( + DHKEM_X25519_SHA256_CHACHA20::setup_psk_recipient( + &enc, &sk, &[0u8; 64], &[0u8; 65], b"psk_id" + ) + .is_err() + ); + assert!( + DHKEM_X25519_SHA256_CHACHA20::setup_psk_recipient( + &enc, &sk, &[0u8; 64], &[0u8; 32], b"psk_id" + ) + .is_ok() + ); + assert!( + DHKEM_X25519_SHA256_CHACHA20::setup_psk_recipient( + &enc, &sk, &[0u8; 64], &[0u8; 64], b"psk_id" + ) + .is_ok() + ); // Export let mut out = [0u8; 64]; @@ -678,52 +686,72 @@ mod test { fn test_error_on_lengths_authpsk() { let (sk, pk) = DhKem::derive_keypair(&[0u8; 64]).unwrap(); // Info - assert!(DHKEM_X25519_SHA256_CHACHA20::setup_authpsk_sender( - &pk, &[0u8; 65], &[0u8; 64], b"psk_id", &sk - ) - .is_err()); - assert!(DHKEM_X25519_SHA256_CHACHA20::setup_authpsk_sender( - &pk, &[0u8; 64], &[0u8; 64], b"psk_id", &sk - ) - .is_ok()); + assert!( + DHKEM_X25519_SHA256_CHACHA20::setup_authpsk_sender( + &pk, &[0u8; 65], &[0u8; 64], b"psk_id", &sk + ) + .is_err() + ); + assert!( + DHKEM_X25519_SHA256_CHACHA20::setup_authpsk_sender( + &pk, &[0u8; 64], &[0u8; 64], b"psk_id", &sk + ) + .is_ok() + ); // PSK - assert!(DHKEM_X25519_SHA256_CHACHA20::setup_authpsk_sender( - &pk, &[0u8; 64], &[0u8; 65], b"psk_id", &sk - ) - .is_err()); - assert!(DHKEM_X25519_SHA256_CHACHA20::setup_authpsk_sender( - &pk, &[0u8; 64], &[0u8; 31], b"psk_id", &sk - ) - .is_err()); - assert!(DHKEM_X25519_SHA256_CHACHA20::setup_authpsk_sender( - &pk, &[0u8; 64], &[0u8; 32], b"psk_id", &sk - ) - .is_ok()); - assert!(DHKEM_X25519_SHA256_CHACHA20::setup_authpsk_sender( - &pk, &[0u8; 64], &[0u8; 64], b"psk_id", &sk - ) - .is_ok()); + assert!( + DHKEM_X25519_SHA256_CHACHA20::setup_authpsk_sender( + &pk, &[0u8; 64], &[0u8; 65], b"psk_id", &sk + ) + .is_err() + ); + assert!( + DHKEM_X25519_SHA256_CHACHA20::setup_authpsk_sender( + &pk, &[0u8; 64], &[0u8; 31], b"psk_id", &sk + ) + .is_err() + ); + assert!( + DHKEM_X25519_SHA256_CHACHA20::setup_authpsk_sender( + &pk, &[0u8; 64], &[0u8; 32], b"psk_id", &sk + ) + .is_ok() + ); + assert!( + DHKEM_X25519_SHA256_CHACHA20::setup_authpsk_sender( + &pk, &[0u8; 64], &[0u8; 64], b"psk_id", &sk + ) + .is_ok() + ); let (ctx, enc) = DHKEM_X25519_SHA256_CHACHA20::setup_authpsk_sender( &pk, &[0u8; 64], &[0u8; 64], b"psk_id", &sk, ) .unwrap(); - assert!(DHKEM_X25519_SHA256_CHACHA20::setup_authpsk_recipient( - &enc, &sk, &[0u8; 64], &[0u8; 31], b"psk_id", &pk - ) - .is_err()); - assert!(DHKEM_X25519_SHA256_CHACHA20::setup_authpsk_recipient( - &enc, &sk, &[0u8; 64], &[0u8; 65], b"psk_id", &pk - ) - .is_err()); - assert!(DHKEM_X25519_SHA256_CHACHA20::setup_authpsk_recipient( - &enc, &sk, &[0u8; 64], &[0u8; 32], b"psk_id", &pk - ) - .is_ok()); - assert!(DHKEM_X25519_SHA256_CHACHA20::setup_authpsk_recipient( - &enc, &sk, &[0u8; 64], &[0u8; 64], b"psk_id", &pk - ) - .is_ok()); + assert!( + DHKEM_X25519_SHA256_CHACHA20::setup_authpsk_recipient( + &enc, &sk, &[0u8; 64], &[0u8; 31], b"psk_id", &pk + ) + .is_err() + ); + assert!( + DHKEM_X25519_SHA256_CHACHA20::setup_authpsk_recipient( + &enc, &sk, &[0u8; 64], &[0u8; 65], b"psk_id", &pk + ) + .is_err() + ); + assert!( + DHKEM_X25519_SHA256_CHACHA20::setup_authpsk_recipient( + &enc, &sk, &[0u8; 64], &[0u8; 32], b"psk_id", &pk + ) + .is_ok() + ); + assert!( + DHKEM_X25519_SHA256_CHACHA20::setup_authpsk_recipient( + &enc, &sk, &[0u8; 64], &[0u8; 64], b"psk_id", &pk + ) + .is_ok() + ); // Export let mut out = [0u8; 64]; @@ -770,7 +798,7 @@ mod test { fn gen_kp(seed: &[u8]) -> Result<(Vec, Vec), UnknownCryptoError> { let (sk, pk) = DhKem::derive_keypair(seed)?; - Ok((sk.unprotected_as_bytes().to_vec(), pk.to_bytes().to_vec())) + Ok((sk.unprotected_as_ref().to_vec(), pk.as_ref().to_vec())) } fn setup_fresh_sender( @@ -784,9 +812,9 @@ mod test { where Self: Sized, { - let pubkey_r = PublicKey::from_slice(pubkey_r)?; + let pubkey_r = PublicKey::try_from(pubkey_r)?; let (ctx, enc) = ModeBase::::new_sender(&pubkey_r, info)?; - public_ct_out.copy_from_slice(&enc.to_bytes()); + public_ct_out.copy_from_slice(enc.as_ref()); Ok(ctx) } @@ -802,8 +830,8 @@ mod test { where Self: Sized, { - let enc = PublicKey::from_slice(enc)?; - let secret_key_r = PrivateKey::from_slice(secret_key_r)?; + let enc = PublicKey::try_from(enc)?; + let secret_key_r = PrivateKey::try_from(secret_key_r)?; ModeBase::::new_recipient(&enc, &secret_key_r, info) } @@ -838,7 +866,7 @@ mod test { plaintext: &[u8], aad: &[u8], ) -> Result<(Vec, Vec), UnknownCryptoError> { - let pubkey_r = PublicKey::from_slice(pubkey_r)?; + let pubkey_r = PublicKey::try_from(pubkey_r)?; let mut dst_kem_out = vec![0u8; 32]; let mut dst_out = vec![0u8; plaintext.len() + 16]; let enc = ModeBase::::base_seal( @@ -848,7 +876,7 @@ mod test { aad, &mut dst_out, )?; - dst_kem_out.copy_from_slice(&enc.to_bytes()); + dst_kem_out.copy_from_slice(enc.as_ref()); Ok((dst_kem_out, dst_out)) } @@ -863,8 +891,8 @@ mod test { ciphertext: &[u8], aad: &[u8], ) -> Result, UnknownCryptoError> { - let enc = PublicKey::from_slice(enc)?; - let secret_key_r = PrivateKey::from_slice(secret_key_r)?; + let enc = PublicKey::try_from(enc)?; + let secret_key_r = PrivateKey::try_from(secret_key_r)?; let mut dst_out = vec![0u8; ciphertext.len() - 16]; ModeBase::::base_open( &enc, @@ -888,7 +916,7 @@ mod test { fn gen_kp(seed: &[u8]) -> Result<(Vec, Vec), UnknownCryptoError> { let (sk, pk) = DhKem::derive_keypair(seed)?; - Ok((sk.unprotected_as_bytes().to_vec(), pk.to_bytes().to_vec())) + Ok((sk.unprotected_as_ref().to_vec(), pk.as_ref().to_vec())) } fn setup_fresh_sender( @@ -902,10 +930,10 @@ mod test { where Self: Sized, { - let pubkey_r = PublicKey::from_slice(pubkey_r)?; + let pubkey_r = PublicKey::try_from(pubkey_r)?; let (ctx, enc) = ModePsk::::new_sender(&pubkey_r, info, psk, psk_id)?; - public_ct_out.copy_from_slice(&enc.to_bytes()); + public_ct_out.copy_from_slice(enc.as_ref()); Ok(ctx) } @@ -921,8 +949,8 @@ mod test { where Self: Sized, { - let enc = PublicKey::from_slice(enc)?; - let secret_key_r = PrivateKey::from_slice(secret_key_r)?; + let enc = PublicKey::try_from(enc)?; + let secret_key_r = PrivateKey::try_from(secret_key_r)?; ModePsk::::new_recipient( &enc, &secret_key_r, @@ -963,7 +991,7 @@ mod test { plaintext: &[u8], aad: &[u8], ) -> Result<(Vec, Vec), UnknownCryptoError> { - let pubkey_r = PublicKey::from_slice(pubkey_r)?; + let pubkey_r = PublicKey::try_from(pubkey_r)?; let mut dst_kem_out = vec![0u8; 32]; let mut dst_out = vec![0u8; plaintext.len() + 16]; let enc = ModePsk::::psk_seal( @@ -975,7 +1003,7 @@ mod test { aad, &mut dst_out, )?; - dst_kem_out.copy_from_slice(&enc.to_bytes()); + dst_kem_out.copy_from_slice(enc.as_ref()); Ok((dst_kem_out, dst_out)) } @@ -990,8 +1018,8 @@ mod test { ciphertext: &[u8], aad: &[u8], ) -> Result, UnknownCryptoError> { - let enc = PublicKey::from_slice(enc)?; - let secret_key_r = PrivateKey::from_slice(secret_key_r)?; + let enc = PublicKey::try_from(enc)?; + let secret_key_r = PrivateKey::try_from(secret_key_r)?; let mut dst_out = vec![0u8; ciphertext.len() - 16]; ModePsk::::psk_open( &enc, @@ -1017,7 +1045,7 @@ mod test { fn gen_kp(seed: &[u8]) -> Result<(Vec, Vec), UnknownCryptoError> { let (sk, pk) = DhKem::derive_keypair(seed)?; - Ok((sk.unprotected_as_bytes().to_vec(), pk.to_bytes().to_vec())) + Ok((sk.unprotected_as_ref().to_vec(), pk.as_ref().to_vec())) } fn setup_fresh_sender( @@ -1031,14 +1059,14 @@ mod test { where Self: Sized, { - let secret_key_s = PrivateKey::from_slice(secret_key_s)?; - let pubkey_r = PublicKey::from_slice(pubkey_r)?; + let secret_key_s = PrivateKey::try_from(secret_key_s)?; + let pubkey_r = PublicKey::try_from(pubkey_r)?; let (ctx, enc) = ModeAuth::::new_sender( &pubkey_r, info, &secret_key_s, )?; - public_ct_out.copy_from_slice(&enc.to_bytes()); + public_ct_out.copy_from_slice(enc.as_ref()); Ok(ctx) } @@ -1054,9 +1082,9 @@ mod test { where Self: Sized, { - let enc = PublicKey::from_slice(enc)?; - let secret_key_r = PrivateKey::from_slice(secret_key_r)?; - let pubkey_s = PublicKey::from_slice(pubkey_s)?; + let enc = PublicKey::try_from(enc)?; + let secret_key_r = PrivateKey::try_from(secret_key_r)?; + let pubkey_s = PublicKey::try_from(pubkey_s)?; ModeAuth::::new_recipient( &enc, &secret_key_r, @@ -1096,8 +1124,8 @@ mod test { plaintext: &[u8], aad: &[u8], ) -> Result<(Vec, Vec), UnknownCryptoError> { - let secret_key_s = PrivateKey::from_slice(secret_key_s)?; - let pubkey_r = PublicKey::from_slice(pubkey_r)?; + let secret_key_s = PrivateKey::try_from(secret_key_s)?; + let pubkey_r = PublicKey::try_from(pubkey_r)?; let mut dst_kem_out = vec![0u8; 32]; let mut dst_out = vec![0u8; plaintext.len() + 16]; let enc = ModeAuth::::auth_seal( @@ -1108,7 +1136,7 @@ mod test { aad, &mut dst_out, )?; - dst_kem_out.copy_from_slice(&enc.to_bytes()); + dst_kem_out.copy_from_slice(enc.as_ref()); Ok((dst_kem_out, dst_out)) } @@ -1123,9 +1151,9 @@ mod test { ciphertext: &[u8], aad: &[u8], ) -> Result, UnknownCryptoError> { - let enc = PublicKey::from_slice(enc)?; - let secret_key_r = PrivateKey::from_slice(secret_key_r)?; - let pubkey_s = PublicKey::from_slice(pubkey_s)?; + let enc = PublicKey::try_from(enc)?; + let secret_key_r = PrivateKey::try_from(secret_key_r)?; + let pubkey_s = PublicKey::try_from(pubkey_s)?; let mut dst_out = vec![0u8; ciphertext.len() - 16]; ModeAuth::::auth_open( &enc, @@ -1150,7 +1178,7 @@ mod test { fn gen_kp(seed: &[u8]) -> Result<(Vec, Vec), UnknownCryptoError> { let (sk, pk) = DhKem::derive_keypair(seed)?; - Ok((sk.unprotected_as_bytes().to_vec(), pk.to_bytes().to_vec())) + Ok((sk.unprotected_as_ref().to_vec(), pk.as_ref().to_vec())) } fn setup_fresh_sender( @@ -1164,8 +1192,8 @@ mod test { where Self: Sized, { - let secret_key_s = PrivateKey::from_slice(secret_key_s)?; - let pubkey_r = PublicKey::from_slice(pubkey_r)?; + let secret_key_s = PrivateKey::try_from(secret_key_s)?; + let pubkey_r = PublicKey::try_from(pubkey_r)?; let (ctx, enc) = ModeAuthPsk::::new_sender( &pubkey_r, info, @@ -1173,7 +1201,7 @@ mod test { psk_id, &secret_key_s, )?; - public_ct_out.copy_from_slice(&enc.to_bytes()); + public_ct_out.copy_from_slice(enc.as_ref()); Ok(ctx) } @@ -1189,9 +1217,9 @@ mod test { where Self: Sized, { - let enc = PublicKey::from_slice(enc)?; - let secret_key_r = PrivateKey::from_slice(secret_key_r)?; - let pubkey_s = PublicKey::from_slice(pubkey_s)?; + let enc = PublicKey::try_from(enc)?; + let secret_key_r = PrivateKey::try_from(secret_key_r)?; + let pubkey_s = PublicKey::try_from(pubkey_s)?; ModeAuthPsk::::new_recipient( &enc, &secret_key_r, @@ -1233,8 +1261,8 @@ mod test { plaintext: &[u8], aad: &[u8], ) -> Result<(Vec, Vec), UnknownCryptoError> { - let secret_key_s = PrivateKey::from_slice(secret_key_s)?; - let pubkey_r = PublicKey::from_slice(pubkey_r)?; + let secret_key_s = PrivateKey::try_from(secret_key_s)?; + let pubkey_r = PublicKey::try_from(pubkey_r)?; let mut dst_kem_out = vec![0u8; 32]; let mut dst_out = vec![0u8; plaintext.len() + 16]; let enc = ModeAuthPsk::::authpsk_seal( @@ -1247,7 +1275,7 @@ mod test { aad, &mut dst_out, )?; - dst_kem_out.copy_from_slice(&enc.to_bytes()); + dst_kem_out.copy_from_slice(enc.as_ref()); Ok((dst_kem_out, dst_out)) } @@ -1262,9 +1290,9 @@ mod test { ciphertext: &[u8], aad: &[u8], ) -> Result, UnknownCryptoError> { - let enc = PublicKey::from_slice(enc)?; - let secret_key_r = PrivateKey::from_slice(secret_key_r)?; - let pubkey_s = PublicKey::from_slice(pubkey_s)?; + let enc = PublicKey::try_from(enc)?; + let secret_key_r = PrivateKey::try_from(secret_key_r)?; + let pubkey_s = PublicKey::try_from(pubkey_s)?; let mut dst_out = vec![0u8; ciphertext.len() - 16]; ModeAuthPsk::::authpsk_open( &enc, diff --git a/src/hazardous/kdf/argon2i.rs b/src/hazardous/kdf/argon2i.rs index d0b043ace..8d36a5efc 100644 --- a/src/hazardous/kdf/argon2i.rs +++ b/src/hazardous/kdf/argon2i.rs @@ -48,9 +48,6 @@ //! - `memory` is less than `8`. //! - The hashed password does not match the expected when verifying. //! -//! # Panics: -//! A panic will occur if: -//! //! # Security: //! - Salts should always be generated using a CSPRNG. //! [`secure_rand_bytes()`] can be used for this. diff --git a/src/hazardous/kdf/hkdf.rs b/src/hazardous/kdf/hkdf.rs index f21564937..fa846650a 100644 --- a/src/hazardous/kdf/hkdf.rs +++ b/src/hazardous/kdf/hkdf.rs @@ -247,7 +247,7 @@ pub mod sha256 { dst_out: &mut [u8], ) -> Result<(), UnknownCryptoError> { _expand::( - prk.unprotected_as_bytes(), + prk.unprotected_as_ref(), info, dst_out, ) @@ -331,7 +331,7 @@ pub mod sha384 { dst_out: &mut [u8], ) -> Result<(), UnknownCryptoError> { _expand::( - prk.unprotected_as_bytes(), + prk.unprotected_as_ref(), info, dst_out, ) @@ -407,7 +407,7 @@ pub mod sha512 { dst_out: &mut [u8], ) -> Result<(), UnknownCryptoError> { _expand::( - prk.unprotected_as_bytes(), + prk.unprotected_as_ref(), info, dst_out, ) @@ -473,16 +473,40 @@ mod public { let mut okm_out = [0u8; 255 * SHA256_OUTSIZE + 1]; let prk = sha256::extract(b"", b"").unwrap(); assert!(sha256::expand(&prk, Some(b""), &mut okm_out).is_err()); + assert!( + _expand::( + prk.unprotected_as_ref(), + Some(&[]), + &mut okm_out + ) + .is_err() + ); assert!(sha256::derive_key(b"", b"", Some(b""), &mut okm_out).is_err()); let mut okm_out = [0u8; 255 * SHA384_OUTSIZE + 1]; let prk = sha384::extract(b"", b"").unwrap(); assert!(sha384::expand(&prk, Some(b""), &mut okm_out).is_err()); + assert!( + _expand::( + prk.unprotected_as_ref(), + Some(&[]), + &mut okm_out + ) + .is_err() + ); assert!(sha384::derive_key(b"", b"", Some(b""), &mut okm_out).is_err()); let mut okm_out = [0u8; 255 * SHA512_OUTSIZE + 1]; let prk = sha512::extract(b"", b"").unwrap(); assert!(sha512::expand(&prk, Some(b""), &mut okm_out).is_err()); + assert!( + _expand::( + prk.unprotected_as_ref(), + Some(&[]), + &mut okm_out + ) + .is_err() + ); assert!(sha512::derive_key(b"", b"", Some(b""), &mut okm_out).is_err()); } @@ -491,16 +515,41 @@ mod public { let mut okm_out = [0u8; 255 * SHA256_OUTSIZE]; let prk = sha256::extract(b"", b"").unwrap(); assert!(sha256::expand(&prk, Some(b""), &mut okm_out).is_ok()); + assert!( + _expand::( + prk.unprotected_as_ref(), + Some(&[]), + &mut okm_out + ) + .is_ok() + ); assert!(sha256::derive_key(b"", b"", Some(b""), &mut okm_out).is_ok()); let mut okm_out = [0u8; 255 * SHA384_OUTSIZE]; let prk = sha384::extract(b"", b"").unwrap(); assert!(sha384::expand(&prk, Some(b""), &mut okm_out).is_ok()); + assert!( + _expand::( + prk.unprotected_as_ref(), + Some(&[]), + &mut okm_out + ) + .is_ok() + ); + assert!(sha384::derive_key(b"", b"", Some(b""), &mut okm_out).is_ok()); let mut okm_out = [0u8; 255 * SHA512_OUTSIZE]; let prk = sha512::extract(b"", b"").unwrap(); assert!(sha512::expand(&prk, Some(b""), &mut okm_out).is_ok()); + assert!( + _expand::( + prk.unprotected_as_ref(), + Some(&[]), + &mut okm_out + ) + .is_ok() + ); assert!(sha512::derive_key(b"", b"", Some(b""), &mut okm_out).is_ok()); } @@ -510,14 +559,41 @@ mod public { let prk = sha256::extract(b"", b"").unwrap(); assert!(sha256::expand(&prk, Some(b""), &mut okm_out).is_err()); + assert!( + _expand::( + prk.unprotected_as_ref(), + Some(&[]), + &mut okm_out + ) + .is_err() + ); + assert!(sha256::derive_key(b"", b"", Some(b""), &mut okm_out).is_err()); let prk = sha384::extract(b"", b"").unwrap(); assert!(sha384::expand(&prk, Some(b""), &mut okm_out).is_err()); + assert!( + _expand::( + prk.unprotected_as_ref(), + Some(&[]), + &mut okm_out + ) + .is_err() + ); + assert!(sha384::derive_key(b"", b"", Some(b""), &mut okm_out).is_err()); let prk = sha512::extract(b"", b"").unwrap(); assert!(sha512::expand(&prk, Some(b""), &mut okm_out).is_err()); + assert!( + _expand::( + prk.unprotected_as_ref(), + Some(&[]), + &mut okm_out + ) + .is_err() + ); + assert!(sha512::derive_key(b"", b"", Some(b""), &mut okm_out).is_err()); } diff --git a/src/hazardous/kdf/pbkdf2.rs b/src/hazardous/kdf/pbkdf2.rs index 0f80867ba..71813b87b 100644 --- a/src/hazardous/kdf/pbkdf2.rs +++ b/src/hazardous/kdf/pbkdf2.rs @@ -58,7 +58,7 @@ //! //! let mut salt = [0u8; 64]; //! util::secure_rand_bytes(&mut salt)?; -//! let password = pbkdf2::sha512::Password::from_slice("Secret password".as_bytes())?; +//! let password = pbkdf2::sha512::Password::try_from("Secret password".as_bytes())?; //! let mut dst_out = [0u8; 64]; //! //! pbkdf2::sha512::derive_key(&password, &salt, 10000, &mut dst_out)?; @@ -172,24 +172,8 @@ where /// PBKDF2-HMAC-SHA256 (Password-Based Key Derivation Function 2) as specified in the [RFC 8018](https://tools.ietf.org/html/rfc8018). pub mod sha256 { use super::*; - use crate::hazardous::hash::sha2::sha256::{self, Sha256}; - - construct_hmac_key! { - /// A type to represent the `Password` that PBKDF2 hashes. - /// - /// # Note: - /// Because `Password` is used as a `SecretKey` for HMAC during hashing, `Password` already - /// pads the given password to a length of 64, for use in HMAC, when initialized. - /// - /// Using `unprotected_as_bytes()` will return the password with padding. - /// - /// Using `get_length()` will return the length with padding (always 64). - /// - /// # Panics: - /// A panic will occur if: - /// - Failure to generate random bytes securely. - (Password, Sha256, sha256::SHA256_OUTSIZE, test_pbkdf2_password, sha256::SHA256_BLOCKSIZE) - } + use crate::hazardous::hash::sha2::sha256; + pub use crate::hazardous::mac::hmac::sha256::SecretKey as Password; #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."] /// Derive a key using PBKDF2-HMAC-SHA256. @@ -200,7 +184,7 @@ pub mod sha256 { dst_out: &mut [u8], ) -> Result<(), UnknownCryptoError> { _derive_key::( - password.unprotected_as_bytes(), + password.unprotected_as_ref(), salt, iterations, dst_out, @@ -218,7 +202,7 @@ pub mod sha256 { ) -> Result<(), UnknownCryptoError> { _verify::( expected, - password.unprotected_as_bytes(), + password.unprotected_as_ref(), salt, iterations, dst_out, @@ -229,24 +213,8 @@ pub mod sha256 { /// PBKDF2-HMAC-SHA384 (Password-Based Key Derivation Function 2) as specified in the [RFC 8018](https://tools.ietf.org/html/rfc8018). pub mod sha384 { use super::*; - use crate::hazardous::hash::sha2::sha384::{self, Sha384}; - - construct_hmac_key! { - /// A type to represent the `Password` that PBKDF2 hashes. - /// - /// # Note: - /// Because `Password` is used as a `SecretKey` for HMAC during hashing, `Password` already - /// pads the given password to a length of 128, for use in HMAC, when initialized. - /// - /// Using `unprotected_as_bytes()` will return the password with padding. - /// - /// Using `get_length()` will return the length with padding (always 128). - /// - /// # Panics: - /// A panic will occur if: - /// - Failure to generate random bytes securely. - (Password, Sha384, sha384::SHA384_OUTSIZE, test_pbkdf2_password, sha384::SHA384_BLOCKSIZE) - } + use crate::hazardous::hash::sha2::sha384; + pub use crate::hazardous::mac::hmac::sha384::SecretKey as Password; #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."] /// Derive a key using PBKDF2-HMAC-SHA384. @@ -257,7 +225,7 @@ pub mod sha384 { dst_out: &mut [u8], ) -> Result<(), UnknownCryptoError> { _derive_key::( - password.unprotected_as_bytes(), + password.unprotected_as_ref(), salt, iterations, dst_out, @@ -275,7 +243,7 @@ pub mod sha384 { ) -> Result<(), UnknownCryptoError> { _verify::( expected, - password.unprotected_as_bytes(), + password.unprotected_as_ref(), salt, iterations, dst_out, @@ -286,24 +254,8 @@ pub mod sha384 { /// PBKDF2-HMAC-SHA512 (Password-Based Key Derivation Function 2) as specified in the [RFC 8018](https://tools.ietf.org/html/rfc8018). pub mod sha512 { use super::*; - use crate::hazardous::hash::sha2::sha512::{self, Sha512}; - - construct_hmac_key! { - /// A type to represent the `Password` that PBKDF2 hashes. - /// - /// # Note: - /// Because `Password` is used as a `SecretKey` for HMAC during hashing, `Password` already - /// pads the given password to a length of 128, for use in HMAC, when initialized. - /// - /// Using `unprotected_as_bytes()` will return the password with padding. - /// - /// Using `get_length()` will return the length with padding (always 128). - /// - /// # Panics: - /// A panic will occur if: - /// - Failure to generate random bytes securely. - (Password, Sha512, sha512::SHA512_OUTSIZE, test_pbkdf2_password, sha512::SHA512_BLOCKSIZE) - } + use crate::hazardous::hash::sha2::sha512; + pub use crate::hazardous::mac::hmac::sha512::SecretKey as Password; #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."] /// Derive a key using PBKDF2-HMAC-SHA512. @@ -314,7 +266,7 @@ pub mod sha512 { dst_out: &mut [u8], ) -> Result<(), UnknownCryptoError> { _derive_key::( - password.unprotected_as_bytes(), + password.unprotected_as_ref(), salt, iterations, dst_out, @@ -332,7 +284,7 @@ pub mod sha512 { ) -> Result<(), UnknownCryptoError> { _verify::( expected, - password.unprotected_as_bytes(), + password.unprotected_as_ref(), salt, iterations, dst_out, @@ -350,9 +302,9 @@ mod public { #[test] fn verify_true() { - let password_256 = sha256::Password::from_slice("pass\0word".as_bytes()).unwrap(); - let password_384 = sha384::Password::from_slice("pass\0word".as_bytes()).unwrap(); - let password_512 = sha512::Password::from_slice("pass\0word".as_bytes()).unwrap(); + let password_256 = sha256::Password::try_from("pass\0word".as_bytes()).unwrap(); + let password_384 = sha384::Password::try_from("pass\0word".as_bytes()).unwrap(); + let password_512 = sha512::Password::try_from("pass\0word".as_bytes()).unwrap(); let salt = "sa\0lt".as_bytes(); let iterations: usize = 128; @@ -360,41 +312,47 @@ mod public { let mut okm_out_verify = [0u8; 16]; sha256::derive_key(&password_256, salt, iterations, &mut okm_out).unwrap(); - assert!(sha256::verify( - &okm_out, - &password_256, - salt, - iterations, - &mut okm_out_verify - ) - .is_ok()); + assert!( + sha256::verify( + &okm_out, + &password_256, + salt, + iterations, + &mut okm_out_verify + ) + .is_ok() + ); sha384::derive_key(&password_384, salt, iterations, &mut okm_out).unwrap(); - assert!(sha384::verify( - &okm_out, - &password_384, - salt, - iterations, - &mut okm_out_verify - ) - .is_ok()); + assert!( + sha384::verify( + &okm_out, + &password_384, + salt, + iterations, + &mut okm_out_verify + ) + .is_ok() + ); sha512::derive_key(&password_512, salt, iterations, &mut okm_out).unwrap(); - assert!(sha512::verify( - &okm_out, - &password_512, - salt, - iterations, - &mut okm_out_verify - ) - .is_ok()); + assert!( + sha512::verify( + &okm_out, + &password_512, + salt, + iterations, + &mut okm_out_verify + ) + .is_ok() + ); } #[test] fn verify_false_wrong_salt() { - let password_256 = sha256::Password::from_slice("pass\0word".as_bytes()).unwrap(); - let password_384 = sha384::Password::from_slice("pass\0word".as_bytes()).unwrap(); - let password_512 = sha512::Password::from_slice("pass\0word".as_bytes()).unwrap(); + let password_256 = sha256::Password::try_from("pass\0word".as_bytes()).unwrap(); + let password_384 = sha384::Password::try_from("pass\0word".as_bytes()).unwrap(); + let password_512 = sha512::Password::try_from("pass\0word".as_bytes()).unwrap(); let salt = "sa\0lt".as_bytes(); let iterations: usize = 128; @@ -402,40 +360,46 @@ mod public { let mut okm_out_verify = [0u8; 16]; sha256::derive_key(&password_256, salt, iterations, &mut okm_out).unwrap(); - assert!(sha256::verify( - &okm_out, - &password_256, - b"", - iterations, - &mut okm_out_verify - ) - .is_err()); + assert!( + sha256::verify( + &okm_out, + &password_256, + b"", + iterations, + &mut okm_out_verify + ) + .is_err() + ); sha384::derive_key(&password_384, salt, iterations, &mut okm_out).unwrap(); - assert!(sha384::verify( - &okm_out, - &password_384, - b"", - iterations, - &mut okm_out_verify - ) - .is_err()); + assert!( + sha384::verify( + &okm_out, + &password_384, + b"", + iterations, + &mut okm_out_verify + ) + .is_err() + ); sha512::derive_key(&password_512, salt, iterations, &mut okm_out).unwrap(); - assert!(sha512::verify( - &okm_out, - &password_512, - b"", - iterations, - &mut okm_out_verify - ) - .is_err()); + assert!( + sha512::verify( + &okm_out, + &password_512, + b"", + iterations, + &mut okm_out_verify + ) + .is_err() + ); } #[test] fn verify_false_wrong_password() { - let password_256 = sha256::Password::from_slice("pass\0word".as_bytes()).unwrap(); - let password_384 = sha384::Password::from_slice("pass\0word".as_bytes()).unwrap(); - let password_512 = sha512::Password::from_slice("pass\0word".as_bytes()).unwrap(); + let password_256 = sha256::Password::try_from("pass\0word".as_bytes()).unwrap(); + let password_384 = sha384::Password::try_from("pass\0word".as_bytes()).unwrap(); + let password_512 = sha512::Password::try_from("pass\0word".as_bytes()).unwrap(); let salt = "sa\0lt".as_bytes(); let iterations: usize = 128; @@ -443,41 +407,47 @@ mod public { let mut okm_out_verify = [0u8; 16]; sha256::derive_key(&password_256, salt, iterations, &mut okm_out).unwrap(); - assert!(sha256::verify( - &okm_out, - &sha256::Password::from_slice(b"pass").unwrap(), - salt, - iterations, - &mut okm_out_verify - ) - .is_err()); + assert!( + sha256::verify( + &okm_out, + &sha256::Password::try_from(b"pass").unwrap(), + salt, + iterations, + &mut okm_out_verify + ) + .is_err() + ); sha384::derive_key(&password_384, salt, iterations, &mut okm_out).unwrap(); - assert!(sha384::verify( - &okm_out, - &sha384::Password::from_slice(b"pass").unwrap(), - salt, - iterations, - &mut okm_out_verify - ) - .is_err()); + assert!( + sha384::verify( + &okm_out, + &sha384::Password::try_from(b"pass").unwrap(), + salt, + iterations, + &mut okm_out_verify + ) + .is_err() + ); sha512::derive_key(&password_512, salt, iterations, &mut okm_out).unwrap(); - assert!(sha512::verify( - &okm_out, - &sha512::Password::from_slice(b"pass").unwrap(), - salt, - iterations, - &mut okm_out_verify - ) - .is_err()); + assert!( + sha512::verify( + &okm_out, + &sha512::Password::try_from(b"pass").unwrap(), + salt, + iterations, + &mut okm_out_verify + ) + .is_err() + ); } #[test] fn verify_diff_dklen_error() { - let password_256 = sha256::Password::from_slice("pass\0word".as_bytes()).unwrap(); - let password_384 = sha384::Password::from_slice("pass\0word".as_bytes()).unwrap(); - let password_512 = sha512::Password::from_slice("pass\0word".as_bytes()).unwrap(); + let password_256 = sha256::Password::try_from("pass\0word".as_bytes()).unwrap(); + let password_384 = sha384::Password::try_from("pass\0word".as_bytes()).unwrap(); + let password_512 = sha512::Password::try_from("pass\0word".as_bytes()).unwrap(); let salt = "sa\0lt".as_bytes(); let iterations: usize = 128; @@ -485,41 +455,47 @@ mod public { let mut okm_out_verify = [0u8; 32]; sha256::derive_key(&password_256, salt, iterations, &mut okm_out).unwrap(); - assert!(sha256::verify( - &okm_out, - &password_256, - salt, - iterations, - &mut okm_out_verify - ) - .is_err()); + assert!( + sha256::verify( + &okm_out, + &password_256, + salt, + iterations, + &mut okm_out_verify + ) + .is_err() + ); sha384::derive_key(&password_384, salt, iterations, &mut okm_out).unwrap(); - assert!(sha384::verify( - &okm_out, - &password_384, - salt, - iterations, - &mut okm_out_verify - ) - .is_err()); + assert!( + sha384::verify( + &okm_out, + &password_384, + salt, + iterations, + &mut okm_out_verify + ) + .is_err() + ); sha512::derive_key(&password_512, salt, iterations, &mut okm_out).unwrap(); - assert!(sha512::verify( - &okm_out, - &password_512, - salt, - iterations, - &mut okm_out_verify - ) - .is_err()); + assert!( + sha512::verify( + &okm_out, + &password_512, + salt, + iterations, + &mut okm_out_verify + ) + .is_err() + ); } #[test] fn verify_diff_iter_error() { - let password_256 = sha256::Password::from_slice("pass\0word".as_bytes()).unwrap(); - let password_384 = sha384::Password::from_slice("pass\0word".as_bytes()).unwrap(); - let password_512 = sha512::Password::from_slice("pass\0word".as_bytes()).unwrap(); + let password_256 = sha256::Password::try_from("pass\0word".as_bytes()).unwrap(); + let password_384 = sha384::Password::try_from("pass\0word".as_bytes()).unwrap(); + let password_512 = sha512::Password::try_from("pass\0word".as_bytes()).unwrap(); let salt = "sa\0lt".as_bytes(); let iterations: usize = 128; @@ -548,9 +524,9 @@ mod public { #[test] fn zero_iterations_err() { - let password_256 = sha256::Password::from_slice("pass\0word".as_bytes()).unwrap(); - let password_384 = sha384::Password::from_slice("pass\0word".as_bytes()).unwrap(); - let password_512 = sha512::Password::from_slice("pass\0word".as_bytes()).unwrap(); + let password_256 = sha256::Password::try_from("pass\0word".as_bytes()).unwrap(); + let password_384 = sha384::Password::try_from("pass\0word".as_bytes()).unwrap(); + let password_512 = sha512::Password::try_from("pass\0word".as_bytes()).unwrap(); let salt = "salt".as_bytes(); let iterations: usize = 0; @@ -563,9 +539,9 @@ mod public { #[test] fn zero_dklen_err() { - let password_256 = sha256::Password::from_slice("pass\0word".as_bytes()).unwrap(); - let password_384 = sha384::Password::from_slice("pass\0word".as_bytes()).unwrap(); - let password_512 = sha512::Password::from_slice("pass\0word".as_bytes()).unwrap(); + let password_256 = sha256::Password::try_from("pass\0word".as_bytes()).unwrap(); + let password_384 = sha384::Password::try_from("pass\0word".as_bytes()).unwrap(); + let password_512 = sha512::Password::try_from("pass\0word".as_bytes()).unwrap(); let salt = "salt".as_bytes(); let iterations: usize = 1; diff --git a/src/hazardous/kdf/scrypt.rs b/src/hazardous/kdf/scrypt.rs index f31eae3f4..faecd8110 100644 --- a/src/hazardous/kdf/scrypt.rs +++ b/src/hazardous/kdf/scrypt.rs @@ -319,23 +319,16 @@ pub fn derive_key( let mut x = vec![0u32; 32 * r]; let mut y = vec![0u32; 32 * r]; let mut v = vec![0u32; vlen]; - let pass = pbkdf2::Password::from_slice(password)?; + let pass = pbkdf2::Password::try_from(password)?; let blen: usize = p * 128 * r; - let mut b = vec![0u8; blen]; - - pbkdf2::derive_key(&pass, salt, 1, &mut b).inspect_err(|_| { - zeroize_call!(b); - })?; + let mut b = zeroize_wrap!(vec![0u8; blen]); + pbkdf2::derive_key(&pass, salt, 1, b.as_mut())?; for i in 0..p { smix(&mut b[i * 128 * r..], r, n, &mut v, &mut x, &mut y); } - pbkdf2::derive_key(&pass, &b, 1, dst_out).inspect_err(|_| { - zeroize_call!(b); - })?; - - zeroize_call!(b); + pbkdf2::derive_key(&pass, b.as_ref(), 1, dst_out)?; Ok(()) } diff --git a/src/hazardous/kem/ml_kem/internal/mod.rs b/src/hazardous/kem/ml_kem/internal/mod.rs index a10279d18..82d73b909 100644 --- a/src/hazardous/kem/ml_kem/internal/mod.rs +++ b/src/hazardous/kem/ml_kem/internal/mod.rs @@ -32,11 +32,13 @@ pub(crate) mod serialization; /// Sampling ring elements from seeds. pub(crate) mod sampling; +use crate::generics::sealed::{Data, Sealed, TryFromBytes}; use crate::hazardous::hash::sha3::sha3_256::Sha3_256; use crate::hazardous::hash::sha3::sha3_512::Sha3_512; use crate::hazardous::hash::sha3::shake256; use crate::hazardous::kem::ml_kem::Seed; use crate::{errors::UnknownCryptoError, hazardous::hash::sha3::sha3_256::SHA3_256_OUTSIZE}; +use core::fmt::Debug; use core::marker::PhantomData; use fe::*; use re::*; @@ -88,7 +90,7 @@ pub fn g(c: &[&[u8]]) -> ([u8; 32], ZeroizeWrap<[u8; 32]>) { } /// Internal PKE-related function, for generalizing over the three different PKE parameter-sets. -pub(crate) trait PkeParameters { +pub trait PkeParameters: Debug { const N: usize = 256; const K: usize; const ETA_1: usize; @@ -322,8 +324,8 @@ impl PkeParameters for MlKem1024Internal { } #[derive(Debug, PartialEq, Clone)] -/// ML-KEM encapsulation key. -pub(crate) struct EncapKey { +/// Internal, generic representation of encapsulation key and operation logic. +pub struct EncapKey { pub(crate) bytes: [u8; ENCODED_SIZE], h_ek: [u8; SHA3_256_OUTSIZE], t_hat: [RingElementNTT; K], @@ -348,7 +350,7 @@ impl PartialEq<&[ } impl EncapKey { - pub(crate) fn from_slice(slice: &[u8]) -> Result { + pub(crate) fn from_bytes(slice: &[u8]) -> Result { Pke::encapsulation_key_check(slice)?; let mut t_hat = [RingElementNTT::zero(); K]; @@ -379,7 +381,7 @@ impl EncapKey EncapKey { pub(crate) bytes: [u8; ENCODED_SIZE_DK], - s_hat: [RingElementNTT; K], + pub(crate) s_hat: [RingElementNTT; K], _phantom: PhantomData, } -#[cfg(feature = "zeroize")] -impl< - const K: usize, - const ENCODED_SIZE_EK: usize, - const ENCODED_SIZE_DK: usize, - Pke: PkeParameters, - > Drop for DecapKey +impl + Drop for DecapKey { fn drop(&mut self) { - use zeroize::Zeroize; - self.bytes.iter_mut().zeroize(); - self.s_hat.iter_mut().zeroize(); - } -} - -impl< - const K: usize, - const ENCODED_SIZE_EK: usize, - const ENCODED_SIZE_DK: usize, - Pke: PkeParameters, - > PartialEq> - for DecapKey -{ - fn eq(&self, other: &DecapKey) -> bool { - use subtle::ConstantTimeEq; - - (self - .unprotected_as_bytes() - .ct_eq(other.unprotected_as_bytes())) - .into() + #[cfg(feature = "zeroize")] + { + self.memzero(); + } } } -impl< - const K: usize, - const ENCODED_SIZE_EK: usize, - const ENCODED_SIZE_DK: usize, - Pke: PkeParameters, - > Eq for DecapKey +// SECURITY: Normal AsRef<[u8]> required due to implementation as Data. +impl + AsRef<[u8]> for DecapKey { -} - -impl< - const K: usize, - const ENCODED_SIZE_EK: usize, - const ENCODED_SIZE_DK: usize, - Pke: PkeParameters, - > PartialEq<&[u8]> for DecapKey -{ - fn eq(&self, other: &&[u8]) -> bool { - use subtle::ConstantTimeEq; - - (self.unprotected_as_bytes().ct_eq(*other)).into() + fn as_ref(&self) -> &[u8] { + self.bytes.as_ref() } } -impl< - const K: usize, - const ENCODED_SIZE_EK: usize, - const ENCODED_SIZE_DK: usize, - Pke: PkeParameters, - > core::fmt::Debug for DecapKey +impl + Debug for DecapKey { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{} {{***OMITTED***}}", stringify!($name)) } } -impl< - const K: usize, - const ENCODED_SIZE_EK: usize, - const ENCODED_SIZE_DK: usize, - Pke: PkeParameters, - > DecapKey +impl + DecapKey { pub(crate) fn get_encapsulation_key_bytes(&self) -> &[u8] { &self.bytes[ENCODE_SIZE_POLY * K..(768 * K) + 32] @@ -702,7 +661,7 @@ impl< c_prime: &mut [u8], ek: &EncapKey, ) -> Result<[u8; 32], UnknownCryptoError> { - debug_assert_eq!(self.get_encapsulation_key_bytes(), ek.as_ref()); + debug_assert_eq!(ek, &self.get_encapsulation_key_bytes()); debug_assert_eq!(c.len(), Pke::CIPHERTEXT_SIZE); // Step 1: @@ -750,14 +709,10 @@ impl< ) -> Result<[u8; 32], UnknownCryptoError> { // In this case we aren't provided a cached encapsulation key. let ek = - EncapKey::::from_slice(self.get_encapsulation_key_bytes())?; + EncapKey::::from_bytes(self.get_encapsulation_key_bytes())?; self.mlkem_decap_internal_with_ek(c, c_prime, &ek) } - - pub(crate) fn unprotected_as_bytes(&self) -> &[u8] { - &self.bytes - } } pub(crate) struct KeyPairInternal { @@ -828,7 +783,7 @@ impl KeyPairInternal { // Cache hash of ek so we don't need to re-compute for every encap(). let h_ek = Sha3_256::digest(&ek.bytes)?; - ek.h_ek = h_ek.value; + ek.h_ek = h_ek.data.bytes; // Step 20 for (re, dk_part) in dk @@ -872,7 +827,7 @@ impl KeyPairInternal { // Step 1 + 2. (ekPKE, dkPKE) ← K-PKE.KeyGen(d) Self::keygen( - &seed.unprotected_as_bytes()[..32], + &seed.unprotected_as_ref()[..32], &mut encap_key, &mut decap_key, )?; @@ -885,9 +840,9 @@ impl KeyPairInternal { .copy_from_slice(Sha3_256::digest(&encap_key.bytes).unwrap().as_ref()); decap_key.bytes[(ENCODE_SIZE_POLY * K) + Pke::EK_SIZE + 32 ..(ENCODE_SIZE_POLY * K) + Pke::EK_SIZE + 32 + 32] - .copy_from_slice(&seed.unprotected_as_bytes()[32..64]); + .copy_from_slice(&seed.unprotected_as_ref()[32..64]); - debug_assert_eq!(decap_key.get_encapsulation_key_bytes(), encap_key.as_ref()); + debug_assert_eq!(encap_key, decap_key.get_encapsulation_key_bytes()); Ok((encap_key, decap_key)) } @@ -947,18 +902,343 @@ impl KeyPairInternal { } } +// TypePrimitive + TypeData + Data impls for DecapKey and EncapKey + +impl + Sealed for DecapKey +{ +} + +impl Sealed + for EncapKey +{ +} + +impl + AsRef<[u8; ENCODED_SIZE_DK]> for DecapKey +{ + fn as_ref(&self) -> &[u8; ENCODED_SIZE_DK] { + &self.bytes + } +} + +impl AsRef<[u8; ENCODED_SIZE]> + for EncapKey +{ + fn as_ref(&self) -> &[u8; ENCODED_SIZE] { + &self.bytes + } +} + +// NOTE: unimplemented!() for trait-required methods that are not applicable to this +// scenario using EncapKey<> and DecapKey<>. + +impl + AsMut<[u8]> for DecapKey +{ + fn as_mut(&mut self) -> &mut [u8] { + unimplemented!("CORRECTNESS: DecapKey is not safe to modify only on encoded bytes.") + } +} + +impl + AsMut<[u8; ENCODED_SIZE_DK]> for DecapKey +{ + fn as_mut(&mut self) -> &mut [u8; ENCODED_SIZE_DK] { + unimplemented!("CORRECTNESS: DecapKey is not safe to modify only on encoded bytes.") + } +} + +impl AsMut<[u8]> + for EncapKey +{ + fn as_mut(&mut self) -> &mut [u8] { + unimplemented!("CORRECTNESS: EncapKey is not safe to modify only on encoded bytes.") + } +} + +impl AsMut<[u8; ENCODED_SIZE]> + for EncapKey +{ + fn as_mut(&mut self) -> &mut [u8; ENCODED_SIZE] { + unimplemented!("CORRECTNESS: EncapKey is not safe to modify only on encoded bytes.") + } +} + +impl + Data for DecapKey +{ + fn len(&self) -> usize { + debug_assert_eq!(self.bytes.len(), ENCODED_SIZE_DK); + ENCODED_SIZE_DK + } + + fn is_empty(&self) -> bool { + ENCODED_SIZE_DK == 0 + } + + fn new(_size: usize) -> Result { + unimplemented!("CORRECTNESS: Not applicable for this type.") + } + + #[cfg(feature = "zeroize")] + fn memzero(&mut self) { + use zeroize::Zeroize; + self.bytes.iter_mut().zeroize(); + self.s_hat.iter_mut().zeroize(); + } +} + +impl Data + for EncapKey +{ + fn len(&self) -> usize { + debug_assert_eq!(self.bytes.len(), ENCODED_SIZE); + ENCODED_SIZE + } + + fn is_empty(&self) -> bool { + ENCODED_SIZE == 0 + } + + fn new(_size: usize) -> Result { + unimplemented!("CORRECTNESS: Not applicable for this type.") + } + + #[cfg(feature = "zeroize")] + fn memzero(&mut self) { + unimplemented!( + "SECURITY: EncapKey<> is exposed as Public and should never need memzero as part of Drop." + ); + } +} + +impl + TryFromBytes for DecapKey +{ + fn try_from_bytes(bytes: &[u8]) -> Result { + // NOTE: Doesn't need the parse_bytes() becuase it already uses + // the TypeData::try_from_bytes(), which we define to be custom here. + Self::unchecked_from_slice(bytes) + } +} + +impl TryFromBytes + for EncapKey +{ + fn try_from_bytes(bytes: &[u8]) -> Result { + // NOTE: Doesn't need the parse_bytes() becuase it already uses + // the TypeData::try_from_bytes(), which we define to be custom here. + Self::from_bytes(bytes) + } +} + #[cfg(test)] mod tests { use super::*; - use crate::hazardous::kem::ml_kem::mlkem1024::KeyPair as MlKem1024KeyPair; + use crate::KP; use crate::hazardous::kem::ml_kem::mlkem512::KeyPair as MlKem512KeyPair; use crate::hazardous::kem::ml_kem::mlkem768::KeyPair as MlKem768KeyPair; + use crate::hazardous::kem::ml_kem::mlkem1024::KeyPair as MlKem1024KeyPair; + + #[test] + #[cfg(feature = "safe_api")] // format! is only available with std + fn test_omitted_debug_decapkey() { + let seed = Seed::try_from(&[128u8; 64]).unwrap(); + let kp = MlKem768KeyPair::try_from(&seed).unwrap(); + + let dk = DecapKey::< + { MlKem768Internal::K }, + { MlKem768Internal::EK_SIZE }, + { MlKem768Internal::DK_SIZE }, + MlKem768Internal, + >::try_from_bytes(&kp.private().data.bytes) + .unwrap(); + + let ser = format!("{:?}", dk.bytes.as_ref()); + let test_debug_contents = format!("{:?}", &dk); + assert!(!test_debug_contents.contains(&ser)); + assert!(test_debug_contents.contains(&"{***OMITTED***}".to_string())); + } + + #[test] + #[should_panic] + #[cfg(feature = "zeroize")] + fn test_encapkey_dataimpl_memzero() { + let mut ek = EncapKey::< + { MlKem768Internal::K }, + { MlKem768Internal::EK_SIZE }, + MlKem768Internal, + >::from_bytes(&[0u8; MlKem768Internal::EK_SIZE]) + .unwrap(); + ek.memzero(); + } + + #[test] + #[should_panic] + fn test_encapkey_dataimpl_new() { + _ = EncapKey::< + { MlKem768Internal::K }, + { MlKem768Internal::EK_SIZE }, + MlKem768Internal, + >::new(MlKem768Internal::EK_SIZE); + } + + #[test] + #[should_panic] + fn test_encapkey_dataimpl_as_mut_const() { + let mut ek = EncapKey::< + { MlKem768Internal::K }, + { MlKem768Internal::EK_SIZE }, + MlKem768Internal, + >::try_from_bytes(&[0u8; MlKem768Internal::EK_SIZE]) + .unwrap(); + + assert_eq!( + as AsMut<[u8; MlKem768Internal::EK_SIZE]>>::as_mut(&mut ek), + &[1u8; MlKem768Internal::EK_SIZE] + ); + } + + #[test] + #[should_panic] + fn test_encapkey_dataimpl_as_mut() { + let mut ek = EncapKey::< + { MlKem768Internal::K }, + { MlKem768Internal::EK_SIZE }, + MlKem768Internal, + >::try_from_bytes(&[0u8; MlKem768Internal::EK_SIZE]) + .unwrap(); + + assert_eq!( + as AsMut<[u8]>>::as_mut(&mut ek), + &[1u8; MlKem768Internal::EK_SIZE] + ); + } + + #[test] + #[should_panic] + fn test_decapkey_dataimpl_as_mut_const() { + let seed = Seed::try_from(&[128u8; 64]).unwrap(); + let kp = MlKem768KeyPair::try_from(&seed).unwrap(); + + let mut dk = DecapKey::< + { MlKem768Internal::K }, + { MlKem768Internal::EK_SIZE }, + { MlKem768Internal::DK_SIZE }, + MlKem768Internal, + >::try_from_bytes(&kp.private().data.bytes) + .unwrap(); + + assert_eq!( + as AsMut<[u8; MlKem768Internal::DK_SIZE]>>::as_mut(&mut dk), + &[1u8; MlKem768Internal::DK_SIZE] + ); + } + + #[test] + #[should_panic] + fn test_decapkey_dataimpl_as_mut() { + let seed = Seed::try_from(&[128u8; 64]).unwrap(); + let kp = MlKem768KeyPair::try_from(&seed).unwrap(); + + let mut dk = DecapKey::< + { MlKem768Internal::K }, + { MlKem768Internal::EK_SIZE }, + { MlKem768Internal::DK_SIZE }, + MlKem768Internal, + >::try_from_bytes(&kp.private().data.bytes) + .unwrap(); + + assert_eq!( + as AsMut<[u8]>>::as_mut(&mut dk), + &[1u8; MlKem768Internal::DK_SIZE] + ); + } + + #[test] + #[should_panic] + fn test_decapkey_dataimpl_new() { + _ = DecapKey::< + { MlKem768Internal::K }, + { MlKem768Internal::EK_SIZE }, + { MlKem768Internal::DK_SIZE }, + MlKem768Internal, + >::new(MlKem768Internal::DK_SIZE); + } + + #[test] + fn test_encapkey_dataimpl() { + let seed = Seed::try_from(&[128u8; 64]).unwrap(); + let kp = MlKem768KeyPair::try_from(&seed).unwrap(); + + let valid_ek = EncapKey::< + { MlKem768Internal::K }, + { MlKem768Internal::EK_SIZE }, + MlKem768Internal, + >::try_from_bytes(&kp.public().data.bytes) + .unwrap(); + + assert_eq!(valid_ek.len(), MlKem768Internal::EK_SIZE); + assert!(!valid_ek.is_empty()); + } + + #[test] + fn test_decapkey_dataimpl() { + let seed = Seed::try_from(&[128u8; 64]).unwrap(); + let kp = MlKem768KeyPair::try_from(&seed).unwrap(); + + #[cfg(feature = "zeroize")] + let mut valid_dk = DecapKey::< + { MlKem768Internal::K }, + { MlKem768Internal::EK_SIZE }, + { MlKem768Internal::DK_SIZE }, + MlKem768Internal, + >::try_from_bytes(&kp.private().data.bytes) + .unwrap(); + + #[cfg(not(feature = "zeroize"))] + let valid_dk = DecapKey::< + { MlKem768Internal::K }, + { MlKem768Internal::EK_SIZE }, + { MlKem768Internal::DK_SIZE }, + MlKem768Internal, + >::try_from_bytes(&kp.private().data.bytes) + .unwrap(); + + assert_eq!(valid_dk.len(), MlKem768Internal::DK_SIZE); + assert!(!valid_dk.is_empty()); + + #[cfg(feature = "zeroize")] + { + valid_dk.memzero(); + assert_eq!(valid_dk.bytes, [0u8; MlKem768Internal::DK_SIZE]); + } + } #[test] #[cfg(feature = "safe_api")] fn test_seed_and_dk_mismatch() { - let seed = Seed::from_slice(&[128u8; 64]).unwrap(); - let bad_seed = Seed::from_slice(&[1u8; 64]).unwrap(); + let seed = Seed::try_from(&[128u8; 64]).unwrap(); + let bad_seed = Seed::try_from(&[1u8; 64]).unwrap(); // ML-KEM-512. let kp = MlKem512KeyPair::try_from(&seed).unwrap(); @@ -966,32 +1246,32 @@ mod tests { assert!( KeyPairInternal::::from_keys::<2, 800, 1632, 768>( &seed, - &kp.public().value, - &kp.private().value, + &kp.public().data, + &kp.private().data, ) .is_ok() ); assert!( KeyPairInternal::::from_keys::<2, 800, 1632, 768>( &seed, - &kp_bad.public().value, - &kp.private().value, + &kp_bad.public().data, + &kp.private().data, ) .is_err() ); assert!( KeyPairInternal::::from_keys::<2, 800, 1632, 768>( &seed, - &kp.public().value, - &kp_bad.private().value, + &kp.public().data, + &kp_bad.private().data, ) .is_err() ); assert!( KeyPairInternal::::from_keys::<2, 800, 1632, 768>( &seed, - &kp_bad.public().value, - &kp_bad.private().value, + &kp_bad.public().data, + &kp_bad.private().data, ) .is_err() ); @@ -1002,32 +1282,32 @@ mod tests { assert!( KeyPairInternal::::from_keys::<3, 1184, 2400, 1088>( &seed, - &kp.public().value, - &kp.private().value, + &kp.public().data, + &kp.private().data, ) .is_ok() ); assert!( KeyPairInternal::::from_keys::<3, 1184, 2400, 1088>( &seed, - &kp_bad.public().value, - &kp.private().value, + &kp_bad.public().data, + &kp.private().data, ) .is_err() ); assert!( KeyPairInternal::::from_keys::<3, 1184, 2400, 1088>( &seed, - &kp.public().value, - &kp_bad.private().value, + &kp.public().data, + &kp_bad.private().data, ) .is_err() ); assert!( KeyPairInternal::::from_keys::<3, 1184, 2400, 1088>( &seed, - &kp_bad.public().value, - &kp_bad.private().value, + &kp_bad.public().data, + &kp_bad.private().data, ) .is_err() ); @@ -1038,32 +1318,32 @@ mod tests { assert!( KeyPairInternal::::from_keys::<4, 1568, 3168, 1568>( &seed, - &kp.public().value, - &kp.private().value, + &kp.public().data, + &kp.private().data, ) .is_ok() ); assert!( KeyPairInternal::::from_keys::<4, 1568, 3168, 1568>( &seed, - &kp_bad.public().value, - &kp.private().value, + &kp_bad.public().data, + &kp.private().data, ) .is_err() ); assert!( KeyPairInternal::::from_keys::<4, 1568, 3168, 1568>( &seed, - &kp.public().value, - &kp_bad.private().value, + &kp.public().data, + &kp_bad.private().data, ) .is_err() ); assert!( KeyPairInternal::::from_keys::<4, 1568, 3168, 1568>( &seed, - &kp_bad.public().value, - &kp_bad.private().value, + &kp_bad.public().data, + &kp_bad.private().data, ) .is_err() ); @@ -1071,80 +1351,98 @@ mod tests { #[test] fn test_encap_internal_check_m() { - let testing_seed = Seed::from_slice(&[128u8; 64]).unwrap(); + let testing_seed = Seed::try_from(&[128u8; 64]).unwrap(); let keypair = MlKem512KeyPair::try_from(&testing_seed).unwrap(); let mut c = [0u8; MlKem512Internal::CIPHERTEXT_SIZE]; - assert!(keypair - .public() - .value - .mlkem_encap_internal(&[0u8; 32], &mut c) - .is_ok()); - assert!(keypair - .public() - .value - .mlkem_encap_internal(&[0u8; 31], &mut c) - .is_err()); - assert!(keypair - .public() - .value - .mlkem_encap_internal(&[0u8; 33], &mut c) - .is_err()); + assert!( + keypair + .public() + .data + .mlkem_encap_internal(&[0u8; 32], &mut c) + .is_ok() + ); + assert!( + keypair + .public() + .data + .mlkem_encap_internal(&[0u8; 31], &mut c) + .is_err() + ); + assert!( + keypair + .public() + .data + .mlkem_encap_internal(&[0u8; 33], &mut c) + .is_err() + ); let keypair = MlKem768KeyPair::try_from(&testing_seed).unwrap(); let mut c = [0u8; MlKem768Internal::CIPHERTEXT_SIZE]; - assert!(keypair - .public() - .value - .mlkem_encap_internal(&[0u8; 32], &mut c) - .is_ok()); - assert!(keypair - .public() - .value - .mlkem_encap_internal(&[0u8; 31], &mut c) - .is_err()); - assert!(keypair - .public() - .value - .mlkem_encap_internal(&[0u8; 33], &mut c) - .is_err()); + assert!( + keypair + .public() + .data + .mlkem_encap_internal(&[0u8; 32], &mut c) + .is_ok() + ); + assert!( + keypair + .public() + .data + .mlkem_encap_internal(&[0u8; 31], &mut c) + .is_err() + ); + assert!( + keypair + .public() + .data + .mlkem_encap_internal(&[0u8; 33], &mut c) + .is_err() + ); let keypair = MlKem1024KeyPair::try_from(&testing_seed).unwrap(); let mut c = [0u8; MlKem1024Internal::CIPHERTEXT_SIZE]; - assert!(keypair - .public() - .value - .mlkem_encap_internal(&[0u8; 32], &mut c) - .is_ok()); - assert!(keypair - .public() - .value - .mlkem_encap_internal(&[0u8; 31], &mut c) - .is_err()); - assert!(keypair - .public() - .value - .mlkem_encap_internal(&[0u8; 33], &mut c) - .is_err()); + assert!( + keypair + .public() + .data + .mlkem_encap_internal(&[0u8; 32], &mut c) + .is_ok() + ); + assert!( + keypair + .public() + .data + .mlkem_encap_internal(&[0u8; 31], &mut c) + .is_err() + ); + assert!( + keypair + .public() + .data + .mlkem_encap_internal(&[0u8; 33], &mut c) + .is_err() + ); } #[test] #[cfg(feature = "safe_api")] fn test_omitted_debug() { - let testing_seed = Seed::from_slice(&[128u8; 64]).unwrap(); + let testing_seed = Seed::try_from(&[128u8; 64]).unwrap(); let keypair = MlKem512KeyPair::try_from(&testing_seed).unwrap(); - let secret = format!("{:?}", keypair.private().value.bytes); + let secret = format!("{:?}", keypair.private().data.bytes); let test_debug_contents = format!("{:?}", keypair.private()); assert!(!test_debug_contents.contains(&secret)); let keypair = MlKem768KeyPair::try_from(&testing_seed).unwrap(); - let secret = format!("{:?}", keypair.private().value.bytes); + let secret = format!("{:?}", keypair.private().data.bytes); let test_debug_contents = format!("{:?}", keypair.private()); assert!(!test_debug_contents.contains(&secret)); let keypair = MlKem1024KeyPair::try_from(&testing_seed).unwrap(); - let secret = format!("{:?}", keypair.private().value.bytes); + let secret = format!("{:?}", keypair.private().data.bytes); let test_debug_contents = format!("{:?}", keypair.private()); assert!(!test_debug_contents.contains(&secret)); } @@ -1254,11 +1552,11 @@ mod tests { .is_err() ); - let testing_seed = Seed::from_slice(&[128u8; 64]).unwrap(); + let testing_seed = Seed::try_from(&[128u8; 64]).unwrap(); let keypair = $keypair::try_from(&testing_seed).unwrap(); - assert!($pke::encapsulation_key_check(&keypair.public().value.as_ref()).is_ok()); - assert!($pke::decapsulation_key_check(&keypair.private().value.unprotected_as_bytes()).is_ok()); - let mut dk = keypair.private().value.unprotected_as_bytes().to_vec(); + assert!($pke::encapsulation_key_check(&keypair.public().as_ref()).is_ok()); + assert!($pke::decapsulation_key_check(&keypair.private().unprotected_as_ref()).is_ok()); + let mut dk = keypair.private().unprotected_as_ref().to_vec(); // Modify the hash part let correct = dk[(768 * $pke::K) + 32]; diff --git a/src/hazardous/kem/ml_kem/internal/serialization.rs b/src/hazardous/kem/ml_kem/internal/serialization.rs index 6507e8d1a..6943d68d6 100644 --- a/src/hazardous/kem/ml_kem/internal/serialization.rs +++ b/src/hazardous/kem/ml_kem/internal/serialization.rs @@ -20,7 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -use super::fe::{barrett_reduce, FieldElement}; +use super::fe::{FieldElement, barrett_reduce}; #[cfg(test)] /// FIPS-203, Algorithm 3. diff --git a/src/hazardous/kem/ml_kem/mlkem1024.rs b/src/hazardous/kem/ml_kem/mlkem1024.rs index 162c7eec1..daa42cd4b 100644 --- a/src/hazardous/kem/ml_kem/mlkem1024.rs +++ b/src/hazardous/kem/ml_kem/mlkem1024.rs @@ -25,6 +25,8 @@ //! In general, it is highly recommended to use the [`KeyPair`] type to deal with decapsulating operations, or decapsulation keys in general. //! //! A [`KeyPair`] requires, or automatically generates, a [`Seed`]. It cannot be made solely from encoded/serialized decapsulation key in bytes, unless a [`Seed`] is also provided. +//! [`KeyPair`] also internally caches the [`EncapsulationKey`] used during decapsulation, making it more efficient when used to decapsulate multiple +//! KEM ciphertext with a given private [`DecapsulationKey`]. //! //! A seed is only 64 bytes, is fully FIPS compliant, and hardens against attacks described [here](https://eprint.iacr.org/2024/523). //! @@ -40,377 +42,442 @@ //! # Errors: //! An error will be returned if: //! - [`getrandom::fill()`] fails during encapsulation. -//! - `m` is not 32 bytes. -//! -//! # Panics: -//! A panic will occur if: //! - [`getrandom::fill()`] fails during [`KeyPair::generate()`]. +//! - `m` is not 32 bytes. //! //! # Security: //! - It is critical that both the seed and explicit randomness `m`, used for key generation and encapsulation //! are generated using a strong CSPRNG. -//! - Users should always prefer encapsulation without specifying explicit randomness, if possible. `encap_deterministic()` +//! - Users should always prefer encapsulation without specifying explicit randomness, if possible. [`EncapsulationKey::encap_deterministic()`] //! exists mainly for `no_std` usage. //! - Prefer using [`KeyPair`] to create and use ML-KEM keys, which is MAL-BIND-K-CT secure. //! //! # Example: //! ```rust //! # #[cfg(feature = "safe_api")] { +//! use orion::KP; //! use orion::hazardous::kem::mlkem1024::*; //! -//! let keypair = KeyPair::generate()?; +//! let kp = KeyPair::generate()?; //! -//! let (sender_shared_secret, sender_ciphertext) = MlKem1024::encap(keypair.public())?; -//! let recipient_shared_secret = MlKem1024::decap(keypair.private(), &sender_ciphertext)?; +//! let ek = EncapsulationKey::try_from(kp.public().as_ref())?; +//! let (sender_shared_secret, sender_ciphertext) = ek.encap()?; +//! let recipient_shared_secret = kp.decap(&sender_ciphertext)?; //! //! assert_eq!(sender_shared_secret, recipient_shared_secret); //! # } //! # Ok::<(), orion::errors::UnknownCryptoError>(()) //! ``` //! [`getrandom::fill()`]: getrandom::fill -//! [`encap()`]: mlkem1024::MlKem1024::encap -//! [`decap()`]: mlkem1024::MlKem1024::decap //! [`KeyPair::generate()`]: mlkem1024::KeyPair::generate //! [`KeyPair`]: mlkem1024::KeyPair +//! [`EncapsulationKey`]: mlkem1024::EncapsulationKey +//! [`EncapsulationKey::encap_deterministic()`]: mlkem1024::EncapsulationKey::encap_deterministic //! [`Seed`]: mlkem1024::Seed //! [`DecapsulationKey`]: mlkem1024::DecapsulationKey -//! [`DecapsulationKey::unchecked_from_slice()`]: mlkem1024::DecapsulationKey::unchecked_from_slice +//! [`DecapsulationKey::unchecked_from_slice()`]: mlkem1024::DecapsulationKey::unchecked_from_slice +use crate::KP; use crate::errors::UnknownCryptoError; +use crate::generics::sealed::{Sealed, TryFromBytes}; +use crate::generics::{ByteArrayData, Public, Secret, TypeSpec}; use crate::hazardous::kem::ml_kem::internal::*; + +pub use crate::hazardous::kem::ml_kem::SEED_SIZE; pub use crate::hazardous::kem::ml_kem::Seed; -construct_secret_key! { - /// A type to represent the `SharedSecret` that ML-KEM-1024 produces. - /// - /// This type simply holds bytes. Creating an instance from slices or similar, - /// performs no checks whatsoever. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 32 bytes. - (SharedSecret, test_shared_key, MlKem1024Internal::SHARED_SECRET_SIZE, MlKem1024Internal::SHARED_SECRET_SIZE) -} +/// Size of private [`EncapsulationKey`]. +pub const EK_SIZE: usize = MlKem1024Internal::EK_SIZE; -impl_from_trait!(SharedSecret, MlKem1024Internal::SHARED_SECRET_SIZE); +/// Size of public [`DecapsulationKey`]. +pub const DK_SIZE: usize = MlKem1024Internal::DK_SIZE; -construct_public! { - /// A type to represent the KEM `Ciphertext` that ML-KEM-1024 returns. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 1568 bytes. - (Ciphertext, test_kem_ciphertext, MlKem1024Internal::CIPHERTEXT_SIZE, MlKem1024Internal::CIPHERTEXT_SIZE) -} +/// Size of public [`Ciphertext`]. +pub const CIPHERTEXT_SIZE: usize = MlKem1024Internal::CIPHERTEXT_SIZE; -impl_from_trait!(Ciphertext, MlKem1024Internal::CIPHERTEXT_SIZE); +/// Size of private [`SharedSecret`]. +pub const SHARED_SECRET_SIZE: usize = MlKem1024Internal::SHARED_SECRET_SIZE; -#[derive(Debug, PartialEq)] -/// A keypair of ML-KEM-1024 keys, that are derived from a given seed. -pub struct KeyPair { - seed: Seed, - dk: DecapsulationKey, -} +/// ML-KEM-1024 ciphertext. +pub type Ciphertext = Public; -impl KeyPair { - #[cfg(feature = "safe_api")] - #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] - /// Generate a fresh [KeyPair]. - pub fn generate() -> Result { - let seed = Seed::generate(); - let (ek, dk) = KeyPairInternal::::from_seed::<4, 1568, 3168>(&seed)?; +/// ML-KEM-1024 shared secret. +pub type SharedSecret = Secret; - Ok(Self { - seed, - dk: DecapsulationKey { - value: dk, - cached_ek: EncapsulationKey { value: ek }, - }, - }) - } +/// ML-KEM-1024 encapsulation key. +pub type EncapsulationKey = Public; - #[cfg(feature = "safe_api")] - #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] - /// Instantiate a [KeyPair] with all key validation checks, described - /// in FIPS-203, Section 7.1, 7.2 and 7.3. - /// - /// The output keypair is the equivalent of using `KeyPair::try_from(seed: &Seed)`, but this - /// can be used, in order to check whether a decapsulation key - /// is valid in relation to the `seed` provided. - pub fn from_keys(seed: &Seed, dk: &DecapsulationKey) -> Result { - let unchecked_ek = EncapsulationKey::try_from(dk)?; - let (ek, dk) = KeyPairInternal::::from_keys::<4, 1568, 3168, 1568>( - seed, - &unchecked_ek.value, - &dk.value, - )?; +/// ML-KEM-1024 decapsulation key. +pub type DecapsulationKey = Secret; - Ok(Self { - seed: Seed::from_slice(seed.unprotected_as_bytes()).unwrap(), - dk: DecapsulationKey { - value: dk, - cached_ek: EncapsulationKey { value: ek }, - }, - }) - } +#[derive(Debug)] +/// ML-KEM-1024 shared secret implementation. See [`SharedSecret`] type for convenience. +pub struct MlKem1024SharedSecret {} +impl Sealed for MlKem1024SharedSecret {} - /// Get the [Seed] used to generate this keypair. Use this function in order to store - /// the private part of the keypair and regenerate it, when needed. - pub fn seed(&self) -> &Seed { - &self.seed +impl TypeSpec for MlKem1024SharedSecret { + const NAME: &'static str = stringify!(SharedSecret); + type TypeData = ByteArrayData; +} + +impl From<[u8; SHARED_SECRET_SIZE]> for Secret { + fn from(value: [u8; SHARED_SECRET_SIZE]) -> Self { + Self::from_data(::TypeData::from(value)) } +} + +#[derive(Debug, Clone, Copy)] +/// ML-KEM-1024 ciphertext implementation. See [`Ciphertext`] type for convenience. +pub struct Mlkem1024Ciphertext {} +impl Sealed for Mlkem1024Ciphertext {} + +impl TypeSpec for Mlkem1024Ciphertext { + const NAME: &'static str = stringify!(Ciphertext); + type TypeData = ByteArrayData; +} - /// Get the public [EncapsulationKey] corresponding to this keypair. - pub fn public(&self) -> &EncapsulationKey { - &self.dk.cached_ek +impl From<[u8; CIPHERTEXT_SIZE]> for Public { + fn from(value: [u8; CIPHERTEXT_SIZE]) -> Self { + Self::from_data(::TypeData::from(value)) } +} - /// Get the private [DecapsulationKey] used to generate this keypair. In order to store the private - /// part of this [KeyPair], use [KeyPair::seed()] instead. - pub fn private(&self) -> &DecapsulationKey { - &self.dk +#[derive(Debug)] +/// ML-KEM-1024 decapsulation key implementation. See [`DecapsulationKey`] type for convenience. +pub struct MlKem1024DecapKey {} +impl Sealed for MlKem1024DecapKey {} +impl TypeSpec for MlKem1024DecapKey { + const NAME: &'static str = stringify!(DecapsulationKey); + // Key-check logic in Data-impl under [`DecapKey`] (applies to `parse_bytes()`). + type TypeData = DecapKey< + { MlKem1024Internal::K }, + { MlKem1024Internal::EK_SIZE }, + { MlKem1024Internal::DK_SIZE }, + MlKem1024Internal, + >; +} + +#[derive(Debug, Clone, Copy)] +/// ML-KEM-1024 encapsulation key implementation. See [`EncapsulationKey`] type for convenience. +pub struct MlKem1024EncapKey {} +impl Sealed for MlKem1024EncapKey {} +impl TypeSpec for MlKem1024EncapKey { + const NAME: &'static str = stringify!(EncapsulationKey); + // Key-check logic in Data-impl under [`DecapKey`] (applies to `parse_bytes()`). + type TypeData = + EncapKey<{ MlKem1024Internal::K }, { MlKem1024Internal::EK_SIZE }, MlKem1024Internal>; + + /// SECURITY: Override to vartime-[`PartialEq`] on a non-secret type, with a var-time one + /// to selectively only compare the encoded representation of encapsulation key. + fn vartime_partial_eq(lhs: &Self::TypeData, rhs: &[u8]) -> bool { + // NOTE: This compares only the encoded encapsulation key, so make sure the other fields + // aren't modifiable after instantiation, otherwise the encoded bytes might not correspond + // to the RingElements/Polynomials. + lhs.bytes.as_ref() == rhs } } -impl TryFrom<&Seed> for KeyPair { +impl TryFrom<&DecapsulationKey> for Public { type Error = UnknownCryptoError; - fn try_from(value: &Seed) -> Result { - let (ek, dk) = KeyPairInternal::::from_seed::<4, 1568, 3168>(value)?; - - Ok(Self { - seed: Seed::from_slice(value.unprotected_as_bytes()).unwrap(), - dk: DecapsulationKey { - value: dk, - cached_ek: EncapsulationKey { value: ek }, - }, - }) + fn try_from(value: &DecapsulationKey) -> Result { + Ok(Self::from_data(EncapKey::< + { MlKem1024Internal::K }, + { MlKem1024Internal::EK_SIZE }, + MlKem1024Internal, + >::from_bytes( + value.data.get_encapsulation_key_bytes() + )?)) } } -#[derive(Debug, PartialEq)] -/// A type to represent the `DecapsulationKey` that ML-KEM-1024 produces. -pub struct DecapsulationKey { - pub(crate) value: DecapKey<4, 1568, 3168, MlKem1024Internal>, - // NOTE(brycx): This is simply a cache of the encapsulation key, so we avoid recomputing it - // on decap() operations. This is not a part of PartialEq, AsRef<> implementations or other logic - // pertaining to the `DecapsulationKey`, serving a purely internal purpose. - pub(crate) cached_ek: EncapsulationKey, -} +impl EncapsulationKey { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + /// Given the [`EncapsulationKey`], generate a [`SharedSecret`] and associated [`Ciphertext`]. + pub fn encap(&self) -> Result<(SharedSecret, Ciphertext), UnknownCryptoError> { + let mut m = zeroize_wrap!([0u8; 32]); + getrandom::fill(m.as_mut())?; + + self.encap_deterministic(m.as_ref()) + } + + /// Given the [`EncapsulationKey`] and randomness `m`, generate a [`SharedSecret`] and associated [`Ciphertext`]. + pub fn encap_deterministic( + &self, + m: &[u8], + ) -> Result<(SharedSecret, Ciphertext), UnknownCryptoError> { + if m.len() != 32 { + return Err(UnknownCryptoError); + } + + let mut c = Ciphertext::try_from(&[0u8; MlKem1024Internal::CIPHERTEXT_SIZE])?; -impl PartialEq<&[u8]> for DecapsulationKey { - fn eq(&self, other: &&[u8]) -> bool { - // Defer to DecapKey<> impl ct-eq - self.value == *other + #[cfg(feature = "zeroize")] + let mut k_internal = self + .data + .mlkem_encap_internal(m.as_ref(), c.data.as_mut())?; + #[cfg(not(feature = "zeroize"))] + let k_internal = self + .data + .mlkem_encap_internal(m.as_ref(), c.data.as_mut())?; + + let k = SharedSecret::try_from(k_internal.as_slice())?; + zeroize_call!(k_internal); + + Ok((k, c)) } } impl DecapsulationKey { - /// Instantiate a [DecapsulationKey] with only key-checks from FIPS-203, section 7.3. Not MAL-BIND-K-CT secure. + /// Instantiate a [`DecapsulationKey`] with only key-checks from FIPS-203, section 7.3. Not MAL-BIND-K-CT secure. pub fn unchecked_from_slice(slice: &[u8]) -> Result { - let dk_unchecked = - DecapKey::<4, 1568, 3168, MlKem1024Internal>::unchecked_from_slice(slice)?; - let ek_unchecked = - EncapsulationKey::from_slice(dk_unchecked.get_encapsulation_key_bytes())?; - - Ok(Self { - value: dk_unchecked, - cached_ek: ek_unchecked, - }) + let dk_unchecked = DecapKey::< + { MlKem1024Internal::K }, + { MlKem1024Internal::EK_SIZE }, + { MlKem1024Internal::DK_SIZE }, + MlKem1024Internal, + >::try_from_bytes(slice)?; + + Ok(Self::from_data(dk_unchecked)) } - /// Perform decapsulation of a [Ciphertext]. + /// Perform decapsulation of a [`Ciphertext`]. pub fn decap(&self, c: &Ciphertext) -> Result { + let ek = Public::::try_from(self.data.get_encapsulation_key_bytes())?; let mut c_prime_buf = [0u8; MlKem1024Internal::CIPHERTEXT_SIZE]; #[cfg(feature = "zeroize")] - let mut k_internal = self.value.mlkem_decap_internal_with_ek( - c.as_ref(), - &mut c_prime_buf, - &self.cached_ek.value, - )?; + let mut k_internal = + self.data + .mlkem_decap_internal_with_ek(c.as_ref(), &mut c_prime_buf, &ek.data)?; #[cfg(not(feature = "zeroize"))] - let k_internal = self.value.mlkem_decap_internal_with_ek( - c.as_ref(), - &mut c_prime_buf, - &self.cached_ek.value, - )?; + let k_internal = + self.data + .mlkem_decap_internal_with_ek(c.as_ref(), &mut c_prime_buf, &ek.data)?; - let k = SharedSecret::from_slice(&k_internal)?; + let k = SharedSecret::try_from(&k_internal)?; zeroize_call!(k_internal); Ok(k) } } -#[derive(Debug, PartialEq, Clone)] -/// A type to represent the `EncapsulationKey` that ML-KEM-1024 returns. -pub struct EncapsulationKey { - pub(crate) value: EncapKey<4, 1568, MlKem1024Internal>, +#[derive(Debug, PartialEq)] +/// ML-KEM-1024 keypair. +/// +/// This type uses cached encapsulation keys, saving the computation involved when doing decapsulation. +/// Meaning, once [`KeyPair`] has been instantiated, it is more efficient to use for decapsulation +/// than the [`DecapsulationKey`] type directly. +pub struct KeyPair { + seed: Seed, + private: DecapsulationKey, + public: EncapsulationKey, } -impl PartialEq<&[u8]> for EncapsulationKey { - fn eq(&self, other: &&[u8]) -> bool { - self.value == *other +impl KP for KeyPair { + fn public(&self) -> &EncapsulationKey { + &self.public } -} - -impl TryFrom<&DecapsulationKey> for EncapsulationKey { - type Error = UnknownCryptoError; - fn try_from(value: &DecapsulationKey) -> Result { - Ok(Self { - value: EncapKey::<4, 1568, MlKem1024Internal>::from_slice( - value.value.get_encapsulation_key_bytes(), - )?, - }) + fn private(&self) -> &DecapsulationKey { + &self.private } } -impl TryFrom<&[u8]> for EncapsulationKey { +impl TryFrom<&Seed> for KeyPair { type Error = UnknownCryptoError; - /// Instantiate a [EncapsulationKey] with key-checks from FIPS-203, section 7.2. - fn try_from(value: &[u8]) -> Result { + fn try_from(value: &Seed) -> Result { + let (ek, dk) = KeyPairInternal::::from_seed::< + { MlKem1024Internal::K }, + { MlKem1024Internal::EK_SIZE }, + { MlKem1024Internal::DK_SIZE }, + >(value)?; + Ok(Self { - value: EncapKey::<4, 1568, MlKem1024Internal>::from_slice(value)?, + seed: Seed::from_data(value.data.clone()), + private: Secret::::from_data(dk), + public: Public::::from_data(ek), }) } } -impl AsRef<[u8]> for EncapsulationKey { - fn as_ref(&self) -> &[u8] { - self.value.as_ref() +impl KeyPair { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + /// Generate a fresh [`KeyPair`]. + pub fn generate() -> Result { + let seed = Seed::generate()?; + Self::new(seed) + } + + /// Reference to the private [`Seed`]. + pub fn seed(&self) -> &Seed { + &self.seed } -} -impl EncapsulationKey { - /// Instantiate a [EncapsulationKey] with key-checks from FIPS-203, section 7.2. - pub fn from_slice(slice: &[u8]) -> Result { - Self::try_from(slice) + /// Create a new instance from a private [`Seed`]. + pub fn new(seed: Seed) -> Result { + let (ek, dk) = KeyPairInternal::::from_seed::< + { MlKem1024Internal::K }, + { MlKem1024Internal::EK_SIZE }, + { MlKem1024Internal::DK_SIZE }, + >(&seed)?; + + Ok(Self { + seed, + private: DecapsulationKey::from_data(dk), + public: EncapsulationKey::from_data(ek), + }) } #[cfg(feature = "safe_api")] #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] - /// Given the [EncapsulationKey], generate a [SharedSecret] and associated [Ciphertext]. - pub fn encap(&self) -> Result<(SharedSecret, Ciphertext), UnknownCryptoError> { - let mut m = zeroize_wrap!([0u8; 32]); - getrandom::fill(m.as_mut())?; + /// Instantiate a [`KeyPair`] with all key validation checks, described + /// in FIPS-203, Section 7.1, 7.2 and 7.3. + /// + /// The output keypair is the equivalent of using [`Self::try_from`], but this + /// can be used, in order to check whether a decapsulation key + /// is valid in relation to the `seed` provided. + pub fn from_keys(seed: &Seed, dk: &DecapsulationKey) -> Result { + let unchecked_ek = Public::::try_from(dk)?; + let (ek, dk) = KeyPairInternal::::from_keys::< + { MlKem1024Internal::K }, + { MlKem1024Internal::EK_SIZE }, + { MlKem1024Internal::DK_SIZE }, + { MlKem1024Internal::CIPHERTEXT_SIZE }, + >(seed, &unchecked_ek.data, &dk.data)?; - self.encap_deterministic(m.as_ref()) + Ok(Self { + seed: Seed::from_data(seed.data.clone()), + private: Secret::::from_data(dk), + public: Public::::from_data(ek), + }) } - /// Given the [EncapsulationKey] and randomness `m`, generate a [SharedSecret] and associated [Ciphertext]. - pub fn encap_deterministic( - &self, - m: &[u8], - ) -> Result<(SharedSecret, Ciphertext), UnknownCryptoError> { - if m.len() != 32 { - return Err(UnknownCryptoError); - } - - let mut c = Ciphertext::from_slice(&[0u8; MlKem1024Internal::CIPHERTEXT_SIZE])?; + /// Perform decapsulation of a [`Ciphertext`], using internally cached [`EncapsulationKey`]. + pub fn decap(&self, c: &Ciphertext) -> Result { + let mut c_prime_buf = [0u8; MlKem1024Internal::CIPHERTEXT_SIZE]; #[cfg(feature = "zeroize")] - let mut k_internal = self.value.mlkem_encap_internal(m.as_ref(), &mut c.value)?; + let mut k_internal = self.private.data.mlkem_decap_internal_with_ek( + c.as_ref(), + &mut c_prime_buf, + &self.public.data, + )?; #[cfg(not(feature = "zeroize"))] - let k_internal = self.value.mlkem_encap_internal(m.as_ref(), &mut c.value)?; + let k_internal = self.private.data.mlkem_decap_internal_with_ek( + c.as_ref(), + &mut c_prime_buf, + &self.public.data, + )?; - let k = SharedSecret::from_slice(k_internal.as_slice())?; + let k = SharedSecret::try_from(&k_internal)?; zeroize_call!(k_internal); - Ok((k, c)) + Ok(k) } } -#[derive(PartialEq, Debug)] -/// ML-KEM-1024. -pub struct MlKem1024; +#[cfg(test)] +mod tests { + use super::*; -impl MlKem1024 { - /// Encapsulation key size (bytes). - pub const EK_SIZE: usize = MlKem1024Internal::EK_SIZE; - /// Decapsulation key size (bytes). - pub const DK_SIZE: usize = MlKem1024Internal::DK_SIZE; - /// Ciphertext size (bytes). - pub const CIPHERTEXT_SIZE: usize = MlKem1024Internal::CIPHERTEXT_SIZE; - /// Shared Secret size (bytes). - pub const SHARED_SECRET_SIZE: usize = MlKem1024Internal::SHARED_SECRET_SIZE; + // NOTE(brycx): SecretNewtype/PublicNewtype tests aren't run for Encapsulation/Decapsulation keys + // because their underling TypeData structure is not compatible with the generic tests. - #[cfg(feature = "safe_api")] - #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] - /// Given the [EncapsulationKey], generate a [SharedSecret] and associated [Ciphertext]. - pub fn encap(ek: &EncapsulationKey) -> Result<(SharedSecret, Ciphertext), UnknownCryptoError> { - ek.encap() + #[test] + fn test_shared_secret() { + use crate::test_framework::newtypes::secret::SecretNewtype; + SecretNewtype::test_no_generate::< + SHARED_SECRET_SIZE, + SHARED_SECRET_SIZE, + MlKem1024SharedSecret, + >(); + // Test of From<[u8; N]> + assert_ne!( + SharedSecret::from([0u8; SHARED_SECRET_SIZE]), + SharedSecret::from([1u8; SHARED_SECRET_SIZE]) + ) } - /// Given the [DecapsulationKey], produce a [SharedSecret] using the [Ciphertext]. - pub fn decap( - dk: &DecapsulationKey, - c: &Ciphertext, - ) -> Result { - dk.decap(c) + #[test] + #[cfg(feature = "serde")] + fn test_encapuslation_key_serialization() { + use crate::test_framework::newtypes::public::PublicNewtype; + PublicNewtype::test_serialization::(); } -} -#[cfg(test)] -mod tests { - use super::*; + #[test] + fn test_ciphertext() { + use crate::test_framework::newtypes::public::PublicNewtype; + PublicNewtype::test_no_generate::(); + // Test of From<[u8; N]> + assert_ne!( + Ciphertext::from([0u8; CIPHERTEXT_SIZE]), + Ciphertext::from([1u8; CIPHERTEXT_SIZE]) + ); + + #[cfg(feature = "serde")] + PublicNewtype::test_serialization::(); + } #[cfg(feature = "safe_api")] use crate::test_framework::kem_interface::{KemTester, TestableKem}; #[cfg(feature = "safe_api")] - impl TestableKem for MlKem1024 { + impl TestableKem for KeyPair { fn keygen(seed: &[u8]) -> Result<(Vec, Vec), UnknownCryptoError> { - let kp = KeyPair::try_from(&Seed::from_slice(seed).unwrap()).unwrap(); + let kp = KeyPair::try_from(&Seed::try_from(seed)?)?; Ok(( - kp.dk.cached_ek.as_ref().to_vec(), - kp.dk.value.unprotected_as_bytes().to_vec(), + kp.public.data.bytes.as_ref().to_vec(), + kp.private.data.bytes.as_ref().to_vec(), )) } fn ciphertext_from_bytes(b: &[u8]) -> Result { - Ciphertext::from_slice(b) + Ciphertext::try_from(b) } fn encap(ek: &[u8]) -> Result<(SharedSecret, Ciphertext), UnknownCryptoError> { - let ek = EncapsulationKey::from_slice(ek).unwrap(); + let ek = Public::::try_from(ek)?; ek.encap() } fn decap(dk: &[u8], c: &Ciphertext) -> Result { - let dk = DecapsulationKey::unchecked_from_slice(dk).unwrap(); + let dk = DecapsulationKey::unchecked_from_slice(dk)?; dk.decap(c) } } #[test] fn test_keypair_dk_ek_match_internal() { - let seed = Seed::from_slice(&[128u8; 64]).unwrap(); + let seed = Seed::try_from(&[128u8; 64]).unwrap(); let kp = KeyPair::try_from(&seed).unwrap(); - assert_eq!(kp.public(), &kp.private().cached_ek); + assert_eq!( + &kp.public().as_ref(), + &kp.private().data.get_encapsulation_key_bytes() + ); + assert_eq!(kp.seed(), &seed); } #[test] #[cfg(feature = "safe_api")] fn test_dk_cached_ek() { - let seed = Seed::from_slice(&[128u8; 64]).unwrap(); + let seed = Seed::try_from(&[128u8; 64]).unwrap(); let kp = KeyPair::try_from(&seed).unwrap(); let (ss_pubapi, ct_pubapi) = kp.public().encap_deterministic(&[125u8; 32]).unwrap(); let mut c_prime = [0u8; MlKem1024Internal::CIPHERTEXT_SIZE]; // This call re-computes encap key internally from the bytes a decapkey would store. let ss_privapi = kp .private() - .value + .data .mlkem_decap_internal(ct_pubapi.as_ref(), &mut c_prime) .unwrap(); - assert_eq!(ss_privapi.as_ref(), ss_pubapi.unprotected_as_bytes()); - assert_eq!( - MlKem1024::decap(kp.private(), &ct_pubapi).unwrap(), - ss_pubapi - ); + assert_eq!(ss_privapi.as_ref(), ss_pubapi.unprotected_as_ref()); + assert_eq!(kp.decap(&ct_pubapi).unwrap(), ss_pubapi); } #[cfg(feature = "safe_api")] @@ -418,8 +485,8 @@ mod tests { fn test_dk_to_ek_conversions() { let kp = KeyPair::generate().unwrap(); assert_eq!( - kp.dk.cached_ek, - EncapsulationKey::try_from(kp.private()).unwrap() + Public::::try_from(kp.private()).unwrap(), + kp.public.as_ref(), ); } @@ -443,11 +510,11 @@ mod tests { #[cfg(feature = "safe_api")] #[test] fn test_dk_ek_partialeq() { - let s0 = Seed::generate(); + let s0 = Seed::generate().unwrap(); let kp = KeyPair::try_from(&s0).unwrap(); - let dk_bytes = kp.private().value.bytes; - let ek_bytes = kp.public().value.bytes; + let dk_bytes = kp.private().data.bytes; + let ek_bytes = kp.public().data.bytes; assert_eq!( KeyPair::try_from(&s0).unwrap().private(), @@ -459,13 +526,11 @@ mod tests { #[cfg(feature = "safe_api")] #[test] fn test_keypair_from_keys() { - let s0 = Seed::generate(); - let s1 = Seed::generate(); + let s0 = Seed::generate().unwrap(); + let s1 = Seed::generate().unwrap(); let kp0 = KeyPair::try_from(&s0).unwrap(); let kp1 = KeyPair::try_from(&s1).unwrap(); - assert_eq!(kp0.seed(), &s0); - assert_eq!(kp1.seed(), &s1); assert!(KeyPair::from_keys(&s0, kp0.private()).is_ok()); assert!(KeyPair::from_keys(&s1, kp1.private()).is_ok()); @@ -474,26 +539,24 @@ mod tests { let kp0_keys = KeyPair::from_keys(&s0, kp0.private()).unwrap(); let kp1_keys = KeyPair::from_keys(&s1, kp1.private()).unwrap(); - assert_eq!(kp0.seed(), kp0_keys.seed()); - assert_eq!(kp1.seed(), kp1_keys.seed()); assert_eq!(kp0.private(), kp0_keys.private()); assert_eq!(kp0.public(), kp0_keys.public()); + assert_eq!(kp1.private(), kp1_keys.private()); + assert_eq!(kp1.public(), kp1_keys.public()); } #[cfg(feature = "safe_api")] #[test] fn run_basic_kem_tests() { - let seed = Seed::generate(); - KemTester::::run_all_tests( - seed.unprotected_as_bytes(), - ); + let seed = Seed::generate().unwrap(); + KemTester::::run_all_tests(seed.unprotected_as_ref()); } #[test] /// Basic no_std-compatible test. fn basic_roundtrip() { - let seed = Seed::from_slice(&[127u8; 64]).unwrap(); + let seed = Seed::try_from(&[127u8; 64]).unwrap(); let kp = KeyPair::try_from(&seed).unwrap(); let (k, c) = kp.public().encap_deterministic(&[255u8; 32]).unwrap(); diff --git a/src/hazardous/kem/ml_kem/mlkem512.rs b/src/hazardous/kem/ml_kem/mlkem512.rs index 81c822fd3..6b021bad3 100644 --- a/src/hazardous/kem/ml_kem/mlkem512.rs +++ b/src/hazardous/kem/ml_kem/mlkem512.rs @@ -25,6 +25,8 @@ //! In general, it is highly recommended to use the [`KeyPair`] type to deal with decapsulating operations, or decapsulation keys in general. //! //! A [`KeyPair`] requires, or automatically generates, a [`Seed`]. It cannot be made solely from encoded/serialized decapsulation key in bytes, unless a [`Seed`] is also provided. +//! [`KeyPair`] also internally caches the [`EncapsulationKey`] used during decapsulation, making it more efficient when used to decapsulate multiple +//! KEM ciphertext with a given private [`DecapsulationKey`]. //! //! A seed is only 64 bytes, is fully FIPS compliant, and hardens against attacks described [here](https://eprint.iacr.org/2024/523). //! @@ -40,376 +42,443 @@ //! # Errors: //! An error will be returned if: //! - [`getrandom::fill()`] fails during encapsulation. -//! - `m` is not 32 bytes. -//! -//! # Panics: -//! A panic will occur if: //! - [`getrandom::fill()`] fails during [`KeyPair::generate()`]. +//! - `m` is not 32 bytes. //! //! # Security: //! - It is critical that both the seed and explicit randomness `m`, used for key generation and encapsulation //! are generated using a strong CSPRNG. -//! - Users should always prefer encapsulation without specifying explicit randomness, if possible. `encap_deterministic()` +//! - Users should always prefer encapsulation without specifying explicit randomness, if possible. [`EncapsulationKey::encap_deterministic()`] //! exists mainly for `no_std` usage. //! - Prefer using [`KeyPair`] to create and use ML-KEM keys, which is MAL-BIND-K-CT secure. //! //! # Example: //! ```rust //! # #[cfg(feature = "safe_api")] { +//! use orion::KP; //! use orion::hazardous::kem::mlkem512::*; //! -//! let keypair = KeyPair::generate()?; +//! let kp = KeyPair::generate()?; //! -//! let (sender_shared_secret, sender_ciphertext) = MlKem512::encap(keypair.public())?; -//! let recipient_shared_secret = MlKem512::decap(keypair.private(), &sender_ciphertext)?; +//! let ek = EncapsulationKey::try_from(kp.public().as_ref())?; +//! let (sender_shared_secret, sender_ciphertext) = ek.encap()?; +//! let recipient_shared_secret = kp.decap(&sender_ciphertext)?; //! //! assert_eq!(sender_shared_secret, recipient_shared_secret); //! # } //! # Ok::<(), orion::errors::UnknownCryptoError>(()) //! ``` //! [`getrandom::fill()`]: getrandom::fill -//! [`encap()`]: mlkem512::MlKem512::encap -//! [`decap()`]: mlkem512::MlKem512::decap //! [`KeyPair::generate()`]: mlkem512::KeyPair::generate //! [`KeyPair`]: mlkem512::KeyPair +//! [`EncapsulationKey`]: mlkem512::EncapsulationKey +//! [`EncapsulationKey::encap_deterministic()`]: mlkem512::EncapsulationKey::encap_deterministic //! [`Seed`]: mlkem512::Seed //! [`DecapsulationKey`]: mlkem512::DecapsulationKey -//! [`DecapsulationKey::unchecked_from_slice()`]: mlkem512::DecapsulationKey::unchecked_from_slice +//! [`DecapsulationKey::unchecked_from_slice()`]: mlkem512::DecapsulationKey::unchecked_from_slice +use crate::KP; use crate::errors::UnknownCryptoError; +use crate::generics::sealed::{Sealed, TryFromBytes}; +use crate::generics::{ByteArrayData, Public, Secret, TypeSpec}; use crate::hazardous::kem::ml_kem::internal::*; + +pub use crate::hazardous::kem::ml_kem::SEED_SIZE; pub use crate::hazardous::kem::ml_kem::Seed; -construct_secret_key! { - /// A type to represent the `SharedSecret` that ML-KEM-512 produces. - /// - /// This type simply holds bytes. Creating an instance from slices or similar, - /// performs no checks whatsoever. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 32 bytes. - (SharedSecret, test_shared_key, MlKem512Internal::SHARED_SECRET_SIZE, MlKem512Internal::SHARED_SECRET_SIZE) -} +/// Size of private [`EncapsulationKey`]. +pub const EK_SIZE: usize = MlKem512Internal::EK_SIZE; -impl_from_trait!(SharedSecret, MlKem512Internal::SHARED_SECRET_SIZE); +/// Size of public [`DecapsulationKey`]. +pub const DK_SIZE: usize = MlKem512Internal::DK_SIZE; -construct_public! { - /// A type to represent the KEM `Ciphertext` that ML-KEM-512 returns. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 768 bytes. - (Ciphertext, test_kem_ciphertext, MlKem512Internal::CIPHERTEXT_SIZE, MlKem512Internal::CIPHERTEXT_SIZE) -} +/// Size of public [`Ciphertext`]. +pub const CIPHERTEXT_SIZE: usize = MlKem512Internal::CIPHERTEXT_SIZE; -impl_from_trait!(Ciphertext, MlKem512Internal::CIPHERTEXT_SIZE); +/// Size of private [`SharedSecret`]. +pub const SHARED_SECRET_SIZE: usize = MlKem512Internal::SHARED_SECRET_SIZE; -#[derive(Debug, PartialEq)] -/// A keypair of ML-KEM-512 keys, that are derived from a given seed. -pub struct KeyPair { - seed: Seed, - dk: DecapsulationKey, -} +/// ML-KEM-512 ciphertext. +pub type Ciphertext = Public; -impl KeyPair { - #[cfg(feature = "safe_api")] - #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] - /// Generate a fresh [KeyPair]. - pub fn generate() -> Result { - let seed = Seed::generate(); - let (ek, dk) = KeyPairInternal::::from_seed::<2, 800, 1632>(&seed)?; +/// ML-KEM-512 shared secret. +pub type SharedSecret = Secret; - Ok(Self { - seed, - dk: DecapsulationKey { - value: dk, - cached_ek: EncapsulationKey { value: ek }, - }, - }) - } +/// ML-KEM-512 encapsulation key. +pub type EncapsulationKey = Public; - #[cfg(feature = "safe_api")] - #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] - /// Instantiate a [KeyPair] with all key validation checks, described - /// in FIPS-203, Section 7.1, 7.2 and 7.3. - /// - /// The output keypair is the equivalent of using `KeyPair::try_from(seed: &Seed)`, but this - /// can be used, in order to check whether a decapsulation key - /// is valid in relation to the `seed` provided. - pub fn from_keys(seed: &Seed, dk: &DecapsulationKey) -> Result { - let unchecked_ek = EncapsulationKey::try_from(dk)?; - let (ek, dk) = KeyPairInternal::::from_keys::<2, 800, 1632, 768>( - seed, - &unchecked_ek.value, - &dk.value, - )?; +/// ML-KEM-512 decapsulation key. +pub type DecapsulationKey = Secret; - Ok(Self { - seed: Seed::from_slice(seed.unprotected_as_bytes()).unwrap(), - dk: DecapsulationKey { - value: dk, - cached_ek: EncapsulationKey { value: ek }, - }, - }) - } +#[derive(Debug)] +/// ML-KEM-512 shared secret implementation. See [`SharedSecret`] type for convenience. +pub struct MlKem512SharedSecret {} +impl Sealed for MlKem512SharedSecret {} - /// Get the [Seed] used to generate this keypair. Use this function in order to store - /// the private part of the keypair and regenerate it, when needed. - pub fn seed(&self) -> &Seed { - &self.seed +impl TypeSpec for MlKem512SharedSecret { + const NAME: &'static str = stringify!(SharedSecret); + type TypeData = ByteArrayData; +} + +impl From<[u8; SHARED_SECRET_SIZE]> for Secret { + fn from(value: [u8; SHARED_SECRET_SIZE]) -> Self { + Self::from_data(::TypeData::from(value)) } +} + +#[derive(Debug, Clone, Copy)] +/// ML-KEM-512 ciphertext implementation. See [`Ciphertext`] type for convenience. +pub struct Mlkem512Ciphertext {} +impl Sealed for Mlkem512Ciphertext {} + +impl TypeSpec for Mlkem512Ciphertext { + const NAME: &'static str = stringify!(Ciphertext); + type TypeData = ByteArrayData; +} - /// Get the public [EncapsulationKey] corresponding to this keypair. - pub fn public(&self) -> &EncapsulationKey { - &self.dk.cached_ek +impl From<[u8; CIPHERTEXT_SIZE]> for Public { + fn from(value: [u8; CIPHERTEXT_SIZE]) -> Self { + Self::from_data(::TypeData::from(value)) } +} - /// Get the private [DecapsulationKey] used to generate this keypair. In order to store the private - /// part of this [KeyPair], use [KeyPair::seed()] instead. - pub fn private(&self) -> &DecapsulationKey { - &self.dk +#[derive(Debug)] +/// ML-KEM-512 decapsulation key implementation. See [`DecapsulationKey`] type for convenience. +pub struct MlKem512DecapKey {} +impl Sealed for MlKem512DecapKey {} +impl TypeSpec for MlKem512DecapKey { + const NAME: &'static str = stringify!(DecapsulationKey); + // Key-check logic in Data-impl under [`DecapKey`] (applies to `parse_bytes()`). + type TypeData = DecapKey< + { MlKem512Internal::K }, + { MlKem512Internal::EK_SIZE }, + { MlKem512Internal::DK_SIZE }, + MlKem512Internal, + >; +} + +#[derive(Debug, Clone, Copy)] +/// ML-KEM-512 encapsulation key implementation. See [`EncapsulationKey`] type for convenience. +pub struct MlKem512EncapKey {} +impl Sealed for MlKem512EncapKey {} +impl TypeSpec for MlKem512EncapKey { + const NAME: &'static str = stringify!(EncapsulationKey); + // Key-check logic in Data-impl under [`DecapKey`] (applies to `parse_bytes()`). + type TypeData = + EncapKey<{ MlKem512Internal::K }, { MlKem512Internal::EK_SIZE }, MlKem512Internal>; + + /// SECURITY: Override to vartime-[`PartialEq`] on a non-secret type, with a var-time one + /// to selectively only compare the encoded representation of encapsulation key. + fn vartime_partial_eq(lhs: &Self::TypeData, rhs: &[u8]) -> bool { + // NOTE: This compares only the encoded encapsulation key, so make sure the other fields + // aren't modifiable after instantiation, otherwise the encoded bytes might not correspond + // to the RingElements/Polynomials. + lhs.bytes.as_ref() == rhs } } -impl TryFrom<&Seed> for KeyPair { +impl TryFrom<&DecapsulationKey> for Public { type Error = UnknownCryptoError; - fn try_from(value: &Seed) -> Result { - let (ek, dk) = KeyPairInternal::::from_seed::<2, 800, 1632>(value)?; - - Ok(Self { - seed: Seed::from_slice(value.unprotected_as_bytes()).unwrap(), - dk: DecapsulationKey { - value: dk, - cached_ek: EncapsulationKey { value: ek }, - }, - }) + fn try_from(value: &DecapsulationKey) -> Result { + Ok(Self::from_data(EncapKey::< + { MlKem512Internal::K }, + { MlKem512Internal::EK_SIZE }, + MlKem512Internal, + >::from_bytes( + value.data.get_encapsulation_key_bytes() + )?)) } } -#[derive(Debug, PartialEq)] -/// A type to represent the `DecapsulationKey` that ML-KEM-512 produces. -pub struct DecapsulationKey { - pub(crate) value: DecapKey<2, 800, 1632, MlKem512Internal>, - // NOTE(brycx): This is simply a cache of the encapsulation key, so we avoid recomputing it - // on decap() operations. This is not a part of PartialEq, AsRef<> implementations or other logic - // pertaining to the `DecapsulationKey`, serving a purely internal purpose. - pub(crate) cached_ek: EncapsulationKey, -} +impl EncapsulationKey { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + /// Given the [`EncapsulationKey`], generate a [`SharedSecret`] and associated [`Ciphertext`]. + pub fn encap(&self) -> Result<(SharedSecret, Ciphertext), UnknownCryptoError> { + let mut m = zeroize_wrap!([0u8; 32]); + getrandom::fill(m.as_mut())?; + + self.encap_deterministic(m.as_ref()) + } + + /// Given the [`EncapsulationKey`] and randomness `m`, generate a [`SharedSecret`] and associated [`Ciphertext`]. + pub fn encap_deterministic( + &self, + m: &[u8], + ) -> Result<(SharedSecret, Ciphertext), UnknownCryptoError> { + if m.len() != 32 { + return Err(UnknownCryptoError); + } + + let mut c = Ciphertext::try_from(&[0u8; MlKem512Internal::CIPHERTEXT_SIZE])?; -impl PartialEq<&[u8]> for DecapsulationKey { - fn eq(&self, other: &&[u8]) -> bool { - // Defer to DecapKey<> impl ct-eq - self.value == *other + #[cfg(feature = "zeroize")] + let mut k_internal = self + .data + .mlkem_encap_internal(m.as_ref(), c.data.as_mut())?; + #[cfg(not(feature = "zeroize"))] + let k_internal = self + .data + .mlkem_encap_internal(m.as_ref(), c.data.as_mut())?; + + let k = SharedSecret::try_from(k_internal.as_slice())?; + zeroize_call!(k_internal); + + Ok((k, c)) } } impl DecapsulationKey { - /// Instantiate a [DecapsulationKey] with only key-checks from FIPS-203, section 7.3. Not MAL-BIND-K-CT secure. + /// Instantiate a [`DecapsulationKey`] with only key-checks from FIPS-203, section 7.3. Not MAL-BIND-K-CT secure. pub fn unchecked_from_slice(slice: &[u8]) -> Result { - let dk_unchecked = DecapKey::<2, 800, 1632, MlKem512Internal>::unchecked_from_slice(slice)?; - let ek_unchecked = - EncapsulationKey::from_slice(dk_unchecked.get_encapsulation_key_bytes())?; - - Ok(Self { - value: dk_unchecked, - cached_ek: ek_unchecked, - }) + let dk_unchecked = DecapKey::< + { MlKem512Internal::K }, + { MlKem512Internal::EK_SIZE }, + { MlKem512Internal::DK_SIZE }, + MlKem512Internal, + >::try_from_bytes(slice)?; + + Ok(Self::from_data(dk_unchecked)) } - /// Perform decapsulation of a [Ciphertext]. + /// Perform decapsulation of a [`Ciphertext`]. pub fn decap(&self, c: &Ciphertext) -> Result { + let ek = Public::::try_from(self.data.get_encapsulation_key_bytes())?; let mut c_prime_buf = [0u8; MlKem512Internal::CIPHERTEXT_SIZE]; #[cfg(feature = "zeroize")] - let mut k_internal = self.value.mlkem_decap_internal_with_ek( - c.as_ref(), - &mut c_prime_buf, - &self.cached_ek.value, - )?; + let mut k_internal = + self.data + .mlkem_decap_internal_with_ek(c.as_ref(), &mut c_prime_buf, &ek.data)?; #[cfg(not(feature = "zeroize"))] - let k_internal = self.value.mlkem_decap_internal_with_ek( - c.as_ref(), - &mut c_prime_buf, - &self.cached_ek.value, - )?; + let k_internal = + self.data + .mlkem_decap_internal_with_ek(c.as_ref(), &mut c_prime_buf, &ek.data)?; - let k = SharedSecret::from_slice(&k_internal)?; + let k = SharedSecret::try_from(&k_internal)?; zeroize_call!(k_internal); Ok(k) } } -#[derive(Debug, PartialEq, Clone)] -/// A type to represent the `EncapsulationKey` that ML-KEM-512 returns. -pub struct EncapsulationKey { - pub(crate) value: EncapKey<2, 800, MlKem512Internal>, +#[derive(Debug, PartialEq)] +/// ML-KEM-512 keypair. +/// +/// This type uses cached encapsulation keys, saving the computation involved when doing decapsulation. +/// Meaning, once [`KeyPair`] has been instantiated, it is more efficient to use for decapsulation +/// than the [`DecapsulationKey`] type directly. +pub struct KeyPair { + seed: Seed, + private: DecapsulationKey, + public: EncapsulationKey, } -impl PartialEq<&[u8]> for EncapsulationKey { - fn eq(&self, other: &&[u8]) -> bool { - self.value == *other +impl KP for KeyPair { + fn public(&self) -> &EncapsulationKey { + &self.public } -} - -impl TryFrom<&DecapsulationKey> for EncapsulationKey { - type Error = UnknownCryptoError; - fn try_from(value: &DecapsulationKey) -> Result { - Ok(Self { - value: EncapKey::<2, 800, MlKem512Internal>::from_slice( - value.value.get_encapsulation_key_bytes(), - )?, - }) + fn private(&self) -> &DecapsulationKey { + &self.private } } -impl TryFrom<&[u8]> for EncapsulationKey { +impl TryFrom<&Seed> for KeyPair { type Error = UnknownCryptoError; - /// Instantiate a [EncapsulationKey] with key-checks from FIPS-203, section 7.2. - fn try_from(value: &[u8]) -> Result { + fn try_from(value: &Seed) -> Result { + let (ek, dk) = KeyPairInternal::::from_seed::< + { MlKem512Internal::K }, + { MlKem512Internal::EK_SIZE }, + { MlKem512Internal::DK_SIZE }, + >(value)?; + Ok(Self { - value: EncapKey::<2, 800, MlKem512Internal>::from_slice(value)?, + seed: Seed::from_data(value.data.clone()), + private: Secret::::from_data(dk), + public: Public::::from_data(ek), }) } } -impl AsRef<[u8]> for EncapsulationKey { - fn as_ref(&self) -> &[u8] { - self.value.as_ref() +impl KeyPair { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + /// Generate a fresh [`KeyPair`]. + pub fn generate() -> Result { + let seed = Seed::generate()?; + Self::new(seed) + } + + /// Reference to the private [`Seed`]. + pub fn seed(&self) -> &Seed { + &self.seed } -} -impl EncapsulationKey { - /// Instantiate a [EncapsulationKey] with key-checks from FIPS-203, section 7.2. - pub fn from_slice(slice: &[u8]) -> Result { - Self::try_from(slice) + /// Create a new instance from a private [`Seed`]. + pub fn new(seed: Seed) -> Result { + let (ek, dk) = KeyPairInternal::::from_seed::< + { MlKem512Internal::K }, + { MlKem512Internal::EK_SIZE }, + { MlKem512Internal::DK_SIZE }, + >(&seed)?; + + Ok(Self { + seed, + private: DecapsulationKey::from_data(dk), + public: EncapsulationKey::from_data(ek), + }) } #[cfg(feature = "safe_api")] #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] - /// Given the [EncapsulationKey], generate a [SharedSecret] and associated [Ciphertext]. - pub fn encap(&self) -> Result<(SharedSecret, Ciphertext), UnknownCryptoError> { - let mut m = zeroize_wrap!([0u8; 32]); - getrandom::fill(m.as_mut())?; + /// Instantiate a [`KeyPair`] with all key validation checks, described + /// in FIPS-203, Section 7.1, 7.2 and 7.3. + /// + /// The output keypair is the equivalent of using [`Self::try_from`], but this + /// can be used, in order to check whether a decapsulation key + /// is valid in relation to the `seed` provided. + pub fn from_keys(seed: &Seed, dk: &DecapsulationKey) -> Result { + let unchecked_ek = Public::::try_from(dk)?; + let (ek, dk) = KeyPairInternal::::from_keys::< + { MlKem512Internal::K }, + { MlKem512Internal::EK_SIZE }, + { MlKem512Internal::DK_SIZE }, + { MlKem512Internal::CIPHERTEXT_SIZE }, + >(seed, &unchecked_ek.data, &dk.data)?; - self.encap_deterministic(m.as_ref()) + Ok(Self { + seed: Seed::from_data(seed.data.clone()), + private: Secret::::from_data(dk), + public: Public::::from_data(ek), + }) } - /// Given the [EncapsulationKey] and randomness `m`, generate a [SharedSecret] and associated [Ciphertext]. - pub fn encap_deterministic( - &self, - m: &[u8], - ) -> Result<(SharedSecret, Ciphertext), UnknownCryptoError> { - if m.len() != 32 { - return Err(UnknownCryptoError); - } - - let mut c = Ciphertext::from_slice(&[0u8; MlKem512Internal::CIPHERTEXT_SIZE])?; + /// Perform decapsulation of a [`Ciphertext`], using internally cached [`EncapsulationKey`]. + pub fn decap(&self, c: &Ciphertext) -> Result { + let mut c_prime_buf = [0u8; MlKem512Internal::CIPHERTEXT_SIZE]; #[cfg(feature = "zeroize")] - let mut k_internal = self.value.mlkem_encap_internal(m.as_ref(), &mut c.value)?; + let mut k_internal = self.private.data.mlkem_decap_internal_with_ek( + c.as_ref(), + &mut c_prime_buf, + &self.public.data, + )?; #[cfg(not(feature = "zeroize"))] - let k_internal = self.value.mlkem_encap_internal(m.as_ref(), &mut c.value)?; + let k_internal = self.private.data.mlkem_decap_internal_with_ek( + c.as_ref(), + &mut c_prime_buf, + &self.public.data, + )?; - let k = SharedSecret::from_slice(k_internal.as_slice())?; + let k = SharedSecret::try_from(&k_internal)?; zeroize_call!(k_internal); - Ok((k, c)) + Ok(k) } } -#[derive(PartialEq, Debug)] -/// ML-KEM-512. -pub struct MlKem512; +#[cfg(test)] +mod tests { + use super::*; -impl MlKem512 { - /// Encapsulation key size (bytes). - pub const EK_SIZE: usize = MlKem512Internal::EK_SIZE; - /// Decapsulation key size (bytes). - pub const DK_SIZE: usize = MlKem512Internal::DK_SIZE; - /// Ciphertext size (bytes). - pub const CIPHERTEXT_SIZE: usize = MlKem512Internal::CIPHERTEXT_SIZE; - /// Shared Secret size (bytes). - pub const SHARED_SECRET_SIZE: usize = MlKem512Internal::SHARED_SECRET_SIZE; + // NOTE(brycx): SecretNewtype/PublicNewtype tests aren't run for Encapsulation/Decapsulation keys + // because their underling TypeData structure is not compatible with the generic tests. - #[cfg(feature = "safe_api")] - #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] - /// Given the [EncapsulationKey], generate a [SharedSecret] and associated [Ciphertext]. - pub fn encap(ek: &EncapsulationKey) -> Result<(SharedSecret, Ciphertext), UnknownCryptoError> { - ek.encap() + #[test] + fn test_shared_secret() { + use crate::test_framework::newtypes::secret::SecretNewtype; + SecretNewtype::test_no_generate::< + SHARED_SECRET_SIZE, + SHARED_SECRET_SIZE, + MlKem512SharedSecret, + >(); + // Test of From<[u8; N]> + assert_ne!( + SharedSecret::from([0u8; SHARED_SECRET_SIZE]), + SharedSecret::from([1u8; SHARED_SECRET_SIZE]) + ) } - /// Given the [DecapsulationKey], produce a [SharedSecret] using the [Ciphertext]. - pub fn decap( - dk: &DecapsulationKey, - c: &Ciphertext, - ) -> Result { - dk.decap(c) + #[test] + #[cfg(feature = "serde")] + fn test_encapuslation_key_serialization() { + use crate::test_framework::newtypes::public::PublicNewtype; + PublicNewtype::test_serialization::(); } -} -#[cfg(test)] -mod tests { - use super::*; + #[test] + fn test_ciphertext() { + use super::*; + use crate::test_framework::newtypes::public::PublicNewtype; + PublicNewtype::test_no_generate::(); + // Test of From<[u8; N]> + assert_ne!( + Ciphertext::from([0u8; CIPHERTEXT_SIZE]), + Ciphertext::from([1u8; CIPHERTEXT_SIZE]) + ); + + #[cfg(feature = "serde")] + PublicNewtype::test_serialization::(); + } #[cfg(feature = "safe_api")] use crate::test_framework::kem_interface::{KemTester, TestableKem}; #[cfg(feature = "safe_api")] - impl TestableKem for MlKem512 { + impl TestableKem for KeyPair { fn keygen(seed: &[u8]) -> Result<(Vec, Vec), UnknownCryptoError> { - let kp = KeyPair::try_from(&Seed::from_slice(seed).unwrap()).unwrap(); + let kp = KeyPair::try_from(&Seed::try_from(seed)?)?; Ok(( - kp.dk.cached_ek.as_ref().to_vec(), - kp.dk.value.unprotected_as_bytes().to_vec(), + kp.public.data.bytes.as_ref().to_vec(), + kp.private.data.bytes.as_ref().to_vec(), )) } fn ciphertext_from_bytes(b: &[u8]) -> Result { - Ciphertext::from_slice(b) + Ciphertext::try_from(b) } fn encap(ek: &[u8]) -> Result<(SharedSecret, Ciphertext), UnknownCryptoError> { - let ek = EncapsulationKey::from_slice(ek).unwrap(); + let ek = Public::::try_from(ek)?; ek.encap() } fn decap(dk: &[u8], c: &Ciphertext) -> Result { - let dk = DecapsulationKey::unchecked_from_slice(dk).unwrap(); + let dk = DecapsulationKey::unchecked_from_slice(dk)?; dk.decap(c) } } #[test] fn test_keypair_dk_ek_match_internal() { - let seed = Seed::from_slice(&[128u8; 64]).unwrap(); + let seed = Seed::try_from(&[128u8; 64]).unwrap(); let kp = KeyPair::try_from(&seed).unwrap(); - assert_eq!(kp.public(), &kp.private().cached_ek); + assert_eq!( + &kp.public().as_ref(), + &kp.private().data.get_encapsulation_key_bytes() + ); + assert_eq!(kp.seed(), &seed); } #[test] #[cfg(feature = "safe_api")] fn test_dk_cached_ek() { - let seed = Seed::from_slice(&[128u8; 64]).unwrap(); + let seed = Seed::try_from(&[128u8; 64]).unwrap(); let kp = KeyPair::try_from(&seed).unwrap(); let (ss_pubapi, ct_pubapi) = kp.public().encap_deterministic(&[125u8; 32]).unwrap(); let mut c_prime = [0u8; MlKem512Internal::CIPHERTEXT_SIZE]; // This call re-computes encap key internally from the bytes a decapkey would store. let ss_privapi = kp .private() - .value + .data .mlkem_decap_internal(ct_pubapi.as_ref(), &mut c_prime) .unwrap(); - assert_eq!(ss_privapi.as_ref(), ss_pubapi.unprotected_as_bytes()); - assert_eq!( - MlKem512::decap(kp.private(), &ct_pubapi).unwrap(), - ss_pubapi - ); + assert_eq!(ss_privapi.as_ref(), ss_pubapi.unprotected_as_ref()); + assert_eq!(kp.decap(&ct_pubapi).unwrap(), ss_pubapi); } #[cfg(feature = "safe_api")] @@ -417,8 +486,8 @@ mod tests { fn test_dk_to_ek_conversions() { let kp = KeyPair::generate().unwrap(); assert_eq!( - kp.dk.cached_ek, - EncapsulationKey::try_from(kp.private()).unwrap() + Public::::try_from(kp.private()).unwrap(), + kp.public.as_ref(), ); } @@ -442,11 +511,11 @@ mod tests { #[cfg(feature = "safe_api")] #[test] fn test_dk_ek_partialeq() { - let s0 = Seed::generate(); + let s0 = Seed::generate().unwrap(); let kp = KeyPair::try_from(&s0).unwrap(); - let dk_bytes = kp.private().value.bytes; - let ek_bytes = kp.public().value.bytes; + let dk_bytes = kp.private().data.bytes; + let ek_bytes = kp.public().data.bytes; assert_eq!( KeyPair::try_from(&s0).unwrap().private(), @@ -458,13 +527,11 @@ mod tests { #[cfg(feature = "safe_api")] #[test] fn test_keypair_from_keys() { - let s0 = Seed::generate(); - let s1 = Seed::generate(); + let s0 = Seed::generate().unwrap(); + let s1 = Seed::generate().unwrap(); let kp0 = KeyPair::try_from(&s0).unwrap(); let kp1 = KeyPair::try_from(&s1).unwrap(); - assert_eq!(kp0.seed(), &s0); - assert_eq!(kp1.seed(), &s1); assert!(KeyPair::from_keys(&s0, kp0.private()).is_ok()); assert!(KeyPair::from_keys(&s1, kp1.private()).is_ok()); @@ -473,24 +540,24 @@ mod tests { let kp0_keys = KeyPair::from_keys(&s0, kp0.private()).unwrap(); let kp1_keys = KeyPair::from_keys(&s1, kp1.private()).unwrap(); - assert_eq!(kp0.seed(), kp0_keys.seed()); - assert_eq!(kp1.seed(), kp1_keys.seed()); assert_eq!(kp0.private(), kp0_keys.private()); assert_eq!(kp0.public(), kp0_keys.public()); + assert_eq!(kp1.private(), kp1_keys.private()); + assert_eq!(kp1.public(), kp1_keys.public()); } #[cfg(feature = "safe_api")] #[test] fn run_basic_kem_tests() { - let seed = Seed::generate(); - KemTester::::run_all_tests(seed.unprotected_as_bytes()); + let seed = Seed::generate().unwrap(); + KemTester::::run_all_tests(seed.unprotected_as_ref()); } #[test] /// Basic no_std-compatible test. fn basic_roundtrip() { - let seed = Seed::from_slice(&[127u8; 64]).unwrap(); + let seed = Seed::try_from(&[127u8; 64]).unwrap(); let kp = KeyPair::try_from(&seed).unwrap(); let (k, c) = kp.public().encap_deterministic(&[255u8; 32]).unwrap(); diff --git a/src/hazardous/kem/ml_kem/mlkem768.rs b/src/hazardous/kem/ml_kem/mlkem768.rs index 68d8d2bb1..36d1e93f7 100644 --- a/src/hazardous/kem/ml_kem/mlkem768.rs +++ b/src/hazardous/kem/ml_kem/mlkem768.rs @@ -25,6 +25,8 @@ //! In general, it is highly recommended to use the [`KeyPair`] type to deal with decapsulating operations, or decapsulation keys in general. //! //! A [`KeyPair`] requires, or automatically generates, a [`Seed`]. It cannot be made solely from encoded/serialized decapsulation key in bytes, unless a [`Seed`] is also provided. +//! [`KeyPair`] also internally caches the [`EncapsulationKey`] used during decapsulation, making it more efficient when used to decapsulate multiple +//! KEM ciphertext with a given private [`DecapsulationKey`]. //! //! A seed is only 64 bytes, is fully FIPS compliant, and hardens against attacks described [here](https://eprint.iacr.org/2024/523). //! @@ -40,377 +42,443 @@ //! # Errors: //! An error will be returned if: //! - [`getrandom::fill()`] fails during encapsulation. -//! - `m` is not 32 bytes. -//! -//! # Panics: -//! A panic will occur if: //! - [`getrandom::fill()`] fails during [`KeyPair::generate()`]. +//! - `m` is not 32 bytes. //! //! # Security: //! - It is critical that both the seed and explicit randomness `m`, used for key generation and encapsulation //! are generated using a strong CSPRNG. -//! - Users should always prefer encapsulation without specifying explicit randomness, if possible. `encap_deterministic()` +//! - Users should always prefer encapsulation without specifying explicit randomness, if possible. [`EncapsulationKey::encap_deterministic()`] //! exists mainly for `no_std` usage. //! - Prefer using [`KeyPair`] to create and use ML-KEM keys, which is MAL-BIND-K-CT secure. //! //! # Example: //! ```rust //! # #[cfg(feature = "safe_api")] { +//! use orion::KP; //! use orion::hazardous::kem::mlkem768::*; //! -//! let keypair = KeyPair::generate()?; +//! let kp = KeyPair::generate()?; //! -//! let (sender_shared_secret, sender_ciphertext) = MlKem768::encap(keypair.public())?; -//! let recipient_shared_secret = MlKem768::decap(keypair.private(), &sender_ciphertext)?; +//! let ek = EncapsulationKey::try_from(kp.public().as_ref())?; +//! let (sender_shared_secret, sender_ciphertext) = ek.encap()?; +//! let recipient_shared_secret = kp.decap(&sender_ciphertext)?; //! //! assert_eq!(sender_shared_secret, recipient_shared_secret); //! # } //! # Ok::<(), orion::errors::UnknownCryptoError>(()) //! ``` //! [`getrandom::fill()`]: getrandom::fill -//! [`encap()`]: mlkem768::MlKem768::encap -//! [`decap()`]: mlkem768::MlKem768::decap //! [`KeyPair::generate()`]: mlkem768::KeyPair::generate //! [`KeyPair`]: mlkem768::KeyPair +//! [`EncapsulationKey`]: mlkem768::EncapsulationKey +//! [`EncapsulationKey::encap_deterministic()`]: mlkem768::EncapsulationKey::encap_deterministic //! [`Seed`]: mlkem768::Seed //! [`DecapsulationKey`]: mlkem768::DecapsulationKey -//! [`DecapsulationKey::unchecked_from_slice()`]: mlkem768::DecapsulationKey::unchecked_from_slice +//! [`DecapsulationKey::unchecked_from_slice()`]: mlkem768::DecapsulationKey::unchecked_from_slice +use crate::KP; use crate::errors::UnknownCryptoError; +use crate::generics::sealed::{Sealed, TryFromBytes}; +use crate::generics::{ByteArrayData, Public, Secret, TypeSpec}; use crate::hazardous::kem::ml_kem::internal::*; + +pub use crate::hazardous::kem::ml_kem::SEED_SIZE; pub use crate::hazardous::kem::ml_kem::Seed; -construct_secret_key! { - /// A type to represent the `SharedSecret` that ML-KEM-768 produces. - /// - /// This type simply holds bytes. Creating an instance from slices or similar, - /// performs no checks whatsoever. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 32 bytes. - (SharedSecret, test_shared_key, MlKem768Internal::SHARED_SECRET_SIZE, MlKem768Internal::SHARED_SECRET_SIZE) -} +/// Size of private [`EncapsulationKey`]. +pub const EK_SIZE: usize = MlKem768Internal::EK_SIZE; -impl_from_trait!(SharedSecret, MlKem768Internal::SHARED_SECRET_SIZE); +/// Size of public [`DecapsulationKey`]. +pub const DK_SIZE: usize = MlKem768Internal::DK_SIZE; -construct_public! { - /// A type to represent the KEM `Ciphertext` that ML-KEM-768 returns. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 1088 bytes. - (Ciphertext, test_kem_ciphertext, MlKem768Internal::CIPHERTEXT_SIZE, MlKem768Internal::CIPHERTEXT_SIZE) -} +/// Size of public [`Ciphertext`]. +pub const CIPHERTEXT_SIZE: usize = MlKem768Internal::CIPHERTEXT_SIZE; -impl_from_trait!(Ciphertext, MlKem768Internal::CIPHERTEXT_SIZE); +/// Size of private [`SharedSecret`]. +pub const SHARED_SECRET_SIZE: usize = MlKem768Internal::SHARED_SECRET_SIZE; -#[derive(Debug, PartialEq)] -/// A keypair of ML-KEM-768 keys, that are derived from a given seed. -pub struct KeyPair { - seed: Seed, - dk: DecapsulationKey, -} +/// ML-KEM-768 ciphertext. +pub type Ciphertext = Public; -impl KeyPair { - #[cfg(feature = "safe_api")] - #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] - /// Generate a fresh [KeyPair]. - pub fn generate() -> Result { - let seed = Seed::generate(); - let (ek, dk) = KeyPairInternal::::from_seed::<3, 1184, 2400>(&seed)?; +/// ML-KEM-768 shared secret. +pub type SharedSecret = Secret; - Ok(Self { - seed, - dk: DecapsulationKey { - value: dk, - cached_ek: EncapsulationKey { value: ek }, - }, - }) - } +/// ML-KEM-768 encapsulation key. +pub type EncapsulationKey = Public; - #[cfg(feature = "safe_api")] - #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] - /// Instantiate a [KeyPair] with all key validation checks, described - /// in FIPS-203, Section 7.1, 7.2 and 7.3. - /// - /// The output keypair is the equivalent of using `KeyPair::try_from(seed: &Seed)`, but this - /// can be used, in order to check whether a decapsulation key - /// is valid in relation to the `seed` provided. - pub fn from_keys(seed: &Seed, dk: &DecapsulationKey) -> Result { - let unchecked_ek = EncapsulationKey::try_from(dk)?; - let (ek, dk) = KeyPairInternal::::from_keys::<3, 1184, 2400, 1088>( - seed, - &unchecked_ek.value, - &dk.value, - )?; +/// ML-KEM-768 decapsulation key. +pub type DecapsulationKey = Secret; - Ok(Self { - seed: Seed::from_slice(seed.unprotected_as_bytes()).unwrap(), - dk: DecapsulationKey { - value: dk, - cached_ek: EncapsulationKey { value: ek }, - }, - }) - } +#[derive(Debug)] +/// ML-KEM-768 shared secret implementation. See [`SharedSecret`] type for convenience. +pub struct MlKem768SharedSecret {} +impl Sealed for MlKem768SharedSecret {} - /// Get the [Seed] used to generate this keypair. Use this function in order to store - /// the private part of the keypair and regenerate it, when needed. - pub fn seed(&self) -> &Seed { - &self.seed +impl TypeSpec for MlKem768SharedSecret { + const NAME: &'static str = stringify!(SharedSecret); + type TypeData = ByteArrayData; +} + +impl From<[u8; SHARED_SECRET_SIZE]> for Secret { + fn from(value: [u8; SHARED_SECRET_SIZE]) -> Self { + Self::from_data(::TypeData::from(value)) } +} + +#[derive(Debug, Clone, Copy)] +/// ML-KEM-768 ciphertext implementation. See [`Ciphertext`] type for convenience. +pub struct Mlkem768Ciphertext {} +impl Sealed for Mlkem768Ciphertext {} + +impl TypeSpec for Mlkem768Ciphertext { + const NAME: &'static str = stringify!(Ciphertext); + type TypeData = ByteArrayData; +} - /// Get the public [EncapsulationKey] corresponding to this keypair. - pub fn public(&self) -> &EncapsulationKey { - &self.dk.cached_ek +impl From<[u8; CIPHERTEXT_SIZE]> for Public { + fn from(value: [u8; CIPHERTEXT_SIZE]) -> Self { + Self::from_data(::TypeData::from(value)) } +} - /// Get the private [DecapsulationKey] used to generate this keypair. In order to store the private - /// part of this [KeyPair], use [KeyPair::seed()] instead. - pub fn private(&self) -> &DecapsulationKey { - &self.dk +#[derive(Debug)] +/// ML-KEM-768 decapsulation key implementation. See [`DecapsulationKey`] type for convenience. +pub struct MlKem768DecapKey {} +impl Sealed for MlKem768DecapKey {} +impl TypeSpec for MlKem768DecapKey { + const NAME: &'static str = stringify!(DecapsulationKey); + // Key-check logic in Data-impl under [`DecapKey`] (applies to `parse_bytes()`). + type TypeData = DecapKey< + { MlKem768Internal::K }, + { MlKem768Internal::EK_SIZE }, + { MlKem768Internal::DK_SIZE }, + MlKem768Internal, + >; +} + +#[derive(Debug, Clone, Copy)] +/// ML-KEM-768 encapsulation key implementation. See [`EncapsulationKey`] type for convenience. +pub struct MlKem768EncapKey {} +impl Sealed for MlKem768EncapKey {} +impl TypeSpec for MlKem768EncapKey { + const NAME: &'static str = stringify!(EncapsulationKey); + // Key-check logic in Data-impl under [`DecapKey`] (applies to `parse_bytes()`). + type TypeData = + EncapKey<{ MlKem768Internal::K }, { MlKem768Internal::EK_SIZE }, MlKem768Internal>; + + /// SECURITY: Override to vartime-[`PartialEq`] on a non-secret type, with a var-time one + /// to selectively only compare the encoded representation of encapsulation key. + fn vartime_partial_eq(lhs: &Self::TypeData, rhs: &[u8]) -> bool { + // NOTE: This compares only the encoded encapsulation key, so make sure the other fields + // aren't modifiable after instantiation, otherwise the encoded bytes might not correspond + // to the RingElements/Polynomials. + lhs.bytes.as_ref() == rhs } } -impl TryFrom<&Seed> for KeyPair { +impl TryFrom<&DecapsulationKey> for Public { type Error = UnknownCryptoError; - fn try_from(value: &Seed) -> Result { - let (ek, dk) = KeyPairInternal::::from_seed::<3, 1184, 2400>(value)?; - - Ok(Self { - seed: Seed::from_slice(value.unprotected_as_bytes()).unwrap(), - dk: DecapsulationKey { - value: dk, - cached_ek: EncapsulationKey { value: ek }, - }, - }) + fn try_from(value: &DecapsulationKey) -> Result { + Ok(Self::from_data(EncapKey::< + { MlKem768Internal::K }, + { MlKem768Internal::EK_SIZE }, + MlKem768Internal, + >::from_bytes( + value.data.get_encapsulation_key_bytes() + )?)) } } -#[derive(Debug, PartialEq)] -/// A type to represent the `DecapsulationKey` that ML-KEM-768 produces. -pub struct DecapsulationKey { - pub(crate) value: DecapKey<3, 1184, 2400, MlKem768Internal>, - // NOTE(brycx): This is simply a cache of the encapsulation key, so we avoid recomputing it - // on decap() operations. This is not a part of PartialEq, AsRef<> implementations or other logic - // pertaining to the `DecapsulationKey`, serving a purely internal purpose. - pub(crate) cached_ek: EncapsulationKey, -} +impl EncapsulationKey { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + /// Given the [`EncapsulationKey`], generate a [`SharedSecret`] and associated [`Ciphertext`]. + pub fn encap(&self) -> Result<(SharedSecret, Ciphertext), UnknownCryptoError> { + let mut m = zeroize_wrap!([0u8; 32]); + getrandom::fill(m.as_mut())?; + + self.encap_deterministic(m.as_ref()) + } + + /// Given the [`EncapsulationKey`] and randomness `m`, generate a [`SharedSecret`] and associated [`Ciphertext`]. + pub fn encap_deterministic( + &self, + m: &[u8], + ) -> Result<(SharedSecret, Ciphertext), UnknownCryptoError> { + if m.len() != 32 { + return Err(UnknownCryptoError); + } + + let mut c = Ciphertext::try_from(&[0u8; MlKem768Internal::CIPHERTEXT_SIZE])?; -impl PartialEq<&[u8]> for DecapsulationKey { - fn eq(&self, other: &&[u8]) -> bool { - // Defer to DecapKey<> impl ct-eq - self.value == *other + #[cfg(feature = "zeroize")] + let mut k_internal = self + .data + .mlkem_encap_internal(m.as_ref(), c.data.as_mut())?; + #[cfg(not(feature = "zeroize"))] + let k_internal = self + .data + .mlkem_encap_internal(m.as_ref(), c.data.as_mut())?; + + let k = SharedSecret::try_from(k_internal.as_slice())?; + zeroize_call!(k_internal); + + Ok((k, c)) } } impl DecapsulationKey { - /// Instantiate a [DecapsulationKey] with only key-checks from FIPS-203, section 7.3. Not MAL-BIND-K-CT secure. + /// Instantiate a [`DecapsulationKey`] with only key-checks from FIPS-203, section 7.3. Not MAL-BIND-K-CT secure. pub fn unchecked_from_slice(slice: &[u8]) -> Result { - let dk_unchecked = - DecapKey::<3, 1184, 2400, MlKem768Internal>::unchecked_from_slice(slice)?; - let ek_unchecked = - EncapsulationKey::from_slice(dk_unchecked.get_encapsulation_key_bytes())?; - - Ok(Self { - value: dk_unchecked, - cached_ek: ek_unchecked, - }) + let dk_unchecked = DecapKey::< + { MlKem768Internal::K }, + { MlKem768Internal::EK_SIZE }, + { MlKem768Internal::DK_SIZE }, + MlKem768Internal, + >::try_from_bytes(slice)?; + + Ok(Self::from_data(dk_unchecked)) } - /// Perform decapsulation of a [Ciphertext]. + /// Perform decapsulation of a [`Ciphertext`]. pub fn decap(&self, c: &Ciphertext) -> Result { + let ek = Public::::try_from(self.data.get_encapsulation_key_bytes())?; let mut c_prime_buf = [0u8; MlKem768Internal::CIPHERTEXT_SIZE]; #[cfg(feature = "zeroize")] - let mut k_internal = self.value.mlkem_decap_internal_with_ek( - c.as_ref(), - &mut c_prime_buf, - &self.cached_ek.value, - )?; + let mut k_internal = + self.data + .mlkem_decap_internal_with_ek(c.as_ref(), &mut c_prime_buf, &ek.data)?; #[cfg(not(feature = "zeroize"))] - let k_internal = self.value.mlkem_decap_internal_with_ek( - c.as_ref(), - &mut c_prime_buf, - &self.cached_ek.value, - )?; + let k_internal = + self.data + .mlkem_decap_internal_with_ek(c.as_ref(), &mut c_prime_buf, &ek.data)?; - let k = SharedSecret::from_slice(&k_internal)?; + let k = SharedSecret::try_from(&k_internal)?; zeroize_call!(k_internal); Ok(k) } } -#[derive(Debug, PartialEq, Clone)] -/// A type to represent the `EncapsulationKey` that ML-KEM-768 returns. -pub struct EncapsulationKey { - pub(crate) value: EncapKey<3, 1184, MlKem768Internal>, +#[derive(Debug, PartialEq)] +/// ML-KEM-768 keypair. +/// +/// This type uses cached encapsulation keys, saving the computation involved when doing decapsulation. +/// Meaning, once [`KeyPair`] has been instantiated, it is more efficient to use for decapsulation +/// than the [`DecapsulationKey`] type directly. +pub struct KeyPair { + seed: Seed, + private: DecapsulationKey, + pub(crate) public: EncapsulationKey, } -impl PartialEq<&[u8]> for EncapsulationKey { - fn eq(&self, other: &&[u8]) -> bool { - self.value == *other +impl KP for KeyPair { + fn public(&self) -> &EncapsulationKey { + &self.public } -} - -impl TryFrom<&DecapsulationKey> for EncapsulationKey { - type Error = UnknownCryptoError; - fn try_from(value: &DecapsulationKey) -> Result { - Ok(Self { - value: EncapKey::<3, 1184, MlKem768Internal>::from_slice( - value.value.get_encapsulation_key_bytes(), - )?, - }) + fn private(&self) -> &DecapsulationKey { + &self.private } } -impl TryFrom<&[u8]> for EncapsulationKey { +impl TryFrom<&Seed> for KeyPair { type Error = UnknownCryptoError; - /// Instantiate a [EncapsulationKey] with key-checks from FIPS-203, section 7.2. - fn try_from(value: &[u8]) -> Result { + fn try_from(value: &Seed) -> Result { + let (ek, dk) = KeyPairInternal::::from_seed::< + { MlKem768Internal::K }, + { MlKem768Internal::EK_SIZE }, + { MlKem768Internal::DK_SIZE }, + >(value)?; + Ok(Self { - value: EncapKey::<3, 1184, MlKem768Internal>::from_slice(value)?, + seed: Seed::from_data(value.data.clone()), + private: Secret::::from_data(dk), + public: Public::::from_data(ek), }) } } -impl AsRef<[u8]> for EncapsulationKey { - fn as_ref(&self) -> &[u8] { - self.value.as_ref() +impl KeyPair { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + /// Generate a fresh [`KeyPair`]. + pub fn generate() -> Result { + let seed = Seed::generate()?; + Self::new(seed) + } + + /// Reference to the private [`Seed`]. + pub fn seed(&self) -> &Seed { + &self.seed } -} -impl EncapsulationKey { - /// Instantiate a [EncapsulationKey] with key-checks from FIPS-203, section 7.2. - pub fn from_slice(slice: &[u8]) -> Result { - Self::try_from(slice) + /// Create a new instance from a private [`Seed`]. + pub fn new(seed: Seed) -> Result { + let (ek, dk) = KeyPairInternal::::from_seed::< + { MlKem768Internal::K }, + { MlKem768Internal::EK_SIZE }, + { MlKem768Internal::DK_SIZE }, + >(&seed)?; + + Ok(Self { + seed, + private: DecapsulationKey::from_data(dk), + public: EncapsulationKey::from_data(ek), + }) } #[cfg(feature = "safe_api")] #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] - /// Given the [EncapsulationKey], generate a [SharedSecret] and associated [Ciphertext]. - pub fn encap(&self) -> Result<(SharedSecret, Ciphertext), UnknownCryptoError> { - let mut m = zeroize_wrap!([0u8; 32]); - getrandom::fill(m.as_mut())?; + /// Instantiate a [`KeyPair`] with all key validation checks, described + /// in FIPS-203, Section 7.1, 7.2 and 7.3. + /// + /// The output keypair is the equivalent of using [`Self::try_from`], but this + /// can be used, in order to check whether a decapsulation key + /// is valid in relation to the `seed` provided. + pub fn from_keys(seed: &Seed, dk: &DecapsulationKey) -> Result { + let unchecked_ek = Public::::try_from(dk)?; + let (ek, dk) = KeyPairInternal::::from_keys::< + { MlKem768Internal::K }, + { MlKem768Internal::EK_SIZE }, + { MlKem768Internal::DK_SIZE }, + { MlKem768Internal::CIPHERTEXT_SIZE }, + >(seed, &unchecked_ek.data, &dk.data)?; - self.encap_deterministic(m.as_ref()) + Ok(Self { + seed: Seed::from_data(seed.data.clone()), + private: Secret::::from_data(dk), + public: Public::::from_data(ek), + }) } - /// Given the [EncapsulationKey] and randomness `m`, generate a [SharedSecret] and associated [Ciphertext]. - pub fn encap_deterministic( - &self, - m: &[u8], - ) -> Result<(SharedSecret, Ciphertext), UnknownCryptoError> { - if m.len() != 32 { - return Err(UnknownCryptoError); - } - - let mut c = Ciphertext::from_slice(&[0u8; MlKem768Internal::CIPHERTEXT_SIZE])?; + /// Perform decapsulation of a [`Ciphertext`], using internally cached [`EncapsulationKey`]. + pub fn decap(&self, c: &Ciphertext) -> Result { + let mut c_prime_buf = [0u8; MlKem768Internal::CIPHERTEXT_SIZE]; #[cfg(feature = "zeroize")] - let mut k_internal = self.value.mlkem_encap_internal(m.as_ref(), &mut c.value)?; + let mut k_internal = self.private.data.mlkem_decap_internal_with_ek( + c.as_ref(), + &mut c_prime_buf, + &self.public.data, + )?; #[cfg(not(feature = "zeroize"))] - let k_internal = self.value.mlkem_encap_internal(m.as_ref(), &mut c.value)?; + let k_internal = self.private.data.mlkem_decap_internal_with_ek( + c.as_ref(), + &mut c_prime_buf, + &self.public.data, + )?; - let k = SharedSecret::from_slice(k_internal.as_slice())?; + let k = SharedSecret::try_from(&k_internal)?; zeroize_call!(k_internal); - Ok((k, c)) + Ok(k) } } -#[derive(PartialEq, Debug)] -/// ML-KEM-768. -pub struct MlKem768; +#[cfg(test)] +mod tests { + use super::*; -impl MlKem768 { - /// Encapsulation key size (bytes). - pub const EK_SIZE: usize = MlKem768Internal::EK_SIZE; - /// Decapsulation key size (bytes). - pub const DK_SIZE: usize = MlKem768Internal::DK_SIZE; - /// Ciphertext size (bytes). - pub const CIPHERTEXT_SIZE: usize = MlKem768Internal::CIPHERTEXT_SIZE; - /// Shared Secret size (bytes). - pub const SHARED_SECRET_SIZE: usize = MlKem768Internal::SHARED_SECRET_SIZE; + // NOTE(brycx): SecretNewtype/PublicNewtype tests aren't run for Encapsulation/Decapsulation keys + // because their underling TypeData structure is not compatible with the generic tests. - #[cfg(feature = "safe_api")] - #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] - /// Given the [EncapsulationKey], generate a [SharedSecret] and associated [Ciphertext]. - pub fn encap(ek: &EncapsulationKey) -> Result<(SharedSecret, Ciphertext), UnknownCryptoError> { - ek.encap() + #[test] + fn test_shared_secret() { + use crate::test_framework::newtypes::secret::SecretNewtype; + SecretNewtype::test_no_generate::< + SHARED_SECRET_SIZE, + SHARED_SECRET_SIZE, + MlKem768SharedSecret, + >(); + // Test of From<[u8; N]> + assert_ne!( + SharedSecret::from([0u8; SHARED_SECRET_SIZE]), + SharedSecret::from([1u8; SHARED_SECRET_SIZE]) + ) } - /// Given the [DecapsulationKey], produce a [SharedSecret] using the [Ciphertext]. - pub fn decap( - dk: &DecapsulationKey, - c: &Ciphertext, - ) -> Result { - dk.decap(c) + #[test] + #[cfg(feature = "serde")] + fn test_encapuslation_key_serialization() { + use crate::test_framework::newtypes::public::PublicNewtype; + PublicNewtype::test_serialization::(); } -} -#[cfg(test)] -mod tests { - use super::*; + #[test] + fn test_ciphertext() { + use super::*; + use crate::test_framework::newtypes::public::PublicNewtype; + PublicNewtype::test_no_generate::(); + // Test of From<[u8; N]> + assert_ne!( + Ciphertext::from([0u8; CIPHERTEXT_SIZE]), + Ciphertext::from([1u8; CIPHERTEXT_SIZE]) + ); + + #[cfg(feature = "serde")] + PublicNewtype::test_serialization::(); + } #[cfg(feature = "safe_api")] use crate::test_framework::kem_interface::{KemTester, TestableKem}; #[cfg(feature = "safe_api")] - impl TestableKem for MlKem768 { + impl TestableKem for KeyPair { fn keygen(seed: &[u8]) -> Result<(Vec, Vec), UnknownCryptoError> { - let kp = KeyPair::try_from(&Seed::from_slice(seed).unwrap()).unwrap(); + let kp = KeyPair::try_from(&Seed::try_from(seed)?)?; Ok(( - kp.dk.cached_ek.as_ref().to_vec(), - kp.dk.value.unprotected_as_bytes().to_vec(), + kp.public.data.bytes.as_ref().to_vec(), + kp.private.data.bytes.as_ref().to_vec(), )) } fn ciphertext_from_bytes(b: &[u8]) -> Result { - Ciphertext::from_slice(b) + Ciphertext::try_from(b) } fn encap(ek: &[u8]) -> Result<(SharedSecret, Ciphertext), UnknownCryptoError> { - let ek = EncapsulationKey::from_slice(ek).unwrap(); + let ek = Public::::try_from(ek)?; ek.encap() } fn decap(dk: &[u8], c: &Ciphertext) -> Result { - let dk = DecapsulationKey::unchecked_from_slice(dk).unwrap(); + let dk = DecapsulationKey::unchecked_from_slice(dk)?; dk.decap(c) } } #[test] fn test_keypair_dk_ek_match_internal() { - let seed = Seed::from_slice(&[128u8; 64]).unwrap(); + let seed = Seed::try_from(&[128u8; 64]).unwrap(); let kp = KeyPair::try_from(&seed).unwrap(); - assert_eq!(kp.public(), &kp.private().cached_ek); + assert_eq!( + &kp.public().as_ref(), + &kp.private().data.get_encapsulation_key_bytes() + ); + assert_eq!(kp.seed(), &seed); } #[test] #[cfg(feature = "safe_api")] fn test_dk_cached_ek() { - let seed = Seed::from_slice(&[128u8; 64]).unwrap(); + let seed = Seed::try_from(&[128u8; 64]).unwrap(); let kp = KeyPair::try_from(&seed).unwrap(); let (ss_pubapi, ct_pubapi) = kp.public().encap_deterministic(&[125u8; 32]).unwrap(); let mut c_prime = [0u8; MlKem768Internal::CIPHERTEXT_SIZE]; // This call re-computes encap key internally from the bytes a decapkey would store. let ss_privapi = kp .private() - .value + .data .mlkem_decap_internal(ct_pubapi.as_ref(), &mut c_prime) .unwrap(); - assert_eq!(ss_privapi.as_ref(), ss_pubapi.unprotected_as_bytes()); - assert_eq!( - MlKem768::decap(kp.private(), &ct_pubapi).unwrap(), - ss_pubapi - ); + assert_eq!(ss_privapi.as_ref(), ss_pubapi.unprotected_as_ref()); + assert_eq!(kp.decap(&ct_pubapi).unwrap(), ss_pubapi); } #[cfg(feature = "safe_api")] @@ -418,8 +486,8 @@ mod tests { fn test_dk_to_ek_conversions() { let kp = KeyPair::generate().unwrap(); assert_eq!( - kp.dk.cached_ek, - EncapsulationKey::try_from(kp.private()).unwrap() + Public::::try_from(kp.private()).unwrap(), + kp.public.as_ref(), ); } @@ -443,11 +511,11 @@ mod tests { #[cfg(feature = "safe_api")] #[test] fn test_dk_ek_partialeq() { - let s0 = Seed::generate(); + let s0 = Seed::generate().unwrap(); let kp = KeyPair::try_from(&s0).unwrap(); - let dk_bytes = kp.private().value.bytes; - let ek_bytes = kp.public().value.bytes; + let dk_bytes = kp.private().data.bytes; + let ek_bytes = kp.public().data.bytes; assert_eq!( KeyPair::try_from(&s0).unwrap().private(), @@ -459,13 +527,11 @@ mod tests { #[cfg(feature = "safe_api")] #[test] fn test_keypair_from_keys() { - let s0 = Seed::generate(); - let s1 = Seed::generate(); + let s0 = Seed::generate().unwrap(); + let s1 = Seed::generate().unwrap(); let kp0 = KeyPair::try_from(&s0).unwrap(); let kp1 = KeyPair::try_from(&s1).unwrap(); - assert_eq!(kp0.seed(), &s0); - assert_eq!(kp1.seed(), &s1); assert!(KeyPair::from_keys(&s0, kp0.private()).is_ok()); assert!(KeyPair::from_keys(&s1, kp1.private()).is_ok()); @@ -474,24 +540,24 @@ mod tests { let kp0_keys = KeyPair::from_keys(&s0, kp0.private()).unwrap(); let kp1_keys = KeyPair::from_keys(&s1, kp1.private()).unwrap(); - assert_eq!(kp0.seed(), kp0_keys.seed()); - assert_eq!(kp1.seed(), kp1_keys.seed()); assert_eq!(kp0.private(), kp0_keys.private()); assert_eq!(kp0.public(), kp0_keys.public()); + assert_eq!(kp1.private(), kp1_keys.private()); + assert_eq!(kp1.public(), kp1_keys.public()); } #[cfg(feature = "safe_api")] #[test] fn run_basic_kem_tests() { - let seed = Seed::generate(); - KemTester::::run_all_tests(seed.unprotected_as_bytes()); + let seed = Seed::generate().unwrap(); + KemTester::::run_all_tests(seed.unprotected_as_ref()); } #[test] /// Basic no_std-compatible test. fn basic_roundtrip() { - let seed = Seed::from_slice(&[127u8; 64]).unwrap(); + let seed = Seed::try_from(&[127u8; 64]).unwrap(); let kp = KeyPair::try_from(&seed).unwrap(); let (k, c) = kp.public().encap_deterministic(&[255u8; 32]).unwrap(); diff --git a/src/hazardous/kem/ml_kem/mod.rs b/src/hazardous/kem/ml_kem/mod.rs index 393099260..db7bf79fd 100644 --- a/src/hazardous/kem/ml_kem/mod.rs +++ b/src/hazardous/kem/ml_kem/mod.rs @@ -20,7 +20,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -use crate::errors::UnknownCryptoError; +use crate::generics::GenerateSecret; +use crate::generics::{ByteArrayData, Secret, TypeSpec, sealed::Sealed}; +#[cfg(feature = "safe_api")] +use crate::{errors::UnknownCryptoError, generics::sealed::Data}; /// Internal implementation logic for ML-KEM. pub mod internal; @@ -34,15 +37,49 @@ pub mod mlkem768; /// ML-KEM-1024 as specified in [FIPS-203](https://doi.org/10.6028/NIST.FIPS.203). pub mod mlkem1024; -construct_secret_key! { - /// A type to represent the `d||z` seed used by ML-KEM to produce - /// a decapsulation key and its corresponding encapsulation key. - /// - /// It it crucial for the security of ML-KEM that these be generated - /// using a CSPRNG. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 64 bytes. - (Seed, test_ml_kem_seed, 64, 64, 64) +/// Size of the seed used in ML-KEM. +pub const SEED_SIZE: usize = 64; + +#[derive(Debug)] +/// ML-KEM seed implementation. See [`Seed`] type for convenience. +pub struct MlKemSeed {} +impl Sealed for MlKemSeed {} + +impl TypeSpec for MlKemSeed { + const NAME: &'static str = stringify!(Seed); + type TypeData = ByteArrayData; +} + +impl From<[u8; SEED_SIZE]> for Secret { + fn from(value: [u8; SEED_SIZE]) -> Self { + Self::from_data(::TypeData::from(value)) + } +} + +impl GenerateSecret for MlKemSeed { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + fn generate() -> Result, UnknownCryptoError> { + let mut data = Self::TypeData::new(SEED_SIZE)?; + crate::util::secure_rand_bytes(&mut data.bytes)?; + Ok(Secret::from_data(data)) + } +} + +/// ML-KEM seed. +/// +/// Represent the `d||z` seed used by ML-KEM to produce +/// a decapsulation key and its corresponding encapsulation key. +/// +/// **SECURITY**: It it crucial for the security of ML-KEM that these be generated +/// using a CSPRNG. +pub type Seed = Secret; + +#[test] +fn test_mlkem_seed() { + use crate::test_framework::newtypes::secret::SecretNewtype; + SecretNewtype::test_with_generate::(); + + // Test of From<[u8; N]> + assert_ne!(Seed::from([0u8; SEED_SIZE]), Seed::from([1u8; SEED_SIZE])); } diff --git a/src/hazardous/kem/mod.rs b/src/hazardous/kem/mod.rs index a7eb67039..b68b17d82 100644 --- a/src/hazardous/kem/mod.rs +++ b/src/hazardous/kem/mod.rs @@ -26,9 +26,9 @@ pub mod x25519_hkdf_sha256; /// ML-KEM as specified in [FIPS-203](https://doi.org/10.6028/NIST.FIPS.203). mod ml_kem; -pub use ml_kem::mlkem1024; pub use ml_kem::mlkem512; pub use ml_kem::mlkem768; +pub use ml_kem::mlkem1024; -/// X-Wing hybrid KEM as specified in [draft-connolly-cfrg-xwing-kem-06](https://www.ietf.org/archive/id/draft-connolly-cfrg-xwing-kem-06.html). +/// X-Wing hybrid KEM as specified in [draft-connolly-cfrg-xwing-kem-10](https://www.ietf.org/archive/id/draft-connolly-cfrg-xwing-kem-10.html). pub mod xwing; diff --git a/src/hazardous/kem/x25519_hkdf_sha256.rs b/src/hazardous/kem/x25519_hkdf_sha256.rs index 136ef0d50..a8758a823 100644 --- a/src/hazardous/kem/x25519_hkdf_sha256.rs +++ b/src/hazardous/kem/x25519_hkdf_sha256.rs @@ -31,10 +31,7 @@ //! An error will be returned if: //! - If a shared X25519 secret is all-zero. //! - If `ikm.len() < 32` when calling [`derive_keypair()`]. -//! -//! # Panics: -//! A panic will occur if: -//! - [`generate()`] panics during [`encap()`], [`auth_encap()`], [`decap()`] or [`auth_decap()`]. +//! - [`generate()`] errors during [`encap()`], [`auth_encap()`], [`decap()`] or [`auth_decap()`]. //! //! # Security: //! - The `ikm` used as input for [`derive_keypair()`] must never be reused. @@ -72,21 +69,39 @@ use crate::errors::UnknownCryptoError; use crate::hazardous::ecc::x25519; use crate::hazardous::kdf::hkdf; +use crate::generics::ByteArrayData; +use crate::generics::Secret; +use crate::generics::TypeSpec; +use crate::generics::sealed::Sealed; pub use crate::hazardous::ecc::x25519::PrivateKey; pub use crate::hazardous::ecc::x25519::PublicKey; -construct_secret_key! { - /// A type to represent the `SharedSecret` that DH-KEM(X25519, HKDF-SHA256) produces. - /// - /// This type simply holds bytes. Creating an instance from slices or similar, - /// performs no checks whatsoever. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 32 bytes. - (SharedSecret, test_shared_key, 32, 32) +/// Size of private [`SharedSecret`]. +pub const SHARED_SECRET_SIZE: usize = 32; + +/// DH-KEM(X25519, HKDF-SHA256) shared secret. +pub type SharedSecret = Secret; + +#[derive(Debug)] +/// DH-KEM(X25519, HKDF-SHA256) shared secret implementation. See [`SharedSecret`] type for convenience. +/// +/// This type simply holds bytes. Creating an instance from slices or similar, +/// performs no checks whatsoever. +pub struct DhKemSharedSecret {} +impl Sealed for DhKemSharedSecret {} + +impl TypeSpec for DhKemSharedSecret { + const NAME: &'static str = stringify!(SharedSecret); + type TypeData = ByteArrayData; } +impl From<[u8; SHARED_SECRET_SIZE]> for SharedSecret { + fn from(value: [u8; SHARED_SECRET_SIZE]) -> Self { + Self::from_data(::TypeData::from(value)) + } +} + +#[derive(Debug)] /// DHKEM(X25519, HKDF-SHA256) as specified in HPKE [RFC 9180](https://www.rfc-editor.org/rfc/rfc9180.html). pub struct DhKem {} @@ -125,7 +140,7 @@ impl DhKem { ) -> Result<(), UnknownCryptoError> { let l: u16 = out.len().try_into().map_err(|_| UnknownCryptoError)?; hkdf::sha256::expand_with_parts( - prk.unprotected_as_bytes(), + prk.unprotected_as_ref(), Some(&[ &l.to_be_bytes(), Self::HPKE_VERSION_ID.as_bytes(), @@ -157,7 +172,7 @@ impl DhKem { #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] /// Generate random X25519 keypair. pub fn generate_keypair() -> Result<(PrivateKey, PublicKey), UnknownCryptoError> { - let sk = PrivateKey::generate(); + let sk = PrivateKey::generate()?; let pk = PublicKey::try_from(&sk)?; Ok((sk, pk)) @@ -173,7 +188,7 @@ impl DhKem { let mut sk_bytes = zeroize_wrap!([0u8; x25519::PRIVATE_KEY_SIZE]); Self::labeled_expand(&dkp_prk, b"sk", b"", sk_bytes.as_mut_slice())?; - let sk = PrivateKey::from_slice(sk_bytes.as_slice())?; + let sk = PrivateKey::try_from(sk_bytes.as_slice())?; let pk = PublicKey::try_from(&sk)?; Ok((sk, pk)) @@ -186,7 +201,7 @@ impl DhKem { pub fn encap( public_recipient: &PublicKey, ) -> Result<(SharedSecret, PublicKey), UnknownCryptoError> { - let secret_ephemeral = PrivateKey::generate(); + let secret_ephemeral = PrivateKey::generate()?; Self::encap_deterministic(public_recipient, secret_ephemeral) } @@ -199,14 +214,14 @@ impl DhKem { let dh = x25519::key_agreement(&secret_ephemeral, public_recipient)?; let mut kem_context = [0u8; 32 + 32]; - kem_context[..32].copy_from_slice(&public_ephemeral.to_bytes()); - kem_context[32..64].copy_from_slice(&public_recipient.to_bytes()); + kem_context[..32].copy_from_slice(public_ephemeral.as_ref()); + kem_context[32..64].copy_from_slice(public_recipient.as_ref()); - let mut shared_secret = SharedSecret::from_slice(&[0u8; Self::N_SECRET as usize])?; + let mut shared_secret = SharedSecret::from([0u8; Self::N_SECRET as usize]); Self::extract_and_expand( - dh.unprotected_as_bytes(), + dh.unprotected_as_ref(), &kem_context, - &mut shared_secret.value, + shared_secret.data.as_mut(), )?; Ok((shared_secret, public_ephemeral)) @@ -221,14 +236,14 @@ impl DhKem { let dh = x25519::key_agreement(secret_recipient, public_ephemeral)?; let mut kem_context = [0u8; 32 + 32]; - kem_context[..32].copy_from_slice(&public_ephemeral.to_bytes()); - kem_context[32..64].copy_from_slice(&PublicKey::try_from(secret_recipient)?.to_bytes()); + kem_context[..32].copy_from_slice(public_ephemeral.as_ref()); + kem_context[32..64].copy_from_slice(PublicKey::try_from(secret_recipient)?.as_ref()); - let mut shared_secret = SharedSecret::from_slice(&[0u8; Self::N_SECRET as usize])?; + let mut shared_secret = SharedSecret::from([0u8; Self::N_SECRET as usize]); Self::extract_and_expand( - dh.unprotected_as_bytes(), + dh.unprotected_as_ref(), &kem_context, - &mut shared_secret.value, + shared_secret.data.as_mut(), )?; Ok(shared_secret) @@ -242,7 +257,7 @@ impl DhKem { public_recipient: &PublicKey, secret_sender: &PrivateKey, ) -> Result<(SharedSecret, PublicKey), UnknownCryptoError> { - let secret_ephemeral = PrivateKey::generate(); + let secret_ephemeral = PrivateKey::generate()?; Self::auth_encap_deterministic(public_recipient, secret_sender, secret_ephemeral) } @@ -256,19 +271,19 @@ impl DhKem { let mut dh = zeroize_wrap!([0u8; 64]); dh[..32].copy_from_slice( - x25519::key_agreement(&secret_ephemeral, public_recipient)?.unprotected_as_bytes(), + x25519::key_agreement(&secret_ephemeral, public_recipient)?.unprotected_as_ref(), ); dh[32..64].copy_from_slice( - x25519::key_agreement(secret_sender, public_recipient)?.unprotected_as_bytes(), + x25519::key_agreement(secret_sender, public_recipient)?.unprotected_as_ref(), ); let mut kem_context = [0u8; 32 * 3]; - kem_context[..32].copy_from_slice(&public_ephemeral.to_bytes()); - kem_context[32..64].copy_from_slice(&public_recipient.to_bytes()); - kem_context[64..96].copy_from_slice(&PublicKey::try_from(secret_sender)?.to_bytes()); + kem_context[..32].copy_from_slice(public_ephemeral.as_ref()); + kem_context[32..64].copy_from_slice(public_recipient.as_ref()); + kem_context[64..96].copy_from_slice(PublicKey::try_from(secret_sender)?.as_ref()); - let mut shared_secret = SharedSecret::from_slice(&[0u8; Self::N_SECRET as usize])?; - Self::extract_and_expand(dh.as_slice(), &kem_context, &mut shared_secret.value)?; + let mut shared_secret = SharedSecret::from([0u8; Self::N_SECRET as usize]); + Self::extract_and_expand(dh.as_slice(), &kem_context, shared_secret.data.as_mut())?; Ok((shared_secret, public_ephemeral)) } @@ -282,19 +297,19 @@ impl DhKem { ) -> Result { let mut dh = zeroize_wrap!([0u8; 64]); dh[..32].copy_from_slice( - x25519::key_agreement(secret_recipient, public_ephemeral)?.unprotected_as_bytes(), + x25519::key_agreement(secret_recipient, public_ephemeral)?.unprotected_as_ref(), ); dh[32..64].copy_from_slice( - x25519::key_agreement(secret_recipient, public_sender)?.unprotected_as_bytes(), + x25519::key_agreement(secret_recipient, public_sender)?.unprotected_as_ref(), ); let mut kem_context = [0u8; 32 * 3]; - kem_context[..32].copy_from_slice(&public_ephemeral.to_bytes()); - kem_context[32..64].copy_from_slice(&PublicKey::try_from(secret_recipient)?.to_bytes()); - kem_context[64..96].copy_from_slice(&public_sender.to_bytes()); + kem_context[..32].copy_from_slice(public_ephemeral.as_ref()); + kem_context[32..64].copy_from_slice(PublicKey::try_from(secret_recipient)?.as_ref()); + kem_context[64..96].copy_from_slice(public_sender.as_ref()); - let mut shared_secret = SharedSecret::from_slice(&[0u8; Self::N_SECRET as usize])?; - Self::extract_and_expand(dh.as_slice(), &kem_context, &mut shared_secret.value)?; + let mut shared_secret = SharedSecret::from([0u8; Self::N_SECRET as usize]); + Self::extract_and_expand(dh.as_slice(), &kem_context, shared_secret.data.as_mut())?; Ok(shared_secret) } @@ -306,6 +321,13 @@ mod public { use crate::hazardous::ecc::x25519::{PrivateKey, PublicKey}; use crate::hazardous::kem::x25519_hkdf_sha256::*; + #[test] + fn test_shared_secret() { + use crate::test_framework::newtypes::secret::SecretNewtype; + SecretNewtype::test_no_generate::( + ); + } + #[test] fn error_on_short_ikm() { assert!(DhKem::derive_keypair(&[0u8; 31]).is_err()); @@ -331,7 +353,7 @@ mod public { #[test] #[cfg(feature = "safe_api")] fn encap_decap_roundtrip() { - let recipient_secret = PrivateKey::generate(); + let recipient_secret = PrivateKey::generate().unwrap(); let recipient_public = PublicKey::try_from(&recipient_secret).unwrap(); let (shared_secret_1, public_eph) = DhKem::encap(&recipient_public).unwrap(); @@ -343,10 +365,10 @@ mod public { #[test] #[cfg(feature = "safe_api")] fn auth_encap_decap_roundtrip() { - let sender_secret = PrivateKey::generate(); + let sender_secret = PrivateKey::generate().unwrap(); let sender_public = PublicKey::try_from(&sender_secret).unwrap(); - let recipient_secret = PrivateKey::generate(); + let recipient_secret = PrivateKey::generate().unwrap(); let recipient_public = PublicKey::try_from(&recipient_secret).unwrap(); let (shared_secret_1, public_eph) = diff --git a/src/hazardous/kem/xwing.rs b/src/hazardous/kem/xwing.rs index 6a51d6050..a4528fd3d 100644 --- a/src/hazardous/kem/xwing.rs +++ b/src/hazardous/kem/xwing.rs @@ -20,6 +20,12 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +//! ### X-Wing key usage recommendations +//! +//! In general, it is highly recommended to use the [`KeyPair`] type to deal with decapsulating operations, or decapsulation keys in general. +//! [`KeyPair`] internally caches the [`EncapsulationKey`]s used during decapsulation, making it more efficient when used to decapsulate multiple +//! KEM ciphertext with a given private [`DecapsulationKey`]. +//! //! # Parameters: //! - `ek`: The public encapsulation key, for which a shared secret and ciphertext is generated. //! - `dk`: The secret decapsulation key, for which a ciphertext is used to derive a shared secret. @@ -29,276 +35,340 @@ //! # Errors: //! An error will be returned if: //! - `eseed` is not 64 bytes. -//! - [`getrandom::fill()`] fails during encapsulation. -//! -//! # Panics: -//! A panic will occur if: -//! - [`getrandom::fill()`] fails during [`KeyPair::generate()`]. +//! - [`getrandom::fill()`] fails during [`EncapsulationKey::encap()`]. +//! - [`getrandom::fill()`] fails during [`DecapsulationKey::generate()`]/[`KeyPair::generate()`]. //! //! # Security: //! - It is critical that both the seed and explicit randomness `eseed`, used for key generation and encapsulation //! are generated using a strong CSPRNG. -//! - Users should always prefer encapsulation without specifying explicit randomness, if possible. `encap_deterministic()` -//! exists mainly for `no_std` usage. +//! - Users should always prefer encapsulation without specifying explicit randomness, if possible. +//! [`EncapsulationKey::encap_deterministic()`] exists mainly for `no_std` usage. //! //! # Example: //! ```rust //! # #[cfg(feature = "safe_api")] { +//! use orion::KP; //! use orion::hazardous::kem::xwing::*; //! -//! let keypair = KeyPair::generate()?; +//! let kp = KeyPair::generate()?; //! -//! let (sender_shared_secret, sender_ciphertext) = XWing::encap(keypair.public())?; -//! let recipient_shared_secret = XWing::decap(keypair.private(), &sender_ciphertext)?; +//! let ek = EncapsulationKey::try_from(kp.public().as_ref())?; +//! let (sender_shared_secret, sender_ciphertext) = ek.encap()?; +//! let recipient_shared_secret = kp.decap(&sender_ciphertext)?; //! //! assert_eq!(sender_shared_secret, recipient_shared_secret); //! # } //! # Ok::<(), orion::errors::UnknownCryptoError>(()) //! ``` //! [`getrandom::fill()`]: getrandom::fill -//! [`KeyPair::generate()`]: mlkem1024::KeyPair::generate - +//! [`DecapsulationKey::generate()`]: xwing::DecapsulationKey::generate +//! [`KeyPair::generate()`]: xwing::KeyPair::generate +//! [`EncapsulationKey::encap()`]: xwing::EncapsulationKey::encap +//! [`EncapsulationKey::encap_deterministic()`]: xwing::EncapsulationKey::encap_deterministic +//! [`KeyPair`]: xwing::KeyPair +//! [`DecapsulationKey`]: xwing::DecapsulationKey +//! [`EncapsulationKey`]: xwing::EncapsulationKey + +use crate::KP; use crate::errors::UnknownCryptoError; +use crate::generics::GenerateSecret; +use crate::generics::sealed::Sealed; +use crate::generics::{ByteArrayData, Public, Secret, TypeSpec, sealed::Data}; use crate::hazardous::ecc::x25519; use crate::hazardous::hash::sha3::sha3_256; use crate::hazardous::hash::sha3::shake256::Shake256; use crate::hazardous::kem::ml_kem::mlkem768; -/// Size of private [DecapsulationKey]. -pub const PRIVATE_KEY_SIZE: usize = 32; +/// KEM-label used by X-Wing. +const LABEL: &[u8; 6] = b"\\.//^\\"; + +/// Size of private [`EncapsulationKey`]. +pub const EK_SIZE: usize = 1216; -/// Size of public [EncapsulationKey]. -pub const PUBLIC_KEY_SIZE: usize = 1216; +/// Size of public [`DecapsulationKey`]. +pub const DK_SIZE: usize = 32; -/// Size of public [Ciphertext]. +/// Size of public [`Ciphertext`]. pub const CIPHERTEXT_SIZE: usize = 1120; -/// Size of private [SharedSecret]. +/// Size of private [`SharedSecret`]. pub const SHARED_SECRET_SIZE: usize = 32; -construct_public! { - /// A type to represent the public `EncapsulationKey` that X-Wing uses. - /// - /// This type simply holds bytes and performs no checks whatsoever. If an invalid - /// ML-KEM-768 is part of the bytes parsed from this type, the check will first surface - /// when encapsulation is performed. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 1216 bytes. - (EncapsulationKey, test_kem_encapkey, PUBLIC_KEY_SIZE, PUBLIC_KEY_SIZE) -} +/// X-Wing encapsulation key. +/// +/// **SECURITY**: This type simply holds bytes and performs no checks whatsoever. If an invalid +/// ML-KEM-768 is part of the bytes parsed from this type, the check will first surface +/// when encapsulation is performed. +pub type EncapsulationKey = Public; -impl_from_trait!(EncapsulationKey, PUBLIC_KEY_SIZE); +/// X-Wing ciphertext. +pub type Ciphertext = Public; -construct_public! { - /// A type to represent the KEM `Ciphertext` that X-Wing returns. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 1120 bytes. - (Ciphertext, test_kem_ciphertext, CIPHERTEXT_SIZE, CIPHERTEXT_SIZE) -} +/// X-Wing decapsulation key. +pub type DecapsulationKey = Secret; + +/// X-Wing shared secret. +pub type SharedSecret = Secret; -impl_from_trait!(Ciphertext, CIPHERTEXT_SIZE); - -construct_secret_key! { - /// A type to represent the private `SharedSecret` that X-Wing returns. - /// - /// This type simply holds bytes. Creating an instance from slices or similar, - /// performs no checks whatsoever. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 32 bytes. - (SharedSecret, test_sharedsecret_key, SHARED_SECRET_SIZE, SHARED_SECRET_SIZE) +#[derive(Debug, Clone, Copy)] +/// X-Wing encapsulation key implementation. See [`EncapsulationKey`] type for convenience. +/// +/// +/// **SECURITY**: This type simply holds bytes and performs no checks whatsoever. If an invalid +/// ML-KEM-768 is part of the bytes parsed from this type, the check will first surface +/// when encapsulation is performed. +pub struct XWingEncapKey {} +impl Sealed for XWingEncapKey {} + +impl TypeSpec for XWingEncapKey { + const NAME: &'static str = stringify!(EncapsulationKey); + type TypeData = ByteArrayData; } -impl_from_trait!(SharedSecret, SHARED_SECRET_SIZE); - -construct_secret_key! { - /// A type to represent the private `Seed` that X-Wing uses. - /// - /// This type simply holds bytes. Creating an instance from slices or similar, - /// performs no checks whatsoever. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 32 bytes. - (Seed, test_seed, PRIVATE_KEY_SIZE, PRIVATE_KEY_SIZE, PRIVATE_KEY_SIZE) +impl From<[u8; EK_SIZE]> for Public { + fn from(value: [u8; EK_SIZE]) -> Self { + Self::from_data(::TypeData::from(value)) + } } -impl_from_trait!(Seed, PRIVATE_KEY_SIZE); +#[derive(Debug)] +/// X-Wing decapsulation key implementation. See [`DecapsulationKey`] type for convenience. +pub struct XWingDecapKey {} +impl Sealed for XWingDecapKey {} -#[derive(Debug, PartialEq)] -/// A type to represent the `DecapsulationKey` that X-Wing produces. -/// This type's foremost responsibility is to cache key-expansions, -/// to be re-used across multiple decapsulations with a single secret. -/// -/// Calling [DecapsulationKey::unprotected_as_bytes] is equivalent to -/// calling [Seed::unprotected_as_bytes]. -pub struct DecapsulationKey { - seed: Seed, - kp_m: mlkem768::KeyPair, - sk_x: x25519::PrivateKey, - pk_x: x25519::PublicKey, +impl TypeSpec for XWingDecapKey { + const NAME: &'static str = stringify!(DecapsulationKey); + type TypeData = ByteArrayData; } -impl DecapsulationKey { - #[inline] - /// Return the object as byte slice. __**Warning**__: Should not be used unless strictly - /// needed. This __**breaks protections**__ that the type implements. - pub fn unprotected_as_bytes(&self) -> &[u8] { - self.seed.unprotected_as_bytes() +impl From<[u8; DK_SIZE]> for Secret { + fn from(value: [u8; DK_SIZE]) -> Self { + Self::from_data(::TypeData::from(value)) } } -#[derive(Debug, PartialEq)] -/// A keypair of X-Wing keys. -pub struct KeyPair { - ek: EncapsulationKey, - dk: DecapsulationKey, +impl GenerateSecret for XWingDecapKey { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + fn generate() -> Result, UnknownCryptoError> { + let mut data = Self::TypeData::new(DK_SIZE)?; + crate::util::secure_rand_bytes(&mut data.bytes)?; + Ok(Secret::from_data(data)) + } } -impl TryFrom<&Seed> for KeyPair { - type Error = UnknownCryptoError; +#[derive(Debug, Clone, Copy)] +/// X-Wing ciphertext implementation. See [`Ciphertext`] type for convenience. +pub struct XWingCiphertext {} +impl Sealed for XWingCiphertext {} - fn try_from(value: &Seed) -> Result { - KeyPair::generate_deterministic(value) - } +impl TypeSpec for XWingCiphertext { + const NAME: &'static str = stringify!(Ciphertext); + type TypeData = ByteArrayData; } -impl KeyPair { - /// Deterministically generate a [KeyPair] from a private [Seed]. - pub fn generate_deterministic(seed: &Seed) -> Result { - let mut expanded = zeroize_wrap!([0u8; 96]); +impl From<[u8; CIPHERTEXT_SIZE]> for Public { + fn from(value: [u8; CIPHERTEXT_SIZE]) -> Self { + Self::from_data(::TypeData::from(value)) + } +} - let mut shake = Shake256::new(); - shake.absorb(seed.unprotected_as_bytes())?; - shake.squeeze(expanded.as_mut())?; +#[derive(Debug)] +/// X-Wing shared secret implementation. See [`SharedSecret`] type for convenience. +pub struct XWingSharedSecret {} +impl Sealed for XWingSharedSecret {} - let seed_m = mlkem768::Seed::from_slice(&expanded[..64])?; - let kp_m = mlkem768::KeyPair::try_from(&seed_m)?; - let sk_x = x25519::PrivateKey::from_slice(&expanded[64..96])?; - let pk_x = x25519::PublicKey::try_from(&sk_x)?; +impl TypeSpec for XWingSharedSecret { + const NAME: &'static str = stringify!(XWingSharedSecret); + type TypeData = ByteArrayData; +} - let mut xwing_pk = [0u8; mlkem768::MlKem768::EK_SIZE + x25519::PUBLIC_KEY_SIZE]; - xwing_pk[..mlkem768::MlKem768::EK_SIZE].copy_from_slice(kp_m.public().as_ref()); - xwing_pk[mlkem768::MlKem768::EK_SIZE..].copy_from_slice(&pk_x.to_bytes()); - - Ok(Self { - ek: EncapsulationKey::from(xwing_pk), - dk: DecapsulationKey { - seed: Seed::from_slice(seed.unprotected_as_bytes())?, - kp_m, - sk_x, - pk_x, - }, - }) +impl From<[u8; SHARED_SECRET_SIZE]> for Secret { + fn from(value: [u8; SHARED_SECRET_SIZE]) -> Self { + Self::from_data(::TypeData::from(value)) } +} - #[cfg(feature = "safe_api")] - #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] - /// Generate a fresh [KeyPair]. - pub fn generate() -> Result { - let seed = Seed::generate(); - Self::generate_deterministic(&seed) - } +impl TryFrom<&DecapsulationKey> for EncapsulationKey { + type Error = UnknownCryptoError; - /// Get the public [EncapsulationKey] corresponding to this keypair. - pub fn public(&self) -> &EncapsulationKey { - &self.ek - } + fn try_from(value: &DecapsulationKey) -> Result { + let kp = value.expand_into_keypair()?; - /// Get the private [DecapsulationKey] used to generate this keypair. - pub fn private(&self) -> &DecapsulationKey { - &self.dk + Ok(kp.public) } } -#[derive(Debug, PartialEq)] -/// X-Wing hybrid KEM. -pub struct XWing; - -impl XWing { - const LABEL: &[u8; 6] = b"\\.//^\\"; - - fn combiner( - ss_m: &[u8], - ss_x: &[u8], - ct_x: &[u8], - pk_x: &[u8], - ) -> Result { - let mut ctx = sha3_256::Sha3_256::new(); - ctx.update(ss_m)?; - ctx.update(ss_x)?; - ctx.update(ct_x)?; - ctx.update(pk_x)?; - ctx.update(Self::LABEL)?; - - let mut digest = zeroize_wrap!([0u8; 32]); - ctx._finalize_internal(digest.as_mut())?; - - SharedSecret::from_slice(&digest[..]) +/// [Section 5.3 - Combiner](https://www.ietf.org/archive/id/draft-connolly-cfrg-xwing-kem-10.html#section-5.3). +fn combiner( + ss_m: &[u8], + ss_x: &[u8], + ct_x: &[u8], + pk_x: &[u8], +) -> Result { + let mut ctx = sha3_256::Sha3_256::new(); + ctx.update(ss_m)?; + ctx.update(ss_x)?; + ctx.update(ct_x)?; + ctx.update(pk_x)?; + ctx.update(LABEL)?; + + let mut shared_secret = Secret::::from_data( + ::TypeData::new(SHARED_SECRET_SIZE)?, + ); + + ctx._finalize_internal(&mut shared_secret.data.bytes)?; + + Ok(shared_secret) +} + +impl EncapsulationKey { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + /// Given the [`EncapsulationKey`], generate a [`SharedSecret`] and associated [`Ciphertext`]. + pub fn encap(&self) -> Result<(SharedSecret, Ciphertext), UnknownCryptoError> { + let mut m = zeroize_wrap!([0u8; 64]); + getrandom::fill(m.as_mut())?; + + self.encap_deterministic(m.as_ref()) } - /// Given the [EncapsulationKey] and securely generated randomness `eseed`, generate a [SharedSecret] and associated [Ciphertext]. + /// Given the [`EncapsulationKey`] and securely generated randomness `eseed`, generate a [`SharedSecret`] and associated [`Ciphertext`]. pub fn encap_deterministic( - ek: &EncapsulationKey, + &self, eseed: &[u8], ) -> Result<(SharedSecret, Ciphertext), UnknownCryptoError> { if eseed.len() != 64 { return Err(UnknownCryptoError); } - let pk_m = &ek.as_ref()[..mlkem768::MlKem768::EK_SIZE]; - let pk_x = &ek.as_ref()[mlkem768::MlKem768::EK_SIZE..]; - let ek_x = x25519::PrivateKey::from_slice(&eseed[32..64])?; - let ct_x = x25519::PublicKey::try_from(&ek_x)?.to_bytes(); - let ss_x = x25519::key_agreement(&ek_x, &x25519::PublicKey::from_slice(pk_x)?)?; - let mlkem768_encapkey = mlkem768::EncapsulationKey::from_slice(pk_m)?; + let pk_m = &self.data.bytes[..mlkem768::EK_SIZE]; + let pk_x = &self.data.bytes[mlkem768::EK_SIZE..]; + let ek_x = x25519::PrivateKey::try_from(&eseed[32..64])?; + let ct_x = x25519::PublicKey::try_from(&ek_x)?; + let ss_x = x25519::key_agreement(&ek_x, &x25519::PublicKey::try_from(pk_x)?)?; + let mlkem768_encapkey = mlkem768::EncapsulationKey::try_from(pk_m)?; let (ss_m, ct_m) = mlkem768_encapkey.encap_deterministic(&eseed[..32])?; - let ss = Self::combiner( - ss_m.unprotected_as_bytes(), - ss_x.unprotected_as_bytes(), - &ct_x, + let ss = combiner( + ss_m.unprotected_as_ref(), + ss_x.unprotected_as_ref(), + ct_x.as_ref(), pk_x, )?; - let mut ct = [0u8; mlkem768::MlKem768::CIPHERTEXT_SIZE + x25519::PUBLIC_KEY_SIZE]; - ct[..mlkem768::MlKem768::CIPHERTEXT_SIZE].copy_from_slice(ct_m.as_ref()); - ct[mlkem768::MlKem768::CIPHERTEXT_SIZE..].copy_from_slice(&ct_x); + let mut ct = Public::::from_data( + ::TypeData::new(CIPHERTEXT_SIZE)?, + ); - Ok((ss, Ciphertext::from(ct))) + ct.data.bytes[..mlkem768::CIPHERTEXT_SIZE].copy_from_slice(ct_m.as_ref()); + ct.data.bytes[mlkem768::CIPHERTEXT_SIZE..].copy_from_slice(ct_x.as_ref()); + + Ok((ss, ct)) } +} +impl DecapsulationKey { + /// Equivalent to [Section 5.2 - Key generation](https://www.ietf.org/archive/id/draft-connolly-cfrg-xwing-kem-10.html#section-5.2). + fn expand_into_keypair(&self) -> Result { + let mut expanded = zeroize_wrap!([0u8; 96]); + + let mut shake = Shake256::new(); + shake.absorb(self.data.as_ref())?; + shake.squeeze(expanded.as_mut())?; + + let seed_m = mlkem768::Seed::try_from(&expanded[..64])?; + let kp_m = mlkem768::KeyPair::new(seed_m)?; + let sk_x = x25519::PrivateKey::try_from(&expanded[64..96])?; + let pk_x = x25519::PublicKey::try_from(&sk_x)?; + + let mut xwing_pk = Public::::from_data( + ::TypeData::new(EK_SIZE)?, + ); + + xwing_pk.data.bytes[..mlkem768::EK_SIZE].copy_from_slice(kp_m.public().as_ref()); + xwing_pk.data.bytes[mlkem768::EK_SIZE..].copy_from_slice(pk_x.as_ref()); + + Ok(KeyPair { + private: DecapsulationKey::from_data(self.data.clone()), + public: xwing_pk, + kp_m, + sk_x, + pk_x, + }) + } + + /// Perform decapsulation of a [`Ciphertext`]. + pub fn decap(&self, c: &Ciphertext) -> Result { + let kp = self.expand_into_keypair()?; + let ct_m = &c.as_ref()[..mlkem768::CIPHERTEXT_SIZE]; + let ct_x = &c.as_ref()[mlkem768::CIPHERTEXT_SIZE..]; + + let ss_m = kp.kp_m.decap(&mlkem768::Ciphertext::try_from(ct_m)?)?; + let ss_x = x25519::key_agreement(&kp.sk_x, &x25519::PublicKey::try_from(ct_x)?)?; + + combiner( + ss_m.unprotected_as_ref(), + ss_x.unprotected_as_ref(), + ct_x, + kp.pk_x.as_ref(), + ) + } +} + +#[derive(Debug, PartialEq)] +/// X-Wing keypair. +/// +/// This type uses cached encapsulation keys, saving the computation involved when doing decapsulation. +/// Meaning, once [`KeyPair`] has been instantiated, it is more efficient to use for decapsulation +/// than the [`DecapsulationKey`] type directly. +pub struct KeyPair { + private: DecapsulationKey, + public: EncapsulationKey, + // The following variants are kept for caching purposes. + kp_m: mlkem768::KeyPair, + sk_x: x25519::PrivateKey, + pk_x: x25519::PublicKey, +} + +impl KP for KeyPair { + fn public(&self) -> &EncapsulationKey { + &self.public + } + + fn private(&self) -> &DecapsulationKey { + &self.private + } +} + +impl TryFrom<&DecapsulationKey> for KeyPair { + type Error = UnknownCryptoError; + + fn try_from(value: &DecapsulationKey) -> Result { + value.expand_into_keypair() + } +} + +impl KeyPair { #[cfg(feature = "safe_api")] #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] - /// Given the [EncapsulationKey], generate a [SharedSecret] and associated [Ciphertext]. - pub fn encap(ek: &EncapsulationKey) -> Result<(SharedSecret, Ciphertext), UnknownCryptoError> { - let mut eseed = zeroize_wrap!([0u8; 64]); - getrandom::fill(eseed.as_mut())?; - - Self::encap_deterministic(ek, eseed.as_ref()) + /// Generate a fresh [`KeyPair`]. + pub fn generate() -> Result { + DecapsulationKey::generate()?.expand_into_keypair() } - /// Given the [DecapsulationKey], produce a [SharedSecret] using the [Ciphertext]. - pub fn decap( - dk: &DecapsulationKey, - c: &Ciphertext, - ) -> Result { - let ct_m = &c.as_ref()[..mlkem768::MlKem768::CIPHERTEXT_SIZE]; - let ct_x = &c.as_ref()[mlkem768::MlKem768::CIPHERTEXT_SIZE..]; - - let ss_m = - mlkem768::MlKem768::decap(dk.kp_m.private(), &mlkem768::Ciphertext::from_slice(ct_m)?)?; - let ss_x = x25519::key_agreement(&dk.sk_x, &x25519::PublicKey::from_slice(ct_x)?)?; - - Self::combiner( - ss_m.unprotected_as_bytes(), - ss_x.unprotected_as_bytes(), + /// Perform decapsulation of a [`Ciphertext`], using internally cached [`EncapsulationKey`]. + pub fn decap(&self, c: &Ciphertext) -> Result { + let ct_m = &c.as_ref()[..mlkem768::CIPHERTEXT_SIZE]; + let ct_x = &c.as_ref()[mlkem768::CIPHERTEXT_SIZE..]; + + let ss_m = self.kp_m.decap(&mlkem768::Ciphertext::try_from(ct_m)?)?; + let ss_x = x25519::key_agreement(&self.sk_x, &x25519::PublicKey::try_from(ct_x)?)?; + + combiner( + ss_m.unprotected_as_ref(), + ss_x.unprotected_as_ref(), ct_x, - &dk.pk_x.to_bytes(), + self.pk_x.as_ref(), ) } } @@ -307,72 +377,264 @@ impl XWing { mod tests { use super::*; + #[test] + // NOTE: The spec operates on arbitrary bytes for encapsulation keys. + // Internally, X25519 should ignore hibit, but since the raw bytes are + // also used in hash-combiner, there can be a difference, meaning two + // different shared secrets from two encapsulation keys where only hibit + // of X25519 part differ! + // The spec mentions nothing about canocnicality. + fn test_highbit_not_ignored_in_x25519_parts_pk() { + let eseed = &[111u8; 64]; + let dk = DecapsulationKey::try_from(&[127u8; DK_SIZE]).unwrap(); + let ek = EncapsulationKey::try_from(&dk).unwrap(); + let kp = KeyPair::try_from(&dk).unwrap(); + assert_eq!(&ek, kp.public()); + + let (ss, _) = ek.encap_deterministic(eseed).unwrap(); + + // Modify hibit in X25519-part of X-Wing key + let mut ek_hibit_zero = ek.clone(); + ek_hibit_zero.data.bytes[EK_SIZE - 1] &= 0x7F; + let mut ek_hibit_one = ek.clone(); + ek_hibit_one.data.bytes[EK_SIZE - 1] |= 0x80; + + let (ss_zero, _) = ek_hibit_zero.encap_deterministic(eseed).unwrap(); + let (ss_one, _) = ek_hibit_one.encap_deterministic(eseed).unwrap(); + + assert_ne!(&ss_zero, &ss_one); + // Our generated one is always masked + assert_eq!(ek.data.bytes[EK_SIZE - 1] & 0x80, 0); + assert_eq!(&ss, &ss_zero); + } + + #[test] + // NOTE: The spec operates on arbitrary bytes for ciphertexts. + // Internally, X25519 should ignore hibit, but since the raw bytes are + // also used in hash-combiner, there can be a difference, meaning two + // different shared secrets from two ciphertexts where only hibit + // of X25519 part differ! + fn test_highbit_not_ignored_in_x25519_parts_ct() { + let eseed = &[111u8; 64]; + let dk = DecapsulationKey::try_from(&[127u8; DK_SIZE]).unwrap(); + let kp = KeyPair::try_from(&dk).unwrap(); + assert_eq!(&dk, kp.private()); + let (ss, ct) = kp.public().encap_deterministic(eseed).unwrap(); + + // Modify hibit in X25519-part of X-Wing ciphertext + let mut ct_hibit_zero = ct.clone(); + ct_hibit_zero.data.bytes[CIPHERTEXT_SIZE - 1] &= 0x7F; + + let mut ct_hibit_one = ct.clone(); + ct_hibit_one.data.bytes[CIPHERTEXT_SIZE - 1] |= 0x80; + + let ss_zero = kp.decap(&ct_hibit_zero).unwrap(); + let ss_one = kp.decap(&ct_hibit_one).unwrap(); + + assert_ne!(&ss_zero, &ss_one); + // Our generated one is always masked + assert_eq!(ct.data.bytes[CIPHERTEXT_SIZE - 1] & 0x80, 0); + assert_eq!(&ss, &ss_zero); + } + + #[test] + fn test_higbit_handling_consistency_circl() { + // These test vectors have been generated with Cloudflare CIRCL github.com/cloudflare/circl v1.6.3 + // to ensure the X25519 canonicality is handled the same way. + let mut seed = [0u8; DK_SIZE]; + let mut eseed = [0u8; 64]; + let mut ct0 = [0u8; CIPHERTEXT_SIZE]; + let mut ct1 = [0u8; CIPHERTEXT_SIZE]; + let mut ek0 = [0u8; EK_SIZE]; + let mut ek1 = [0u8; EK_SIZE]; + let mut ss_ct0 = [0u8; SHARED_SECRET_SIZE]; + let mut ss_ct1 = [0u8; SHARED_SECRET_SIZE]; + let mut ss_ek0 = [0u8; SHARED_SECRET_SIZE]; + let mut ss_ek1 = [0u8; SHARED_SECRET_SIZE]; + + hex::decode_to_slice( + "babababababababababababababababababababababababababababababababa", + &mut seed, + ) + .unwrap(); + hex::decode_to_slice("abababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab", &mut eseed).unwrap(); + hex::decode_to_slice("a4c2e17f7c9961c9d78771605e5620ba80502305bb07b22ef5dd35b7483d78eed714fc7060e08fdcaf46a726a3ae66043f709098bfe8980a87b4435878ce4551c8470289b64f158245ed91407bb2ddd754006548666d5999916c3feef8867fcd4f94a0bba5be3810ec13348ca93679020cc9b2a247c69a31520fe42fed5778907b9dfc3fffb7d8e79e4eac4eb9b034f0c9fa9037c5e6f3aa0e69661eba51d721539dfd71eac8f8a26220c710e8bea9ebbc82292f1d3cad8368b6f577f6e300a2125024f2b1a3c175efea0268f5ad68b28636954c3e8e68e5389eb6bd7e7725e467e0169ced4a8ee19992a1bffb539545be100071620d585867d9a9fd3773b5edf80b43fe7258b2df7ee6c2ab9e76c1b0cd6e39a10bb6060fb310a9e71d54ff4685616d9bf404377b47dbbe059ddfcbf6bf2c608851a6aee2a9f94ef0b7f86c249d2c186a514930f1725eeb0310dced247c74dc0f4d0b3a21880e28bb5bc9b203e674c373ffd20e8e8b4795499da8abc177de3c7fc99fc121b014d9288d5bf4e9cde18b7273722aedfaeca7804bf9a3ead7a066c9018fcc0e51280254641752f54a6600118644ab46f5add39cee3009774e488052b84c854e199833bc22238bbd70b645556c3a5b542ee4b8a69c15aec8a3c3db01e1e3ec8642207110c998c7d09ffe4417443f41c61490c210316c656d7a60063c20ebb9b56eb4b7e228a2a1b10044413ba563fccb1e6d1ba4d58823cb8d70bb28b6f76d428c9cee6036f53d8560a69b1dbec521af0c1c00badd504c71c0c9dfb974ffc4ae414487770f657cca648f98621aafa142bd11225cc4ba96a61d9df48fc6a553422ece6727e09c2cf4282fd2e9d03f2ca78797341119a4ef247176978eba2b0a5650d652012dd5632d98efd4cd98952762a90a4321d6ccb5b320a5a4f6191287887f29214415e7cec7a84120344110a0a8097bf5d6f147eddd1df1e545670f7f9bd4f06b167ded2a1265d74fadf8b511a93602e33bd4f288b22e00b316935739c25f2ff759d2caa64290040bbe4e43827f24230eaeba5dbd1ed91b93477fad7c0f684d80522c6573c91368334d933ad2b427222da05cbfff5e43bc9ffd243f5fca6771e0faa76cc3fcdaf59e0496708a05e7f62fa9f5b3978ec61e622552864518eafc8797b599ac7884e2c6aa9ae760c5c3fdc227a88d6d1c2848e4585efa3c635b2d68049726ed5d852f93755f7e9095584edf2761dbd37c78387faaccba3872c0bc0286ab4036ac223cb81ca2397b6c95751f3b0a85de92d5c5c6646cdc3aa79e3d90c3f54a398b9fde574d81ba34e62f2173ffe6989c0c0dfec846acec38f8293b0fdf6e733c739ff68c76a4df0271e42bd0bc64ae3585e5e649f5399d3e97443c1fdf554f2d93faa2a4abc5c35a0640e314faa9dd8d8fc56720704af30c4758096b99bf9040965424ed4f79569564c86b05527017608433271165818ed852e5e0740ca7c0ba5f0a5c6b56f00e216c4aaf0394b151fb0b1ac0f5f061af0999fe49274797a63819e3712d851a0e5d79b831c5e34ab22b41a198171de209b8b8faca23a11c624859", &mut ct0).unwrap(); + hex::decode_to_slice("a4c2e17f7c9961c9d78771605e5620ba80502305bb07b22ef5dd35b7483d78eed714fc7060e08fdcaf46a726a3ae66043f709098bfe8980a87b4435878ce4551c8470289b64f158245ed91407bb2ddd754006548666d5999916c3feef8867fcd4f94a0bba5be3810ec13348ca93679020cc9b2a247c69a31520fe42fed5778907b9dfc3fffb7d8e79e4eac4eb9b034f0c9fa9037c5e6f3aa0e69661eba51d721539dfd71eac8f8a26220c710e8bea9ebbc82292f1d3cad8368b6f577f6e300a2125024f2b1a3c175efea0268f5ad68b28636954c3e8e68e5389eb6bd7e7725e467e0169ced4a8ee19992a1bffb539545be100071620d585867d9a9fd3773b5edf80b43fe7258b2df7ee6c2ab9e76c1b0cd6e39a10bb6060fb310a9e71d54ff4685616d9bf404377b47dbbe059ddfcbf6bf2c608851a6aee2a9f94ef0b7f86c249d2c186a514930f1725eeb0310dced247c74dc0f4d0b3a21880e28bb5bc9b203e674c373ffd20e8e8b4795499da8abc177de3c7fc99fc121b014d9288d5bf4e9cde18b7273722aedfaeca7804bf9a3ead7a066c9018fcc0e51280254641752f54a6600118644ab46f5add39cee3009774e488052b84c854e199833bc22238bbd70b645556c3a5b542ee4b8a69c15aec8a3c3db01e1e3ec8642207110c998c7d09ffe4417443f41c61490c210316c656d7a60063c20ebb9b56eb4b7e228a2a1b10044413ba563fccb1e6d1ba4d58823cb8d70bb28b6f76d428c9cee6036f53d8560a69b1dbec521af0c1c00badd504c71c0c9dfb974ffc4ae414487770f657cca648f98621aafa142bd11225cc4ba96a61d9df48fc6a553422ece6727e09c2cf4282fd2e9d03f2ca78797341119a4ef247176978eba2b0a5650d652012dd5632d98efd4cd98952762a90a4321d6ccb5b320a5a4f6191287887f29214415e7cec7a84120344110a0a8097bf5d6f147eddd1df1e545670f7f9bd4f06b167ded2a1265d74fadf8b511a93602e33bd4f288b22e00b316935739c25f2ff759d2caa64290040bbe4e43827f24230eaeba5dbd1ed91b93477fad7c0f684d80522c6573c91368334d933ad2b427222da05cbfff5e43bc9ffd243f5fca6771e0faa76cc3fcdaf59e0496708a05e7f62fa9f5b3978ec61e622552864518eafc8797b599ac7884e2c6aa9ae760c5c3fdc227a88d6d1c2848e4585efa3c635b2d68049726ed5d852f93755f7e9095584edf2761dbd37c78387faaccba3872c0bc0286ab4036ac223cb81ca2397b6c95751f3b0a85de92d5c5c6646cdc3aa79e3d90c3f54a398b9fde574d81ba34e62f2173ffe6989c0c0dfec846acec38f8293b0fdf6e733c739ff68c76a4df0271e42bd0bc64ae3585e5e649f5399d3e97443c1fdf554f2d93faa2a4abc5c35a0640e314faa9dd8d8fc56720704af30c4758096b99bf9040965424ed4f79569564c86b05527017608433271165818ed852e5e0740ca7c0ba5f0a5c6b56f00e216c4aaf0394b151fb0b1ac0f5f061af0999fe49274797a63819e3712d851a0e5d79b831c5e34ab22b41a198171de209b8b8faca23a11c6248d9", &mut ct1).unwrap(); + hex::decode_to_slice("6573c7d6c029d9da4102d0aeccbac81924cf49270045629fdb192d5b761621c96b2ec3bb2d58c493d32d474524237b214462cf23dbcf637140558521acc5253eca4ff880b195e219b53bcb291990ef78476a9a0aaff555c13a502593c923a57c830c9d4d593ba5dc4fcf920c49f99a4c391167b2b8814959cb5c1e0c7437187522e87c71586a8712f117297593f25447aec86a6e1543e3e600594bb0ab28c1ee314a3fe561d6e2349529bb31888e58fac519934f26739723203ac27bbffc27cd1fb9982b3040fa626ebfa46d43680f2973106b5227f0f4c4f8e61ab2b27a248c3b6bab75b5075b6b34922e726a5252c4fcb00231d72e77e4818dd441e1d17af725905c3433eed2c2673a67a5cc01dc8754b6984a40f911bf0584bad031a277bc3a564c111218f06a350282360b1a03faea4134965b7c69a4a9e320cab0583662813ac7bc9e79a107f6c1d53858823197f478bdb9068bb4d6354df1b557a71e8f461b6b961548db6dab5342c2a4a4c9f390fae15166a72e6ac686790508fde275df5037929036bd3a1ec63b8e531551da2c4f5380b19f2310065a08cba60c24a33680169ac8061f97e39eac98b476ec9851a9033ac38bed54742da0a6738cb3c01190e0702bbf0216ada263728a48afe41d80925a475a5919b88394c216061a6c8c4b0094342e00a4970ff0a44f8633856127ab580a3eab8d407b99256abba2060ca633828624958dbc20ab91c5d46c3a7f594562eba603ec015e55867c835c7063beea9997d7586ef0e3b597448720224dc6c902a871114d7262f4f5c4b48268b9013b0fa237ca0c69be7c75b4410d4cfbbf023bc361b54b5e63312029199b18bb7709cc47a0bfe1a5bbe8a29d95e98331f8c355db01ee526a13882b62c45d221b17acd6cfbfb048637c5f55f31dfd74bd1cd57714a93f8928cf9bc461ef92728a88ccbab621118102c9b681dc095bb59158a472b3860c5e4788c6a6fa38294499a47140985c3eddcc20fd315759401e61421cf8b7b6b3b63d92e2784587c23bf797940ba1e4b815a3d946a3537563e04b20d499f155721ae459a7eca871aa7c094233711037fb32170916032bd90a1f6b8c55b68fa6952b7478120e00afbaf4ca14e72ffab8a34cca269dd403a5545a40b0211a9867fba2c8811598d173986f193a28f191a363105421c4eb774f2861376afac847145c53c7b038db0462ac66bef256b9961cc1a5a819ab52f61c3172f072a0f5129d4aa8f6a8637a8c2064959606a22befc38070c9a8b7c5bd606519da64af71a7083cd29ee1ebb7089459aaf92f16463ccbcbc30f5c99b9322444851653eb8a4b8acb997522c2825a3971373b3117bce88b657395b0d6aaf6178022543151c09d74471fa0c68e7d8143417aaf2a30bf4fb2c5c90625bdb5744b179b12d8565a199ac7d30ea3b0565b124d33304057391751ac54bf520d9a434cf0c3508b57a596417b57464fc91894c13cb8167ca8f655ba0fd60ab1114592b70c01da79c2d9c487b462c6bb298bf29ad813443eea44c42cba27987d33e651ab894ec4e827a4d27e05469e609032a90211a48791966c0c1bb98f61872321300ff3199ac39b8d46971a84a0ca8dfa2096c0844408a0853cd4a86eac43a68250cadbf9af2b2edc0ab44b8b986da75f9eee966b0ad23485a7d52f335a8934962eccd40886795e7ecca991812775e7bc66c46809", &mut ek0).unwrap(); + hex::decode_to_slice("6573c7d6c029d9da4102d0aeccbac81924cf49270045629fdb192d5b761621c96b2ec3bb2d58c493d32d474524237b214462cf23dbcf637140558521acc5253eca4ff880b195e219b53bcb291990ef78476a9a0aaff555c13a502593c923a57c830c9d4d593ba5dc4fcf920c49f99a4c391167b2b8814959cb5c1e0c7437187522e87c71586a8712f117297593f25447aec86a6e1543e3e600594bb0ab28c1ee314a3fe561d6e2349529bb31888e58fac519934f26739723203ac27bbffc27cd1fb9982b3040fa626ebfa46d43680f2973106b5227f0f4c4f8e61ab2b27a248c3b6bab75b5075b6b34922e726a5252c4fcb00231d72e77e4818dd441e1d17af725905c3433eed2c2673a67a5cc01dc8754b6984a40f911bf0584bad031a277bc3a564c111218f06a350282360b1a03faea4134965b7c69a4a9e320cab0583662813ac7bc9e79a107f6c1d53858823197f478bdb9068bb4d6354df1b557a71e8f461b6b961548db6dab5342c2a4a4c9f390fae15166a72e6ac686790508fde275df5037929036bd3a1ec63b8e531551da2c4f5380b19f2310065a08cba60c24a33680169ac8061f97e39eac98b476ec9851a9033ac38bed54742da0a6738cb3c01190e0702bbf0216ada263728a48afe41d80925a475a5919b88394c216061a6c8c4b0094342e00a4970ff0a44f8633856127ab580a3eab8d407b99256abba2060ca633828624958dbc20ab91c5d46c3a7f594562eba603ec015e55867c835c7063beea9997d7586ef0e3b597448720224dc6c902a871114d7262f4f5c4b48268b9013b0fa237ca0c69be7c75b4410d4cfbbf023bc361b54b5e63312029199b18bb7709cc47a0bfe1a5bbe8a29d95e98331f8c355db01ee526a13882b62c45d221b17acd6cfbfb048637c5f55f31dfd74bd1cd57714a93f8928cf9bc461ef92728a88ccbab621118102c9b681dc095bb59158a472b3860c5e4788c6a6fa38294499a47140985c3eddcc20fd315759401e61421cf8b7b6b3b63d92e2784587c23bf797940ba1e4b815a3d946a3537563e04b20d499f155721ae459a7eca871aa7c094233711037fb32170916032bd90a1f6b8c55b68fa6952b7478120e00afbaf4ca14e72ffab8a34cca269dd403a5545a40b0211a9867fba2c8811598d173986f193a28f191a363105421c4eb774f2861376afac847145c53c7b038db0462ac66bef256b9961cc1a5a819ab52f61c3172f072a0f5129d4aa8f6a8637a8c2064959606a22befc38070c9a8b7c5bd606519da64af71a7083cd29ee1ebb7089459aaf92f16463ccbcbc30f5c99b9322444851653eb8a4b8acb997522c2825a3971373b3117bce88b657395b0d6aaf6178022543151c09d74471fa0c68e7d8143417aaf2a30bf4fb2c5c90625bdb5744b179b12d8565a199ac7d30ea3b0565b124d33304057391751ac54bf520d9a434cf0c3508b57a596417b57464fc91894c13cb8167ca8f655ba0fd60ab1114592b70c01da79c2d9c487b462c6bb298bf29ad813443eea44c42cba27987d33e651ab894ec4e827a4d27e05469e609032a90211a48791966c0c1bb98f61872321300ff3199ac39b8d46971a84a0ca8dfa2096c0844408a0853cd4a86eac43a68250cadbf9af2b2edc0ab44b8b986da75f9eee966b0ad23485a7d52f335a8934962eccd40886795e7ecca991812775e7bc66c46889", &mut ek1).unwrap(); + hex::decode_to_slice( + "b0c2808b0a0441a6b8889470c05a7a3e5bcf891b16511408237ceb8128c83ef4", + &mut ss_ct0, + ) + .unwrap(); + hex::decode_to_slice( + "fbd7a487e9d7e9e26489c6993a786d02c9abc7e48a45112ebde1b182ada3578a", + &mut ss_ct1, + ) + .unwrap(); + hex::decode_to_slice( + "b0c2808b0a0441a6b8889470c05a7a3e5bcf891b16511408237ceb8128c83ef4", + &mut ss_ek0, + ) + .unwrap(); + hex::decode_to_slice( + "03ff3eb147f85d888230b72e599a7c2b06ba747cdd2b4c6642e6ae5be437a5e7", + &mut ss_ek1, + ) + .unwrap(); + + let dk = DecapsulationKey::from(seed); + assert_eq!(dk.unprotected_as_ref(), &seed); + let kp = KeyPair::try_from(&dk).unwrap(); + assert_eq!(kp.private(), &dk); + + let ek0 = EncapsulationKey::try_from(&ek0).unwrap(); + assert_eq!(kp.public(), &ek0); + let ek1 = EncapsulationKey::try_from(&ek1).unwrap(); + assert_ne!(kp.public(), &ek1); + + let (ss0, _ct0_rt) = ek0.encap_deterministic(&eseed).unwrap(); + let (ss1, _ct1_rt) = ek1.encap_deterministic(&eseed).unwrap(); + assert_eq!(ss0.unprotected_as_ref(), &ss_ek0); + assert_eq!(ss1.unprotected_as_ref(), &ss_ek1); + + assert_eq!( + &ss_ct0, + kp.decap(&Ciphertext::from(ct0)) + .unwrap() + .unprotected_as_ref() + ); + assert_eq!( + &ss_ct1, + kp.decap(&Ciphertext::from(ct1)) + .unwrap() + .unprotected_as_ref() + ); + } + + #[test] + fn test_decapsulation_key() { + use crate::test_framework::newtypes::secret::SecretNewtype; + SecretNewtype::test_with_generate::(); + + // Test of From<[u8; N]> + assert_ne!( + DecapsulationKey::from([0u8; DK_SIZE]), + DecapsulationKey::from([1u8; DK_SIZE]) + ) + } + + #[test] + fn test_shared_secret() { + use crate::test_framework::newtypes::secret::SecretNewtype; + SecretNewtype::test_no_generate::( + ); + + // Test of From<[u8; N]> + assert_ne!( + SharedSecret::from([0u8; SHARED_SECRET_SIZE]), + SharedSecret::from([1u8; SHARED_SECRET_SIZE]) + ) + } + + #[test] + fn test_encapuslation_key() { + use crate::test_framework::newtypes::public::PublicNewtype; + PublicNewtype::test_no_generate::(); + + // Test of From<[u8; N]> + assert_ne!( + EncapsulationKey::from([0u8; EK_SIZE]), + EncapsulationKey::from([1u8; EK_SIZE]) + ); + + #[cfg(feature = "serde")] + PublicNewtype::test_serialization::(); + } + + #[test] + fn test_ciphertext() { + use crate::test_framework::newtypes::public::PublicNewtype; + PublicNewtype::test_no_generate::(); + + // Test of From<[u8; N]> + assert_ne!( + Ciphertext::from([0u8; CIPHERTEXT_SIZE]), + Ciphertext::from([1u8; CIPHERTEXT_SIZE]) + ); + + #[cfg(feature = "serde")] + PublicNewtype::test_serialization::(); + } + #[cfg(feature = "safe_api")] use crate::test_framework::kem_interface::{KemTester, TestableKem}; #[cfg(feature = "safe_api")] - impl TestableKem for XWing { + impl TestableKem for KeyPair { fn keygen(seed: &[u8]) -> Result<(Vec, Vec), UnknownCryptoError> { - let kp = KeyPair::try_from(&Seed::from_slice(seed).unwrap()).unwrap(); + let kp = KeyPair::try_from(&DecapsulationKey::try_from(seed)?)?; Ok(( - kp.ek.as_ref().to_vec(), - kp.dk.unprotected_as_bytes().to_vec(), + kp.public.as_ref().to_vec(), + kp.private.unprotected_as_ref().to_vec(), )) } fn ciphertext_from_bytes(b: &[u8]) -> Result { - Ciphertext::from_slice(b) + Ciphertext::try_from(b) } fn encap(ek: &[u8]) -> Result<(SharedSecret, Ciphertext), UnknownCryptoError> { - let ek = EncapsulationKey::from_slice(ek).unwrap(); - XWing::encap(&ek) + let ek = EncapsulationKey::try_from(ek)?; + ek.encap() } fn decap(dk: &[u8], c: &Ciphertext) -> Result { - let kp = KeyPair::try_from(&Seed::from_slice(dk)?).unwrap(); - XWing::decap(kp.private(), c) + let dk = DecapsulationKey::try_from(dk)?; + let kp = KeyPair::try_from(&dk)?; + assert_eq!(dk.decap(c).unwrap(), kp.decap(c).unwrap()); + + dk.decap(c) } } #[cfg(feature = "safe_api")] #[test] fn run_basic_kem_tests() { - let seed = Seed::generate(); - KemTester::::run_all_tests(seed.unprotected_as_bytes()); + // Tests were designed with ML-KEM notation in mind, but with X-Wing the seed and decapsulation key + // should be the same. + let seed = DecapsulationKey::generate().unwrap(); + KemTester::::run_all_tests(seed.unprotected_as_ref()); } #[test] /// Basic no_std-compatible test. fn basic_roundtrip() { - let seed = Seed::from_slice(&[127u8; 32]).unwrap(); - let kp = KeyPair::try_from(&seed).unwrap(); + let dk = DecapsulationKey::try_from(&[127u8; DK_SIZE]).unwrap(); + let kp = KeyPair::try_from(&dk).unwrap(); - let (k, c) = XWing::encap_deterministic(kp.public(), &[255u8; 64]).unwrap(); - let k_prime = XWing::decap(kp.private(), &c).unwrap(); + let (k, c) = kp.public().encap_deterministic(&[255u8; 64]).unwrap(); + let k_prime = kp.private().decap(&c).unwrap(); assert_eq!(k, k_prime); } #[test] fn get_decapskey_as_bytes_is_seed() { - let seed = Seed::from_slice(&[127u8; 32]).unwrap(); + let seed = DecapsulationKey::try_from(&[127u8; DK_SIZE]).unwrap(); let kp = KeyPair::try_from(&seed).unwrap(); - assert_eq!( - seed.unprotected_as_bytes(), - kp.private().unprotected_as_bytes() - ); + assert_eq!(seed.unprotected_as_ref(), kp.private().unprotected_as_ref()); } #[test] fn bad_eseed_lens() { - let seed = Seed::from_slice(&[127u8; 32]).unwrap(); + let seed = DecapsulationKey::try_from(&[127u8; DK_SIZE]).unwrap(); let kp = KeyPair::try_from(&seed).unwrap(); - assert!(XWing::encap_deterministic(kp.public(), &[255u8; 64]).is_ok()); - assert!(XWing::encap_deterministic(kp.public(), &[255u8; 63]).is_err()); - assert!(XWing::encap_deterministic(kp.public(), &[255u8; 65]).is_err()); + assert!(kp.public().encap_deterministic(&[255u8; 64]).is_ok()); + assert!(kp.public().encap_deterministic(&[255u8; 63]).is_err()); + assert!(kp.public().encap_deterministic(&[255u8; 65]).is_err()); } } diff --git a/src/hazardous/mac/blake2b.rs b/src/hazardous/mac/blake2b.rs index f6eabbceb..a6d8934cd 100644 --- a/src/hazardous/mac/blake2b.rs +++ b/src/hazardous/mac/blake2b.rs @@ -51,7 +51,7 @@ //! # #[cfg(feature = "safe_api")] { //! use orion::hazardous::mac::blake2b::{Blake2b, SecretKey}; //! -//! let key = SecretKey::generate(); +//! let key = SecretKey::generate()?; //! //! let mut state = Blake2b::new(&key, 64)?; //! state.update(b"Some data")?; @@ -68,30 +68,81 @@ //! [`hash::blake2::blake2b`]: crate::hazardous::hash::blake2::blake2b use crate::errors::UnknownCryptoError; +use crate::generics::GenerateSecret; +#[cfg(feature = "safe_api")] +use crate::generics::sealed::Data; +use crate::generics::sealed::Sealed; +use crate::generics::{ByteArrayVecData, Secret, TypeSpec}; use crate::hazardous::hash::blake2::blake2b_core::{self, BLAKE2B_KEYSIZE, BLAKE2B_OUTSIZE}; +#[cfg(feature = "serde")] +use alloc::vec::Vec; -construct_secret_key! { - /// A type to represent the secret key that BLAKE2b uses for keyed mode. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is empty. - /// - `slice` is greater than 64 bytes. - /// - /// # Panics: - /// A panic will occur if: - /// - Failure to generate random bytes securely. - (SecretKey, test_secret_key, 1, BLAKE2B_KEYSIZE, 32) +#[derive(Debug)] +/// Marker type for BLAKE2b key. See [`SecretKey`] type for convenience. +pub struct Blake2bKey {} +impl Sealed for Blake2bKey {} + +impl TypeSpec for Blake2bKey { + const NAME: &'static str = stringify!(SecretKey); + type TypeData = ByteArrayVecData<1, BLAKE2B_KEYSIZE>; } -construct_tag! { - /// A type to represent the `Tag` that BLAKE2b returns. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is empty. - /// - `slice` is greater than 64 bytes. - (Tag, test_tag, 1, BLAKE2B_OUTSIZE) +impl GenerateSecret for Blake2bKey { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + fn generate() -> Result, UnknownCryptoError> { + let mut data = Self::TypeData::new(32)?; + crate::util::secure_rand_bytes(&mut data.bytes)?; + Ok(Secret::from_data(data)) + } +} + +/// A type to represent the secret key that BLAKE2b uses for keyed mode. +pub type SecretKey = Secret; + +#[derive(Debug, Clone)] +/// Marker type for BLAKE2b MAC/Tag. See [`Tag`] type for convenience. +pub struct Blake2bTag {} +impl Sealed for Blake2bTag {} + +impl TypeSpec for Blake2bTag { + const NAME: &'static str = stringify!(Tag); + type TypeData = ByteArrayVecData<1, BLAKE2B_OUTSIZE>; +} + +/// A type to represent the MAC/Tag that BLAKE2b returns for keyed mode. +pub type Tag = Secret; + +#[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] +/// This type tries to serialize as a `&[u8]` would. Note that the serialized +/// type likely does not have the same protections that Orion provides, such +/// as constant-time operations. A good rule of thumb is to only serialize +/// these types for storage. Don't operate on the serialized types. +impl serde::Serialize for Tag { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + let bytes: &[u8] = &self.data.as_ref(); + bytes.serialize(serializer) + } +} + +#[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] +/// This type tries to deserialize as a `Vec` would. If it succeeds, the public data +/// will be built using `Self::try_from`. +/// +/// Note that **this allocates** once to store the referenced bytes on the heap. +impl<'de> serde::Deserialize<'de> for Tag { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + let bytes = Vec::::deserialize(deserializer)?; + TryFrom::try_from(bytes.as_slice()).map_err(serde::de::Error::custom) + } } #[derive(Debug, Clone)] @@ -105,14 +156,14 @@ impl Blake2b { /// Initialize a `Blake2b` struct with a given size (in bytes) and key. pub fn new(secret_key: &SecretKey, size: usize) -> Result { Ok(Self { - _state: blake2b_core::State::_new(secret_key.unprotected_as_bytes(), size)?, + _state: blake2b_core::State::_new(secret_key.unprotected_as_ref(), size)?, }) } #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."] /// Reset to `new()` state. pub fn reset(&mut self, secret_key: &SecretKey) -> Result<(), UnknownCryptoError> { - self._state._reset(secret_key.unprotected_as_bytes()) + self._state._reset(secret_key.unprotected_as_ref()) } #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."] @@ -127,7 +178,7 @@ impl Blake2b { let mut tmp = zeroize_wrap!([0u8; BLAKE2B_OUTSIZE]); self._state._finalize(&mut tmp)?; - Tag::from_slice(&tmp[..self._state.size]) + Tag::try_from(&tmp[..self._state.size]) } #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."] @@ -151,10 +202,43 @@ impl Blake2b { #[cfg(test)] mod public { + use super::*; + + #[test] + fn test_blake2b_key() { + use crate::test_framework::newtypes::secret::SecretNewtype; + SecretNewtype::test_with_generate::<1, BLAKE2B_KEYSIZE, 32, Blake2bKey>(); + } + + #[test] + fn test_blake2b_tag() { + use crate::test_framework::newtypes::secret::SecretNewtype; + SecretNewtype::test_no_generate::<1, BLAKE2B_OUTSIZE, Blake2bTag>(); + } + + #[test] + #[cfg(feature = "serde")] + fn test_serde_serialized_equivalence_to_bytes_fn() { + let bytes = [38u8; BLAKE2B_OUTSIZE]; + let secret_type = Tag::try_from(&bytes).unwrap(); + let serialized_from_bytes = serde_json::to_value(bytes.as_slice()).unwrap(); + let serialized_from_secret_type = serde_json::to_value(&secret_type).unwrap(); + assert_eq!(serialized_from_bytes, serialized_from_secret_type); + } + + #[test] + #[cfg(feature = "serde")] + fn test_serde_deserialized_equivalence_to_bytes_fn() { + let bytes = [38u8; BLAKE2B_OUTSIZE]; + let serialized_from_bytes = serde_json::to_value(bytes.as_slice()).unwrap(); + let secret_type: Tag = serde_json::from_value(serialized_from_bytes).unwrap(); + assert_eq!(secret_type.unprotected_as_ref(), bytes.as_slice()); + } + mod test_streaming_interface_no_key { use crate::errors::UnknownCryptoError; use crate::hazardous::hash::blake2::blake2b_core::{ - compare_blake2b_states, BLAKE2B_BLOCKSIZE, BLAKE2B_OUTSIZE, + BLAKE2B_BLOCKSIZE, BLAKE2B_OUTSIZE, compare_blake2b_states, }; use crate::hazardous::mac::blake2b::{Blake2b, SecretKey, Tag}; use crate::test_framework::incremental_interface::{ @@ -165,7 +249,7 @@ mod public { impl TestableStreamingContext for Blake2b { fn reset(&mut self) -> Result<(), UnknownCryptoError> { - let key = SecretKey::from_slice(&KEY).unwrap(); + let key = SecretKey::try_from(KEY.as_slice()).unwrap(); self.reset(&key) } @@ -178,7 +262,7 @@ mod public { } fn one_shot(input: &[u8]) -> Result { - let key = SecretKey::from_slice(&KEY).unwrap(); + let key = SecretKey::try_from(KEY.as_slice()).unwrap(); let mut ctx = Blake2b::new(&key, BLAKE2B_OUTSIZE)?; ctx.update(input)?; ctx.finalize() @@ -201,7 +285,7 @@ mod public { #[test] fn default_consistency_tests() { - let key = SecretKey::from_slice(&KEY).unwrap(); + let key = SecretKey::try_from(KEY.as_slice()).unwrap(); let initial_state: Blake2b = Blake2b::new(&key, BLAKE2B_OUTSIZE).unwrap(); let test_runner = StreamingContextConsistencyTester::::new( @@ -216,7 +300,7 @@ mod public { /// Related bug: https://github.com/orion-rs/orion/issues/46 /// Test different streaming state usage patterns. fn prop_input_to_consistency(data: Vec) -> bool { - let key = SecretKey::from_slice(&KEY).unwrap(); + let key = SecretKey::try_from(KEY.as_slice()).unwrap(); let initial_state: Blake2b = Blake2b::new(&key, BLAKE2B_OUTSIZE).unwrap(); let test_runner = StreamingContextConsistencyTester::::new( @@ -233,7 +317,7 @@ mod public { #[test] fn test_init_size() { - let sk = SecretKey::from_slice(&[0u8; 32]).unwrap(); + let sk = SecretKey::try_from([0u8; 32].as_slice()).unwrap(); assert!(Blake2b::new(&sk, 0).is_err()); assert!(Blake2b::new(&sk, 65).is_err()); assert!(Blake2b::new(&sk, 1).is_ok()); @@ -250,11 +334,11 @@ mod public { /// When using a different key, verify() should always yield an error. /// NOTE: Using different and same input data is tested with TestableStreamingContext. fn prop_verify_diff_key_false(data: Vec) -> bool { - let sk = SecretKey::generate(); + let sk = SecretKey::generate().unwrap(); let mut state = Blake2b::new(&sk, 64).unwrap(); state.update(&data[..]).unwrap(); let tag = state.finalize().unwrap(); - let bad_sk = SecretKey::generate(); + let bad_sk = SecretKey::generate().unwrap(); Blake2b::verify(&tag, &bad_sk, 64, &data[..]).is_err() } @@ -269,7 +353,7 @@ mod public { (_, _) => (32, 64), }; - let sk = SecretKey::generate(); + let sk = SecretKey::generate().unwrap(); let mut state = Blake2b::new(&sk, size_one).unwrap(); state.update(&data[..]).unwrap(); let tag = state.finalize().unwrap(); @@ -376,7 +460,7 @@ mod public { #[test] /// Related bug: https://github.com/orion-rs/orion/issues/46 fn test_produce_same_state() { - let sk = SecretKey::from_slice(b"Testing").unwrap(); + let sk = SecretKey::try_from(b"Testing".as_slice()).unwrap(); produces_same_state(&sk, 1, b"Tests"); produces_same_state(&sk, 32, b"Tests"); produces_same_state(&sk, 64, b"Tests"); @@ -386,7 +470,7 @@ mod public { #[test] /// Related bug: https://github.com/orion-rs/orion/issues/46 fn test_produce_same_hash() { - let sk = SecretKey::from_slice(b"Testing").unwrap(); + let sk = SecretKey::try_from(b"Testing".as_slice()).unwrap(); produces_same_hash(&sk, 1, b"Tests"); produces_same_hash(&sk, 32, b"Tests"); produces_same_hash(&sk, 64, b"Tests"); @@ -407,7 +491,7 @@ mod public { if (1..=BLAKE2B_OUTSIZE).contains(&size) { // Will panic on incorrect results. - let sk = SecretKey::generate(); + let sk = SecretKey::generate().unwrap(); produces_same_hash(&sk, size, &data[..]); } @@ -423,7 +507,7 @@ mod public { if (1..=BLAKE2B_OUTSIZE).contains(&size) { // Will panic on incorrect results. - let sk = SecretKey::generate(); + let sk = SecretKey::generate().unwrap(); produces_same_state(&sk, size, &data[..]); } diff --git a/src/hazardous/mac/hmac.rs b/src/hazardous/mac/hmac.rs index 93c4d5d08..0f68f0748 100644 --- a/src/hazardous/mac/hmac.rs +++ b/src/hazardous/mac/hmac.rs @@ -46,7 +46,7 @@ //! # #[cfg(feature = "safe_api")] { //! use orion::hazardous::mac::hmac::sha512::{HmacSha512, SecretKey}; //! -//! let key = SecretKey::generate(); +//! let key = SecretKey::generate()?; //! //! let mut state = HmacSha512::new(&key); //! state.update(b"Some message.")?; @@ -222,34 +222,105 @@ impl Hmac { /// HMAC-SHA256 (Hash-based Message Authentication Code) as specified in the [RFC 2104](https://tools.ietf.org/html/rfc2104). pub mod sha256 { use super::*; - use crate::hazardous::hash::sha2::sha256::{self, Sha256}; + use crate::generics::GenerateSecret; + use crate::{ + generics::{ByteArrayData, Secret, TypeSpec, sealed::Data, sealed::Sealed}, + hazardous::hash::sha2::sha256::{self, Sha256}, + }; + #[cfg(feature = "serde")] + use alloc::vec::Vec; + + #[derive(Debug)] + /// Marker type for HMAC-SHA256 secret key. See [`SecretKey`] type for convenience. + /// + /// # Note: + /// [`SecretKey`] pads the secret key for use with HMAC to a length of [`sha256::SHA256_BLOCKSIZE`], when initialized. + /// + /// Using [`SecretKey::unprotected_as_ref()`] will return the secret key with padding. + /// + /// [`SecretKey::len()`] will return the length with padding (always [`sha256::SHA256_BLOCKSIZE`]). + pub struct HmacSha256Key {} + impl Sealed for HmacSha256Key {} - construct_hmac_key! { - /// A type to represent the `SecretKey` that HMAC uses for authentication. - /// - /// # Note: - /// `SecretKey` pads the secret key for use with HMAC to a length of 64, when initialized. - /// - /// Using `unprotected_as_bytes()` will return the secret key with padding. - /// - /// `len()` will return the length with padding (always 64). - /// - /// # Panics: - /// A panic will occur if: - /// - Failure to generate random bytes securely. - (SecretKey, Sha256, sha256::SHA256_OUTSIZE, test_hmac_key, sha256::SHA256_BLOCKSIZE) + impl TypeSpec for HmacSha256Key { + const NAME: &'static str = stringify!(SecretKey); + type TypeData = ByteArrayData<{ sha256::SHA256_BLOCKSIZE }>; + + fn parse_bytes(bytes: &[u8]) -> Result { + let mut data = ByteArrayData::new(sha256::SHA256_BLOCKSIZE)?; + + if bytes.len() > sha256::SHA256_BLOCKSIZE { + Sha256::_digest(bytes, &mut data.bytes[..sha256::SHA256_OUTSIZE])?; + } else { + data.bytes[..bytes.len()].copy_from_slice(bytes); + } + + Ok(data) + } } - construct_tag! { - /// A type to represent the `Tag` that HMAC returns. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 32 bytes. - (Tag, test_tag, sha256::SHA256_OUTSIZE, sha256::SHA256_OUTSIZE) + impl GenerateSecret for HmacSha256Key { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + fn generate() -> Result, UnknownCryptoError> { + let mut data = Self::TypeData::new(sha256::SHA256_BLOCKSIZE)?; + crate::util::secure_rand_bytes(&mut data.bytes)?; + Ok(Secret::from_data(data)) + } } - impl_from_trait!(Tag, sha256::SHA256_OUTSIZE); + /// A type to represent the secret key that HMAC-SHA256 uses. + pub type SecretKey = Secret; + + #[derive(Debug, Clone)] + /// Marker type for HMAC-SHA256 MAC/Tag. See [`Tag`] type for convenience. + pub struct HmacSha256Tag {} + impl Sealed for HmacSha256Tag {} + impl TypeSpec for HmacSha256Tag { + const NAME: &'static str = stringify!(Tag); + type TypeData = ByteArrayData<{ sha256::SHA256_OUTSIZE }>; + } + + impl From<[u8; sha256::SHA256_OUTSIZE]> for Secret { + fn from(value: [u8; sha256::SHA256_OUTSIZE]) -> Self { + Self::from_data(::TypeData::from(value)) + } + } + + /// A type to represent the MAC/Tag that HMAC-SHA256 returns. + pub type Tag = Secret; + + #[cfg(feature = "serde")] + #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] + /// This type tries to serialize as a `&[u8]` would. Note that the serialized + /// type likely does not have the same protections that Orion provides, such + /// as constant-time operations. A good rule of thumb is to only serialize + /// these types for storage. Don't operate on the serialized types. + impl serde::Serialize for Tag { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + let bytes: &[u8] = &self.data.as_ref(); + bytes.serialize(serializer) + } + } + + #[cfg(feature = "serde")] + #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] + /// This type tries to deserialize as a `Vec` would. If it succeeds, the public data + /// will be built using `Self::try_from`. + /// + /// Note that **this allocates** once to store the referenced bytes on the heap. + impl<'de> serde::Deserialize<'de> for Tag { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + let bytes = Vec::::deserialize(deserializer)?; + TryFrom::try_from(bytes.as_slice()).map_err(serde::de::Error::custom) + } + } use super::Hmac; @@ -270,7 +341,7 @@ pub mod sha256 { /// Initialize `HmacSha256` struct with a given key. pub fn new(secret_key: &SecretKey) -> Self { // NOTE: `secret_key` has been pre-padded so .unwrap() is OK. - Self::_new(secret_key.unprotected_as_bytes()).unwrap() + Self::_new(secret_key.unprotected_as_ref()).unwrap() } /// Reset to `new()` state. @@ -357,10 +428,52 @@ pub mod sha256 { mod public { use super::*; + #[test] + fn test_key() { + use super::*; + use crate::test_framework::newtypes::secret::SecretNewtype; + SecretNewtype::test_with_generate::< + { sha256::SHA256_BLOCKSIZE }, + { sha256::SHA256_BLOCKSIZE }, + { sha256::SHA256_BLOCKSIZE }, + HmacSha256Key, + >(); + } + + #[test] + fn test_tag() { + use super::*; + use crate::test_framework::newtypes::secret::SecretNewtype; + SecretNewtype::test_no_generate::< + { sha256::SHA256_OUTSIZE }, + { sha256::SHA256_OUTSIZE }, + HmacSha256Tag, + >(); + } + + #[test] + #[cfg(feature = "serde")] + fn test_serde_serialized_equivalence_to_bytes_fn() { + let bytes = [38u8; sha256::SHA256_OUTSIZE]; + let secret_type = Tag::try_from(&bytes).unwrap(); + let serialized_from_bytes = serde_json::to_value(bytes.as_slice()).unwrap(); + let serialized_from_secret_type = serde_json::to_value(&secret_type).unwrap(); + assert_eq!(serialized_from_bytes, serialized_from_secret_type); + } + + #[test] + #[cfg(feature = "serde")] + fn test_serde_deserialized_equivalence_to_bytes_fn() { + let bytes = [38u8; sha256::SHA256_OUTSIZE]; + let serialized_from_bytes = serde_json::to_value(bytes.as_slice()).unwrap(); + let secret_type: Tag = serde_json::from_value(serialized_from_bytes).unwrap(); + assert_eq!(secret_type.unprotected_as_ref(), bytes.as_slice()); + } + #[test] #[cfg(feature = "safe_api")] fn test_debug_impl() { - let secret_key = SecretKey::generate(); + let secret_key = SecretKey::generate().unwrap(); let initial_state = HmacSha256::new(&secret_key); let debug = format!("{initial_state:?}"); let expected = "HmacSha256 { _state: Hmac { working_hasher: [***OMITTED***], opad_hasher: [***OMITTED***], ipad_hasher: [***OMITTED***], is_finalized: false } }"; @@ -376,11 +489,11 @@ pub mod sha256 { /// When using a different key, verify() should always yield an error. /// NOTE: Using different and same input data is tested with TestableStreamingContext. fn prop_verify_diff_key_false(data: Vec) -> bool { - let sk = SecretKey::generate(); + let sk = SecretKey::generate().unwrap(); let mut state = HmacSha256::new(&sk); state.update(&data[..]).unwrap(); let tag = state.finalize().unwrap(); - let bad_sk = SecretKey::generate(); + let bad_sk = SecretKey::generate().unwrap(); HmacSha256::verify(&tag, &bad_sk, &data[..]).is_err() } @@ -407,13 +520,17 @@ pub mod sha256 { } fn one_shot(input: &[u8]) -> Result { - HmacSha256::hmac(&SecretKey::from_slice(&KEY).unwrap(), input) + HmacSha256::hmac(&SecretKey::try_from(KEY.as_slice()).unwrap(), input) } fn verify_result(expected: &Tag, input: &[u8]) -> Result<(), UnknownCryptoError> { // This will only run verification tests on differing input. They do not // include tests for different secret keys. - HmacSha256::verify(expected, &SecretKey::from_slice(&KEY).unwrap(), input) + HmacSha256::verify( + expected, + &SecretKey::try_from(KEY.as_slice()).unwrap(), + input, + ) } fn compare_states(state_1: &HmacSha256, state_2: &HmacSha256) { @@ -423,7 +540,7 @@ pub mod sha256 { #[test] fn default_consistency_tests() { - let initial_state = HmacSha256::new(&SecretKey::from_slice(&KEY).unwrap()); + let initial_state = HmacSha256::new(&SecretKey::try_from(KEY.as_slice()).unwrap()); let test_runner = StreamingContextConsistencyTester::::new( initial_state, @@ -437,7 +554,7 @@ pub mod sha256 { /// Related bug: https://github.com/orion-rs/orion/issues/46 /// Test different streaming state usage patterns. fn prop_input_to_consistency(data: Vec) -> bool { - let initial_state = HmacSha256::new(&SecretKey::from_slice(&KEY).unwrap()); + let initial_state = HmacSha256::new(&SecretKey::try_from(KEY.as_slice()).unwrap()); let test_runner = StreamingContextConsistencyTester::::new( initial_state, @@ -453,34 +570,105 @@ pub mod sha256 { /// HMAC-SHA384 (Hash-based Message Authentication Code) as specified in the [RFC 2104](https://tools.ietf.org/html/rfc2104). pub mod sha384 { use super::*; - use crate::hazardous::hash::sha2::sha384::{self, Sha384}; + use crate::generics::GenerateSecret; + use crate::{ + generics::{ByteArrayData, Secret, TypeSpec, sealed::Data, sealed::Sealed}, + hazardous::hash::sha2::sha384::{self, Sha384}, + }; + #[cfg(feature = "serde")] + use alloc::vec::Vec; + + #[derive(Debug)] + /// Marker type for HMAC-SHA384 secret key. See [`SecretKey`] type for convenience. + /// # Note: + /// [`SecretKey`] pads the secret key for use with HMAC to a length of [`sha384::SHA384_BLOCKSIZE`], when initialized. + /// + /// Using [`SecretKey::unprotected_as_ref()`] will return the secret key with padding. + /// + /// [`SecretKey::len()`] will return the length with padding (always [`sha384::SHA384_BLOCKSIZE`]). + pub struct HmacSha384Key {} + impl Sealed for HmacSha384Key {} - construct_hmac_key! { - /// A type to represent the `SecretKey` that HMAC uses for authentication. - /// - /// # Note: - /// `SecretKey` pads the secret key for use with HMAC to a length of 128, when initialized. - /// - /// Using `unprotected_as_bytes()` will return the secret key with padding. - /// - /// `len()` will return the length with padding (always 128). - /// - /// # Panics: - /// A panic will occur if: - /// - Failure to generate random bytes securely. - (SecretKey, Sha384, sha384::SHA384_OUTSIZE, test_hmac_key, sha384::SHA384_BLOCKSIZE) + impl TypeSpec for HmacSha384Key { + const NAME: &'static str = stringify!(SecretKey); + type TypeData = ByteArrayData<{ sha384::SHA384_BLOCKSIZE }>; + + fn parse_bytes(bytes: &[u8]) -> Result { + let mut data = ByteArrayData::new(sha384::SHA384_BLOCKSIZE)?; + + if bytes.len() > sha384::SHA384_BLOCKSIZE { + Sha384::_digest(bytes, &mut data.bytes[..sha384::SHA384_OUTSIZE])?; + } else { + data.bytes[..bytes.len()].copy_from_slice(bytes); + } + + Ok(data) + } } - construct_tag! { - /// A type to represent the `Tag` that HMAC returns. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 48 bytes. - (Tag, test_tag, sha384::SHA384_OUTSIZE, sha384::SHA384_OUTSIZE) + impl GenerateSecret for HmacSha384Key { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + fn generate() -> Result, UnknownCryptoError> { + let mut data = Self::TypeData::new(sha384::SHA384_BLOCKSIZE)?; + crate::util::secure_rand_bytes(&mut data.bytes)?; + Ok(Secret::from_data(data)) + } } - impl_from_trait!(Tag, sha384::SHA384_OUTSIZE); + /// A type to represent the secret key that HMAC-SHA384 uses. + pub type SecretKey = Secret; + + #[derive(Debug, Clone)] + /// Marker type for HMAC-SHA384 MAC/Tag. See [`Tag`] type for convenience. + pub struct HmacSha384Tag {} + impl Sealed for HmacSha384Tag {} + + impl TypeSpec for HmacSha384Tag { + const NAME: &'static str = stringify!(Tag); + type TypeData = ByteArrayData<{ sha384::SHA384_OUTSIZE }>; + } + + impl From<[u8; sha384::SHA384_OUTSIZE]> for Secret { + fn from(value: [u8; sha384::SHA384_OUTSIZE]) -> Self { + Self::from_data(::TypeData::from(value)) + } + } + + /// A type to represent the MAC/Tag that HMAC-SHA384 returns. + pub type Tag = Secret; + + #[cfg(feature = "serde")] + #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] + /// This type tries to serialize as a `&[u8]` would. Note that the serialized + /// type likely does not have the same protections that Orion provides, such + /// as constant-time operations. A good rule of thumb is to only serialize + /// these types for storage. Don't operate on the serialized types. + impl serde::Serialize for Tag { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + let bytes: &[u8] = &self.data.as_ref(); + bytes.serialize(serializer) + } + } + + #[cfg(feature = "serde")] + #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] + /// This type tries to deserialize as a `Vec` would. If it succeeds, the public data + /// will be built using `Self::try_from`. + /// + /// Note that **this allocates** once to store the referenced bytes on the heap. + impl<'de> serde::Deserialize<'de> for Tag { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + let bytes = Vec::::deserialize(deserializer)?; + TryFrom::try_from(bytes.as_slice()).map_err(serde::de::Error::custom) + } + } use super::Hmac; @@ -501,7 +689,7 @@ pub mod sha384 { /// Initialize `HmacSha384` struct with a given key. pub fn new(secret_key: &SecretKey) -> Self { // NOTE: `secret_key` has been pre-padded so .unwrap() is OK. - Self::_new(secret_key.unprotected_as_bytes()).unwrap() + Self::_new(secret_key.unprotected_as_ref()).unwrap() } /// Reset to `new()` state. @@ -588,10 +776,52 @@ pub mod sha384 { mod public { use super::*; + #[test] + fn test_key() { + use super::*; + use crate::test_framework::newtypes::secret::SecretNewtype; + SecretNewtype::test_with_generate::< + { sha384::SHA384_BLOCKSIZE }, + { sha384::SHA384_BLOCKSIZE }, + { sha384::SHA384_BLOCKSIZE }, + HmacSha384Key, + >(); + } + + #[test] + fn test_tag() { + use super::*; + use crate::test_framework::newtypes::secret::SecretNewtype; + SecretNewtype::test_no_generate::< + { sha384::SHA384_OUTSIZE }, + { sha384::SHA384_OUTSIZE }, + HmacSha384Tag, + >(); + } + + #[test] + #[cfg(feature = "serde")] + fn test_serde_serialized_equivalence_to_bytes_fn() { + let bytes = [38u8; sha384::SHA384_OUTSIZE]; + let secret_type = Tag::try_from(&bytes).unwrap(); + let serialized_from_bytes = serde_json::to_value(bytes.as_slice()).unwrap(); + let serialized_from_secret_type = serde_json::to_value(&secret_type).unwrap(); + assert_eq!(serialized_from_bytes, serialized_from_secret_type); + } + + #[test] + #[cfg(feature = "serde")] + fn test_serde_deserialized_equivalence_to_bytes_fn() { + let bytes = [38u8; sha384::SHA384_OUTSIZE]; + let serialized_from_bytes = serde_json::to_value(bytes.as_slice()).unwrap(); + let secret_type: Tag = serde_json::from_value(serialized_from_bytes).unwrap(); + assert_eq!(secret_type.unprotected_as_ref(), bytes.as_slice()); + } + #[test] #[cfg(feature = "safe_api")] fn test_debug_impl() { - let secret_key = SecretKey::generate(); + let secret_key = SecretKey::generate().unwrap(); let initial_state = HmacSha384::new(&secret_key); let debug = format!("{initial_state:?}"); let expected = "HmacSha384 { _state: Hmac { working_hasher: [***OMITTED***], opad_hasher: [***OMITTED***], ipad_hasher: [***OMITTED***], is_finalized: false } }"; @@ -607,11 +837,11 @@ pub mod sha384 { /// When using a different key, verify() should always yield an error. /// NOTE: Using different and same input data is tested with TestableStreamingContext. fn prop_verify_diff_key_false(data: Vec) -> bool { - let sk = SecretKey::generate(); + let sk = SecretKey::generate().unwrap(); let mut state = HmacSha384::new(&sk); state.update(&data[..]).unwrap(); let tag = state.finalize().unwrap(); - let bad_sk = SecretKey::generate(); + let bad_sk = SecretKey::generate().unwrap(); HmacSha384::verify(&tag, &bad_sk, &data[..]).is_err() } @@ -638,13 +868,17 @@ pub mod sha384 { } fn one_shot(input: &[u8]) -> Result { - HmacSha384::hmac(&SecretKey::from_slice(&KEY).unwrap(), input) + HmacSha384::hmac(&SecretKey::try_from(KEY.as_slice()).unwrap(), input) } fn verify_result(expected: &Tag, input: &[u8]) -> Result<(), UnknownCryptoError> { // This will only run verification tests on differing input. They do not // include tests for different secret keys. - HmacSha384::verify(expected, &SecretKey::from_slice(&KEY).unwrap(), input) + HmacSha384::verify( + expected, + &SecretKey::try_from(KEY.as_slice()).unwrap(), + input, + ) } fn compare_states(state_1: &HmacSha384, state_2: &HmacSha384) { @@ -654,7 +888,7 @@ pub mod sha384 { #[test] fn default_consistency_tests() { - let initial_state = HmacSha384::new(&SecretKey::from_slice(&KEY).unwrap()); + let initial_state = HmacSha384::new(&SecretKey::try_from(KEY.as_slice()).unwrap()); let test_runner = StreamingContextConsistencyTester::::new( initial_state, @@ -668,7 +902,7 @@ pub mod sha384 { /// Related bug: https://github.com/orion-rs/orion/issues/46 /// Test different streaming state usage patterns. fn prop_input_to_consistency(data: Vec) -> bool { - let initial_state = HmacSha384::new(&SecretKey::from_slice(&KEY).unwrap()); + let initial_state = HmacSha384::new(&SecretKey::try_from(KEY.as_slice()).unwrap()); let test_runner = StreamingContextConsistencyTester::::new( initial_state, @@ -684,34 +918,104 @@ pub mod sha384 { /// HMAC-SHA512 (Hash-based Message Authentication Code) as specified in the [RFC 2104](https://tools.ietf.org/html/rfc2104). pub mod sha512 { use super::*; - use crate::hazardous::hash::sha2::sha512::{self, Sha512}; + use crate::generics::GenerateSecret; + use crate::{ + generics::{ByteArrayData, Secret, TypeSpec, sealed::Data, sealed::Sealed}, + hazardous::hash::sha2::sha512::{self, Sha512}, + }; + #[cfg(feature = "serde")] + use alloc::vec::Vec; + + #[derive(Debug)] + /// Marker type for HMAC-SHA512 secret key. See [`SecretKey`] type for convenience. + /// # Note: + /// [`SecretKey`] pads the secret key for use with HMAC to a length of [`sha512::SHA512_BLOCKSIZE`], when initialized. + /// + /// Using [`SecretKey::unprotected_as_ref()`] will return the secret key with padding. + /// + /// [`SecretKey::len()`] will return the length with padding (always [`sha512::SHA512_BLOCKSIZE`]). + pub struct HmacSha512Key {} + impl Sealed for HmacSha512Key {} - construct_hmac_key! { - /// A type to represent the `SecretKey` that HMAC uses for authentication. - /// - /// # Note: - /// `SecretKey` pads the secret key for use with HMAC to a length of 128, when initialized. - /// - /// Using `unprotected_as_bytes()` will return the secret key with padding. - /// - /// `len()` will return the length with padding (always 128). - /// - /// # Panics: - /// A panic will occur if: - /// - Failure to generate random bytes securely. - (SecretKey, Sha512, sha512::SHA512_OUTSIZE, test_hmac_key, sha512::SHA512_BLOCKSIZE) + impl TypeSpec for HmacSha512Key { + const NAME: &'static str = stringify!(SecretKey); + type TypeData = ByteArrayData<{ sha512::SHA512_BLOCKSIZE }>; + + fn parse_bytes(bytes: &[u8]) -> Result { + let mut data = ByteArrayData::new(sha512::SHA512_BLOCKSIZE)?; + + if bytes.len() > sha512::SHA512_BLOCKSIZE { + Sha512::_digest(bytes, &mut data.bytes[..sha512::SHA512_OUTSIZE])?; + } else { + data.bytes[..bytes.len()].copy_from_slice(bytes); + } + + Ok(data) + } } - construct_tag! { - /// A type to represent the `Tag` that HMAC returns. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 64 bytes. - (Tag, test_tag, sha512::SHA512_OUTSIZE, sha512::SHA512_OUTSIZE) + impl GenerateSecret for HmacSha512Key { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + fn generate() -> Result, UnknownCryptoError> { + let mut data = Self::TypeData::new(sha512::SHA512_BLOCKSIZE)?; + crate::util::secure_rand_bytes(&mut data.bytes)?; + Ok(Secret::from_data(data)) + } + } + + /// A type to represent the secret key that HMAC-SHA512 uses. + pub type SecretKey = Secret; + + #[derive(Debug, Clone)] + /// Marker type for HMAC-SHA512 MAC/Tag. See [`Tag`] type for convenience. + pub struct HmacSha512Tag {} + impl Sealed for HmacSha512Tag {} + impl TypeSpec for HmacSha512Tag { + const NAME: &'static str = stringify!(Tag); + type TypeData = ByteArrayData<{ sha512::SHA512_OUTSIZE }>; } - impl_from_trait!(Tag, sha512::SHA512_OUTSIZE); + impl From<[u8; sha512::SHA512_OUTSIZE]> for Secret { + fn from(value: [u8; sha512::SHA512_OUTSIZE]) -> Self { + Self::from_data(::TypeData::from(value)) + } + } + + /// A type to represent the MAC/Tag that HMAC-SHA512 returns. + pub type Tag = Secret; + + #[cfg(feature = "serde")] + #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] + /// This type tries to serialize as a `&[u8]` would. Note that the serialized + /// type likely does not have the same protections that Orion provides, such + /// as constant-time operations. A good rule of thumb is to only serialize + /// these types for storage. Don't operate on the serialized types. + impl serde::Serialize for Tag { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + let bytes: &[u8] = &self.data.as_ref(); + bytes.serialize(serializer) + } + } + + #[cfg(feature = "serde")] + #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] + /// This type tries to deserialize as a `Vec` would. If it succeeds, the public data + /// will be built using `Self::try_from`. + /// + /// Note that **this allocates** once to store the referenced bytes on the heap. + impl<'de> serde::Deserialize<'de> for Tag { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + let bytes = Vec::::deserialize(deserializer)?; + TryFrom::try_from(bytes.as_slice()).map_err(serde::de::Error::custom) + } + } use super::Hmac; @@ -732,7 +1036,7 @@ pub mod sha512 { /// Initialize `HmacSha512` struct with a given key. pub fn new(secret_key: &SecretKey) -> Self { // NOTE: `secret_key` has been pre-padded so .unwrap() is OK. - Self::_new(secret_key.unprotected_as_bytes()).unwrap() + Self::_new(secret_key.unprotected_as_ref()).unwrap() } /// Reset to `new()` state. @@ -819,10 +1123,52 @@ pub mod sha512 { mod public { use super::*; + #[test] + fn test_key() { + use super::*; + use crate::test_framework::newtypes::secret::SecretNewtype; + SecretNewtype::test_with_generate::< + { sha512::SHA512_BLOCKSIZE }, + { sha512::SHA512_BLOCKSIZE }, + { sha512::SHA512_BLOCKSIZE }, + HmacSha512Key, + >(); + } + + #[test] + fn test_tag() { + use super::*; + use crate::test_framework::newtypes::secret::SecretNewtype; + SecretNewtype::test_no_generate::< + { sha512::SHA512_OUTSIZE }, + { sha512::SHA512_OUTSIZE }, + HmacSha512Tag, + >(); + } + + #[test] + #[cfg(feature = "serde")] + fn test_serde_serialized_equivalence_to_bytes_fn() { + let bytes = [38u8; sha512::SHA512_OUTSIZE]; + let secret_type = Tag::try_from(&bytes).unwrap(); + let serialized_from_bytes = serde_json::to_value(bytes.as_slice()).unwrap(); + let serialized_from_secret_type = serde_json::to_value(&secret_type).unwrap(); + assert_eq!(serialized_from_bytes, serialized_from_secret_type); + } + + #[test] + #[cfg(feature = "serde")] + fn test_serde_deserialized_equivalence_to_bytes_fn() { + let bytes = [38u8; sha512::SHA512_OUTSIZE]; + let serialized_from_bytes = serde_json::to_value(bytes.as_slice()).unwrap(); + let secret_type: Tag = serde_json::from_value(serialized_from_bytes).unwrap(); + assert_eq!(secret_type.unprotected_as_ref(), bytes.as_slice()); + } + #[test] #[cfg(feature = "safe_api")] fn test_debug_impl() { - let secret_key = SecretKey::generate(); + let secret_key = SecretKey::generate().unwrap(); let initial_state = HmacSha512::new(&secret_key); let debug = format!("{initial_state:?}"); let expected = "HmacSha512 { _state: Hmac { working_hasher: [***OMITTED***], opad_hasher: [***OMITTED***], ipad_hasher: [***OMITTED***], is_finalized: false } }"; @@ -838,11 +1184,11 @@ pub mod sha512 { /// When using a different key, verify() should always yield an error. /// NOTE: Using different and same input data is tested with TestableStreamingContext. fn prop_verify_diff_key_false(data: Vec) -> bool { - let sk = SecretKey::generate(); + let sk = SecretKey::generate().unwrap(); let mut state = HmacSha512::new(&sk); state.update(&data[..]).unwrap(); let tag = state.finalize().unwrap(); - let bad_sk = SecretKey::generate(); + let bad_sk = SecretKey::generate().unwrap(); HmacSha512::verify(&tag, &bad_sk, &data[..]).is_err() } @@ -869,13 +1215,17 @@ pub mod sha512 { } fn one_shot(input: &[u8]) -> Result { - HmacSha512::hmac(&SecretKey::from_slice(&KEY).unwrap(), input) + HmacSha512::hmac(&SecretKey::try_from(KEY.as_slice()).unwrap(), input) } fn verify_result(expected: &Tag, input: &[u8]) -> Result<(), UnknownCryptoError> { // This will only run verification tests on differing input. They do not // include tests for different secret keys. - HmacSha512::verify(expected, &SecretKey::from_slice(&KEY).unwrap(), input) + HmacSha512::verify( + expected, + &SecretKey::try_from(KEY.as_slice()).unwrap(), + input, + ) } fn compare_states(state_1: &HmacSha512, state_2: &HmacSha512) { @@ -885,7 +1235,7 @@ pub mod sha512 { #[test] fn default_consistency_tests() { - let initial_state = HmacSha512::new(&SecretKey::from_slice(&KEY).unwrap()); + let initial_state = HmacSha512::new(&SecretKey::try_from(KEY.as_slice()).unwrap()); let test_runner = StreamingContextConsistencyTester::::new( initial_state, @@ -899,7 +1249,7 @@ pub mod sha512 { /// Related bug: https://github.com/orion-rs/orion/issues/46 /// Test different streaming state usage patterns. fn prop_input_to_consistency(data: Vec) -> bool { - let initial_state = HmacSha512::new(&SecretKey::from_slice(&KEY).unwrap()); + let initial_state = HmacSha512::new(&SecretKey::try_from(KEY.as_slice()).unwrap()); let test_runner = StreamingContextConsistencyTester::::new( initial_state, diff --git a/src/hazardous/mac/poly1305.rs b/src/hazardous/mac/poly1305.rs index 320325a31..7f02b4664 100644 --- a/src/hazardous/mac/poly1305.rs +++ b/src/hazardous/mac/poly1305.rs @@ -52,7 +52,7 @@ //! # #[cfg(feature = "safe_api")] { //! use orion::hazardous::mac::poly1305::{OneTimeKey, Poly1305}; //! -//! let one_time_key = OneTimeKey::generate(); +//! let one_time_key = OneTimeKey::generate()?; //! let msg = "Some message."; //! //! let mut poly1305_state = Poly1305::new(&one_time_key); @@ -71,50 +71,109 @@ //! [poly1305-donna]: https://github.com/floodyberry/poly1305-donna //! [Cryptographic Right Answers]: https://latacora.micro.blog/2018/04/03/cryptographic-right-answers.html +use crate::generics::GenerateSecret; use crate::{ errors::UnknownCryptoError, + generics::{ByteArrayData, Secret, TypeSpec, sealed::Sealed}, util::endianness::{load_u32_le, store_u32_into_le}, }; +#[cfg(feature = "serde")] +use alloc::vec::Vec; use fiat_crypto::poly1305_32::{ fiat_poly1305_add, fiat_poly1305_carry, fiat_poly1305_carry_mul, fiat_poly1305_from_bytes, fiat_poly1305_loose_field_element, fiat_poly1305_relax, fiat_poly1305_selectznz, fiat_poly1305_subborrowx_u26, fiat_poly1305_tight_field_element, fiat_poly1305_u1, }; +#[cfg(feature = "safe_api")] +use crate::generics::sealed::Data; + /// The blocksize which Poly1305 operates on. const POLY1305_BLOCKSIZE: usize = 16; /// The output size for Poly1305. pub const POLY1305_OUTSIZE: usize = 16; /// The key size for Poly1305. pub const POLY1305_KEYSIZE: usize = 32; -/// Type for a Poly1305 tag. -type Poly1305Tag = [u8; POLY1305_OUTSIZE]; - -construct_secret_key! { - /// A type to represent the `OneTimeKey` that Poly1305 uses for authentication. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 32 bytes. - /// - /// # Panics: - /// A panic will occur if: - /// - Failure to generate random bytes securely. - (OneTimeKey, test_one_time_key, POLY1305_KEYSIZE, POLY1305_KEYSIZE, POLY1305_KEYSIZE) + +#[derive(Debug)] +/// Marker type for Poly1305 onetime-key. See [`OneTimeKey`] type for convenience. +pub struct Poly1305Key {} +impl Sealed for Poly1305Key {} + +impl TypeSpec for Poly1305Key { + const NAME: &'static str = stringify!(OneTimeKey); + type TypeData = ByteArrayData; } -impl_from_trait!(OneTimeKey, POLY1305_KEYSIZE); +impl From<[u8; POLY1305_KEYSIZE]> for Secret { + fn from(value: [u8; POLY1305_KEYSIZE]) -> Self { + Self::from_data(::TypeData::from(value)) + } +} + +impl GenerateSecret for Poly1305Key { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + fn generate() -> Result, UnknownCryptoError> { + let mut data = Self::TypeData::new(POLY1305_KEYSIZE)?; + crate::util::secure_rand_bytes(&mut data.bytes)?; + Ok(Secret::from_data(data)) + } +} -construct_tag! { - /// A type to represent the `Tag` that Poly1305 returns. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 16 bytes. - (Tag, test_tag, POLY1305_OUTSIZE, POLY1305_OUTSIZE) +/// A type to represent the [`OneTimeKey`] that [`Poly1305`] uses for authentication. +pub type OneTimeKey = Secret; + +#[derive(Debug, Clone)] +/// Marker type for Poly1305 MAC/Tag. See [`Tag`] type for convenience. +pub struct Poly1305Tag {} +impl Sealed for Poly1305Tag {} + +impl TypeSpec for Poly1305Tag { + const NAME: &'static str = stringify!(Tag); + type TypeData = ByteArrayData; +} + +impl From<[u8; POLY1305_OUTSIZE]> for Secret { + fn from(value: [u8; POLY1305_OUTSIZE]) -> Self { + Self::from_data(::TypeData::from(value)) + } +} + +/// A type to represent the MAC/Tag that Poly1305 returns. +pub type Tag = Secret; + +#[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] +/// This type tries to serialize as a `&[u8]` would. Note that the serialized +/// type likely does not have the same protections that Orion provides, such +/// as constant-time operations. A good rule of thumb is to only serialize +/// these types for storage. Don't operate on the serialized types. +impl serde::Serialize for Tag { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + let bytes: &[u8] = &self.data.as_ref(); + bytes.serialize(serializer) + } } -impl_from_trait!(Tag, POLY1305_OUTSIZE); +#[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] +/// This type tries to deserialize as a `Vec` would. If it succeeds, the public data +/// will be built using `Self::try_from`. +/// +/// Note that **this allocates** once to store the referenced bytes on the heap. +impl<'de> serde::Deserialize<'de> for Tag { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + let bytes = Vec::::deserialize(deserializer)?; + TryFrom::try_from(bytes.as_slice()).map_err(serde::de::Error::custom) + } +} #[derive(Clone)] /// Poly1305 streaming state. @@ -241,16 +300,16 @@ impl Poly1305 { is_finalized: false, }; - state.r[0] = (load_u32_le(&one_time_key.unprotected_as_bytes()[0..4])) & 0x3ffffff; - state.r[1] = (load_u32_le(&one_time_key.unprotected_as_bytes()[3..7]) >> 2) & 0x3ffff03; - state.r[2] = (load_u32_le(&one_time_key.unprotected_as_bytes()[6..10]) >> 4) & 0x3ffc0ff; - state.r[3] = (load_u32_le(&one_time_key.unprotected_as_bytes()[9..13]) >> 6) & 0x3f03fff; - state.r[4] = (load_u32_le(&one_time_key.unprotected_as_bytes()[12..16]) >> 8) & 0x00fffff; + state.r[0] = (load_u32_le(&one_time_key.unprotected_as_ref()[0..4])) & 0x3ffffff; + state.r[1] = (load_u32_le(&one_time_key.unprotected_as_ref()[3..7]) >> 2) & 0x3ffff03; + state.r[2] = (load_u32_le(&one_time_key.unprotected_as_ref()[6..10]) >> 4) & 0x3ffc0ff; + state.r[3] = (load_u32_le(&one_time_key.unprotected_as_ref()[9..13]) >> 6) & 0x3f03fff; + state.r[4] = (load_u32_le(&one_time_key.unprotected_as_ref()[12..16]) >> 8) & 0x00fffff; - state.s[0] = load_u32_le(&one_time_key.unprotected_as_bytes()[16..20]); - state.s[1] = load_u32_le(&one_time_key.unprotected_as_bytes()[20..24]); - state.s[2] = load_u32_le(&one_time_key.unprotected_as_bytes()[24..28]); - state.s[3] = load_u32_le(&one_time_key.unprotected_as_bytes()[28..32]); + state.s[0] = load_u32_le(&one_time_key.unprotected_as_ref()[16..20]); + state.s[1] = load_u32_le(&one_time_key.unprotected_as_ref()[20..24]); + state.s[2] = load_u32_le(&one_time_key.unprotected_as_ref()[24..28]); + state.s[3] = load_u32_le(&one_time_key.unprotected_as_ref()[28..32]); state } @@ -346,7 +405,7 @@ impl Poly1305 { self.is_finalized = true; - let mut local_buffer: Poly1305Tag = self.buffer; + let mut local_buffer: [u8; POLY1305_OUTSIZE] = self.buffer; if self.leftover != 0 { local_buffer[self.leftover] = 1; @@ -396,10 +455,58 @@ impl Poly1305 { mod public { use super::*; + #[test] + fn test_onetime_key() { + use super::*; + use crate::test_framework::newtypes::secret::SecretNewtype; + SecretNewtype::test_with_generate::< + POLY1305_KEYSIZE, + POLY1305_KEYSIZE, + POLY1305_KEYSIZE, + Poly1305Key, + >(); + // Test of From<[u8; N]> + assert_ne!( + OneTimeKey::from([0u8; POLY1305_KEYSIZE]), + OneTimeKey::from([1u8; POLY1305_KEYSIZE]) + ) + } + + #[test] + fn test_tag() { + use super::*; + use crate::test_framework::newtypes::secret::SecretNewtype; + SecretNewtype::test_no_generate::(); + // Test of From<[u8; N]> + assert_ne!( + Tag::from([0u8; POLY1305_OUTSIZE]), + Tag::from([1u8; POLY1305_OUTSIZE]) + ); + } + + #[test] + #[cfg(feature = "serde")] + fn test_serde_serialized_equivalence_to_bytes_fn() { + let bytes = [38u8; POLY1305_OUTSIZE]; + let secret_type = Tag::try_from(&bytes).unwrap(); + let serialized_from_bytes = serde_json::to_value(bytes.as_slice()).unwrap(); + let serialized_from_secret_type = serde_json::to_value(&secret_type).unwrap(); + assert_eq!(serialized_from_bytes, serialized_from_secret_type); + } + + #[test] + #[cfg(feature = "serde")] + fn test_serde_deserialized_equivalence_to_bytes_fn() { + let bytes = [38u8; POLY1305_OUTSIZE]; + let serialized_from_bytes = serde_json::to_value(bytes.as_slice()).unwrap(); + let secret_type: Tag = serde_json::from_value(serialized_from_bytes).unwrap(); + assert_eq!(secret_type.unprotected_as_ref(), bytes.as_slice()); + } + #[test] #[cfg(feature = "safe_api")] fn test_debug_impl() { - let secret_key = OneTimeKey::generate(); + let secret_key = OneTimeKey::generate().unwrap(); let initial_state = Poly1305::new(&secret_key); let debug = format!("{initial_state:?}"); let expected = "Poly1305 { a: [***OMITTED***], r: [***OMITTED***], s: [***OMITTED***], leftover: [***OMITTED***], buffer: [***OMITTED***], is_finalized: false }"; @@ -415,11 +522,11 @@ mod public { /// When using a different key, verify() should always yield an error. /// NOTE: Using different and same input data is tested with TestableStreamingContext. fn prop_verify_diff_key_false(data: Vec) -> bool { - let sk = OneTimeKey::generate(); + let sk = OneTimeKey::generate().unwrap(); let mut state = Poly1305::new(&sk); state.update(&data[..]).unwrap(); let tag = state.finalize().unwrap(); - let bad_sk = OneTimeKey::generate(); + let bad_sk = OneTimeKey::generate().unwrap(); Poly1305::verify(&tag, &bad_sk, &data[..]).is_err() } @@ -450,13 +557,13 @@ mod public { } fn one_shot(input: &[u8]) -> Result { - Poly1305::poly1305(&OneTimeKey::from_slice(&KEY).unwrap(), input) + Poly1305::poly1305(&OneTimeKey::try_from(&KEY).unwrap(), input) } fn verify_result(expected: &Tag, input: &[u8]) -> Result<(), UnknownCryptoError> { // This will only run verification tests on differing input. They do not // include tests for different secret keys. - Poly1305::verify(expected, &OneTimeKey::from_slice(&KEY).unwrap(), input) + Poly1305::verify(expected, &OneTimeKey::try_from(&KEY).unwrap(), input) } fn compare_states(state_1: &Poly1305, state_2: &Poly1305) { @@ -471,7 +578,7 @@ mod public { #[test] fn default_consistency_tests() { - let initial_state: Poly1305 = Poly1305::new(&OneTimeKey::from_slice(&KEY).unwrap()); + let initial_state: Poly1305 = Poly1305::new(&OneTimeKey::try_from(&KEY).unwrap()); let test_runner = StreamingContextConsistencyTester::::new( initial_state, @@ -485,7 +592,7 @@ mod public { /// Related bug: https://github.com/orion-rs/orion/issues/46 /// Test different streaming state usage patterns. fn prop_input_to_consistency(data: Vec) -> bool { - let initial_state: Poly1305 = Poly1305::new(&OneTimeKey::from_slice(&KEY).unwrap()); + let initial_state: Poly1305 = Poly1305::new(&OneTimeKey::try_from(&KEY).unwrap()); let test_runner = StreamingContextConsistencyTester::::new( initial_state, @@ -507,7 +614,7 @@ mod private { #[test] fn test_process_err_on_finalized() { - let sk = OneTimeKey::from_slice(&[0u8; 32]).unwrap(); + let sk = OneTimeKey::try_from(&[0u8; 32]).unwrap(); let mut state = Poly1305::new(&sk); state.process_pad_to_blocksize(&[0u8; 16]).unwrap(); @@ -517,7 +624,7 @@ mod private { #[test] fn test_process_pad_no_pad() { - let sk = OneTimeKey::from_slice(&[0u8; 32]).unwrap(); + let sk = OneTimeKey::try_from(&[0u8; 32]).unwrap(); let mut state_pad = Poly1305::new(&sk); let mut state_no_pad = Poly1305::new(&sk); @@ -542,7 +649,7 @@ mod private { let block_2 = [0u8; 17]; let block_3 = [0u8; 16]; - let sk = OneTimeKey::from_slice(&[0u8; 32]).unwrap(); + let sk = OneTimeKey::try_from(&[0u8; 32]).unwrap(); let mut state = Poly1305::new(&sk); assert!(state.process_block(&block_0).is_err()); @@ -558,7 +665,7 @@ mod private { #[test] fn test_process_no_panic() { let block = [0u8; 16]; - let sk = OneTimeKey::from_slice(&[0u8; 32]).unwrap(); + let sk = OneTimeKey::try_from(&[0u8; 32]).unwrap(); let mut state = Poly1305::new(&sk); // Should not panic state.process_end_of_stream(); diff --git a/src/hazardous/stream/chacha20.rs b/src/hazardous/stream/chacha20.rs index 034f88583..5d7e17537 100644 --- a/src/hazardous/stream/chacha20.rs +++ b/src/hazardous/stream/chacha20.rs @@ -72,7 +72,7 @@ //! # #[cfg(feature = "safe_api")] { //! use orion::hazardous::stream::chacha20; //! -//! let secret_key = chacha20::SecretKey::generate(); +//! let secret_key = chacha20::SecretKey::generate()?; //! //! // WARNING: This nonce is only meant for demonstration and should not //! // be repeated. Please read the security section. @@ -95,7 +95,12 @@ //! [`SecretKey::generate()`]: chacha20::SecretKey::generate() //! [`XChaCha20Poly1305`]: super::aead::xchacha20poly1305 //! [RFC]: https://tools.ietf.org/html/rfc8439 +use crate::GenerateSecret; use crate::errors::UnknownCryptoError; +#[cfg(feature = "safe_api")] +use crate::generics::sealed::Data; +use crate::generics::sealed::Sealed; +use crate::generics::{ByteArrayData, Public, Secret, TypeSpec}; use crate::util::endianness::load_u32_le; use crate::util::u32x4::U32x4; #[cfg(feature = "zeroize")] @@ -112,32 +117,54 @@ const HCHACHA_OUTSIZE: usize = 32; /// The nonce size for HChaCha20. pub(crate) const HCHACHA_NONCESIZE: usize = 16; -construct_secret_key! { - /// A type to represent the `SecretKey` that Chacha20, XChaCha20, ChaCha20-Poly1305 and - /// XChaCha20-Poly1305 use. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 32 bytes. - /// - /// # Panics: - /// A panic will occur if: - /// - Failure to generate random bytes securely. - (SecretKey, test_secret_key, CHACHA_KEYSIZE, CHACHA_KEYSIZE, CHACHA_KEYSIZE) +#[derive(Debug)] +/// Marker type for ChaCha20 key. See [`SecretKey`] type for convenience. +pub struct ChaCha20Key {} +impl Sealed for ChaCha20Key {} + +impl TypeSpec for ChaCha20Key { + const NAME: &'static str = stringify!(SecretKey); + type TypeData = ByteArrayData; } -impl_from_trait!(SecretKey, CHACHA_KEYSIZE); +impl From<[u8; CHACHA_KEYSIZE]> for Secret { + fn from(value: [u8; CHACHA_KEYSIZE]) -> Self { + Self::from_data(::TypeData::from(value)) + } +} -construct_public! { - /// A type that represents a `Nonce` that ChaCha20 and ChaCha20-Poly1305 use. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 12 bytes. - (Nonce, test_nonce, IETF_CHACHA_NONCESIZE, IETF_CHACHA_NONCESIZE) +impl GenerateSecret for ChaCha20Key { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + fn generate() -> Result, UnknownCryptoError> { + let mut data = Self::TypeData::new(CHACHA_KEYSIZE)?; + crate::util::secure_rand_bytes(&mut data.bytes)?; + Ok(Secret::from_data(data)) + } } -impl_from_trait!(Nonce, IETF_CHACHA_NONCESIZE); +/// A type to represent the `SecretKey` that Chacha20, XChaCha20, ChaCha20-Poly1305 and +/// XChaCha20-Poly1305 use. +pub type SecretKey = Secret; + +#[derive(Debug, Clone, Copy)] +/// Marker type for ChaCha20 nonce. See [`Nonce`] type for convenience. +pub struct ChaCha20Nonce {} +impl Sealed for ChaCha20Nonce {} + +impl TypeSpec for ChaCha20Nonce { + const NAME: &'static str = stringify!(Nonce); + type TypeData = ByteArrayData; +} + +impl From<[u8; IETF_CHACHA_NONCESIZE]> for Public { + fn from(value: [u8; IETF_CHACHA_NONCESIZE]) -> Self { + Self::from_data(::TypeData::from(value)) + } +} + +/// A type that represents a `Nonce` that ChaCha20 and ChaCha20-Poly1305 use. +pub type Nonce = Public; macro_rules! ROUND { ($r0:expr, $r1:expr, $r2:expr, $r3:expr) => { @@ -339,7 +366,7 @@ pub(crate) fn encrypt_in_place( return Err(UnknownCryptoError); } - let mut ctx = ChaCha20::new(secret_key.unprotected_as_bytes(), nonce.as_ref(), true)?; + let mut ctx = ChaCha20::new(secret_key.unprotected_as_ref(), nonce.as_ref(), true)?; let mut keystream_block = zeroize_wrap!([0u8; CHACHA_BLOCKSIZE]); xor_keystream(&mut ctx, initial_counter, keystream_block.as_mut(), bytes) } @@ -360,7 +387,7 @@ pub fn encrypt( return Err(UnknownCryptoError); } - let mut ctx = ChaCha20::new(secret_key.unprotected_as_bytes(), nonce.as_ref(), true)?; + let mut ctx = ChaCha20::new(secret_key.unprotected_as_ref(), nonce.as_ref(), true)?; let mut keystream_block = zeroize_wrap!([0u8; CHACHA_BLOCKSIZE]); for (ctr, (p_block, c_block)) in plaintext @@ -402,7 +429,7 @@ pub(super) fn hchacha20( secret_key: &SecretKey, nonce: &[u8], ) -> Result<[u8; HCHACHA_OUTSIZE], UnknownCryptoError> { - let mut chacha_state = ChaCha20::new(secret_key.unprotected_as_bytes(), nonce, false)?; + let mut chacha_state = ChaCha20::new(secret_key.unprotected_as_ref(), nonce, false)?; let mut keystream_block = [0u8; HCHACHA_OUTSIZE]; chacha_state.keystream_block(0, &mut keystream_block); @@ -414,12 +441,38 @@ pub(super) fn hchacha20( mod public { use super::*; + #[test] + fn test_chacha20_key() { + use super::*; + use crate::test_framework::newtypes::secret::SecretNewtype; + SecretNewtype::test_with_generate::< + CHACHA_KEYSIZE, + CHACHA_KEYSIZE, + CHACHA_KEYSIZE, + ChaCha20Key, + >(); + } + + #[test] + fn test_chacha20_nonce() { + use super::*; + use crate::test_framework::newtypes::public::PublicNewtype; + PublicNewtype::test_no_generate::< + IETF_CHACHA_NONCESIZE, + IETF_CHACHA_NONCESIZE, + ChaCha20Nonce, + >(); + + #[cfg(feature = "serde")] + PublicNewtype::test_serialization::(); + } + #[cfg(feature = "safe_api")] #[test] // See https://github.com/orion-rs/orion/issues/308 fn test_plaintext_left_in_dst_out() { - let k = SecretKey::generate(); - let n = Nonce::from_slice(&[0u8; 12]).unwrap(); + let k = SecretKey::generate().unwrap(); + let n = Nonce::try_from(&[0u8; 12]).unwrap(); let ic: u32 = u32::MAX - 1; let text = [b'x'; 128 + 4]; @@ -432,10 +485,10 @@ mod public { #[test] fn test_xor_keystream_err_initial_ctr_overflow() { - let sk = SecretKey::from_slice(&[0u8; 32]).unwrap(); - let nonce = Nonce::from_slice(&[0u8; 12]).unwrap(); + let sk = SecretKey::try_from(&[0u8; 32]).unwrap(); + let nonce = Nonce::try_from(&[0u8; 12]).unwrap(); let mut chacha_state = - ChaCha20::new(sk.unprotected_as_bytes(), nonce.as_ref(), true).unwrap(); + ChaCha20::new(sk.unprotected_as_ref(), nonce.as_ref(), true).unwrap(); let mut tmp_block = [0u8; CHACHA_BLOCKSIZE]; let mut bytes = [12u8; CHACHA_BLOCKSIZE * 2]; @@ -449,23 +502,23 @@ mod public { use crate::test_framework::streamcipher_interface::*; impl TestingRandom for SecretKey { - fn gen() -> Self { - Self::generate() + fn gen_new() -> Self { + Self::generate().unwrap() } } impl TestingRandom for Nonce { - fn gen() -> Self { + fn gen_new() -> Self { let mut n = [0u8; IETF_CHACHA_NONCESIZE]; crate::util::secure_rand_bytes(&mut n).unwrap(); - Self::from_slice(&n).unwrap() + Self::try_from(&n).unwrap() } } #[quickcheck] fn prop_streamcipher_interface(input: Vec, counter: u32) -> bool { - let secret_key = SecretKey::generate(); - let nonce = Nonce::from_slice(&[0u8; IETF_CHACHA_NONCESIZE]).unwrap(); + let secret_key = SecretKey::generate().unwrap(); + let nonce = Nonce::try_from(&[0u8; IETF_CHACHA_NONCESIZE]).unwrap(); StreamCipherTestRunner(encrypt, decrypt, secret_key, nonce, counter, &input, None); test_diff_params_diff_output(&encrypt, &decrypt); @@ -473,6 +526,7 @@ mod public { } } + #[cfg(any(feature = "safe_api", feature = "alloc"))] // hex crate uses Vec, so we need std. mod test_hchacha20 { use super::*; @@ -481,19 +535,19 @@ mod public { #[test] fn test_nonce_length() { - assert!(hchacha20(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 16],).is_ok()); - assert!(hchacha20(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 17],).is_err()); - assert!(hchacha20(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 15],).is_err()); - assert!(hchacha20(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 0],).is_err()); + assert!(hchacha20(&SecretKey::try_from(&[0u8; 32]).unwrap(), &[0u8; 16],).is_ok()); + assert!(hchacha20(&SecretKey::try_from(&[0u8; 32]).unwrap(), &[0u8; 17],).is_err()); + assert!(hchacha20(&SecretKey::try_from(&[0u8; 32]).unwrap(), &[0u8; 15],).is_err()); + assert!(hchacha20(&SecretKey::try_from(&[0u8; 32]).unwrap(), &[0u8; 0],).is_err()); } #[test] fn test_diff_keys_diff_output() { let keystream1 = - hchacha20(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 16]).unwrap(); + hchacha20(&SecretKey::try_from(&[0u8; 32]).unwrap(), &[0u8; 16]).unwrap(); let keystream2 = - hchacha20(&SecretKey::from_slice(&[1u8; 32]).unwrap(), &[0u8; 16]).unwrap(); + hchacha20(&SecretKey::try_from(&[1u8; 32]).unwrap(), &[0u8; 16]).unwrap(); assert_ne!(keystream1, keystream2); } @@ -501,17 +555,17 @@ mod public { #[test] fn test_diff_nonce_diff_output() { let keystream1 = - hchacha20(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 16]).unwrap(); + hchacha20(&SecretKey::try_from(&[0u8; 32]).unwrap(), &[0u8; 16]).unwrap(); let keystream2 = - hchacha20(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[1u8; 16]).unwrap(); + hchacha20(&SecretKey::try_from(&[0u8; 32]).unwrap(), &[1u8; 16]).unwrap(); assert_ne!(keystream1, keystream2); } pub fn hchacha_test_runner(key: &str, nonce: &str, output_expected: &str) { let actual: [u8; 32] = hchacha20( - &SecretKey::from_slice(&decode(key).unwrap()).unwrap(), + &SecretKey::try_from(&decode(key).unwrap()).unwrap(), &decode(nonce).unwrap(), ) .unwrap(); diff --git a/src/hazardous/stream/xchacha20.rs b/src/hazardous/stream/xchacha20.rs index 01379fae5..4dfc4d9cb 100644 --- a/src/hazardous/stream/xchacha20.rs +++ b/src/hazardous/stream/xchacha20.rs @@ -65,8 +65,8 @@ //! # #[cfg(feature = "safe_api")] { //! use orion::hazardous::stream::xchacha20; //! -//! let secret_key = xchacha20::SecretKey::generate(); -//! let nonce = xchacha20::Nonce::generate(); +//! let secret_key = xchacha20::SecretKey::generate()?; +//! let nonce = xchacha20::Nonce::generate()?; //! let message = "Data to protect".as_bytes(); //! //! // Length of this message is 15 @@ -85,29 +85,47 @@ //! [`SecretKey::generate()`]: xchacha20::SecretKey::generate() //! [`Nonce::generate()`]: xchacha20::Nonce::generate() //! [`XChaCha20Poly1305`]: super::aead::xchacha20poly1305 +use crate::generics::GeneratePublic; +#[cfg(feature = "safe_api")] +use crate::generics::sealed::Data; pub use crate::hazardous::stream::chacha20::SecretKey; use crate::{ errors::UnknownCryptoError, - hazardous::stream::chacha20::{self, Nonce as IETFNonce, IETF_CHACHA_NONCESIZE}, + generics::{ByteArrayData, Public, TypeSpec, sealed::Sealed}, + hazardous::stream::chacha20::{self, IETF_CHACHA_NONCESIZE, Nonce as IETFNonce}, }; /// The nonce size for XChaCha20. pub const XCHACHA_NONCESIZE: usize = 24; -construct_public! { - /// A type that represents a `Nonce` that XChaCha20, XChaCha20-Poly1305 use. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is not 24 bytes. - /// - /// # Panics: - /// A panic will occur if: - /// - Failure to generate random bytes securely. - (Nonce, test_nonce, XCHACHA_NONCESIZE, XCHACHA_NONCESIZE, XCHACHA_NONCESIZE) +#[derive(Debug, Clone, Copy)] +/// Marker type for ChaCha20 nonce. See [`Nonce`] type for convenience. +pub struct XChaCha20Nonce {} +impl Sealed for XChaCha20Nonce {} + +impl TypeSpec for XChaCha20Nonce { + const NAME: &'static str = stringify!(Nonce); + type TypeData = ByteArrayData; } -impl_from_trait!(Nonce, XCHACHA_NONCESIZE); +impl From<[u8; XCHACHA_NONCESIZE]> for Public { + fn from(value: [u8; XCHACHA_NONCESIZE]) -> Self { + Self::from_data(::TypeData::from(value)) + } +} + +impl GeneratePublic for XChaCha20Nonce { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + fn generate() -> Result, UnknownCryptoError> { + let mut data = Self::TypeData::new(XCHACHA_NONCESIZE)?; + crate::util::secure_rand_bytes(&mut data.bytes)?; + Ok(Public::from_data(data)) + } +} + +/// A type that represents a [`Nonce`] that XChaCha20 and XChaCha20-Poly1305 use. +pub type Nonce = Public; /// Generate a subkey using HChaCha20 for XChaCha20 and corresponding nonce. pub(crate) fn subkey_and_nonce(secret_key: &SecretKey, nonce: &Nonce) -> (SecretKey, IETFNonce) { @@ -152,21 +170,36 @@ pub fn decrypt( mod public { use super::*; + #[test] + fn test_xchacha20_nonce() { + use super::*; + use crate::test_framework::newtypes::public::PublicNewtype; + PublicNewtype::test_with_generate::< + XCHACHA_NONCESIZE, + XCHACHA_NONCESIZE, + XCHACHA_NONCESIZE, + XChaCha20Nonce, + >(); + + #[cfg(feature = "serde")] + PublicNewtype::test_serialization::(); + } + mod test_encrypt_decrypt { use super::*; use crate::test_framework::streamcipher_interface::*; impl TestingRandom for Nonce { - fn gen() -> Self { - Self::generate() + fn gen_new() -> Self { + Self::generate().unwrap() } } #[quickcheck] #[cfg(feature = "safe_api")] fn prop_streamcipher_interface(input: Vec, counter: u32) -> bool { - let secret_key = SecretKey::generate(); - let nonce = Nonce::generate(); + let secret_key = SecretKey::generate().unwrap(); + let nonce = Nonce::generate().unwrap(); StreamCipherTestRunner(encrypt, decrypt, secret_key, nonce, counter, &input, None); test_diff_params_diff_output(&encrypt, &decrypt); diff --git a/src/high_level/aead.rs b/src/high_level/aead.rs index 21851ced7..71e9cdaea 100644 --- a/src/high_level/aead.rs +++ b/src/high_level/aead.rs @@ -20,7 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -//! Authenticated secret-key encryption. +//! Authenticated secret-key encryption (XChaCha20-Poly1305). //! //! # Use case: //! `orion::aead` can be used to encrypt data in a way that detects if the @@ -52,24 +52,24 @@ //! ([`XCHACHA_NONCESIZE`] + [`POLY1305_OUTSIZE`] + 1). //! - The received tag does not match the calculated tag when calling [`open`]. //! - `plaintext.len()` + [`XCHACHA_NONCESIZE`] + [`POLY1305_OUTSIZE`] overflows when calling [`seal`]. +//! - Failure to generate random bytes securely. //! //! # Panics: //! A panic will occur if: //! - More than 2^32-1 * 64 bytes of data are processed. -//! - Failure to generate random bytes securely. //! //! # Security: //! - It is critical for security that a given nonce is not re-used with a given //! key. Should this happen, the security of all data that has been encrypted with //! that given key is compromised. -//! - To securely generate a strong key, use [`SecretKey::default()`]. +//! - To securely generate a strong key, use [`SecretKey::generate()`]. //! - The length of the `plaintext` is not hidden, only its contents. //! //! # Example: //! ```rust //! use orion::aead; //! -//! let secret_key = aead::SecretKey::default(); +//! let secret_key = aead::SecretKey::generate()?; //! let ciphertext = aead::seal(&secret_key, b"Secret message")?; //! let decrypted_data = aead::open(&secret_key, &ciphertext)?; //! @@ -85,10 +85,7 @@ use crate::{ hazardous::{ aead, mac::poly1305::POLY1305_OUTSIZE, - stream::{ - chacha20, - xchacha20::{Nonce, XCHACHA_NONCESIZE}, - }, + stream::xchacha20::{Nonce, SecretKey as XChaCha20Key, XCHACHA_NONCESIZE}, }, }; @@ -108,11 +105,11 @@ pub fn seal(secret_key: &SecretKey, plaintext: &[u8]) -> Result, Unknown }; let mut dst_out = vec![0u8; out_len]; - let nonce = Nonce::generate(); + let nonce = Nonce::generate()?; dst_out[..XCHACHA_NONCESIZE].copy_from_slice(nonce.as_ref()); aead::xchacha20poly1305::seal( - &chacha20::SecretKey::from_slice(secret_key.unprotected_as_bytes())?, + &XChaCha20Key::try_from(secret_key.unprotected_as_ref())?, &nonce, plaintext, None, @@ -137,8 +134,8 @@ pub fn open( vec![0u8; ciphertext_with_tag_and_nonce.len() - (XCHACHA_NONCESIZE + POLY1305_OUTSIZE)]; aead::xchacha20poly1305::open( - &chacha20::SecretKey::from_slice(secret_key.unprotected_as_bytes())?, - &Nonce::from_slice(&ciphertext_with_tag_and_nonce[..XCHACHA_NONCESIZE])?, + &XChaCha20Key::try_from(secret_key.unprotected_as_ref())?, + &Nonce::try_from(&ciphertext_with_tag_and_nonce[..XCHACHA_NONCESIZE])?, &ciphertext_with_tag_and_nonce[XCHACHA_NONCESIZE..], None, &mut dst_out, @@ -174,11 +171,11 @@ pub mod streaming { //! a dropped or reordered message within the stream. //! - More than 2^32-3 * 64 bytes of data are processed when encrypting/decrypting a single chunk. //! - [`ABYTES`] + `plaintext.len()` overflows when encrypting. + //! - Failure to generate random bytes securely. //! //! # Panics: //! A panic will occur if: //! - 64 + (`ciphertext.len()` - [`ABYTES`]) overflows when decrypting. - //! - Failure to generate random bytes securely. //! //! # Security: //! - It is critical for security that a given nonce is not re-used with a given @@ -197,7 +194,7 @@ pub mod streaming { //! let src = [255u8; 4096]; // Some example input stream. //! let mut out: Vec> = Vec::with_capacity(4096 / 128); //! - //! let secret_key = SecretKey::default(); + //! let secret_key = SecretKey::generate()?; //! //! // Encryption: //! let (mut sealer, nonce) = StreamSealer::new(&secret_key)?; @@ -233,13 +230,13 @@ pub mod streaming { //! ``` //! [`ABYTES`]: crate::hazardous::aead::streaming::ABYTES //! [`StreamTag`]: crate::hazardous::aead::streaming::StreamTag - //! [`SecretKey::generate()`]: super::SecretKey::generate + //! [`SecretKey::generate().unwrap()`]: super::SecretKey::generate //! ["secretstream" API]: https://download.libsodium.org/doc/secret-key_cryptography/secretstream use super::*; + pub use crate::hazardous::aead::streaming::ABYTES; pub use crate::hazardous::aead::streaming::Nonce; pub use crate::hazardous::aead::streaming::StreamTag; - pub use crate::hazardous::aead::streaming::ABYTES; #[derive(Debug)] /// Streaming authenticated encryption. @@ -251,11 +248,11 @@ pub mod streaming { #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."] /// Initialize a `StreamSealer` struct with a given key. pub fn new(secret_key: &SecretKey) -> Result<(Self, Nonce), UnknownCryptoError> { - let nonce = Nonce::generate(); - let sk = &aead::streaming::SecretKey::from_slice(secret_key.unprotected_as_bytes())?; + let nonce = Nonce::generate()?; + let sk = aead::streaming::SecretKey::try_from(secret_key.unprotected_as_ref())?; let sealer = Self { - internal_sealer: aead::streaming::StreamXChaCha20Poly1305::new(sk, &nonce), + internal_sealer: aead::streaming::StreamXChaCha20Poly1305::new(&sk, &nonce), }; Ok((sealer, nonce)) } @@ -290,10 +287,10 @@ pub mod streaming { #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."] /// Initialize a `StreamOpener` struct with a given key and nonce. pub fn new(secret_key: &SecretKey, nonce: &Nonce) -> Result { - let sk = &chacha20::SecretKey::from_slice(secret_key.unprotected_as_bytes())?; + let sk = aead::streaming::SecretKey::try_from(secret_key.unprotected_as_ref())?; Ok(Self { - internal_sealer: aead::streaming::StreamXChaCha20Poly1305::new(sk, nonce), + internal_sealer: aead::streaming::StreamXChaCha20Poly1305::new(&sk, nonce), }) } #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."] @@ -326,7 +323,7 @@ mod public { #[test] fn test_auth_enc_encryption_decryption() { - let key = SecretKey::default(); + let key = SecretKey::generate().unwrap(); let plaintext = "Secret message".as_bytes(); let dst_ciphertext = seal(&key, plaintext).unwrap(); @@ -337,7 +334,7 @@ mod public { #[test] fn test_auth_enc_plaintext_empty_err() { - let key = SecretKey::default(); + let key = SecretKey::generate().unwrap(); let plaintext = "".as_bytes(); assert!(seal(&key, plaintext).is_err()); @@ -345,7 +342,7 @@ mod public { #[test] fn test_auth_enc_ciphertext_less_than_41_err() { - let key = SecretKey::default(); + let key = SecretKey::generate().unwrap(); let ciphertext = [0u8; XCHACHA_NONCESIZE + POLY1305_OUTSIZE]; assert!(open(&key, &ciphertext).is_err()); @@ -353,7 +350,7 @@ mod public { #[test] fn test_modified_nonce_err() { - let key = SecretKey::default(); + let key = SecretKey::generate().unwrap(); let plaintext = "Secret message".as_bytes(); let mut dst_ciphertext = seal(&key, plaintext).unwrap(); @@ -364,7 +361,7 @@ mod public { #[test] fn test_modified_ciphertext_err() { - let key = SecretKey::default(); + let key = SecretKey::generate().unwrap(); let plaintext = "Secret message".as_bytes(); let mut dst_ciphertext = seal(&key, plaintext).unwrap(); @@ -375,7 +372,7 @@ mod public { #[test] fn test_modified_tag_err() { - let key = SecretKey::default(); + let key = SecretKey::generate().unwrap(); let plaintext = "Secret message".as_bytes(); let mut dst_ciphertext = seal(&key, plaintext).unwrap(); @@ -387,22 +384,13 @@ mod public { #[test] fn test_diff_secret_key_err() { - let key = SecretKey::default(); + let key = SecretKey::generate().unwrap(); let plaintext = "Secret message".as_bytes(); let dst_ciphertext = seal(&key, plaintext).unwrap(); - let bad_key = SecretKey::default(); + let bad_key = SecretKey::generate().unwrap(); assert!(open(&bad_key, &dst_ciphertext).is_err()); } - - #[test] - fn test_secret_length_err() { - let key = SecretKey::generate(31).unwrap(); - let plaintext = "Secret message".as_bytes(); - - assert!(seal(&key, plaintext).is_err()); - assert!(open(&key, plaintext).is_err()); - } } mod test_stream_seal_open { @@ -411,7 +399,7 @@ mod public { #[test] fn test_auth_enc_encryption_decryption() { - let key = SecretKey::default(); + let key = SecretKey::generate().unwrap(); let (mut sealer, nonce) = StreamSealer::new(&key).unwrap(); let mut opener = StreamOpener::new(&key, &nonce).unwrap(); let plaintext = "Secret message".as_bytes(); @@ -425,7 +413,7 @@ mod public { #[test] fn test_seal_chunk_plaintext_empty_ok() { - let key = SecretKey::default(); + let key = SecretKey::generate().unwrap(); let (mut sealer, _) = StreamSealer::new(&key).unwrap(); let plaintext = "".as_bytes(); @@ -434,7 +422,7 @@ mod public { #[test] fn test_open_chunk_less_than_abytes_err() { - let key = SecretKey::default(); + let key = SecretKey::generate().unwrap(); let ciphertext = [0u8; ABYTES - 1]; let (_, nonce) = StreamSealer::new(&key).unwrap(); let mut opener = StreamOpener::new(&key, &nonce).unwrap(); @@ -444,7 +432,7 @@ mod public { #[test] fn test_open_chunk_abytes_exact_ok() { - let key = SecretKey::default(); + let key = SecretKey::generate().unwrap(); let (mut sealer, nonce) = StreamSealer::new(&key).unwrap(); let mut opener = StreamOpener::new(&key, &nonce).unwrap(); let ciphertext = sealer @@ -458,7 +446,7 @@ mod public { #[test] fn test_modified_tag_err() { - let key = SecretKey::default(); + let key = SecretKey::generate().unwrap(); let (mut sealer, nonce) = StreamSealer::new(&key).unwrap(); let mut opener = StreamOpener::new(&key, &nonce).unwrap(); let plaintext = "Secret message".as_bytes(); @@ -471,7 +459,7 @@ mod public { #[test] fn test_modified_ciphertext_err() { - let key = SecretKey::default(); + let key = SecretKey::generate().unwrap(); let (mut sealer, nonce) = StreamSealer::new(&key).unwrap(); let mut opener = StreamOpener::new(&key, &nonce).unwrap(); let plaintext = "Secret message".as_bytes(); @@ -484,7 +472,7 @@ mod public { #[test] fn test_modified_mac_err() { - let key = SecretKey::default(); + let key = SecretKey::generate().unwrap(); let (mut sealer, nonce) = StreamSealer::new(&key).unwrap(); let mut opener = StreamOpener::new(&key, &nonce).unwrap(); let plaintext = "Secret message".as_bytes(); @@ -498,10 +486,10 @@ mod public { #[test] fn test_diff_secret_key_err() { - let key = SecretKey::default(); + let key = SecretKey::generate().unwrap(); let plaintext = "Secret message".as_bytes(); let (mut sealer, nonce) = StreamSealer::new(&key).unwrap(); - let bad_key = SecretKey::default(); + let bad_key = SecretKey::generate().unwrap(); let mut opener = StreamOpener::new(&bad_key, &nonce).unwrap(); let dst_ciphertext = sealer.seal_chunk(plaintext, &StreamTag::Message).unwrap(); @@ -509,16 +497,9 @@ mod public { assert!(opener.open_chunk(&dst_ciphertext).is_err()); } - #[test] - fn test_secret_length_err() { - let key = SecretKey::generate(31).unwrap(); - assert!(StreamSealer::new(&key).is_err()); - assert!(StreamOpener::new(&key, &Nonce::generate()).is_err()); - } - #[test] fn same_input_generates_different_ciphertext() { - let key = SecretKey::default(); + let key = SecretKey::generate().unwrap(); let (mut sealer, nonce) = StreamSealer::new(&key).unwrap(); let plaintext = "Secret message 1".as_bytes(); let cipher1 = sealer.seal_chunk(plaintext, &StreamTag::Message).unwrap(); @@ -539,7 +520,7 @@ mod public { // Two sealers initialized that encrypt the same plaintext // should produce different ciphertexts because the nonce // is randomly generated. - let key = SecretKey::default(); + let key = SecretKey::generate().unwrap(); let (mut sealer_first, _) = StreamSealer::new(&key).unwrap(); let (mut sealer_second, _) = StreamSealer::new(&key).unwrap(); let plaintext = "Secret message 1".as_bytes(); @@ -555,7 +536,7 @@ mod public { #[test] fn test_stream_seal_and_open() { - let key = SecretKey::default(); + let key = SecretKey::generate().unwrap(); let (mut sealer, nonce) = StreamSealer::new(&key).unwrap(); let plaintext1 = "Secret message 1".as_bytes(); let plaintext2 = "Secret message 2".as_bytes(); @@ -579,7 +560,7 @@ mod public { #[quickcheck] #[cfg(feature = "safe_api")] fn prop_stream_seal_open_same_input(input: Vec) -> bool { - let key = SecretKey::default(); + let key = SecretKey::generate().unwrap(); let (mut sealer, nonce) = StreamSealer::new(&key).unwrap(); let ct = sealer.seal_chunk(&input[..], &StreamTag::Message).unwrap(); @@ -600,7 +581,7 @@ mod public { input }; - let sk = SecretKey::default(); + let sk = SecretKey::generate().unwrap(); let ct = seal(&sk, &pt).unwrap(); let pt_decrypted = open(&sk, &ct).unwrap(); @@ -619,8 +600,8 @@ mod public { input }; - let sk = SecretKey::default(); - let sk2 = SecretKey::default(); + let sk = SecretKey::generate().unwrap(); + let sk2 = SecretKey::generate().unwrap(); let ct = seal(&sk, &pt).unwrap(); open(&sk2, &ct).is_err() diff --git a/src/high_level/auth.rs b/src/high_level/auth.rs index 042071a8f..abef806e5 100644 --- a/src/high_level/auth.rs +++ b/src/high_level/auth.rs @@ -50,7 +50,7 @@ //! //! # Security: //! - The secret key should always be generated using a CSPRNG. -//! [`SecretKey::default()`] can be used for +//! [`SecretKey::generate()`] can be used for //! this; it will generate a [`SecretKey`] of 32 bytes. //! - The required minimum length for a [`SecretKey`] is 32 bytes. //! @@ -59,7 +59,7 @@ //! use orion::auth; //! //! // There exists a shared key between the user and API server -//! let key = auth::SecretKey::default(); +//! let key = auth::SecretKey::generate()?; //! //! // User generates message and authentication tag //! let msg = "Some message.".as_bytes(); @@ -72,12 +72,9 @@ #![cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] -pub use super::hltypes::SecretKey; +pub use crate::hazardous::mac::blake2b::SecretKey; pub use crate::hazardous::mac::blake2b::Tag; -use crate::{ - errors::UnknownCryptoError, - hazardous::mac::blake2b::{self, Blake2b}, -}; +use crate::{errors::UnknownCryptoError, hazardous::mac::blake2b::Blake2b}; /// The Tag size (bytes) to be output by BLAKE2b in keyed mode. const BLAKE2B_TAG_SIZE: usize = 32; @@ -90,8 +87,7 @@ pub fn authenticate(secret_key: &SecretKey, data: &[u8]) -> Result) -> bool { - let sk = SecretKey::default(); + let sk = SecretKey::generate().unwrap(); let tag = authenticate(&sk, &input[..]).unwrap(); authenticate_verify(&tag, &sk, &input[..]).is_ok() } @@ -171,8 +166,8 @@ mod public { /// Authentication and verifying that tag with a different key should /// never be true. fn prop_verify_fail_diff_key(input: Vec) -> bool { - let sk = SecretKey::default(); - let sk2 = SecretKey::default(); + let sk = SecretKey::generate().unwrap(); + let sk2 = SecretKey::generate().unwrap(); let tag = authenticate(&sk, &input[..]).unwrap(); authenticate_verify(&tag, &sk2, &input[..]).is_err() @@ -183,7 +178,7 @@ mod public { /// Authentication and verifying that tag with different input should /// never be true. fn prop_verify_fail_diff_input(input: Vec) -> bool { - let sk = SecretKey::default(); + let sk = SecretKey::generate().unwrap(); let tag = authenticate(&sk, &input[..]).unwrap(); authenticate_verify(&tag, &sk, b"Completely wrong input").is_err() @@ -196,8 +191,8 @@ mod public { /// Verify the bounds of 32..=64 (inclusive) for the `SecretKey` used /// in `authenticate/authenticate_verify`. fn prop_authenticate_key_size(input: Vec) -> bool { - let sec_key_res = SecretKey::from_slice(&input); - if input.is_empty() || input.len() >= u32::MAX as usize { + let sec_key_res = SecretKey::try_from(&input); + if input.is_empty() || input.len() > BLAKE2B_KEYSIZE { return sec_key_res.is_err(); } let sec_key = sec_key_res.unwrap(); diff --git a/src/high_level/hltypes.rs b/src/high_level/hltypes.rs index f1f89767a..ec0c7c08d 100644 --- a/src/high_level/hltypes.rs +++ b/src/high_level/hltypes.rs @@ -20,57 +20,92 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -/// These are the different types used by the high-level interface. They are not -/// used in `hazardous`. -use crate::errors::UnknownCryptoError; - -construct_secret_key_variable_size! { - /// A type to represent a secret key. - /// - /// As default it will randomly generate a `SecretKey` of 32 bytes. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is empty. - /// - `length` is 0. - /// - `length` is not less than [`isize::MAX`]. - /// - /// # Panics: - /// A panic will occur if: - /// - Failure to generate random bytes securely. - (SecretKey, test_secret_key, 32) +use crate::generics::{ByteVecData, Public, Secret, TypeSpec, sealed::Data, sealed::Sealed}; +#[cfg(feature = "safe_api")] +use crate::{ + errors::UnknownCryptoError, + generics::{GeneratePublic, GenerateSecret}, +}; + +#[derive(Debug)] +/// Marker type for Orion high-level secret key. +pub struct KeyType {} +impl Sealed for KeyType {} + +impl TypeSpec for KeyType { + const NAME: &'static str = stringify!(SecretKey); + type TypeData = ByteVecData; +} + +impl GenerateSecret for KeyType { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + fn generate() -> Result, UnknownCryptoError> { + let mut data = Self::TypeData::new(32)?; + crate::util::secure_rand_bytes(&mut data.bytes)?; + Ok(Secret::from_data(data)) + } } -construct_salt_variable_size! { - /// A type to represent the `Salt` that Argon2i uses during key derivation. - /// - /// As default it will randomly generate a `Salt` of 16 bytes. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is empty. - /// - `length` is 0. - /// - `length` is not less than [`isize::MAX`]. - /// - /// # Panics: - /// A panic will occur if: - /// - Failure to generate random bytes securely. - (Salt, test_salt, 16) +/// A type to represent a secret key. +/// +/// [`SecretKey::generate()`] will generate a random secret key of 32 bytes. +/// +/// # Errors: +/// An error will be returned if: +/// - `slice` is empty. +/// - `length` is 0. +/// - `length` is not less than [`isize::MAX`]. +/// - Failure to generate random bytes securely. +pub type SecretKey = Secret; + +#[derive(Debug, Clone, Copy)] +/// Marker type for Orion high-level salt for KDF operations. +pub struct SaltType {} +impl Sealed for SaltType {} + +impl TypeSpec for SaltType { + const NAME: &'static str = stringify!(Salt); + type TypeData = ByteVecData; } -construct_secret_key_variable_size! { - /// A type to represent the `Password` that Argon2i hashes and uses for key derivation. - /// - /// As default it will randomly generate a `Password` of 32 bytes. - /// - /// # Errors: - /// An error will be returned if: - /// - `slice` is empty. - /// - `length` is 0. - /// - `length` is not less than [`isize::MAX`]. - /// - /// # Panics: - /// A panic will occur if: - /// - Failure to generate random bytes securely. - (Password, test_password, 32) +impl GeneratePublic for SaltType { + #[cfg(feature = "safe_api")] + #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + fn generate() -> Result, UnknownCryptoError> { + let mut data = Self::TypeData::new(crate::pwhash::SALT_LENGTH)?; + crate::util::secure_rand_bytes(&mut data.bytes)?; + Ok(Public::from_data(data)) + } } + +/// A type to represent the `Salt` that Argon2i uses during key derivation. +/// +/// [`Salt::generate()`] will generate a random salt of 16 bytes. +/// +/// # Errors: +/// An error will be returned if: +/// - `slice` is empty. +/// - `length` is 0. +/// - `length` is not less than [`isize::MAX`]. +/// - Failure to generate random bytes securely. +pub type Salt = Public; + +#[derive(Debug)] +/// Marker type for Orion high-level password. +pub struct PasswordType {} +impl Sealed for PasswordType {} + +impl TypeSpec for PasswordType { + const NAME: &'static str = stringify!(Password); + type TypeData = ByteVecData; +} + +/// A type to represent the `Password` that Argon2i hashes and uses for key derivation. +/// +/// # Errors: +/// An error will be returned if: +/// - `slice` is empty. +/// - `length` is 0. +/// - `length` is not less than [`isize::MAX`]. +pub type Password = Secret; diff --git a/src/high_level/kdf.rs b/src/high_level/kdf.rs index a484884e2..6905f079d 100644 --- a/src/high_level/kdf.rs +++ b/src/high_level/kdf.rs @@ -53,7 +53,7 @@ //! # Security: //! - Choosing the correct cost parameters is important for security. Please refer to //! [libsodium's docs] for a description of how to do this. -//! - The salt should always be generated using a CSPRNG. [`Salt::default()`] +//! - The salt should always be generated using a CSPRNG. [`Salt::generate()`] //! can be used for this, it will generate a [`Salt`] of 16 bytes. //! - The recommended minimum size for a salt is 16 bytes. //! - The recommended minimum size for a derived key is 16 bytes. @@ -64,8 +64,8 @@ //! ```rust //! use orion::kdf; //! -//! let user_password = kdf::Password::from_slice(b"User password")?; -//! let salt = kdf::Salt::default(); +//! let user_password = kdf::Password::try_from(b"User password")?; +//! let salt = kdf::Salt::generate()?; //! //! let derived_key = kdf::derive_key(&user_password, &salt, 3, 1<<16, 32)?; //! @@ -92,16 +92,16 @@ pub fn derive_key( return Err(UnknownCryptoError); } - let mut dk = SecretKey::from_slice(&vec![0u8; length as usize])?; + let mut dk = SecretKey::try_from(&vec![0u8; length as usize])?; argon2i::derive_key( - password.unprotected_as_bytes(), + password.unprotected_as_ref(), salt.as_ref(), iterations, memory, None, None, - &mut dk.value, + dk.data.as_mut(), )?; Ok(dk) @@ -117,8 +117,8 @@ mod public { #[test] fn test_derive_key() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); - let salt = Salt::from_slice(&[0u8; 16]).unwrap(); + let password = Password::try_from([0u8; 64].as_slice()).unwrap(); + let salt = Salt::try_from([0u8; 16].as_slice()).unwrap(); let dk_first = derive_key(&password, &salt, 3, 1024, 32).unwrap(); let dk_second = derive_key(&password, &salt, 3, 1024, 32).unwrap(); @@ -127,8 +127,8 @@ mod public { #[test] fn test_derive_key_err_diff_iter() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); - let salt = Salt::from_slice(&[0u8; 64]).unwrap(); + let password = Password::try_from([0u8; 64].as_slice()).unwrap(); + let salt = Salt::try_from([0u8; 64].as_slice()).unwrap(); let dk = derive_key(&password, &salt, 3, 1024, 32).unwrap(); let dk_diff_iter = derive_key(&password, &salt, 4, 1024, 32).unwrap(); @@ -137,8 +137,8 @@ mod public { #[test] fn test_derive_key_err_diff_mem() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); - let salt = Salt::from_slice(&[0u8; 64]).unwrap(); + let password = Password::try_from([0u8; 64].as_slice()).unwrap(); + let salt = Salt::try_from([0u8; 64].as_slice()).unwrap(); let dk = derive_key(&password, &salt, 3, 1024, 32).unwrap(); let dk_diff_mem = derive_key(&password, &salt, 3, 512, 32).unwrap(); @@ -147,12 +147,12 @@ mod public { #[test] fn test_derive_key_err_diff_salt() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); - let salt = Salt::from_slice(&[0u8; 64]).unwrap(); + let password = Password::try_from([0u8; 64].as_slice()).unwrap(); + let salt = Salt::try_from([0u8; 64].as_slice()).unwrap(); let dk = derive_key(&password, &salt, 3, 1024, 32).unwrap(); let dk_diff_salt = derive_key( &password, - &Salt::from_slice(&[1u8; 64]).unwrap(), + &Salt::try_from([1u8; 64].as_slice()).unwrap(), 3, 1024, 32, @@ -164,8 +164,8 @@ mod public { #[test] fn test_derive_key_err_diff_len() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); - let salt = Salt::from_slice(&[0u8; 64]).unwrap(); + let password = Password::try_from([0u8; 64].as_slice()).unwrap(); + let salt = Salt::try_from([0u8; 64].as_slice()).unwrap(); let dk = derive_key(&password, &salt, 3, 1024, 32).unwrap(); let dk_diff_len = derive_key(&password, &salt, 3, 1024, 64).unwrap(); @@ -174,11 +174,11 @@ mod public { #[test] fn test_derive_key_err_diff_pass() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); - let salt = Salt::from_slice(&[0u8; 64]).unwrap(); + let password = Password::try_from([0u8; 64].as_slice()).unwrap(); + let salt = Salt::try_from([0u8; 64].as_slice()).unwrap(); let dk = derive_key(&password, &salt, 3, 1024, 32).unwrap(); let dk_diff_pass = derive_key( - &Password::from_slice(&[1u8; 64]).unwrap(), + &Password::try_from([1u8; 64].as_slice()).unwrap(), &salt, 3, 1024, @@ -191,8 +191,8 @@ mod public { #[test] fn test_derive_key_bad_length() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); - let salt = Salt::from_slice(&[0u8; 64]).unwrap(); + let password = Password::try_from([0u8; 64].as_slice()).unwrap(); + let salt = Salt::try_from([0u8; 64].as_slice()).unwrap(); assert!(derive_key(&password, &salt, 3, 1024, 3).is_err()); assert!(derive_key(&password, &salt, 3, 1024, 4).is_ok()); @@ -201,8 +201,8 @@ mod public { #[test] fn test_derive_key_bad_iter() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); - let salt = Salt::from_slice(&[0u8; 16]).unwrap(); + let password = Password::try_from([0u8; 64].as_slice()).unwrap(); + let salt = Salt::try_from([0u8; 16].as_slice()).unwrap(); assert!(derive_key(&password, &salt, 2, 1024, 32).is_err()); assert!(derive_key(&password, &salt, 3, 1024, 32).is_ok()); @@ -211,8 +211,8 @@ mod public { #[test] fn test_derive_key_bad_mem() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); - let salt = Salt::from_slice(&[0u8; 16]).unwrap(); + let password = Password::try_from([0u8; 64].as_slice()).unwrap(); + let salt = Salt::try_from([0u8; 16].as_slice()).unwrap(); assert!(derive_key(&password, &salt, 3, 7, 32).is_err()); assert!(derive_key(&password, &salt, 3, 8, 32).is_ok()); diff --git a/src/high_level/kex.rs b/src/high_level/kex.rs index 04413ebee..7cd6b7be5 100644 --- a/src/high_level/kex.rs +++ b/src/high_level/kex.rs @@ -39,11 +39,8 @@ //! //! # Errors: //! An error will be returned if: -//! - If the key exchange results in an all-zero output. -//! -//! # Panics: -//! A panic will occur if: -//! - Failure to generate random bytes securely. +//! - The key exchange results in an all-zero output. +//! - Failure to generate random bytes securely during key-generation. //! //! # Security: //! - The API is designed to be ephemeral and a [`PrivateKey`] should not be used more than once. @@ -87,6 +84,7 @@ pub use super::hltypes::SecretKey; pub use crate::hazardous::ecc::x25519::PrivateKey; pub use crate::hazardous::ecc::x25519::PublicKey; +pub use crate::hazardous::ecc::x25519::SharedKey; use crate::errors::UnknownCryptoError; use crate::hazardous::ecc::x25519; @@ -103,7 +101,7 @@ pub struct EphemeralClientSession { impl EphemeralClientSession { /// Generate a new random key pair. pub fn new() -> Result { - let privkey = PrivateKey::generate(); + let privkey = PrivateKey::generate()?; let pubkey: PublicKey = PublicKey::try_from(&privkey)?; Ok(Self { @@ -132,8 +130,8 @@ impl EphemeralClientSession { let keys = establish_session_keys(&q, &self.public_key, server_public_key)?; Ok(SessionKeys { - rx: SecretKey::from_slice(&keys.as_ref()[..32])?, - tx: SecretKey::from_slice(&keys.as_ref()[32..])?, + rx: SecretKey::try_from(&keys.as_ref()[..32])?, + tx: SecretKey::try_from(&keys.as_ref()[32..])?, }) } } @@ -148,7 +146,7 @@ pub struct EphemeralServerSession { impl EphemeralServerSession { /// Generate a new random key pair. pub fn new() -> Result { - let privkey = PrivateKey::generate(); + let privkey = PrivateKey::generate()?; let pubkey: PublicKey = PublicKey::try_from(&privkey)?; Ok(Self { @@ -177,8 +175,8 @@ impl EphemeralServerSession { let keys = establish_session_keys(&q, client_public_key, &self.public_key)?; Ok(SessionKeys { - rx: SecretKey::from_slice(&keys.as_ref()[32..])?, - tx: SecretKey::from_slice(&keys.as_ref()[..32])?, + rx: SecretKey::try_from(&keys.as_ref()[32..])?, + tx: SecretKey::try_from(&keys.as_ref()[..32])?, }) } } @@ -205,14 +203,14 @@ impl SessionKeys { /// Using BLAKE2b, derive two shared secret from a scalarmult computation. fn establish_session_keys( - shared_secret: &x25519::SharedKey, + shared_secret: &SharedKey, client_pk: &PublicKey, server_pk: &PublicKey, ) -> Result { let mut ctx = Blake2b::new(64)?; - ctx.update(shared_secret.unprotected_as_bytes())?; - ctx.update(&client_pk.to_bytes())?; - ctx.update(&server_pk.to_bytes())?; + ctx.update(shared_secret.unprotected_as_ref())?; + ctx.update(client_pk.as_ref())?; + ctx.update(server_pk.as_ref())?; ctx.finalize() } @@ -253,12 +251,14 @@ mod public { 0xc4, 0x6a, 0xda, 0x09, 0x8d, 0xeb, 0x9c, 0x32, 0xb1, 0xfd, 0x86, 0x62, 0x05, 0x16, 0x5f, 0x49, 0xb8, 0x00, ]; - let server_low_order_pk = PublicKey::from_slice(&low_order_public).unwrap(); + let server_low_order_pk = PublicKey::try_from(&low_order_public[..]).unwrap(); let session_client = EphemeralClientSession::new().unwrap(); - assert!(session_client - .establish_with_server(&server_low_order_pk) - .is_err()); + assert!( + session_client + .establish_with_server(&server_low_order_pk) + .is_err() + ); } // The following are tests generated with sodiumoxide to test basic compatibility with libsodium API. @@ -273,15 +273,15 @@ mod public { let server_rx = "201d1bb45d4b9164f269d59cc00ba1a49c1924c27485bb6e5cc77ea4cc38ec7e"; let server_tx = "37830d33c5de06fbe246db5803ed70284fe9ab78bc6b896a3db3a9b8db50418b"; - let client_public = PublicKey::from_slice(&hex::decode(client_pk).unwrap()).unwrap(); - let client_secret = PrivateKey::from_slice(&hex::decode(client_sk).unwrap()).unwrap(); - let server_public = PublicKey::from_slice(&hex::decode(server_pk).unwrap()).unwrap(); - let server_secret = PrivateKey::from_slice(&hex::decode(server_sk).unwrap()).unwrap(); + let client_public = PublicKey::try_from(&hex::decode(client_pk).unwrap()).unwrap(); + let client_secret = PrivateKey::try_from(&hex::decode(client_sk).unwrap()).unwrap(); + let server_public = PublicKey::try_from(&hex::decode(server_pk).unwrap()).unwrap(); + let server_secret = PrivateKey::try_from(&hex::decode(server_sk).unwrap()).unwrap(); - let client_recv = SecretKey::from_slice(&hex::decode(client_rx).unwrap()).unwrap(); - let client_trans = SecretKey::from_slice(&hex::decode(client_tx).unwrap()).unwrap(); - let server_recv = SecretKey::from_slice(&hex::decode(server_rx).unwrap()).unwrap(); - let server_trans = SecretKey::from_slice(&hex::decode(server_tx).unwrap()).unwrap(); + let client_recv = SecretKey::try_from(&hex::decode(client_rx).unwrap()).unwrap(); + let client_trans = SecretKey::try_from(&hex::decode(client_tx).unwrap()).unwrap(); + let server_recv = SecretKey::try_from(&hex::decode(server_rx).unwrap()).unwrap(); + let server_trans = SecretKey::try_from(&hex::decode(server_tx).unwrap()).unwrap(); let session_client = EphemeralClientSession { private_key: client_secret, @@ -328,15 +328,15 @@ mod public { let server_rx = "f69cf60f763fb2a9c47dc1b3237983ef79cecd26205c68f9c16e91db6c8f3f18"; let server_tx = "25789992d2eac8bc0e1c3322d9b8e26050064ea3cead77ca2cf36966dea54186"; - let client_public = PublicKey::from_slice(&hex::decode(client_pk).unwrap()).unwrap(); - let client_secret = PrivateKey::from_slice(&hex::decode(client_sk).unwrap()).unwrap(); - let server_public = PublicKey::from_slice(&hex::decode(server_pk).unwrap()).unwrap(); - let server_secret = PrivateKey::from_slice(&hex::decode(server_sk).unwrap()).unwrap(); + let client_public = PublicKey::try_from(&hex::decode(client_pk).unwrap()).unwrap(); + let client_secret = PrivateKey::try_from(&hex::decode(client_sk).unwrap()).unwrap(); + let server_public = PublicKey::try_from(&hex::decode(server_pk).unwrap()).unwrap(); + let server_secret = PrivateKey::try_from(&hex::decode(server_sk).unwrap()).unwrap(); - let client_recv = SecretKey::from_slice(&hex::decode(client_rx).unwrap()).unwrap(); - let client_trans = SecretKey::from_slice(&hex::decode(client_tx).unwrap()).unwrap(); - let server_recv = SecretKey::from_slice(&hex::decode(server_rx).unwrap()).unwrap(); - let server_trans = SecretKey::from_slice(&hex::decode(server_tx).unwrap()).unwrap(); + let client_recv = SecretKey::try_from(&hex::decode(client_rx).unwrap()).unwrap(); + let client_trans = SecretKey::try_from(&hex::decode(client_tx).unwrap()).unwrap(); + let server_recv = SecretKey::try_from(&hex::decode(server_rx).unwrap()).unwrap(); + let server_trans = SecretKey::try_from(&hex::decode(server_tx).unwrap()).unwrap(); let session_client = EphemeralClientSession { private_key: client_secret, @@ -383,15 +383,15 @@ mod public { let server_rx = "547c1f1be7abe8d10bf92fb19f79edd2139441b4faa54976b5db90a50b7244c4"; let server_tx = "89f90402d56d5e184b1682c21583e695560e0ab54459d09a51a596a8d33293da"; - let client_public = PublicKey::from_slice(&hex::decode(client_pk).unwrap()).unwrap(); - let client_secret = PrivateKey::from_slice(&hex::decode(client_sk).unwrap()).unwrap(); - let server_public = PublicKey::from_slice(&hex::decode(server_pk).unwrap()).unwrap(); - let server_secret = PrivateKey::from_slice(&hex::decode(server_sk).unwrap()).unwrap(); + let client_public = PublicKey::try_from(&hex::decode(client_pk).unwrap()).unwrap(); + let client_secret = PrivateKey::try_from(&hex::decode(client_sk).unwrap()).unwrap(); + let server_public = PublicKey::try_from(&hex::decode(server_pk).unwrap()).unwrap(); + let server_secret = PrivateKey::try_from(&hex::decode(server_sk).unwrap()).unwrap(); - let client_recv = SecretKey::from_slice(&hex::decode(client_rx).unwrap()).unwrap(); - let client_trans = SecretKey::from_slice(&hex::decode(client_tx).unwrap()).unwrap(); - let server_recv = SecretKey::from_slice(&hex::decode(server_rx).unwrap()).unwrap(); - let server_trans = SecretKey::from_slice(&hex::decode(server_tx).unwrap()).unwrap(); + let client_recv = SecretKey::try_from(&hex::decode(client_rx).unwrap()).unwrap(); + let client_trans = SecretKey::try_from(&hex::decode(client_tx).unwrap()).unwrap(); + let server_recv = SecretKey::try_from(&hex::decode(server_rx).unwrap()).unwrap(); + let server_trans = SecretKey::try_from(&hex::decode(server_tx).unwrap()).unwrap(); let session_client = EphemeralClientSession { private_key: client_secret, @@ -438,15 +438,15 @@ mod public { let server_rx = "fd1ab19e5c6ac0c5508ba129ded170a25c04f6f1ab9ccc3e66cd73988ade8471"; let server_tx = "738d3ff37e8b5d58daf888111359693042508617ef088c2048c0d87bc002ca38"; - let client_public = PublicKey::from_slice(&hex::decode(client_pk).unwrap()).unwrap(); - let client_secret = PrivateKey::from_slice(&hex::decode(client_sk).unwrap()).unwrap(); - let server_public = PublicKey::from_slice(&hex::decode(server_pk).unwrap()).unwrap(); - let server_secret = PrivateKey::from_slice(&hex::decode(server_sk).unwrap()).unwrap(); + let client_public = PublicKey::try_from(&hex::decode(client_pk).unwrap()).unwrap(); + let client_secret = PrivateKey::try_from(&hex::decode(client_sk).unwrap()).unwrap(); + let server_public = PublicKey::try_from(&hex::decode(server_pk).unwrap()).unwrap(); + let server_secret = PrivateKey::try_from(&hex::decode(server_sk).unwrap()).unwrap(); - let client_recv = SecretKey::from_slice(&hex::decode(client_rx).unwrap()).unwrap(); - let client_trans = SecretKey::from_slice(&hex::decode(client_tx).unwrap()).unwrap(); - let server_recv = SecretKey::from_slice(&hex::decode(server_rx).unwrap()).unwrap(); - let server_trans = SecretKey::from_slice(&hex::decode(server_tx).unwrap()).unwrap(); + let client_recv = SecretKey::try_from(&hex::decode(client_rx).unwrap()).unwrap(); + let client_trans = SecretKey::try_from(&hex::decode(client_tx).unwrap()).unwrap(); + let server_recv = SecretKey::try_from(&hex::decode(server_rx).unwrap()).unwrap(); + let server_trans = SecretKey::try_from(&hex::decode(server_tx).unwrap()).unwrap(); let session_client = EphemeralClientSession { private_key: client_secret, @@ -493,15 +493,15 @@ mod public { let server_rx = "a01332e4cb85b2bfac65f86936f27058b339889442c13eee06414bfb2d68c58b"; let server_tx = "8fa8dfc483108262b058b60b11e2f9b5b47287061bde785827afafb102a09ec7"; - let client_public = PublicKey::from_slice(&hex::decode(client_pk).unwrap()).unwrap(); - let client_secret = PrivateKey::from_slice(&hex::decode(client_sk).unwrap()).unwrap(); - let server_public = PublicKey::from_slice(&hex::decode(server_pk).unwrap()).unwrap(); - let server_secret = PrivateKey::from_slice(&hex::decode(server_sk).unwrap()).unwrap(); + let client_public = PublicKey::try_from(&hex::decode(client_pk).unwrap()).unwrap(); + let client_secret = PrivateKey::try_from(&hex::decode(client_sk).unwrap()).unwrap(); + let server_public = PublicKey::try_from(&hex::decode(server_pk).unwrap()).unwrap(); + let server_secret = PrivateKey::try_from(&hex::decode(server_sk).unwrap()).unwrap(); - let client_recv = SecretKey::from_slice(&hex::decode(client_rx).unwrap()).unwrap(); - let client_trans = SecretKey::from_slice(&hex::decode(client_tx).unwrap()).unwrap(); - let server_recv = SecretKey::from_slice(&hex::decode(server_rx).unwrap()).unwrap(); - let server_trans = SecretKey::from_slice(&hex::decode(server_tx).unwrap()).unwrap(); + let client_recv = SecretKey::try_from(&hex::decode(client_rx).unwrap()).unwrap(); + let client_trans = SecretKey::try_from(&hex::decode(client_tx).unwrap()).unwrap(); + let server_recv = SecretKey::try_from(&hex::decode(server_rx).unwrap()).unwrap(); + let server_trans = SecretKey::try_from(&hex::decode(server_tx).unwrap()).unwrap(); let session_client = EphemeralClientSession { private_key: client_secret, diff --git a/src/high_level/pwhash.rs b/src/high_level/pwhash.rs index 7e5c2bf47..1f404ec2a 100644 --- a/src/high_level/pwhash.rs +++ b/src/high_level/pwhash.rs @@ -38,7 +38,7 @@ //! [`PasswordHash`] provides two ways of retrieving the hashed password: //! - [`PasswordHash::unprotected_as_encoded()`] returns the hashed password in an encoded form. //! The encoding specifies the settings used to hash the password. -//! - [`PasswordHash::unprotected_as_bytes()`] returns only the hashed password in raw bytes. +//! - [`PasswordHash::unprotected_as_ref()`] returns only the hashed password in raw bytes. //! //! The following is an example of how the encoded password hash might look: //! ```text @@ -62,13 +62,10 @@ //! - `iterations` is less than 3. //! - The length of the `password` is greater than [`isize::MAX`]. //! - The password hash does not match `expected`. -//! -//! # Panics: -//! A panic will occur if: -//! - Failure to generate random bytes securely. +//! - Failure to generate random bytes securely during [`Salt::generate()`]. //! //! # Security: -//! - [`PasswordHash::unprotected_as_encoded()`] and [`PasswordHash::unprotected_as_bytes()`] should never +//! - [`PasswordHash::unprotected_as_encoded()`] and [`PasswordHash::unprotected_as_ref()`] should never //! be used to compare password hashes, as these will not run in constant-time. //! Either use [`hash_password_verify()`] or compare two [`PasswordHash`]es. //! - Choosing the correct cost parameters is important for security. Please refer to [libsodium's docs] @@ -80,7 +77,7 @@ //! ```rust //! use orion::pwhash; //! -//! let password = pwhash::Password::from_slice(b"Secret password")?; +//! let password = pwhash::Password::try_from(b"Secret password")?; //! //! let hash = pwhash::hash_password(&password, 3, 1<<16)?; //! assert!(pwhash::hash_password_verify(&hash, &password).is_ok()); @@ -135,19 +132,20 @@ pub(crate) const MIN_ITERATIONS: u32 = 3; /// - The encoded password hash length is less than [`PasswordHash::MIN_ENCODED_LEN`] or greater than [`PasswordHash::MAX_ENCODED_LEN`]. /// - The parameters in the encoded password hash are not correctly ordered. The ordering must be: /// `$argon2i$v=19$m=,t=,p=$$` +/// /// # Panics: /// A panic will occur if: /// - Overflowing calculations happen on `usize` when decoding the password and salt from Base64. /// /// # Security: -/// - __**Avoid using**__ `unprotected_as_bytes()` whenever possible, as it breaks all protections +/// - __**Avoid using**__ `unprotected_as_ref()` whenever possible, as it breaks all protections /// that the type implements. -/// - Never use `unprotected_as_bytes()` or `unprotected_as_encoded()` to compare password hashes, +/// - Never use `unprotected_as_ref()` or `unprotected_as_encoded()` to compare password hashes, /// as that will not run in constant-time. Compare `PasswordHash`es directly using `==` instead. /// - The trait `PartialEq<&'_ [u8]>` is implemented for this type so that users are not tempted -/// to call `unprotected_as_bytes` to compare this sensitive value to a byte slice. The trait +/// to call `unprotected_as_ref` to compare this sensitive value to a byte slice. The trait /// is implemented in such a way that the comparison happens in constant time. Thus, users should -/// prefer `SecretType == &[u8]` over `SecretType.unprotected_as_bytes() == &[u8]`. +/// prefer `SecretType == &[u8]` over `SecretType.unprotected_as_ref() == &[u8]`. /// /// Examples are shown below. The examples apply to any type that implements `PartialEq<&'_ [u8]>`. /// ```rust @@ -156,13 +154,13 @@ pub(crate) const MIN_ITERATIONS: u32 = 3; /// /// # fn main() -> Result<(), Box> { /// // Initialize an arbitrary, 64-byte tag. -/// let tag = Tag::from_slice(&[1; 64])?; +/// let tag = Tag::try_from(&[1; 64])?; /// /// // Secure, constant-time comparison with a byte slice /// assert_eq!(tag, &[1; 64][..]); /// /// // Secure, constant-time comparison with another Tag -/// assert_eq!(tag, Tag::from_slice(&[1; 64])?); +/// assert_eq!(tag, Tag::try_from(&[1; 64])?); /// # Ok(()) /// # } /// ``` @@ -239,7 +237,7 @@ impl PasswordHash { Ok(Self { encoded_password_hash, password_hash: password_hash.into(), - salt: Salt::from_slice(salt)?, + salt: Salt::try_from(salt)?, iterations, memory, }) @@ -321,23 +319,23 @@ impl PasswordHash { Ok(Self { encoded_password_hash: password_hash.into(), password_hash: password_hash_raw, - salt: Salt::from_slice(&salt)?, + salt: Salt::try_from(&salt)?, iterations, memory, }) } #[inline] - /// Return encoded password hash. __**Warning**__: Should not be used to verify - /// password hashes. This __**breaks protections**__ that the type implements. + /// Return encoded password hash. **Warning**: Should not be used to verify + /// password hashes. This **breaks protections** that the type implements. pub fn unprotected_as_encoded(&self) -> &str { self.encoded_password_hash.as_ref() } #[inline] - /// Return the password hash as byte slice. __**Warning**__: Should not be used unless strictly - /// needed. This __**breaks protections**__ that the type implements. - pub fn unprotected_as_bytes(&self) -> &[u8] { + /// Return the password hash as byte slice. **Warning**: Should not be used unless strictly + /// needed. This **breaks protections** that the type implements. + pub fn unprotected_as_ref(&self) -> &[u8] { self.password_hash.as_ref() } @@ -353,8 +351,11 @@ impl PasswordHash { /// __NOTE__: This method should always return `false`, since there shouldn't be a way /// to create an empty password hash. pub fn is_empty(&self) -> bool { - debug_assert_eq!(self.encoded_password_hash.is_empty(), self.password_hash.is_empty(), - "Both the encoded password hash and the raw hash must be non-empty or empty at the same time."); + debug_assert_eq!( + self.encoded_password_hash.is_empty(), + self.password_hash.is_empty(), + "Both the encoded password hash and the raw hash must be non-empty or empty at the same time." + ); self.password_hash.is_empty() } } @@ -370,7 +371,21 @@ impl core::fmt::Debug for PasswordHash { } } -impl_ct_partialeq_trait!(PasswordHash, unprotected_as_bytes); +impl PartialEq for PasswordHash { + fn eq(&self, other: &Self) -> bool { + use subtle::ConstantTimeEq; + (self.unprotected_as_ref().ct_eq(other.unprotected_as_ref())).into() + } +} + +impl Eq for PasswordHash {} + +impl PartialEq<&[u8]> for PasswordHash { + fn eq(&self, other: &&[u8]) -> bool { + use subtle::ConstantTimeEq; + (self.unprotected_as_ref().ct_eq(*other)).into() + } +} #[cfg(feature = "serde")] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] @@ -413,11 +428,11 @@ pub fn hash_password( } // Cannot panic as this is a valid size. - let salt = Salt::generate(SALT_LENGTH).unwrap(); + let salt = Salt::generate()?; let mut buffer = zeroize_wrap!([0u8; PWHASH_LENGTH]); argon2i::derive_key( - password.unprotected_as_bytes(), + password.unprotected_as_ref(), salt.as_ref(), iterations, memory, @@ -439,8 +454,8 @@ pub fn hash_password( /// ```rust /// use orion::pwhash; /// -/// let password = pwhash::Password::from_slice(b"Secret password")?; -/// let wrong_password = pwhash::Password::from_slice(b"hunter2")?; +/// let password = pwhash::Password::try_from(b"Secret password")?; +/// let wrong_password = pwhash::Password::try_from(b"hunter2")?; /// /// // Pretend these are stored somewhere and out-of-mind, e.g. in a database. /// let hash1 = pwhash::hash_password(&password, 3, 1<<15)?; @@ -463,8 +478,8 @@ pub fn hash_password_verify( let mut buffer = zeroize_wrap!([0u8; PWHASH_LENGTH]); argon2i::verify( - expected.unprotected_as_bytes(), - password.unprotected_as_bytes(), + expected.unprotected_as_ref(), + password.unprotected_as_ref(), expected.salt.as_ref(), expected.iterations, expected.memory, @@ -519,67 +534,67 @@ mod public { #[test] fn test_encoding_and_verify_1() { - let password = Password::from_slice(b"password").unwrap(); + let password = Password::try_from(b"password".as_slice()).unwrap(); let raw_hash = hex::decode("7d1b1163d3c0b791fea802ae5d1ccbd3fe896c54a1b0277ad96e5a1f311293f7") .unwrap(); let encoded_hash = "$argon2i$v=19$m=65536,t=3,p=1$c29tZXNhbHRzb21lc2FsdA$fRsRY9PAt5H+qAKuXRzL0/6JbFShsCd62W5aHzESk/c"; let expected = PasswordHash::from_encoded(encoded_hash).unwrap(); - assert_eq!(expected.unprotected_as_bytes(), &raw_hash[..]); + assert_eq!(expected.unprotected_as_ref(), &raw_hash[..]); assert!(hash_password_verify(&expected, &password).is_ok()); } #[test] fn test_encoding_and_verify_2() { - let password = Password::from_slice(b"passwordPASSWORDPassword").unwrap(); + let password = Password::try_from(b"passwordPASSWORDPassword".as_slice()).unwrap(); let raw_hash = hex::decode("ed4b0fd657e165f9ffe90f66ff315fbec878e629f03b2d6468d4b17a50c796aa") .unwrap(); let encoded_hash = "$argon2i$v=19$m=65536,t=3,p=1$c29tZXNhbHRzb21lc2FsdA$7UsP1lfhZfn/6Q9m/zFfvsh45inwOy1kaNSxelDHlqo"; let expected = PasswordHash::from_encoded(encoded_hash).unwrap(); - assert_eq!(expected.unprotected_as_bytes(), &raw_hash[..]); + assert_eq!(expected.unprotected_as_ref(), &raw_hash[..]); assert!(hash_password_verify(&expected, &password).is_ok()); } #[test] fn test_encoding_and_verify_3() { // Different salt from test 2 - let password = Password::from_slice(b"passwordPASSWORDPassword").unwrap(); + let password = Password::try_from(b"passwordPASSWORDPassword".as_slice()).unwrap(); let raw_hash = hex::decode("fa9ea96fecd0998251d698c1303edda4df3889a39bfa87cd5e7b8656ef61b510") .unwrap(); let encoded_hash = "$argon2i$v=19$m=65536,t=3,p=1$U29tZVNhbHRTb21lU2FsdA$+p6pb+zQmYJR1pjBMD7dpN84iaOb+ofNXnuGVu9htRA"; let expected = PasswordHash::from_encoded(encoded_hash).unwrap(); - assert_eq!(expected.unprotected_as_bytes(), &raw_hash[..]); + assert_eq!(expected.unprotected_as_ref(), &raw_hash[..]); assert!(hash_password_verify(&expected, &password).is_ok()); } #[test] fn test_encoding_and_verify_4() { - let password = Password::from_slice(b"passwordPASSWORDPassword").unwrap(); + let password = Password::try_from(b"passwordPASSWORDPassword".as_slice()).unwrap(); let raw_hash = hex::decode("fb3e0cdf7b10970bf6711c151861851566006f8986c9109ba2cdd5d98f9ca9d7") .unwrap(); let encoded_hash = "$argon2i$v=19$m=256,t=3,p=1$c29tZXNhbHRzb21lc2FsdA$+z4M33sQlwv2cRwVGGGFFWYAb4mGyRCbos3V2Y+cqdc"; let expected = PasswordHash::from_encoded(encoded_hash).unwrap(); - assert_eq!(expected.unprotected_as_bytes(), &raw_hash[..]); + assert_eq!(expected.unprotected_as_ref(), &raw_hash[..]); assert!(hash_password_verify(&expected, &password).is_ok()); } #[test] fn test_encoding_and_verify_5() { - let password = Password::from_slice(b"passwordPASSWORDPassword").unwrap(); + let password = Password::try_from(b"passwordPASSWORDPassword".as_slice()).unwrap(); let raw_hash = hex::decode("9b134c4c1c34e66170d1088c18be3a8e0f4a1837d4c069703ce62f85248b1e8f") .unwrap(); let encoded_hash = "$argon2i$v=19$m=256,t=4,p=1$c29tZXNhbHRzb21lc2FsdA$mxNMTBw05mFw0QiMGL46jg9KGDfUwGlwPOYvhSSLHo8"; let expected = PasswordHash::from_encoded(encoded_hash).unwrap(); - assert_eq!(expected.unprotected_as_bytes(), &raw_hash[..]); + assert_eq!(expected.unprotected_as_ref(), &raw_hash[..]); assert!(hash_password_verify(&expected, &password).is_ok()); } } @@ -592,7 +607,7 @@ mod public { let password_hash = PasswordHash::from_slice(&[0u8; 32], &[0u8; 16], 3, 1 << 16).unwrap(); assert_eq!(password_hash.len(), 32); - assert_eq!(password_hash.unprotected_as_bytes(), &[0u8; 32]); + assert_eq!(password_hash.unprotected_as_ref(), &[0u8; 32]); let password_hash_again = PasswordHash::from_encoded(password_hash.unprotected_as_encoded()).unwrap(); @@ -604,7 +619,7 @@ mod public { let password_hash = PasswordHash::from_slice(&[0u8; 32], &[0u8; 16], 3, 1 << 16).unwrap(); assert_eq!(password_hash.len(), 32); - assert_eq!(password_hash.unprotected_as_bytes(), &[0u8; 32]); + assert_eq!(password_hash.unprotected_as_ref(), &[0u8; 32]); let password_hash_again = PasswordHash::from_slice(&[1u8; 32], &[0u8; 16], 3, 1 << 16).unwrap(); @@ -682,7 +697,10 @@ mod public { let exact_min = "$argon2i$v=19$m=8,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA"; let less = "$argon2i$v=19$m=7,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA"; // Throws error during parsing as u32 - let u32_overflow = format!("$argon2i$v=19$m={},t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA", u64::MAX); + let u32_overflow = format!( + "$argon2i$v=19$m={},t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA", + u64::MAX + ); assert!(PasswordHash::from_encoded(exact_min).is_ok()); assert!(PasswordHash::from_encoded(less).is_err()); @@ -694,7 +712,10 @@ mod public { let exact_min = "$argon2i$v=19$m=65536,t=3,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA"; let less = "$argon2i$v=19$m=65536,t=2,p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA"; // Throws error during parsing as u32 - let u32_overflow = format!("$argon2i$v=19$m=65536,t={},p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA", u64::MAX); + let u32_overflow = format!( + "$argon2i$v=19$m=65536,t={},p=1$cHBwcHBwcHBwcHBwcHBwcA$MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA", + u64::MAX + ); assert!(PasswordHash::from_encoded(exact_min).is_ok()); assert!(PasswordHash::from_encoded(less).is_err()); @@ -880,7 +901,7 @@ mod public { #[test] fn test_argon2i_verify() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); + let password = Password::try_from(&[0u8; 64][..]).unwrap(); let dk = hash_password(&password, 3, 4096).unwrap(); assert!(hash_password_verify(&dk, &password).is_ok()); @@ -889,10 +910,10 @@ mod public { #[test] fn test_argon2i_verify_err_modified_password() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); + let password = Password::try_from(&[0u8; 64][..]).unwrap(); let dk = hash_password(&password, 3, 4096).unwrap(); - let mut pwd_mod = dk.unprotected_as_bytes().to_vec(); + let mut pwd_mod = dk.unprotected_as_ref().to_vec(); pwd_mod[0..32].copy_from_slice(&[0u8; 32]); let modified = PasswordHash::from_slice(&pwd_mod, dk.salt.as_ref(), 3, 4096).unwrap(); @@ -901,7 +922,7 @@ mod public { #[test] fn test_argon2i_verify_err_modified_memory() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); + let password = Password::try_from(&[0u8; 64][..]).unwrap(); let dk = hash_password(&password, 3, 4096).unwrap(); let encoded = dk.unprotected_as_encoded(); @@ -917,7 +938,7 @@ mod public { #[test] fn test_argon2i_verify_err_modified_iterations() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); + let password = Password::try_from(&[0u8; 64][..]).unwrap(); let dk = hash_password(&password, 3, 4096).unwrap(); let encoded = dk.unprotected_as_encoded(); @@ -933,7 +954,7 @@ mod public { #[test] fn test_argon2i_verify_err_modified_memory_and_iterations() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); + let password = Password::try_from(&[0u8; 64][..]).unwrap(); let dk = hash_password(&password, 3, 4096).unwrap(); let encoded = dk.unprotected_as_encoded(); @@ -951,23 +972,23 @@ mod public { #[test] fn test_argon2i_verify_err_modified_salt() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); + let password = Password::try_from(&[0u8; 64][..]).unwrap(); let dk = hash_password(&password, 3, 4096).unwrap(); let mut salt_mod = dk.salt.as_ref().to_vec(); salt_mod[0..16].copy_from_slice(&[0u8; 16]); let modified = - PasswordHash::from_slice(dk.unprotected_as_bytes(), &salt_mod, 3, 4096).unwrap(); + PasswordHash::from_slice(dk.unprotected_as_ref(), &salt_mod, 3, 4096).unwrap(); assert!(hash_password_verify(&modified, &password).is_err()); } #[test] fn test_argon2i_verify_err_modified_salt_and_password() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); + let password = Password::try_from(&[0u8; 64][..]).unwrap(); let dk = hash_password(&password, 3, 4096).unwrap(); - let mut pwd_mod = dk.unprotected_as_bytes().to_vec(); + let mut pwd_mod = dk.unprotected_as_ref().to_vec(); let mut salt_mod = dk.salt.as_ref().to_vec(); pwd_mod[0..32].copy_from_slice(&[0u8; 32]); salt_mod[0..16].copy_from_slice(&[0u8; 16]); @@ -978,13 +999,13 @@ mod public { #[test] fn test_argon2i_invalid_iterations() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); + let password = Password::try_from(&[0u8; 64][..]).unwrap(); assert!(hash_password(&password, MIN_ITERATIONS - 1, 4096).is_err()); } #[test] fn test_argon2i_invalid_memory() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); + let password = Password::try_from(&[0u8; 64][..]).unwrap(); assert!(hash_password(&password, MIN_ITERATIONS, MIN_MEMORY - 1).is_err()); } } diff --git a/src/lib.rs b/src/lib.rs index def1a06fd..986ac642e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,6 +57,7 @@ #![cfg_attr(not(feature = "safe_api"), no_std)] #![forbid(unsafe_code)] #![deny(clippy::mem_forget)] +#![deny(missing_debug_implementations)] #![warn( missing_docs, rust_2018_idioms, @@ -77,8 +78,10 @@ extern crate alloc; extern crate std as alloc; #[macro_use] -mod typedefs; -pub(crate) use typedefs::ZeroizeWrap; +/// Generic types used throughout the library. +pub mod generics; +pub use crate::generics::{GeneratePublic, GenerateSecret, KP, Public, Secret}; +pub(crate) use generics::ZeroizeWrap; #[macro_use] /// Utilities such as constant-time comparison. diff --git a/src/test_framework/aead_interface.rs b/src/test_framework/aead_interface.rs index dacced229..c383a0267 100644 --- a/src/test_framework/aead_interface.rs +++ b/src/test_framework/aead_interface.rs @@ -179,14 +179,16 @@ fn open_ciphertext_with_tag_length( assert!(opener(key, nonce, &[0u8; 0], default_aad, &mut dst_out_pt).is_err()); - assert!(opener( - key, - nonce, - &vec![0u8; tag_size - 1], - default_aad, - &mut dst_out_pt - ) - .is_err()); + assert!( + opener( + key, + nonce, + &vec![0u8; tag_size - 1], + default_aad, + &mut dst_out_pt + ) + .is_err() + ); let mut dst_out_ct = vec![0u8; tag_size]; sealer(key, nonce, &[0u8; 0], default_aad, &mut dst_out_ct).unwrap(); @@ -333,14 +335,16 @@ fn none_or_empty_some_aad_same_result( assert_eq!(dst_out_ct_none, dst_out_ct_some_empty); let mut dst_out_pt = vec![0u8; input.len()]; - assert!(opener( - key, - nonce, - &dst_out_ct_none, - Some(&[0u8; 0]), - &mut dst_out_pt - ) - .is_ok()); + assert!( + opener( + key, + nonce, + &dst_out_ct_none, + Some(&[0u8; 0]), + &mut dst_out_pt + ) + .is_ok() + ); assert!(opener(key, nonce, &dst_out_ct_some_empty, None, &mut dst_out_pt).is_ok()); } @@ -358,12 +362,12 @@ pub fn test_diff_params_err( Sealer: Fn(&Key, &Nonce, &[u8], Option<&[u8]>, &mut [u8]) -> Result<(), UnknownCryptoError>, Opener: Fn(&Key, &Nonce, &[u8], Option<&[u8]>, &mut [u8]) -> Result<(), UnknownCryptoError>, { - let sk1 = Key::gen(); - let sk2 = Key::gen(); + let sk1 = Key::gen_new(); + let sk2 = Key::gen_new(); assert!(sk1 != sk2); - let n1 = Nonce::gen(); - let n2 = Nonce::gen(); + let n1 = Nonce::gen_new(); + let n2 = Nonce::gen_new(); assert!(n1 != n2); let mut dst_out_ct = vec![0u8; input.len() + tag_size]; diff --git a/src/test_framework/hpke_interface.rs b/src/test_framework/hpke_interface.rs index c48fc0a44..6e7de8309 100644 --- a/src/test_framework/hpke_interface.rs +++ b/src/test_framework/hpke_interface.rs @@ -22,7 +22,7 @@ use crate::errors::UnknownCryptoError; use core::ops::Range; -use rand::{rngs::SmallRng, Rng, RngExt, SeedableRng}; +use rand::{Rng, RngExt, SeedableRng, rngs::SmallRng}; /// A testable HPKE implementation. This is implemented separately for each HPKE mode. pub trait TestableHpke: Clone { @@ -99,6 +99,7 @@ pub trait TestableHpke: Clone { fn export(&self, export_context: &[u8], dst: &mut [u8]) -> Result<(), UnknownCryptoError>; } +#[derive(Debug)] pub struct HpkeTester { hpke_sender: T, hpke_recipient: T, @@ -186,15 +187,21 @@ impl HpkeTester { let mut out_ct1 = vec![0u8; ciphertexts[1].len() - 16]; let mut out_ct2 = vec![0u8; ciphertexts[2].len() - 16]; - assert!(recipient - .open(&ciphertexts[0], &aads[0], &mut out_ct0) - .is_ok()); - assert!(recipient - .open(&ciphertexts[1], &aads[1], &mut out_ct1) - .is_ok()); - assert!(recipient - .open(&ciphertexts[2], &aads[2], &mut out_ct2) - .is_ok()); + assert!( + recipient + .open(&ciphertexts[0], &aads[0], &mut out_ct0) + .is_ok() + ); + assert!( + recipient + .open(&ciphertexts[1], &aads[1], &mut out_ct1) + .is_ok() + ); + assert!( + recipient + .open(&ciphertexts[2], &aads[2], &mut out_ct2) + .is_ok() + ); assert_eq!(out_ct0, plaintexts[0]); assert_eq!(out_ct1, plaintexts[1]); assert_eq!(out_ct2, plaintexts[2]); @@ -205,18 +212,26 @@ impl HpkeTester { let mut out_ct1 = vec![0u8; ciphertexts[1].len() - 16]; let mut out_ct2 = vec![0u8; ciphertexts[2].len() - 16]; - assert!(recipient - .open(&ciphertexts[0], &aads[0], &mut out_ct0) - .is_ok()); - assert!(recipient - .open(&ciphertexts[2], &aads[2], &mut out_ct2) - .is_err()); - assert!(recipient - .open(&ciphertexts[1], &aads[1], &mut out_ct1) - .is_ok()); - assert!(recipient - .open(&ciphertexts[2], &aads[2], &mut out_ct2) - .is_ok()); + assert!( + recipient + .open(&ciphertexts[0], &aads[0], &mut out_ct0) + .is_ok() + ); + assert!( + recipient + .open(&ciphertexts[2], &aads[2], &mut out_ct2) + .is_err() + ); + assert!( + recipient + .open(&ciphertexts[1], &aads[1], &mut out_ct1) + .is_ok() + ); + assert!( + recipient + .open(&ciphertexts[2], &aads[2], &mut out_ct2) + .is_ok() + ); assert_eq!(out_ct0, plaintexts[0]); assert_eq!(out_ct1, plaintexts[1]); assert_eq!(out_ct2, plaintexts[2]); @@ -227,21 +242,31 @@ impl HpkeTester { let mut out_ct1 = vec![0u8; ciphertexts[1].len() - 16]; let mut out_ct2 = vec![0u8; ciphertexts[2].len() - 16]; - assert!(recipient - .open(&ciphertexts[2], &aads[2], &mut out_ct2) - .is_err()); - assert!(recipient - .open(&ciphertexts[1], &aads[1], &mut out_ct1) - .is_err()); - assert!(recipient - .open(&ciphertexts[0], &aads[0], &mut out_ct0) - .is_ok()); - assert!(recipient - .open(&ciphertexts[1], &aads[1], &mut out_ct1) - .is_ok()); - assert!(recipient - .open(&ciphertexts[2], &aads[2], &mut out_ct2) - .is_ok()); + assert!( + recipient + .open(&ciphertexts[2], &aads[2], &mut out_ct2) + .is_err() + ); + assert!( + recipient + .open(&ciphertexts[1], &aads[1], &mut out_ct1) + .is_err() + ); + assert!( + recipient + .open(&ciphertexts[0], &aads[0], &mut out_ct0) + .is_ok() + ); + assert!( + recipient + .open(&ciphertexts[1], &aads[1], &mut out_ct1) + .is_ok() + ); + assert!( + recipient + .open(&ciphertexts[2], &aads[2], &mut out_ct2) + .is_ok() + ); assert_eq!(out_ct0, plaintexts[0]); assert_eq!(out_ct1, plaintexts[1]); assert_eq!(out_ct2, plaintexts[2]); @@ -427,84 +452,100 @@ impl HpkeTester { let (recipient_priv, recipient_pub) = T::gen_kp(valid_kem_ikm_recipient).unwrap(); let mut ct = vec![0u8; T::kem_ct_size()]; - assert!(T::setup_fresh_sender( - &recipient_pub, - valid_info, - valid_psk, - valid_psk_id, - &sender_priv, - &mut ct - ) - .is_ok()); - assert!(T::setup_fresh_recipient( - &ct, - &recipient_priv, - valid_info, - valid_psk, - valid_psk_id, - &sender_pub - ) - .is_ok()); - - // info (applies to all modes) - assert!(T::setup_fresh_sender( - &recipient_pub, - &[0u8; 65], - valid_psk, - valid_psk_id, - &sender_priv, - &mut ct - ) - .is_err()); - assert!(T::setup_fresh_recipient( - &ct, - &recipient_priv, - &[0u8; 65], - valid_psk, - valid_psk_id, - &sender_pub - ) - .is_err()); - - // psk and psk_id - if T::HPKE_MODE == 0x01u8 || T::HPKE_MODE == 0x03u8 { - assert!(T::setup_fresh_sender( + assert!( + T::setup_fresh_sender( &recipient_pub, valid_info, - &[0u8; 65], + valid_psk, valid_psk_id, &sender_priv, &mut ct ) - .is_err()); - assert!(T::setup_fresh_recipient( + .is_ok() + ); + assert!( + T::setup_fresh_recipient( &ct, &recipient_priv, valid_info, - &[0u8; 65], + valid_psk, valid_psk_id, &sender_pub ) - .is_err()); + .is_ok() + ); - assert!(T::setup_fresh_sender( + // info (applies to all modes) + assert!( + T::setup_fresh_sender( &recipient_pub, - valid_info, - valid_psk, &[0u8; 65], + valid_psk, + valid_psk_id, &sender_priv, &mut ct ) - .is_err()); - assert!(T::setup_fresh_recipient( + .is_err() + ); + assert!( + T::setup_fresh_recipient( &ct, &recipient_priv, - valid_info, - valid_psk, &[0u8; 65], + valid_psk, + valid_psk_id, &sender_pub ) - .is_err()); + .is_err() + ); + + // psk and psk_id + if T::HPKE_MODE == 0x01u8 || T::HPKE_MODE == 0x03u8 { + assert!( + T::setup_fresh_sender( + &recipient_pub, + valid_info, + &[0u8; 65], + valid_psk_id, + &sender_priv, + &mut ct + ) + .is_err() + ); + assert!( + T::setup_fresh_recipient( + &ct, + &recipient_priv, + valid_info, + &[0u8; 65], + valid_psk_id, + &sender_pub + ) + .is_err() + ); + + assert!( + T::setup_fresh_sender( + &recipient_pub, + valid_info, + valid_psk, + &[0u8; 65], + &sender_priv, + &mut ct + ) + .is_err() + ); + assert!( + T::setup_fresh_recipient( + &ct, + &recipient_priv, + valid_info, + valid_psk, + &[0u8; 65], + &sender_pub + ) + .is_err() + ); } // ikm (NOTE/TODO: we do NOT restrict this to 64 MAX, this would be breaking change) diff --git a/src/test_framework/incremental_interface.rs b/src/test_framework/incremental_interface.rs index de0aaf8b1..2be7fb2e5 100644 --- a/src/test_framework/incremental_interface.rs +++ b/src/test_framework/incremental_interface.rs @@ -44,6 +44,7 @@ pub trait TestableStreamingContext { fn compare_states(state_1: &Self, state_2: &Self); } +#[derive(Debug)] #[allow(dead_code)] // Allow because blocksize field is only used with std. /// A streaming context tester. pub struct StreamingContextConsistencyTester { diff --git a/src/test_framework/kem_interface.rs b/src/test_framework/kem_interface.rs index 8ebba4867..0381c0840 100644 --- a/src/test_framework/kem_interface.rs +++ b/src/test_framework/kem_interface.rs @@ -33,6 +33,7 @@ pub trait TestableKem> { fn decap(dk: &[u8], c: &C) -> Result; } +#[derive(Debug)] pub struct KemTester { _kem: PhantomData, _return_type_k: PhantomData, diff --git a/src/test_framework/mod.rs b/src/test_framework/mod.rs index f266d748a..dc7abe09f 100644 --- a/src/test_framework/mod.rs +++ b/src/test_framework/mod.rs @@ -39,3 +39,6 @@ pub mod kem_interface; #[cfg(all(test, feature = "safe_api"))] /// Tests for HPKE. pub mod hpke_interface; + +/// Tests for newtypes. +pub mod newtypes; diff --git a/src/test_framework/newtypes/mod.rs b/src/test_framework/newtypes/mod.rs new file mode 100644 index 000000000..07b75b9ac --- /dev/null +++ b/src/test_framework/newtypes/mod.rs @@ -0,0 +1,24 @@ +// MIT License + +// Copyright (c) 2026 The orion Developers + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +pub mod public; +pub mod secret; diff --git a/src/test_framework/newtypes/public.rs b/src/test_framework/newtypes/public.rs new file mode 100644 index 000000000..c3e61f547 --- /dev/null +++ b/src/test_framework/newtypes/public.rs @@ -0,0 +1,239 @@ +// MIT License + +// Copyright (c) 2026 The orion Developers + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use crate::generics::{GeneratePublic, Public, TypeSpec}; + +#[cfg(feature = "alloc")] +use alloc::vec; + +#[derive(Debug)] +pub struct PublicNewtype {} + +impl PublicNewtype { + pub fn test_with_generate< + const MIN: usize, + const MAX: usize, + const GEN_SIZE: usize, + S: TypeSpec + GeneratePublic, + >() { + test_try_from::(); + test_partial_eq::(); + test_as_bytes::(); + + #[cfg(feature = "safe_api")] + { + test_generate::(); + test_normal_debug::(); + } + } + + pub fn test_no_generate() { + test_try_from::(); + test_partial_eq::(); + test_as_bytes::(); + + #[cfg(feature = "safe_api")] + { + test_normal_debug::(); + } + } + + // NOTE: Needs both gates because `serde-json` used for testing is dev-dependency. + #[cfg(all(feature = "serde", test))] + pub fn test_serialization() + where + S::TypeData: AsRef<[u8]>, + { + { + test_serde_serialized_equivalence_to_bytes_fn::(); + test_serde_deserialized_equivalence_to_bytes_fn::(); + } + } +} + +pub fn test_try_from() { + // All types ought to implement: + // - TryFrom<&[u8]> + // - TryFrom<&[u8; N]> + // - TryFrom<&Vec> + // We test both 0 and 0xFF to indicate these tests are meant for abritrarily valid byte sequences. + // If this is not the case, such a type needs more specialized testing than this. + + assert!(Public::::try_from([0u8; MAX].as_slice()).is_ok()); // TryFrom<&[u8]> + assert!(Public::::try_from(&[0u8; MAX]).is_ok()); // TryFrom<&[u8; N]> + assert!(Public::::try_from([0xFFu8; MAX].as_slice()).is_ok()); // TryFrom<&[u8]> + assert!(Public::::try_from(&[0xFFu8; MAX]).is_ok()); // TryFrom<&[u8; N]> + + #[cfg(any(feature = "safe_api", feature = "alloc"))] + { + assert!(Public::::try_from(&vec![0u8; MAX]).is_ok()); // TryFrom<&Vec> + assert!(Public::::try_from(&vec![0xFFu8; MAX]).is_ok()); // TryFrom<&Vec> + } +} + +pub fn test_partial_eq() { + // PartialEq + assert_eq!( + Public::::try_from(&[0u8; MAX]).unwrap(), + Public::::try_from(&[0u8; MAX]).unwrap() + ); + assert_ne!( + Public::::try_from(&[0u8; MIN]).unwrap(), + Public::::try_from(&[1u8; MAX]).unwrap() + ); + + // If we want to compare with Primitive, which is what will be most useful + // for all impls, we need to apply the same S::parse_bytes() as try_from([u8]) + // will, otherwise we're not guaranteed to have the same repr (like X25519 public key) + // where some processing of the bytes happen so it doesn't map over 1-1. + let arr_primitve_zero: [u8; MAX] = S::parse_bytes(&[0u8; MAX]) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + let arr_primitve_one: [u8; MAX] = S::parse_bytes(&[1u8; MAX]) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + + // PartialEq<&[Primitive]> + assert_eq!( + Public::::try_from(&[0u8; MAX]).unwrap(), + arr_primitve_zero.as_slice() + ); + assert_ne!( + Public::::try_from(&[0u8; MAX]).unwrap(), + arr_primitve_one.as_slice() + ); + + // PartialEq<&[Primitive; N]> + assert_eq!( + Public::::try_from(&[0u8; MAX]).unwrap(), + &arr_primitve_zero + ); + assert_ne!( + Public::::try_from(&[0u8; MAX]).unwrap(), + &arr_primitve_one + ); + + // NOTE: It's possible to override the internal PartialEq, + // and we otherwise rely on `subtle` to check and abort + // early on length, mismtach, so we test this happens here + // so we don't forget if we override and allow indexing panics. + assert_ne!( + Public::::try_from(&[0u8; MAX]).unwrap(), + arr_primitve_one.as_slice()[..MAX / 2] + ); + assert_ne!( + Public::::try_from(&[0u8; MAX]).unwrap(), + arr_primitve_one.as_slice()[..0] + ); +} + +pub fn test_as_bytes() { + let test_upper = Public::::try_from(&[0u8; MAX]).unwrap(); + let test_lower = Public::::try_from(&[0u8; MIN]).unwrap(); + + assert_eq!(test_upper.as_ref().len(), test_upper.len()); + assert_eq!(test_upper.len(), MAX); + + assert_eq!(test_lower.as_ref().len(), test_lower.len()); + assert_eq!(test_lower.len(), MIN); + + assert!(!test_upper.is_empty()); + assert!(!test_lower.is_empty()); + + // Test non-fixed-length definitions + #[cfg(any(feature = "safe_api", feature = "alloc"))] + { + if MIN != MAX { + let test_upper = Public::::try_from(&vec![0u8; MAX - 1]).unwrap(); + let test_lower = Public::::try_from(&vec![0u8; MIN + 1]).unwrap(); + + assert_eq!(test_upper.as_ref().len(), test_upper.len()); + assert_eq!(test_upper.len(), MAX - 1); + + assert_eq!(test_lower.as_ref().len(), test_lower.len()); + assert_eq!(test_lower.len(), MIN + 1); + + assert!(!test_upper.is_empty()); + assert!(!test_lower.is_empty()); + } + } +} + +#[cfg(feature = "safe_api")] +pub fn test_generate() { + assert!(Public::::generate().is_ok()); + + let test_zero = Public::::try_from(&vec![0u8; GEN_SIZE]).unwrap(); + + // - A random one should never be all 0's. + // - A random generated one should always be GEN_SIZE in length. + let test_rand = Public::::generate().unwrap(); + assert_ne!(&test_zero, &test_rand); + assert_eq!(test_rand.len(), GEN_SIZE); + assert_ne!(Public::::generate().unwrap(), test_rand); + + // Because we can overload T::parse_slice() on newtypes, meaning parsing + // logic changes, we want to test that if that ever happens, the `GeneratePublic` + // logic still agrees with what is expected from an arbitrary slice. + // In other words: T::try_from(T::generate().as_ref()) should ALWAYS pass when no getrandom failure. + assert!(Public::::try_from(test_rand.as_ref()).is_ok()); +} + +#[cfg(feature = "safe_api")] // format! is only available with std +fn test_normal_debug() { + let public = format!("{:?}", [u8::MAX; MAX].as_ref()); + let test_debug_contents = format!( + "{:?}", + &Public::::try_from([u8::MAX; MAX].as_slice()).unwrap() + ); + assert!(test_debug_contents.contains(&public)); + assert!(test_debug_contents.starts_with(S::NAME)); +} + +#[cfg(test)] +#[cfg(feature = "serde")] +fn test_serde_serialized_equivalence_to_bytes_fn() +where + S::TypeData: AsRef<[u8]>, +{ + let bytes = [38u8; MAX]; + let public_type = Public::::try_from(&bytes).unwrap(); + let serialized_from_bytes = serde_json::to_value(bytes.as_slice()).unwrap(); + let serialized_from_public_type = serde_json::to_value(&public_type).unwrap(); + assert_eq!(serialized_from_bytes, serialized_from_public_type); +} + +#[cfg(test)] +#[cfg(feature = "serde")] +fn test_serde_deserialized_equivalence_to_bytes_fn() +where + S::TypeData: AsRef<[u8]>, +{ + let bytes = [38u8; MAX]; + let serialized_from_bytes = serde_json::to_value(bytes.as_slice()).unwrap(); + let public_type: Public = serde_json::from_value(serialized_from_bytes).unwrap(); + assert_eq!(public_type.data.as_ref(), bytes.as_slice()); +} diff --git a/src/test_framework/newtypes/secret.rs b/src/test_framework/newtypes/secret.rs new file mode 100644 index 000000000..cb0eb6f34 --- /dev/null +++ b/src/test_framework/newtypes/secret.rs @@ -0,0 +1,199 @@ +// MIT License + +// Copyright (c) 2026 The orion Developers + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use crate::generics::{GenerateSecret, Secret, TypeSpec}; + +#[cfg(feature = "alloc")] +use alloc::vec; + +#[derive(Debug)] +pub struct SecretNewtype {} + +impl SecretNewtype { + pub fn test_with_generate< + const MIN: usize, + const MAX: usize, + const GEN_SIZE: usize, + S: TypeSpec + GenerateSecret, + >() { + test_try_from::(); + test_partial_eq::(); + test_as_bytes::(); + + #[cfg(feature = "safe_api")] + { + test_generate::(); + test_omitted_debug::(); + } + } + + pub fn test_no_generate() { + test_try_from::(); + test_partial_eq::(); + test_as_bytes::(); + + #[cfg(feature = "safe_api")] + { + test_omitted_debug::(); + } + } +} + +pub fn test_try_from() { + // All types ought to implement: + // - TryFrom<&[u8]> + // - TryFrom<&[u8; N]> + // - TryFrom<&Vec> + // We test both 0 and MAX to indicate these tests are meant for abritrarily valid byte sequences. + // If this is not the case, such a type needs more specialized testing than this. + + assert!(Secret::::try_from([0u8; MAX].as_slice()).is_ok()); // TryFrom<&[u8]> + assert!(Secret::::try_from(&[0u8; MAX]).is_ok()); // TryFrom<&[u8; N]> + assert!(Secret::::try_from([u8::MAX; MAX].as_slice()).is_ok()); // TryFrom<&[u8]> + assert!(Secret::::try_from(&[u8::MAX; MAX]).is_ok()); // TryFrom<&[u8; N]> + + #[cfg(any(feature = "safe_api", feature = "alloc"))] + { + assert!(Secret::::try_from(&vec![0u8; MAX]).is_ok()); // TryFrom<&Vec> + assert!(Secret::::try_from(&vec![u8::MAX; MAX]).is_ok()); // TryFrom<&Vec> + } +} +pub fn test_partial_eq() { + // PartialEq + assert_eq!( + Secret::::try_from(&[0u8; MAX]).unwrap(), + Secret::::try_from(&[0u8; MAX]).unwrap() + ); + assert_ne!( + Secret::::try_from(&[0u8; MIN]).unwrap(), + Secret::::try_from(&[1u8; MAX]).unwrap() + ); + + // If we want to compare with Primitive, which is what will be most useful + // for all impls, we need to apply the same S::parse_bytes() as try_from([u8]) + // will, otherwise we're not guaranteed to have the same repr (like X25519 public key) + // where some processing of the bytes happen so it doesn't map over 1-1. + let arr_primitve_zero: [u8; MAX] = S::parse_bytes(&[0u8; MAX]) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + let arr_primitve_one: [u8; MAX] = S::parse_bytes(&[1u8; MAX]) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + + // PartialEq<&[Primitive]> + assert_eq!( + Secret::::try_from(&[0u8; MAX]).unwrap(), + arr_primitve_zero.as_slice() + ); + assert_ne!( + Secret::::try_from(&[0u8; MAX]).unwrap(), + arr_primitve_one.as_slice() + ); + + // PartialEq<&[Primitive; N]> + assert_eq!( + Secret::::try_from(&[0u8; MAX]).unwrap(), + &arr_primitve_zero + ); + assert_ne!( + Secret::::try_from(&[0u8; MAX]).unwrap(), + &arr_primitve_one + ); + + // NOTE: It's possible to override the internal PartialEq, + // and we otherwise rely on `subtle` to check and abort + // early on length, mismtach, so we test this happens here + // so we don't forget if we override and allow indexing panics. + assert_ne!( + Secret::::try_from(&[0u8; MAX]).unwrap(), + arr_primitve_one.as_slice()[..MAX / 2] + ); + assert_ne!( + Secret::::try_from(&[0u8; MAX]).unwrap(), + arr_primitve_one.as_slice()[..0] + ); +} + +pub fn test_as_bytes() { + let test_upper = Secret::::try_from(&[0u8; MAX]).unwrap(); + let test_lower = Secret::::try_from(&[0u8; MIN]).unwrap(); + + assert_eq!(test_upper.unprotected_as_ref().len(), test_upper.len()); + assert_eq!(test_upper.len(), MAX); + + assert_eq!(test_lower.unprotected_as_ref().len(), test_lower.len()); + assert_eq!(test_lower.len(), MIN); + + assert!(!test_upper.is_empty()); + assert!(!test_lower.is_empty()); + + // Test non-fixed-length definitions + #[cfg(any(feature = "safe_api", feature = "alloc"))] + { + if MIN != MAX { + let test_upper = Secret::::try_from(&vec![0u8; MAX - 1]).unwrap(); + let test_lower = Secret::::try_from(&vec![0u8; MIN + 1]).unwrap(); + + assert_eq!(test_upper.unprotected_as_ref().len(), test_upper.len()); + assert_eq!(test_upper.len(), MAX - 1); + + assert_eq!(test_lower.unprotected_as_ref().len(), test_lower.len()); + assert_eq!(test_lower.len(), MIN + 1); + + assert!(!test_upper.is_empty()); + assert!(!test_lower.is_empty()); + } + } +} + +#[cfg(feature = "safe_api")] +pub fn test_generate() { + assert!(Secret::::generate().is_ok()); + + let test_zero = Secret::::try_from(&vec![0u8; GEN_SIZE]).unwrap(); + + // - A random one should never be all 0's. + // - A random generated one should always be GEN_SIZE in length. + let test_rand = Secret::::generate().unwrap(); + assert_ne!(&test_zero, &test_rand); + assert_eq!(test_rand.len(), GEN_SIZE); + assert_ne!(Secret::::generate().unwrap(), test_rand); + + // Because we can overload T::parse_slice() on newtypes, meaning parsing + // logic changes, we want to test that if that ever happens, the `GenerateSecret` + // logic still agrees with what is expected from an arbitrary slice. + // In other words: T::try_from(T::generate().as_ref()) should ALWAYS pass when no getrandom failure. + assert!(Secret::::try_from(test_rand.unprotected_as_ref()).is_ok()); +} + +#[cfg(feature = "safe_api")] // format! is only available with std +fn test_omitted_debug() { + let ser = format!("{:?}", [u8::MAX; MAX].as_ref()); + let test_debug_contents = format!("{:?}", Secret::::try_from(&[u8::MAX; MAX]).unwrap()); + assert!(!test_debug_contents.contains(&ser)); + assert!(test_debug_contents.contains(&"{***OMITTED***}".to_string())); + assert!(test_debug_contents.starts_with(S::NAME)); +} diff --git a/src/test_framework/streamcipher_interface.rs b/src/test_framework/streamcipher_interface.rs index 2994a6307..64c74d396 100644 --- a/src/test_framework/streamcipher_interface.rs +++ b/src/test_framework/streamcipher_interface.rs @@ -29,7 +29,7 @@ use crate::errors::UnknownCryptoError; #[cfg(feature = "safe_api")] pub trait TestingRandom { /// Randomly generate self. - fn gen() -> Self; + fn gen_new() -> Self; } #[cfg(feature = "safe_api")] @@ -211,22 +211,26 @@ fn initial_counter_overflow_err( Decryptor: Fn(&Key, &Nonce, u32, &[u8], &mut [u8]) -> Result<(), UnknownCryptoError>, { let mut dst_out = [0u8; 128]; - assert!(encryptor( - key, - nonce, - u32::MAX, - &[0u8; 65], // CHACHA_BLOCKSIZE + 1 one to trigger internal block counter addition. - &mut dst_out - ) - .is_err()); - assert!(decryptor( - key, - nonce, - u32::MAX, - &[0u8; 65], // CHACHA_BLOCKSIZE + 1 one to trigger internal block counter addition. - &mut dst_out - ) - .is_err()); + assert!( + encryptor( + key, + nonce, + u32::MAX, + &[0u8; 65], // CHACHA_BLOCKSIZE + 1 one to trigger internal block counter addition. + &mut dst_out + ) + .is_err() + ); + assert!( + decryptor( + key, + nonce, + u32::MAX, + &[0u8; 65], // CHACHA_BLOCKSIZE + 1 one to trigger internal block counter addition. + &mut dst_out + ) + .is_err() + ); } #[cfg(feature = "safe_api")] @@ -241,22 +245,26 @@ fn initial_counter_max_ok( Decryptor: Fn(&Key, &Nonce, u32, &[u8], &mut [u8]) -> Result<(), UnknownCryptoError>, { let mut dst_out = [0u8; 64]; - assert!(encryptor( - key, - nonce, - u32::MAX, - &[0u8; 64], // Only needs to process one keystream - &mut dst_out - ) - .is_ok()); - assert!(decryptor( - key, - nonce, - u32::MAX, - &[0u8; 64], // Only needs to process one keystream - &mut dst_out - ) - .is_ok()); + assert!( + encryptor( + key, + nonce, + u32::MAX, + &[0u8; 64], // Only needs to process one keystream + &mut dst_out + ) + .is_ok() + ); + assert!( + decryptor( + key, + nonce, + u32::MAX, + &[0u8; 64], // Only needs to process one keystream + &mut dst_out + ) + .is_ok() + ); } #[cfg(test)] @@ -274,12 +282,12 @@ pub fn test_diff_params_diff_output( { let input = &[0u8; 16]; - let sk1 = Key::gen(); - let sk2 = Key::gen(); + let sk1 = Key::gen_new(); + let sk2 = Key::gen_new(); assert!(sk1 != sk2); - let n1 = Nonce::gen(); - let n2 = Nonce::gen(); + let n1 = Nonce::gen_new(); + let n2 = Nonce::gen_new(); assert!(n1 != n2); let c1 = 0u32; diff --git a/src/test_framework/xof_interface.rs b/src/test_framework/xof_interface.rs index 96c0c13cb..f43e7a1bd 100644 --- a/src/test_framework/xof_interface.rs +++ b/src/test_framework/xof_interface.rs @@ -40,6 +40,7 @@ pub trait TestableXofContext { fn compare_states(state_1: &Self, state_2: &Self); } +#[derive(Debug)] #[allow(dead_code)] // Allow because blocksize field is only used with std. /// A streaming context tester. pub struct XofContextConsistencyTester { diff --git a/src/typedefs.rs b/src/typedefs.rs deleted file mode 100644 index d9acde1d6..000000000 --- a/src/typedefs.rs +++ /dev/null @@ -1,1025 +0,0 @@ -// MIT License - -// Copyright (c) 2018-2026 The orion Developers - -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -// Trait implementation macros - -#[cfg(feature = "safe_api")] -/// Macro that implements the `Default` trait using a CSPRNG. -macro_rules! impl_default_trait (($name:ident, $size:expr) => ( - impl Default for $name { - #[cfg(feature = "safe_api")] - #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] - /// Randomly generate using a CSPRNG with recommended size. Not available in `no_std` context. - fn default() -> $name { - let mut value = vec![0u8; $size]; - crate::util::secure_rand_bytes(&mut value).unwrap(); - - $name { value, original_length: $size } - } - } -)); - -/// Macro that implements the `PartialEq` trait on a object called `$name` that -/// provides a given $bytes_function to return a slice. This `PartialEq` will -/// execute in constant-time. -/// -/// This also provides an empty `Eq` implementation. -macro_rules! impl_ct_partialeq_trait (($name:ident, $bytes_function:ident) => ( - impl PartialEq<$name> for $name { - fn eq(&self, other: &$name) -> bool { - use subtle::ConstantTimeEq; - - (self.$bytes_function() - .ct_eq(other.$bytes_function())).into() - } - } - - impl Eq for $name {} - - impl PartialEq<&[u8]> for $name { - fn eq(&self, other: &&[u8]) -> bool { - use subtle::ConstantTimeEq; - - (self.$bytes_function() - .ct_eq(*other)).into() - } - } -)); - -/// Macro that implements the `Debug` trait on a object called `$name`. -/// This `Debug` will omit any fields of object `$name` to avoid them being -/// written to logs. -macro_rules! impl_omitted_debug_trait (($name:ident) => ( - impl core::fmt::Debug for $name { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{} {{***OMITTED***}}", stringify!($name)) - } - } -)); - -/// Macro that implements the `Debug` trait on a object called `$name`. -macro_rules! impl_normal_debug_trait (($name:ident) => ( - impl core::fmt::Debug for $name { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{} {:?}", stringify!($name), &self.value[..]) - } - } -)); - -/// Macro that implements the `serde::{Serialize, Deserialize}` traits. -#[cfg(feature = "serde")] -macro_rules! impl_serde_traits (($name:ident, $bytes_function:ident) => ( - - #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] - /// This type tries to serialize as a `&[u8]` would. Note that the serialized - /// type likely does not have the same protections that Orion provides, such - /// as constant-time operations. A good rule of thumb is to only serialize - /// these types for storage. Don't operate on the serialized types. - impl serde::Serialize for $name { - fn serialize(&self, serializer: S) -> Result - where - S: serde::ser::Serializer, - { - let bytes: &[u8] = &self.$bytes_function(); - bytes.serialize(serializer) - } - } - - #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] - /// This type tries to deserialize as a `Vec` would. If it succeeds, the digest - /// will be built using `Self::from_slice`. - /// - /// Note that **this allocates** once to store the referenced bytes on the heap. - impl<'de> serde::Deserialize<'de> for $name { - fn deserialize(deserializer: D) -> Result - where - D: serde::de::Deserializer<'de>, - { - let bytes = alloc::vec::Vec::::deserialize(deserializer)?; - core::convert::TryFrom::try_from(bytes.as_slice()).map_err(serde::de::Error::custom) - } - } -)); - -/// Macro that implements the `Drop` trait on a object called `$name` which has -/// a field `value`. This `Drop` will zero out the field `value` when the -/// objects destructor is called. -macro_rules! impl_drop_trait (($name:ident) => ( - impl Drop for $name { - fn drop(&mut self) { - #[cfg(feature = "zeroize")] - { - use zeroize::Zeroize; - self.value.iter_mut().zeroize(); - } - } - } -)); - -/// Macro that implements the `AsRef<[u8]>` trait on a object called `$name` -/// which has fields `value` and `original_length`. This will return the inner -/// `value` as a byte slice, and should only be implemented on public types -/// which don't have any special protections. -macro_rules! impl_asref_trait (($name:ident) => ( - impl AsRef<[u8]> for $name { - #[inline] - fn as_ref(&self) -> &[u8] { - self.value[..self.original_length].as_ref() - } - } -)); - -/// Macro that implements the `From<[T]>` trait on a object called `$name` -/// which has fields `value` and `original_length`. It implements From -/// based on `$size` and this macro should, in most cases, only be used for -/// types which have a fixed-length. -macro_rules! impl_from_trait (($name:ident, $size:expr) => ( - impl From<[u8; $size]> for $name { - #[inline] - /// Make an object from a byte array. - fn from(bytes: [u8; $size]) -> $name { - $name { - value: bytes, - original_length: $size - } - } - } -)); - -/// Macro that implements `TryFrom<&[u8]>` on an object called `$name` that -/// implements the method `from_slice`. -macro_rules! impl_try_from_trait (($name:ident) => ( - /// Delegates to `from_slice` implementation - impl TryFrom<&[u8]> for $name { - type Error = UnknownCryptoError; - fn try_from(slice: &[u8]) -> Result { - Self::from_slice(slice) - } - } -)); - -// Function implementation macros - -/// Macro to implement a `from_slice()` function. Returns `UnknownCryptoError` -/// if the slice length is not accepted. -/// $lower_bound and $upper_bound is the inclusive range of which a slice might -/// be acceptable in length. If a slice may only be a fixed size, $lower_bound -/// and $upper_bound should be the same. The `value` field will always be allocated with -/// a size of $upper_bound. -macro_rules! func_from_slice (($name:ident, $lower_bound:expr, $upper_bound:expr) => ( - #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."] - /// Construct from a given byte slice. - pub fn from_slice(slice: &[u8]) -> Result<$name, UnknownCryptoError> { - - let slice_len = slice.len(); - - if !($lower_bound..=$upper_bound).contains(&slice_len) { - return Err(UnknownCryptoError); - } - - let mut value = [0u8; $upper_bound]; - value[..slice_len].copy_from_slice(slice); - - Ok($name { value, original_length: slice_len }) - } -)); - -#[cfg(feature = "safe_api")] -/// Macro to implement a `from_slice()` function. Returns `UnknownCryptoError` -/// if the slice is empty. -macro_rules! func_from_slice_variable_size (($name:ident) => ( - #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."] - #[cfg(feature = "safe_api")] - #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] - /// Construct from a given byte slice. - pub fn from_slice(slice: &[u8]) -> Result<$name, UnknownCryptoError> { - // See issue on `isize` limit: https://github.com/orion-rs/orion/issues/130 - if slice.is_empty() || slice.len() > (isize::MAX as usize) { - return Err(UnknownCryptoError); - } - - Ok($name { value: alloc::vec::Vec::from(slice), original_length: slice.len() }) - } -)); - -/// Macro to implement a `unprotected_as_bytes()` function for objects that -/// implement extra protections. Typically used on objects that implement -/// `Drop`. -macro_rules! func_unprotected_as_bytes (() => ( - #[inline] - /// Return the object as byte slice. __**Warning**__: Should not be used unless strictly - /// needed. This __**breaks protections**__ that the type implements. - pub fn unprotected_as_bytes(&self) -> &[u8] { - self.value[..self.original_length].as_ref() - } -)); - -/// Macro to implement a `len()` function which will return the original_length -/// field. Meaning the amount of bytes the newtype was created from. -macro_rules! func_len (() => ( - #[inline] - /// Return the length of the object. - pub fn len(&self) -> usize { - self.original_length - } -)); - -/// Macro to implement an `is_empty()` function which will return `true` if `self.len() == 0`. -macro_rules! func_is_empty (() => ( - #[inline] - /// Return `true` if this object does not hold any data, `false` otherwise. - /// - /// __NOTE__: This method should always return `false`, since there shouldn't be a way - /// to create an empty instance of this object. - pub fn is_empty(&self) -> bool { - self.original_length == 0 - } -)); - -/// Macro to implement a `generate()` function for objects that benefit from -/// having a CSPRNG available to generate data of a fixed length $gen_length. -macro_rules! func_generate (($name:ident, $upper_bound:expr, $gen_length:expr) => ( - #[cfg(feature = "safe_api")] - #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] - /// Randomly generate using a CSPRNG. Not available in `no_std` context. - pub fn generate() -> $name { - let mut value = [0u8; $upper_bound]; - // This will not panic on size, unless the newtype has been defined with $upper_bound - // or $gen_length equal to 0. - crate::util::secure_rand_bytes(&mut value[..$gen_length]).unwrap(); - - $name { value, original_length: $gen_length } - } -)); - -/// Wrap a value in `Zeroizing` if the `zeroize` feature is enabled, -/// otherwise return the value as-is. -macro_rules! zeroize_wrap { - ($val:expr) => {{ - #[cfg(feature = "zeroize")] - { - zeroize::Zeroizing::new($val) - } - #[cfg(not(feature = "zeroize"))] - { - $val - } - }}; -} - -#[cfg(feature = "zeroize")] -pub(crate) type ZeroizeWrap = zeroize::Zeroizing; -#[cfg(not(feature = "zeroize"))] -pub(crate) type ZeroizeWrap = T; - -macro_rules! zeroize_call { - ($val:expr) => {{ - #[cfg(feature = "zeroize")] - { - zeroize::Zeroize::zeroize(&mut $val); - } - }}; -} - -#[cfg(feature = "safe_api")] -/// Macro to implement a `generate()` function for objects that benefit from -/// having a CSPRNG available to generate data of a variable length. -macro_rules! func_generate_variable_size (($name:ident) => ( - #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."] - #[cfg(feature = "safe_api")] - #[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] - /// Randomly generate using a CSPRNG. Not available in `no_std` context. - pub fn generate(length: usize) -> Result<$name, UnknownCryptoError> { - // See issue on `isize` limit: https://github.com/orion-rs/orion/issues/130 - if length < 1 || length > (isize::MAX as usize) { - return Err(UnknownCryptoError); - } - - let mut value = vec![0u8; length]; - // This cannot panic on size input due to above length checks. - crate::util::secure_rand_bytes(&mut value).unwrap(); - - Ok($name { value, original_length: length }) - } -)); - -/// -/// Test implementation macros -#[cfg(test)] -#[cfg(feature = "serde")] -macro_rules! test_serde_impls (($name:ident, $gen_length:expr) => ( - #[test] - fn test_serde_serialized_equivalence_to_bytes_fn() { - let bytes = &[38u8; $gen_length][..]; - let orion_type = $name::from_slice(bytes).unwrap(); - let serialized_from_bytes = serde_json::to_value(bytes).unwrap(); - let serialized_from_orion_type = serde_json::to_value(&orion_type).unwrap(); - assert_eq!(serialized_from_bytes, serialized_from_orion_type); - } - - #[test] - fn test_serde_deserialized_equivalence_to_bytes_fn() { - let bytes = &[38u8; $gen_length][..]; - let serialized_from_bytes = serde_json::to_value(bytes).unwrap(); - let orion_type: $name = serde_json::from_value(serialized_from_bytes).unwrap(); - assert_eq!(orion_type, bytes); - } -)); - -#[cfg(test)] -macro_rules! test_bound_parameters (($name:ident, $lower_bound:expr, $upper_bound:expr, $gen_length:expr) => ( - #[test] - fn test_bound_params() { - // $lower_bound: - assert!($lower_bound <= $upper_bound); - // $upper_bound: - // $gen_length: - assert!($gen_length <= $upper_bound); - assert!($gen_length >= $lower_bound); - } -)); - -#[cfg(test)] -macro_rules! test_partial_eq (($name:ident, $upper_bound:expr) => ( - #[test] - fn test_partial_eq() { - // PartialEq - assert_eq!($name::from_slice(&[0u8; $upper_bound]).unwrap(), $name::from_slice(&[0u8; $upper_bound]).unwrap()); - assert_ne!($name::from_slice(&[0u8; $upper_bound]).unwrap(), $name::from_slice(&[1u8; $upper_bound]).unwrap()); - // PartialEq<&[u8]> - assert_eq!($name::from_slice(&[0u8; $upper_bound]).unwrap(), [0u8; $upper_bound].as_ref()); - assert_ne!($name::from_slice(&[0u8; $upper_bound]).unwrap(), [1u8; $upper_bound].as_ref()); - } -)); - -#[cfg(test)] -macro_rules! test_from_slice (($name:ident, $lower_bound:expr, $upper_bound:expr) => ( - #[test] - fn test_from_slice() { - assert!($name::from_slice(&[0u8; $upper_bound]).is_ok()); - assert!($name::from_slice(&[0u8; $lower_bound]).is_ok()); - - assert!($name::from_slice(&[0u8; $upper_bound + 1]).is_err()); - assert!($name::from_slice(&[0u8; $lower_bound - 1]).is_err()); - assert!($name::from_slice(&[0u8; 0]).is_err()); - - // Test non-fixed-length definitions - if $upper_bound != $lower_bound { - assert!($name::from_slice(&[0u8; $upper_bound - 1]).is_ok()); - assert!($name::from_slice(&[0u8; $lower_bound + 1]).is_ok()); - } - } -)); - -#[cfg(test)] -macro_rules! test_as_bytes_and_get_length (($name:ident, $lower_bound:expr, $upper_bound:expr, $bytes_function:ident) => ( - #[test] - fn test_as_bytes() { - let test_upper = $name::from_slice(&[0u8; $upper_bound]).unwrap(); - let test_lower = $name::from_slice(&[0u8; $lower_bound]).unwrap(); - - assert_eq!(test_upper.$bytes_function().len(), test_upper.len()); - assert_eq!(test_upper.len(), $upper_bound); - - assert_eq!(test_lower.$bytes_function().len(), test_lower.len()); - assert_eq!(test_lower.len(), $lower_bound); - - assert_eq!(test_upper.is_empty(), false); - assert_eq!(test_lower.is_empty(), false); - - // Test non-fixed-length definitions - if $lower_bound != $upper_bound { - let test_upper = $name::from_slice(&[0u8; $upper_bound - 1]).unwrap(); - let test_lower = $name::from_slice(&[0u8; $lower_bound + 1]).unwrap(); - - assert_eq!(test_upper.$bytes_function().len(), test_upper.len()); - assert_eq!(test_upper.len(), $upper_bound - 1); - - assert_eq!(test_lower.$bytes_function().len(), test_lower.len()); - assert_eq!(test_lower.len(), $lower_bound + 1); - - assert_eq!(test_upper.is_empty(), false); - assert_eq!(test_lower.is_empty(), false); - } - } -)); - -#[cfg(test)] -#[cfg(feature = "safe_api")] -macro_rules! test_generate (($name:ident, $gen_length:expr) => ( - #[test] - #[cfg(feature = "safe_api")] - fn test_generate() { - let test_zero = $name::from_slice(&[0u8; $gen_length]).unwrap(); - // A random one should never be all 0's. - let test_rand = $name::generate(); - assert_ne!(test_zero, test_rand); - // A random generated one should always be $gen_length in length. - assert_eq!(test_rand.len(), $gen_length); - } -)); - -#[cfg(test)] -#[cfg(feature = "safe_api")] -macro_rules! test_omitted_debug (($name:ident, $upper_bound:expr) => ( - #[test] - #[cfg(feature = "safe_api")] - // format! is only available with std - fn test_omitted_debug() { - let secret = format!("{:?}", [0u8; $upper_bound].as_ref()); - let test_debug_contents = format!("{:?}", $name::from_slice(&[0u8; $upper_bound]).unwrap()); - assert!(!test_debug_contents.contains(&secret)); - } -)); - -#[cfg(test)] -#[cfg(feature = "safe_api")] -macro_rules! test_normal_debug (($name:ident, $upper_bound:expr) => ( - #[test] - #[cfg(feature = "safe_api")] - // format! is only available with std - fn test_normal_debug() { - let public = format!("{:?}", [0u8; $upper_bound].as_ref()); - let test_debug_contents = format!("{:?}", $name::from_slice(&[0u8; $upper_bound]).unwrap()); - assert_eq!(test_debug_contents.contains(&public), true); - } -)); - -#[cfg(test)] -#[cfg(feature = "safe_api")] -macro_rules! test_from_slice_variable (($name:ident) => ( - #[test] - #[cfg(feature = "safe_api")] - fn test_from_slice_variable() { - assert!($name::from_slice(&[0u8; 512]).is_ok()); - assert!($name::from_slice(&[0u8; 256]).is_ok()); - assert!($name::from_slice(&[0u8; 1]).is_ok()); - assert!($name::from_slice(&[0u8; 0]).is_err()); - } -)); - -#[cfg(test)] -#[cfg(feature = "safe_api")] -macro_rules! test_generate_variable (($name:ident) => ( - #[test] - #[cfg(feature = "safe_api")] - fn test_generate_variable() { - assert!($name::generate(0).is_err()); - assert!($name::generate((isize::MAX as usize) + 1).is_err()); - assert!($name::generate(1).is_ok()); - assert!($name::generate(64).is_ok()); - - let test_zero = $name::from_slice(&[0u8; 128]).unwrap(); - // A random one should never be all 0's. - let test_rand = $name::generate(128).unwrap(); - assert_ne!(test_zero, test_rand); - assert_eq!(test_rand.len(), 128); - } -)); - -// Newtype implementation macros - -/// Macro to construct a type containing sensitive data, using a fixed-size -/// array. -/// -/// - $name: The name for the newtype. -/// -/// - $test_module_name: The name for the newtype's testing module (usually -/// "test_$name"). -/// -/// - $lower_bound/$upper_bound: An inclusive range that defines what length a -/// secret value might be. Used to validate length of `slice` in from_slice(). -/// $upper_bound also defines the `value` field array allocation size. -/// -/// - $gen_length: The amount of data to be randomly generated when using -/// generate(). -macro_rules! construct_secret_key { - ($(#[$meta:meta])* - ($name:ident, $test_module_name:ident, $lower_bound:expr, $upper_bound:expr)) => ( - $(#[$meta])* - /// - /// # Security: - /// - __**Avoid using**__ `unprotected_as_bytes()` whenever possible, as it breaks all protections - /// that the type implements. - /// - /// - The trait `PartialEq<&'_ [u8]>` is implemented for this type so that users are not tempted - /// to call `unprotected_as_bytes` to compare this sensitive value to a byte slice. The trait - /// is implemented in such a way that the comparison happens in constant time. Thus, users should - /// prefer `SecretType == &[u8]` over `SecretType.unprotected_as_bytes() == &[u8]`. - /// Examples are shown below. The examples apply to any type that implements `PartialEq<&'_ [u8]>`. - /// ```rust - /// # #[cfg(feature = "safe_api")] { - /// use orion::hazardous::stream::chacha20::SecretKey; - /// - /// // Initialize a secret key with random bytes. - /// let secret_key = SecretKey::generate(); - /// - /// // Secure, constant-time comparison with a byte slice - /// assert_ne!(secret_key, &[0; 32][..]); - /// - /// // Secure, constant-time comparison with another SecretKey - /// assert_ne!(secret_key, SecretKey::generate()); - /// # } - /// # Ok::<(), orion::errors::UnknownCryptoError>(()) - /// ``` - pub struct $name { - value: [u8; $upper_bound], - original_length: usize, - } - - impl_omitted_debug_trait!($name); - impl_drop_trait!($name); - impl_ct_partialeq_trait!($name, unprotected_as_bytes); - - impl $name { - func_from_slice!($name, $lower_bound, $upper_bound); - func_unprotected_as_bytes!(); - func_len!(); - func_is_empty!(); - } - - #[cfg(test)] - mod $test_module_name { - use super::*; - - test_bound_parameters!($name, $lower_bound, $upper_bound, $upper_bound); - test_from_slice!($name, $lower_bound, $upper_bound); - test_as_bytes_and_get_length!($name, $lower_bound, $upper_bound, unprotected_as_bytes); - test_partial_eq!($name, $upper_bound); - - #[cfg(test)] - #[cfg(feature = "safe_api")] - mod tests_with_std { - use super::*; - - test_omitted_debug!($name, $upper_bound); - } - } - ); - - ($(#[$meta:meta])* - ($name:ident, $test_module_name:ident, $lower_bound:expr, $upper_bound:expr, $gen_length:expr)) => ( - $(#[$meta])* - /// - /// # Security: - /// - __**Avoid using**__ `unprotected_as_bytes()` whenever possible, as it breaks all protections - /// that the type implements. - /// - /// - The trait `PartialEq<&'_ [u8]>` is implemented for this type so that users are not tempted - /// to call `unprotected_as_bytes` to compare this sensitive value to a byte slice. The trait - /// is implemented in such a way that the comparison happens in constant time. Thus, users should - /// prefer `SecretType == &[u8]` over `SecretType.unprotected_as_bytes() == &[u8]`. - /// Examples are shown below. The examples apply to any type that implements `PartialEq<&'_ [u8]>`. - /// ```rust - /// # #[cfg(feature = "safe_api")] { - /// use orion::hazardous::stream::chacha20::SecretKey; - /// - /// // Initialize a secret key with random bytes. - /// let secret_key = SecretKey::generate(); - /// - /// // Secure, constant-time comparison with a byte slice - /// assert_ne!(secret_key, &[0; 32][..]); - /// - /// // Secure, constant-time comparison with another SecretKey - /// assert_ne!(secret_key, SecretKey::generate()); - /// # } - /// # Ok::<(), orion::errors::UnknownCryptoError>(()) - /// ``` - pub struct $name { - value: [u8; $upper_bound], - original_length: usize, - } - - impl_omitted_debug_trait!($name); - impl_drop_trait!($name); - impl_ct_partialeq_trait!($name, unprotected_as_bytes); - - impl $name { - func_from_slice!($name, $lower_bound, $upper_bound); - func_unprotected_as_bytes!(); - func_generate!($name, $upper_bound, $gen_length); - func_len!(); - func_is_empty!(); - } - - #[cfg(test)] - mod $test_module_name { - use super::*; - - test_bound_parameters!($name, $lower_bound, $upper_bound, $gen_length); - test_from_slice!($name, $lower_bound, $upper_bound); - test_as_bytes_and_get_length!($name, $lower_bound, $upper_bound, unprotected_as_bytes); - test_partial_eq!($name, $upper_bound); - - #[cfg(test)] - #[cfg(feature = "safe_api")] - mod tests_with_std { - use super::*; - - test_generate!($name, $gen_length); - test_omitted_debug!($name, $upper_bound); - } - } - ); -} - -/// Macro to construct a public type containing non-sensitive data, using a -/// fixed-size array. -/// -/// - $name: The name for the newtype. -/// -/// - $test_module_name: The name for the newtype's testing module (usually -/// "test_$name"). -/// -/// - $lower_bound/$upper_bound: An inclusive range that defines what length a -/// public value might be. Used to validate length of `slice` in from_slice(). -/// $upper_bound also defines the `value` field array allocation size. -/// -/// - $gen_length: The amount of data to be randomly generated when using -/// generate(). If not supplied, the public newtype will not have a -/// `generate()` function available. -macro_rules! construct_public { - ($(#[$meta:meta])* - ($name:ident, $test_module_name:ident, $lower_bound:expr, $upper_bound:expr)) => ( - #[derive(Clone, Copy)] - $(#[$meta])* - /// - pub struct $name { - pub(crate) value: [u8; $upper_bound], - original_length: usize, - } - - impl_ct_partialeq_trait!($name, as_ref); - impl_normal_debug_trait!($name); - impl_try_from_trait!($name); - impl_asref_trait!($name); - - #[cfg(feature = "serde")] - impl_serde_traits!($name, as_ref); - - impl $name { - func_from_slice!($name, $lower_bound, $upper_bound); - func_len!(); - func_is_empty!(); - } - - #[cfg(test)] - mod $test_module_name { - use super::*; - // Replace $gen_length with $upper_bound since this doesn't have - // generate() function. - test_bound_parameters!($name, $lower_bound, $upper_bound, $upper_bound); - test_from_slice!($name, $lower_bound, $upper_bound); - test_as_bytes_and_get_length!($name, $lower_bound, $upper_bound, as_ref); - test_partial_eq!($name, $upper_bound); - - #[cfg(feature = "serde")] - test_serde_impls!($name, $upper_bound); - - #[cfg(test)] - #[cfg(feature = "safe_api")] - mod tests_with_std { - use super::*; - - test_normal_debug!($name, $upper_bound); - } - } - ); - - ($(#[$meta:meta])* - ($name:ident, $test_module_name:ident, $lower_bound:expr, $upper_bound:expr, $gen_length:expr)) => ( - #[derive(Clone, Copy)] - $(#[$meta])* - /// - pub struct $name { - pub(crate) value: [u8; $upper_bound], - original_length: usize, - } - - impl_ct_partialeq_trait!($name, as_ref); - impl_normal_debug_trait!($name); - impl_try_from_trait!($name); - impl_asref_trait!($name); - - #[cfg(feature = "serde")] - impl_serde_traits!($name, as_ref); - - impl $name { - func_from_slice!($name, $lower_bound, $upper_bound); - func_generate!($name, $upper_bound, $gen_length); - func_len!(); - func_is_empty!(); - } - - #[cfg(test)] - mod $test_module_name { - use super::*; - test_bound_parameters!($name, $lower_bound, $upper_bound, $upper_bound); - test_from_slice!($name, $lower_bound, $upper_bound); - test_as_bytes_and_get_length!($name, $lower_bound, $upper_bound, as_ref); - test_partial_eq!($name, $upper_bound); - - #[cfg(feature = "serde")] - test_serde_impls!($name, $upper_bound); - - #[cfg(test)] - #[cfg(feature = "safe_api")] - mod tests_with_std { - use super::*; - - test_normal_debug!($name, $upper_bound); - test_generate!($name, $gen_length); - } - } - ); -} - -/// Macro to construct a tag type that MACs return. -macro_rules! construct_tag { - ($(#[$meta:meta])* - ($name:ident, $test_module_name:ident, $lower_bound:expr, $upper_bound:expr)) => ( - #[derive(Clone)] - $(#[$meta])* - /// - /// # Security: - /// - __**Avoid using**__ `unprotected_as_bytes()` whenever possible, as it breaks all protections - /// that the type implements. - /// - /// - The trait `PartialEq<&'_ [u8]>` is implemented for this type so that users are not tempted - /// to call `unprotected_as_bytes` to compare this sensitive value to a byte slice. The trait - /// is implemented in such a way that the comparison happens in constant time. Thus, users should - /// prefer `SecretType == &[u8]` over `SecretType.unprotected_as_bytes() == &[u8]`. - /// Examples are shown below. The examples apply to any type that implements `PartialEq<&'_ [u8]>`. - /// ```rust - /// use orion::hazardous::mac::hmac::sha512::Tag; - /// - /// // Initialize an arbitrary, 64-byte tag. - /// let tag = Tag::from_slice(&[1; 64])?; - /// - /// // Secure, constant-time comparison with a byte slice - /// assert_eq!(tag, &[1; 64][..]); - /// - /// // Secure, constant-time comparison with another Tag - /// assert_eq!(tag, Tag::from_slice(&[1; 64])?); - /// # Ok::<(), orion::errors::UnknownCryptoError>(()) - /// ``` - pub struct $name { - value: [u8; $upper_bound], - original_length: usize, - } - - impl_omitted_debug_trait!($name); - impl_drop_trait!($name); - impl_ct_partialeq_trait!($name, unprotected_as_bytes); - impl_try_from_trait!($name); - - #[cfg(feature = "serde")] - impl_serde_traits!($name, unprotected_as_bytes); - - impl $name { - func_from_slice!($name, $lower_bound, $upper_bound); - func_unprotected_as_bytes!(); - func_len!(); - func_is_empty!(); - } - - #[cfg(test)] - mod $test_module_name { - use super::*; - // Replace $gen_length with $upper_bound since a tag doesn't have - // generate() function. - test_bound_parameters!($name, $lower_bound, $upper_bound, $upper_bound); - test_from_slice!($name, $lower_bound, $upper_bound); - test_as_bytes_and_get_length!($name, $lower_bound, $upper_bound, unprotected_as_bytes); - test_partial_eq!($name, $upper_bound); - - #[cfg(feature = "serde")] - test_serde_impls!($name, $upper_bound); - - #[cfg(test)] - #[cfg(feature = "safe_api")] - mod tests_with_std { - use super::*; - - test_omitted_debug!($name, $upper_bound); - } - } - ); -} - -/// Macro to construct a secret key used for HMAC. This pre-pads the given key -/// to the required length specified by the HMAC specifications. -macro_rules! construct_hmac_key { - ($(#[$meta:meta])* - ($name:ident, $sha2:ident, $sha2_outsize:expr, $test_module_name:ident, $size:expr)) => ( - $(#[$meta])* - /// - /// # Security: - /// - __**Avoid using**__ `unprotected_as_bytes()` whenever possible, as it breaks all protections - /// that the type implements. - /// - /// - The trait `PartialEq<&'_ [u8]>` is implemented for this type so that users are not tempted - /// to call `unprotected_as_bytes` to compare this sensitive value to a byte slice. The trait - /// is implemented in such a way that the comparison happens in constant time. Thus, users should - /// prefer `SecretType == &[u8]` over `SecretType.unprotected_as_bytes() == &[u8]`. - /// Examples are shown below. The examples apply to any type that implements `PartialEq<&'_ [u8]>`. - /// ```rust - /// # #[cfg(feature = "safe_api")] { - /// use orion::hazardous::mac::hmac::sha512::SecretKey; - /// - /// // Initialize a secret key with random bytes. - /// let secret_key = SecretKey::generate(); - /// - /// // Secure, constant-time comparison with a byte slice - /// assert_ne!(secret_key, &[0; 32][..]); - /// - /// // Secure, constant-time comparison with another SecretKey - /// assert_ne!(secret_key, SecretKey::generate()); - /// # } - /// # Ok::<(), orion::errors::UnknownCryptoError>(()) - /// ``` - pub struct $name { - value: [u8; $size], - original_length: usize, - } - - impl_omitted_debug_trait!($name); - impl_drop_trait!($name); - impl_ct_partialeq_trait!($name, unprotected_as_bytes); - - impl $name { - #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."] - /// Construct from a given byte slice. - pub fn from_slice(slice: &[u8]) -> Result<$name, UnknownCryptoError> { - let mut secret_key = [0u8; $size]; - - let slice_len = slice.len(); - - if slice_len > $size { - secret_key[..$sha2_outsize].copy_from_slice(&$sha2::digest(slice)?.as_ref()); - } else { - secret_key[..slice_len].copy_from_slice(slice); - } - - Ok($name { value: secret_key, original_length: $size }) - } - - func_unprotected_as_bytes!(); - func_generate!($name, $size, $size); - func_len!(); - func_is_empty!(); - } - - #[cfg(test)] - mod $test_module_name { - use super::*; - test_as_bytes_and_get_length!($name, $size, $size, unprotected_as_bytes); - test_partial_eq!($name, $size); - - #[test] - fn test_key_size() { - assert!($name::from_slice(&[0u8; $size]).is_ok()); - assert!($name::from_slice(&[0u8; $size - $size]).is_ok()); - assert!($name::from_slice(&[0u8; $size + 1]).is_ok()); - } - - #[cfg(test)] - #[cfg(feature = "safe_api")] - mod tests_with_std { - use super::*; - - test_generate!($name, $size); - test_omitted_debug!($name, $size); - } - } - ); -} - -#[cfg(feature = "safe_api")] -/// Macro to construct a type containing sensitive data which is stored on the -/// heap. -macro_rules! construct_secret_key_variable_size { - ($(#[$meta:meta])* - ($name:ident, $test_module_name:ident, $default_size:expr)) => ( - #[cfg(feature = "safe_api")] - $(#[$meta])* - /// - /// # Security: - /// - __**Avoid using**__ `unprotected_as_bytes()` whenever possible, as it breaks all protections - /// that the type implements. - /// - /// - The trait `PartialEq<&'_ [u8]>` is implemented for this type so that users are not tempted - /// to call `unprotected_as_bytes` to compare this sensitive value to a byte slice. The trait - /// is implemented in such a way that the comparison happens in constant time. Thus, users should - /// prefer `SecretType == &[u8]` over `SecretType.unprotected_as_bytes() == &[u8]`. - /// Examples are shown below. The examples apply to any type that implements `PartialEq<&'_ [u8]>`. - /// ```rust - /// # #[cfg(feature = "safe_api")] { - /// use orion::pwhash::Password; - /// - /// // Initialize a password with 32 random bytes. - /// let password = Password::generate(32)?; - /// - /// // Secure, constant-time comparison with a byte slice - /// assert_ne!(password, &[0; 32][..]); - /// - /// // Secure, constant-time comparison with another Password - /// assert_ne!(password, Password::generate(32)?); - /// # } - /// # Ok::<(), orion::errors::UnknownCryptoError>(()) - /// ``` - pub struct $name { - pub(crate) value: alloc::vec::Vec, - original_length: usize, - } - - impl_omitted_debug_trait!($name); - impl_drop_trait!($name); - impl_ct_partialeq_trait!($name, unprotected_as_bytes); - impl_default_trait!($name, $default_size); - - impl $name { - func_from_slice_variable_size!($name); - func_unprotected_as_bytes!(); - func_len!(); - func_is_empty!(); - func_generate_variable_size!($name); - } - - #[cfg(test)] - mod $test_module_name { - use super::*; - - test_from_slice_variable!($name); - test_as_bytes_and_get_length!($name, 1, $default_size + 1, unprotected_as_bytes); - test_generate_variable!($name); - test_omitted_debug!($name, $default_size); - test_partial_eq!($name, $default_size); - } - ); -} - -#[cfg(feature = "safe_api")] -/// Macro to construct a type containing non-sensitive data which is stored on the -/// heap. -macro_rules! construct_salt_variable_size { - ($(#[$meta:meta])* - ($name:ident, $test_module_name:ident, $default_size:expr)) => ( - #[cfg(feature = "safe_api")] - $(#[$meta])* - /// - pub struct $name { - value: alloc::vec::Vec, - original_length: usize, - } - - impl_normal_debug_trait!($name); - impl_default_trait!($name, $default_size); - impl_ct_partialeq_trait!($name, as_ref); - impl_asref_trait!($name); - impl_try_from_trait!($name); - - #[cfg(feature = "serde")] - impl_serde_traits!($name, as_ref); - - impl $name { - func_from_slice_variable_size!($name); - func_len!(); - func_is_empty!(); - func_generate_variable_size!($name); - } - - #[cfg(test)] - mod $test_module_name { - use super::*; - - test_from_slice_variable!($name); - test_as_bytes_and_get_length!($name, 1, $default_size + 1, as_ref); - test_generate_variable!($name); - test_partial_eq!($name, $default_size); - test_normal_debug!($name, $default_size); - - #[cfg(feature = "serde")] - test_serde_impls!($name, $default_size); - } - ); -} diff --git a/src/util/endianness.rs b/src/util/endianness.rs index 660a83b05..4f8f5f045 100644 --- a/src/util/endianness.rs +++ b/src/util/endianness.rs @@ -309,7 +309,7 @@ mod public { #[cfg(feature = "safe_api")] /// Load and store should not change the result. fn prop_load_store_u32_le(src: Vec) -> bool { - if !src.is_empty() && src.len() % 4 == 0 { + if !src.is_empty() && src.len().is_multiple_of(4) { let mut dst_load = vec![0u32; src.len() / 4]; load_u32_into_le(&src[..], &mut dst_load); // Test that loading a single also is working correctly @@ -328,7 +328,7 @@ mod public { #[cfg(feature = "safe_api")] /// Load and store should not change the result. fn prop_load_store_u64_le(src: Vec) -> bool { - if !src.is_empty() && src.len() % 8 == 0 { + if !src.is_empty() && src.len().is_multiple_of(8) { let mut dst_load = vec![0u64; src.len() / 8]; load_u64_into_le(&src[..], &mut dst_load); let mut dst_store = src.clone(); diff --git a/tests/aead/boringssl_tests.rs b/tests/aead/boringssl_tests.rs index e678743d1..b012179b5 100644 --- a/tests/aead/boringssl_tests.rs +++ b/tests/aead/boringssl_tests.rs @@ -1,5 +1,5 @@ -use crate::aead::wycheproof_test_runner; use crate::TestCaseReader; +use crate::aead::wycheproof_test_runner; use orion::hazardous::{ mac::poly1305::POLY1305_OUTSIZE, stream::{ @@ -51,18 +51,20 @@ fn boringssl_runner(path: &str, is_ietf: bool) { tc.outcome = false; } - assert!(wycheproof_test_runner( - &key[..], - &nonce[..], - &ad[..], - &tag[..], - &input[..], - &expected_output[..], - tc.outcome, - tc.test_case_number, - is_ietf, - ) - .is_ok()); + assert!( + wycheproof_test_runner( + &key[..], + &nonce[..], + &ad[..], + &tag[..], + &input[..], + &expected_output[..], + tc.outcome, + tc.test_case_number, + is_ietf, + ) + .is_ok() + ); // Read the next one test_case = boringssl_reader.next(); diff --git a/tests/aead/mod.rs b/tests/aead/mod.rs index d0bcf89e7..f9b0baf8d 100644 --- a/tests/aead/mod.rs +++ b/tests/aead/mod.rs @@ -26,14 +26,14 @@ fn wycheproof_test_runner( let mut dst_pt_out = vec![0u8; input.len()]; if result { - let key = SecretKey::from_slice(key)?; + let key = SecretKey::try_from(key)?; if is_ietf { - let nonce = chacha20poly1305::Nonce::from_slice(nonce)?; + let nonce = chacha20poly1305::Nonce::try_from(nonce)?; chacha20poly1305::seal(&key, &nonce, input, Some(aad), &mut dst_ct_out)?; chacha20poly1305::open(&key, &nonce, &dst_ct_out, Some(aad), &mut dst_pt_out)?; } else { - let nonce = xchacha20poly1305::Nonce::from_slice(nonce)?; + let nonce = xchacha20poly1305::Nonce::try_from(nonce)?; xchacha20poly1305::seal(&key, &nonce, input, Some(aad), &mut dst_ct_out)?; xchacha20poly1305::open(&key, &nonce, &dst_ct_out, Some(aad), &mut dst_pt_out)?; } @@ -43,7 +43,7 @@ fn wycheproof_test_runner( assert_eq!(dst_pt_out[..].as_ref(), input); } else { // Tests that run here have a "invalid" flag set - let key = match SecretKey::from_slice(key) { + let key = match SecretKey::try_from(key) { Ok(k) => k, Err(UnknownCryptoError) => return Ok(()), // Invalid key size test }; @@ -54,7 +54,7 @@ fn wycheproof_test_runner( let openres: Result<(), UnknownCryptoError>; if is_ietf { - let nonce = match chacha20poly1305::Nonce::from_slice(nonce) { + let nonce = match chacha20poly1305::Nonce::try_from(nonce) { Ok(n) => n, Err(UnknownCryptoError) => return Ok(()), // Invalid nonce size test }; @@ -62,7 +62,7 @@ fn wycheproof_test_runner( sealres = chacha20poly1305::seal(&key, &nonce, input, Some(aad), &mut dst_ct_out); openres = chacha20poly1305::open(&key, &nonce, &dst_ct_out, Some(aad), &mut dst_pt_out); } else { - let nonce = match xchacha20poly1305::Nonce::from_slice(nonce) { + let nonce = match xchacha20poly1305::Nonce::try_from(nonce) { Ok(n) => n, Err(UnknownCryptoError) => return Ok(()), // Invalid nonce size test }; diff --git a/tests/aead/other_xchacha20_poly1305.rs b/tests/aead/other_xchacha20_poly1305.rs index 0199e8a0a..614fefcd4 100644 --- a/tests/aead/other_xchacha20_poly1305.rs +++ b/tests/aead/other_xchacha20_poly1305.rs @@ -49,8 +49,8 @@ mod sodiumoxide_xchacha20_poly1305 { let mut dst_out_pt = vec![0u8; plaintext.len()]; aead::xchacha20poly1305::seal( - &aead::xchacha20poly1305::SecretKey::from_slice(&key).unwrap(), - &aead::xchacha20poly1305::Nonce::from_slice(&nonce).unwrap(), + &aead::xchacha20poly1305::SecretKey::try_from(&key).unwrap(), + &aead::xchacha20poly1305::Nonce::try_from(&nonce).unwrap(), &plaintext, Some(&aad), &mut dst_out_ct, @@ -68,8 +68,8 @@ mod sodiumoxide_xchacha20_poly1305 { ); aead::xchacha20poly1305::open( - &aead::xchacha20poly1305::SecretKey::from_slice(&key).unwrap(), - &aead::xchacha20poly1305::Nonce::from_slice(&nonce).unwrap(), + &aead::xchacha20poly1305::SecretKey::try_from(&key).unwrap(), + &aead::xchacha20poly1305::Nonce::try_from(&nonce).unwrap(), &dst_out_ct, Some(&aad), &mut dst_out_pt, @@ -96,8 +96,8 @@ mod wireguard_xchacha20_poly1305 { // These test vectors use empty ad parameter, see source. aead::xchacha20poly1305::seal( - &aead::xchacha20poly1305::SecretKey::from_slice(key).unwrap(), - &aead::xchacha20poly1305::Nonce::from_slice(nonce).unwrap(), + &aead::xchacha20poly1305::SecretKey::try_from(key).unwrap(), + &aead::xchacha20poly1305::Nonce::try_from(nonce).unwrap(), plaintext, None, &mut dst_out_ct, @@ -115,8 +115,8 @@ mod wireguard_xchacha20_poly1305 { ); aead::xchacha20poly1305::open( - &aead::xchacha20poly1305::SecretKey::from_slice(key).unwrap(), - &aead::xchacha20poly1305::Nonce::from_slice(nonce).unwrap(), + &aead::xchacha20poly1305::SecretKey::try_from(key).unwrap(), + &aead::xchacha20poly1305::Nonce::try_from(nonce).unwrap(), &dst_out_ct, None, &mut dst_out_pt, diff --git a/tests/aead/pynacl_streaming_aead.rs b/tests/aead/pynacl_streaming_aead.rs index 0a7d30fab..c8728da74 100644 --- a/tests/aead/pynacl_streaming_aead.rs +++ b/tests/aead/pynacl_streaming_aead.rs @@ -31,8 +31,8 @@ fn run_tests_from_json(path_to_vectors: &str) { let tests: Vec = serde_json::from_reader(reader).unwrap(); for test in tests.iter() { - let key = SecretKey::from_slice(&decode(&test.key).unwrap()).unwrap(); - let nonce = Nonce::from_slice(&decode(&test.header).unwrap()).unwrap(); + let key = SecretKey::try_from(&decode(&test.key).unwrap()).unwrap(); + let nonce = Nonce::try_from(&decode(&test.header).unwrap()).unwrap(); let mut ctx_seal = StreamXChaCha20Poly1305::new(&key, &nonce); let mut ctx_open = StreamXChaCha20Poly1305::new(&key, &nonce); diff --git a/tests/aead/rfc_chacha20_poly1305.rs b/tests/aead/rfc_chacha20_poly1305.rs index a4675a1d4..bb6bb1858 100644 --- a/tests/aead/rfc_chacha20_poly1305.rs +++ b/tests/aead/rfc_chacha20_poly1305.rs @@ -37,18 +37,20 @@ mod rfc_aead_chacha20_poly1305 { 0x06, 0x91, ]; - assert!(wycheproof_test_runner( - &key, - &nonce, - &aad, - &expected_tag, - plaintext, - &expected_ct, - true, - 0, - true - ) - .is_ok()); + assert!( + wycheproof_test_runner( + &key, + &nonce, + &aad, + &expected_tag, + plaintext, + &expected_ct, + true, + 0, + true + ) + .is_ok() + ); } #[test] @@ -108,8 +110,8 @@ mod rfc_aead_chacha20_poly1305 { let mut dst_out_pt = vec![0u8; ciphertext.len()]; aead::chacha20poly1305::open( - &aead::chacha20poly1305::SecretKey::from_slice(&key).unwrap(), - &aead::chacha20poly1305::Nonce::from_slice(&nonce).unwrap(), + &aead::chacha20poly1305::SecretKey::try_from(&key).unwrap(), + &aead::chacha20poly1305::Nonce::try_from(&nonce).unwrap(), &ct_plus_tag, Some(&aad), &mut dst_out_pt, diff --git a/tests/aead/wycheproof_aead.rs b/tests/aead/wycheproof_aead.rs index 76a0fe7ed..cfcb2dbd3 100644 --- a/tests/aead/wycheproof_aead.rs +++ b/tests/aead/wycheproof_aead.rs @@ -57,18 +57,20 @@ fn wycheproof_runner(path: &str) { _ => panic!("Unexpected test outcome for Wycheproof test"), }; - assert!(super::wycheproof_test_runner( - &decode(&test.key).unwrap(), - &decode(&test.iv).unwrap(), - &decode(&test.aad).unwrap(), - &decode(&test.tag).unwrap(), - &decode(&test.msg).unwrap(), - &decode(&test.ct).unwrap(), - should_test_pass, - test.tcId, - is_ietf, - ) - .is_ok()); + assert!( + super::wycheproof_test_runner( + &decode(&test.key).unwrap(), + &decode(&test.iv).unwrap(), + &decode(&test.aad).unwrap(), + &decode(&test.tag).unwrap(), + &decode(&test.msg).unwrap(), + &decode(&test.ct).unwrap(), + should_test_pass, + test.tcId, + is_ietf, + ) + .is_ok() + ); tests_run += 1; } diff --git a/tests/cae/ctx_test_vectors.rs b/tests/cae/ctx_test_vectors.rs index ac5c8d15c..9ea2ec604 100644 --- a/tests/cae/ctx_test_vectors.rs +++ b/tests/cae/ctx_test_vectors.rs @@ -36,7 +36,7 @@ pub(crate) fn custom_ctx_runner(path: &str) { _ => panic!("Unexpected test outcome for custom CTX tests"), }; - let key = SecretKey::from_slice(&decode(&test.Key).unwrap()).unwrap(); + let key = SecretKey::try_from(&decode(&test.Key).unwrap()).unwrap(); let nonce = &decode(&test.Nonce).unwrap(); let aad = &decode(&test.Ad).unwrap(); let input = &decode(&test.Msg).unwrap(); @@ -59,7 +59,7 @@ pub(crate) fn custom_ctx_runner(path: &str) { if should_test_pass { if is_ietf { - let nonce = chacha20poly1305blake2b::Nonce::from_slice(nonce).unwrap(); + let nonce = chacha20poly1305blake2b::Nonce::try_from(nonce).unwrap(); chacha20poly1305blake2b::seal(&key, &nonce, input, Some(aad), &mut dst_ct_out) .unwrap(); chacha20poly1305blake2b::open( @@ -71,7 +71,7 @@ pub(crate) fn custom_ctx_runner(path: &str) { ) .unwrap(); } else { - let nonce = xchacha20poly1305blake2b::Nonce::from_slice(nonce).unwrap(); + let nonce = xchacha20poly1305blake2b::Nonce::try_from(nonce).unwrap(); xchacha20poly1305blake2b::seal(&key, &nonce, input, Some(aad), &mut dst_ct_out) .unwrap(); xchacha20poly1305blake2b::open( @@ -87,25 +87,17 @@ pub(crate) fn custom_ctx_runner(path: &str) { assert_eq!(dst_ct_out, output); assert_eq!(dst_pt_out[..].as_ref(), input); } else if is_ietf { - let nonce = chacha20poly1305blake2b::Nonce::from_slice(nonce).unwrap(); - assert!(chacha20poly1305blake2b::open( - &key, - &nonce, - &output, - Some(aad), - &mut dst_pt_out + let nonce = chacha20poly1305blake2b::Nonce::try_from(nonce).unwrap(); + assert!( + chacha20poly1305blake2b::open(&key, &nonce, &output, Some(aad), &mut dst_pt_out) + .is_err() ) - .is_err()) } else { - let nonce = xchacha20poly1305blake2b::Nonce::from_slice(nonce).unwrap(); - assert!(xchacha20poly1305blake2b::open( - &key, - &nonce, - &output, - Some(aad), - &mut dst_pt_out + let nonce = xchacha20poly1305blake2b::Nonce::try_from(nonce).unwrap(); + assert!( + xchacha20poly1305blake2b::open(&key, &nonce, &output, Some(aad), &mut dst_pt_out) + .is_err() ) - .is_err()) } } } diff --git a/tests/cae/mod.rs b/tests/cae/mod.rs index 0e325345d..705edb68d 100644 --- a/tests/cae/mod.rs +++ b/tests/cae/mod.rs @@ -35,19 +35,19 @@ fn wycheproof_runner(path: &str) { let input = &decode(&test.msg).unwrap(); let output = &decode(&test.ct).unwrap(); - let key = SecretKey::from_slice(&decode(&test.key).unwrap()).unwrap(); + let key = SecretKey::try_from(&decode(&test.key).unwrap()).unwrap(); let aad = &decode(&test.aad).unwrap(); let mut dst_ct_out = vec![0u8; input.len() + 32]; if is_ietf { let nonce = - chacha20poly1305blake2b::Nonce::from_slice(&decode(&test.iv).unwrap()).unwrap(); + chacha20poly1305blake2b::Nonce::try_from(&decode(&test.iv).unwrap()).unwrap(); chacha20poly1305blake2b::seal(&key, &nonce, input, Some(aad), &mut dst_ct_out) .unwrap(); } else { - let nonce = xchacha20poly1305blake2b::Nonce::from_slice(&decode(&test.iv).unwrap()) - .unwrap(); + let nonce = + xchacha20poly1305blake2b::Nonce::try_from(&decode(&test.iv).unwrap()).unwrap(); xchacha20poly1305blake2b::seal(&key, &nonce, input, Some(aad), &mut dst_ct_out) .unwrap(); } diff --git a/tests/ecc/mod.rs b/tests/ecc/mod.rs index c1d0293a2..f63b11f39 100644 --- a/tests/ecc/mod.rs +++ b/tests/ecc/mod.rs @@ -1,10 +1,10 @@ -use orion::hazardous::ecc::x25519::{key_agreement, PrivateKey, PublicKey}; +use orion::hazardous::ecc::x25519::{PrivateKey, PublicKey, key_agreement}; pub mod wycheproof_x25519; fn x25519_test_runner(expected_result: &[u8; 32], k: &[u8; 32], u: &[u8; 32], valid_result: bool) { - let private = PrivateKey::from_slice(k).unwrap(); - let public = PublicKey::from_slice(u).unwrap(); + let private = PrivateKey::try_from(k).unwrap(); + let public = PublicKey::try_from(u).unwrap(); if valid_result { let actual_result = key_agreement(&private, &public).unwrap(); diff --git a/tests/hash/mod.rs b/tests/hash/mod.rs index 5753c0b46..ed794e2d9 100644 --- a/tests/hash/mod.rs +++ b/tests/hash/mod.rs @@ -23,12 +23,12 @@ fn blake2b_test_runner(input: &[u8], key: &[u8], output: &[u8]) { assert_eq!(digest.len(), output.len()); assert_eq!(digest.as_ref(), output); } else { - let secret_key = mac::blake2b::SecretKey::from_slice(key).unwrap(); + let secret_key = mac::blake2b::SecretKey::try_from(key).unwrap(); let mut state = mac::blake2b::Blake2b::new(&secret_key, output.len()).unwrap(); state.update(input).unwrap(); let tag = state.finalize().unwrap(); assert_eq!(tag.len(), output.len()); - assert_eq!(tag.unprotected_as_bytes(), output); + assert_eq!(tag.unprotected_as_ref(), output); } } diff --git a/tests/hpke/hpke_test_kats.rs b/tests/hpke/hpke_test_kats.rs index 37c79a4aa..1ec8c276b 100644 --- a/tests/hpke/hpke_test_kats.rs +++ b/tests/hpke/hpke_test_kats.rs @@ -77,37 +77,31 @@ fn hpke_runner(path: &str) { ); let secret_sender = - PrivateKey::from_slice(&decode(test.skSm.as_ref().unwrap()).unwrap()).unwrap(); + PrivateKey::try_from(&decode(test.skSm.as_ref().unwrap()).unwrap()).unwrap(); let public_sender = - PublicKey::from_slice(&decode(test.pkSm.as_ref().unwrap()).unwrap()).unwrap(); + PublicKey::try_from(&decode(test.pkSm.as_ref().unwrap()).unwrap()).unwrap(); let derived_kp_sender = DhKem::derive_keypair(&decode(ikm_s).unwrap()).unwrap(); assert_eq!(secret_sender, derived_kp_sender.0); assert_eq!(public_sender, derived_kp_sender.1); assert_eq!( - &public_sender.to_bytes(), + public_sender, decode(test.pkSm.as_ref().unwrap()).unwrap().as_slice() ); } - let secret_recip = PrivateKey::from_slice(&decode(&test.skRm).unwrap()).unwrap(); - let public_recip = PublicKey::from_slice(&decode(&test.pkRm).unwrap()).unwrap(); + let secret_recip = PrivateKey::try_from(&decode(&test.skRm).unwrap()).unwrap(); + let public_recip = PublicKey::try_from(&decode(&test.pkRm).unwrap()).unwrap(); let derived_kp_recip = DhKem::derive_keypair(&decode(&test.ikmR).unwrap()).unwrap(); assert_eq!(secret_recip, derived_kp_recip.0); assert_eq!(public_recip, derived_kp_recip.1); - assert_eq!( - &public_recip.to_bytes(), - decode(&test.pkRm).unwrap().as_slice() - ); + assert_eq!(public_recip, decode(&test.pkRm).unwrap().as_slice()); - let secret_eph = PrivateKey::from_slice(&decode(&test.skEm).unwrap()).unwrap(); - let public_eph = PublicKey::from_slice(&decode(&test.pkEm).unwrap()).unwrap(); + let secret_eph = PrivateKey::try_from(&decode(&test.skEm).unwrap()).unwrap(); + let public_eph = PublicKey::try_from(&decode(&test.pkEm).unwrap()).unwrap(); let derived_kp_eph = DhKem::derive_keypair(&decode(&test.ikmE).unwrap()).unwrap(); assert_eq!(secret_eph, derived_kp_eph.0); assert_eq!(public_eph, derived_kp_eph.1); - assert_eq!( - &public_eph.to_bytes(), - decode(&test.pkEm).unwrap().as_slice() - ); + assert_eq!(public_eph, decode(&test.pkEm).unwrap().as_slice()); let info = hex::decode(test.info).unwrap(); let shared_secret = hex::decode(test.shared_secret).unwrap(); @@ -115,16 +109,16 @@ fn hpke_runner(path: &str) { // implicitly through the encryption and export tests. let _base_nonce = hex::decode(test.base_nonce).unwrap(); let _exporter_secret = hex::decode(test.exporter_secret).unwrap(); - let enc = PublicKey::from_slice(&hex::decode(test.enc).unwrap()).unwrap(); + let enc = PublicKey::try_from(&hex::decode(test.enc).unwrap()).unwrap(); match test.mode as u8 { ModeBase::::MODE_ID => { let (ss, _) = DhKem::encap_deterministic( &public_recip, - PrivateKey::from_slice(&decode(&test.skEm).unwrap()).unwrap(), + PrivateKey::try_from(&decode(&test.skEm).unwrap()).unwrap(), ) .unwrap(); - assert_eq!(ss.unprotected_as_bytes(), &shared_secret); + assert_eq!(ss.unprotected_as_ref(), &shared_secret); let (mut hpke_ctx_s, actual_enc) = ModeBase::::new_sender_deterministic( @@ -183,10 +177,10 @@ fn hpke_runner(path: &str) { ModePsk::::MODE_ID => { let (ss, _) = DhKem::encap_deterministic( &public_recip, - PrivateKey::from_slice(&decode(&test.skEm).unwrap()).unwrap(), + PrivateKey::try_from(&decode(&test.skEm).unwrap()).unwrap(), ) .unwrap(); - assert_eq!(ss.unprotected_as_bytes(), &shared_secret); + assert_eq!(ss.unprotected_as_ref(), &shared_secret); assert!(test.psk.is_some()); assert!(test.psk_id.is_some()); @@ -256,17 +250,17 @@ fn hpke_runner(path: &str) { assert!(test.pkSm.is_some()); assert!(test.skSm.is_some()); let secret_sender = - PrivateKey::from_slice(&decode(test.skSm.unwrap()).unwrap()).unwrap(); + PrivateKey::try_from(&decode(test.skSm.unwrap()).unwrap()).unwrap(); let public_sender = - PublicKey::from_slice(&decode(test.pkSm.unwrap()).unwrap()).unwrap(); + PublicKey::try_from(&decode(test.pkSm.unwrap()).unwrap()).unwrap(); let (ss, _) = DhKem::auth_encap_deterministic( &public_recip, &secret_sender, - PrivateKey::from_slice(&decode(&test.skEm).unwrap()).unwrap(), + PrivateKey::try_from(&decode(&test.skEm).unwrap()).unwrap(), ) .unwrap(); - assert_eq!(ss.unprotected_as_bytes(), &shared_secret); + assert_eq!(ss.unprotected_as_ref(), &shared_secret); let (mut hpke_ctx_s, actual_enc) = ModeAuth::::new_sender_deterministic( @@ -334,17 +328,17 @@ fn hpke_runner(path: &str) { let psk_id = hex::decode(test.psk_id.unwrap()).unwrap(); let secret_sender = - PrivateKey::from_slice(&decode(test.skSm.unwrap()).unwrap()).unwrap(); + PrivateKey::try_from(&decode(test.skSm.unwrap()).unwrap()).unwrap(); let public_sender = - PublicKey::from_slice(&decode(test.pkSm.unwrap()).unwrap()).unwrap(); + PublicKey::try_from(&decode(test.pkSm.unwrap()).unwrap()).unwrap(); let (ss, _) = DhKem::auth_encap_deterministic( &public_recip, &secret_sender, - PrivateKey::from_slice(&decode(&test.skEm).unwrap()).unwrap(), + PrivateKey::try_from(&decode(&test.skEm).unwrap()).unwrap(), ) .unwrap(); - assert_eq!(ss.unprotected_as_bytes(), &shared_secret); + assert_eq!(ss.unprotected_as_ref(), &shared_secret); let (mut hpke_ctx_s, actual_enc) = ModeAuthPsk::::new_sender_deterministic( diff --git a/tests/kdf/custom_pbkdf2.rs b/tests/kdf/custom_pbkdf2.rs index 4975e6297..0dac2d8c2 100644 --- a/tests/kdf/custom_pbkdf2.rs +++ b/tests/kdf/custom_pbkdf2.rs @@ -10,9 +10,9 @@ mod custom_test_vectors { #[test] fn test_case_1() { - let password_256 = sha256::Password::from_slice("password".as_bytes()).unwrap(); - let password_384 = sha384::Password::from_slice("password".as_bytes()).unwrap(); - let password_512 = sha512::Password::from_slice("password".as_bytes()).unwrap(); + let password_256 = sha256::Password::try_from("password".as_bytes()).unwrap(); + let password_384 = sha384::Password::try_from("password".as_bytes()).unwrap(); + let password_512 = sha512::Password::try_from("password".as_bytes()).unwrap(); let salt = "salt".as_bytes(); let iter = 1; let mut dk_out = [0u8; 20]; @@ -29,9 +29,9 @@ mod custom_test_vectors { #[test] fn test_case_2() { - let password_256 = sha256::Password::from_slice("password".as_bytes()).unwrap(); - let password_384 = sha384::Password::from_slice("password".as_bytes()).unwrap(); - let password_512 = sha512::Password::from_slice("password".as_bytes()).unwrap(); + let password_256 = sha256::Password::try_from("password".as_bytes()).unwrap(); + let password_384 = sha384::Password::try_from("password".as_bytes()).unwrap(); + let password_512 = sha512::Password::try_from("password".as_bytes()).unwrap(); let salt = "salt".as_bytes(); let iter = 2; let mut dk_out = [0u8; 20]; @@ -48,9 +48,9 @@ mod custom_test_vectors { #[test] fn test_case_3() { - let password_256 = sha256::Password::from_slice("password".as_bytes()).unwrap(); - let password_384 = sha384::Password::from_slice("password".as_bytes()).unwrap(); - let password_512 = sha512::Password::from_slice("password".as_bytes()).unwrap(); + let password_256 = sha256::Password::try_from("password".as_bytes()).unwrap(); + let password_384 = sha384::Password::try_from("password".as_bytes()).unwrap(); + let password_512 = sha512::Password::try_from("password".as_bytes()).unwrap(); let salt = "salt".as_bytes(); let iter = 4096; let mut dk_out = [0u8; 20]; @@ -68,9 +68,9 @@ mod custom_test_vectors { /* This takes too long for normal tests #[test] fn test_case_4() { - let password_256 = sha256::Password::from_slice("password".as_bytes()).unwrap(); - let password_384 = sha384::Password::from_slice("password".as_bytes()).unwrap(); - let password_512 = sha512::Password::from_slice("password".as_bytes()).unwrap(); + let password_256 = sha256::Password::try_from("password".as_bytes()).unwrap(); + let password_384 = sha384::Password::try_from("password".as_bytes()).unwrap(); + let password_512 = sha512::Password::try_from("password".as_bytes()).unwrap(); let salt = "salt".as_bytes(); let iter = 16777216; let mut dk_out = [0u8; 20]; @@ -89,11 +89,11 @@ mod custom_test_vectors { #[test] fn test_case_5() { let password_256 = - sha256::Password::from_slice("passwordPASSWORDpassword".as_bytes()).unwrap(); + sha256::Password::try_from("passwordPASSWORDpassword".as_bytes()).unwrap(); let password_384 = - sha384::Password::from_slice("passwordPASSWORDpassword".as_bytes()).unwrap(); + sha384::Password::try_from("passwordPASSWORDpassword".as_bytes()).unwrap(); let password_512 = - sha512::Password::from_slice("passwordPASSWORDpassword".as_bytes()).unwrap(); + sha512::Password::try_from("passwordPASSWORDpassword".as_bytes()).unwrap(); let salt = "saltSALTsaltSALTsaltSALTsaltSALTsalt".as_bytes(); let iter = 4096; let mut dk_out = [0u8; 25]; @@ -110,9 +110,9 @@ mod custom_test_vectors { #[test] fn test_case_6() { - let password_256 = sha256::Password::from_slice("pass\0word".as_bytes()).unwrap(); - let password_384 = sha384::Password::from_slice("pass\0word".as_bytes()).unwrap(); - let password_512 = sha512::Password::from_slice("pass\0word".as_bytes()).unwrap(); + let password_256 = sha256::Password::try_from("pass\0word".as_bytes()).unwrap(); + let password_384 = sha384::Password::try_from("pass\0word".as_bytes()).unwrap(); + let password_512 = sha512::Password::try_from("pass\0word".as_bytes()).unwrap(); let salt = "sa\0lt".as_bytes(); let iter = 4096; let mut dk_out = [0u8; 16]; @@ -129,9 +129,9 @@ mod custom_test_vectors { #[test] fn test_case_7() { - let password_256 = sha256::Password::from_slice("passwd".as_bytes()).unwrap(); - let password_384 = sha384::Password::from_slice("passwd".as_bytes()).unwrap(); - let password_512 = sha512::Password::from_slice("passwd".as_bytes()).unwrap(); + let password_256 = sha256::Password::try_from("passwd".as_bytes()).unwrap(); + let password_384 = sha384::Password::try_from("passwd".as_bytes()).unwrap(); + let password_512 = sha512::Password::try_from("passwd".as_bytes()).unwrap(); let salt = "salt".as_bytes(); let iter = 1; let mut dk_out = [0u8; 128]; @@ -148,9 +148,9 @@ mod custom_test_vectors { #[test] fn test_case_8() { - let password_256 = sha256::Password::from_slice("Password".as_bytes()).unwrap(); - let password_384 = sha384::Password::from_slice("Password".as_bytes()).unwrap(); - let password_512 = sha512::Password::from_slice("Password".as_bytes()).unwrap(); + let password_256 = sha256::Password::try_from("Password".as_bytes()).unwrap(); + let password_384 = sha384::Password::try_from("Password".as_bytes()).unwrap(); + let password_512 = sha512::Password::try_from("Password".as_bytes()).unwrap(); let salt = "NaCl".as_bytes(); let iter = 80000; let mut dk_out = [0u8; 128]; @@ -167,9 +167,9 @@ mod custom_test_vectors { #[test] fn test_case_9() { - let password_256 = sha256::Password::from_slice("Password".as_bytes()).unwrap(); - let password_384 = sha384::Password::from_slice("Password".as_bytes()).unwrap(); - let password_512 = sha512::Password::from_slice("Password".as_bytes()).unwrap(); + let password_256 = sha256::Password::try_from("Password".as_bytes()).unwrap(); + let password_384 = sha384::Password::try_from("Password".as_bytes()).unwrap(); + let password_512 = sha512::Password::try_from("Password".as_bytes()).unwrap(); let salt = "sa\0lt".as_bytes(); let iter = 4096; let mut dk_out = [0u8; 256]; diff --git a/tests/kdf/mod.rs b/tests/kdf/mod.rs index 635595593..a6c9b66e0 100644 --- a/tests/kdf/mod.rs +++ b/tests/kdf/mod.rs @@ -28,7 +28,7 @@ macro_rules! impl_hkdf_test_runner (($name:ident, $extract:ident, $derive_key:id ) { if expected_prk.is_some() { let actual_prk = $extract(salt, &ikm).unwrap(); - assert_eq!(actual_prk, $hmac_tag::from_slice(expected_prk.unwrap()).unwrap()); + assert_eq!(actual_prk, $hmac_tag::try_from(expected_prk.unwrap()).unwrap()); } let mut okm_out = vec![0u8; okm_len]; @@ -70,7 +70,7 @@ macro_rules! impl_pbkdf2_test_runner (($name:ident, $password:ident, $derive_key valid_result: bool, ) { let mut dk_out = vec![0u8; dk_len]; - let password = $password::from_slice(password).unwrap(); + let password = $password::try_from(password).unwrap(); if valid_result { assert!($derive_key(&password, salt, iterations, &mut dk_out).is_ok()); @@ -81,17 +81,17 @@ macro_rules! impl_pbkdf2_test_runner (($name:ident, $password:ident, $derive_key } )); -use pbkdf2::sha256::derive_key as pbkdf2_derive_key256; use pbkdf2::sha256::Password as Password256; +use pbkdf2::sha256::derive_key as pbkdf2_derive_key256; impl_pbkdf2_test_runner!(pbkdf2_256_test_runner, Password256, pbkdf2_derive_key256); -use pbkdf2::sha384::derive_key as pbkdf2_derive_key384; use pbkdf2::sha384::Password as Password384; +use pbkdf2::sha384::derive_key as pbkdf2_derive_key384; impl_pbkdf2_test_runner!(pbkdf2_384_test_runner, Password384, pbkdf2_derive_key384); -use pbkdf2::sha512::derive_key as pbkdf2_derive_key512; use pbkdf2::sha512::Password as Password512; +use pbkdf2::sha512::derive_key as pbkdf2_derive_key512; impl_pbkdf2_test_runner!(pbkdf2_512_test_runner, Password512, pbkdf2_derive_key512); diff --git a/tests/kdf/other_argon2i.rs b/tests/kdf/other_argon2i.rs index ccc42e2f7..df29d2235 100644 --- a/tests/kdf/other_argon2i.rs +++ b/tests/kdf/other_argon2i.rs @@ -27,17 +27,19 @@ fn test_case_0() { ) .unwrap(); assert_eq!(expected_hash, actual_hash); - assert!(argon2i::verify( - &expected_hash, - &password, - &salt, - passes, - mem, - Some(secret_value), - Some(associated_data), - &mut actual_hash - ) - .is_ok()); + assert!( + argon2i::verify( + &expected_hash, + &password, + &salt, + passes, + mem, + Some(secret_value), + Some(associated_data), + &mut actual_hash + ) + .is_ok() + ); } #[test] @@ -64,17 +66,19 @@ fn test_case_1() { ) .unwrap(); assert_eq!(expected_hash, actual_hash); - assert!(argon2i::verify( - &expected_hash, - &password, - &salt, - passes, - mem, - Some(secret_value), - Some(associated_data), - &mut actual_hash - ) - .is_ok()); + assert!( + argon2i::verify( + &expected_hash, + &password, + &salt, + passes, + mem, + Some(secret_value), + Some(associated_data), + &mut actual_hash + ) + .is_ok() + ); } #[test] @@ -101,17 +105,19 @@ fn test_case_2() { ) .unwrap(); assert_eq!(expected_hash, actual_hash); - assert!(argon2i::verify( - &expected_hash, - &password, - &salt, - passes, - mem, - Some(secret_value), - Some(associated_data), - &mut actual_hash - ) - .is_ok()); + assert!( + argon2i::verify( + &expected_hash, + &password, + &salt, + passes, + mem, + Some(secret_value), + Some(associated_data), + &mut actual_hash + ) + .is_ok() + ); } #[test] @@ -138,17 +144,19 @@ fn test_case_3() { ) .unwrap(); assert_eq!(expected_hash, actual_hash); - assert!(argon2i::verify( - &expected_hash, - &password, - &salt, - passes, - mem, - Some(secret_value), - Some(associated_data), - &mut actual_hash - ) - .is_ok()); + assert!( + argon2i::verify( + &expected_hash, + &password, + &salt, + passes, + mem, + Some(secret_value), + Some(associated_data), + &mut actual_hash + ) + .is_ok() + ); } #[test] @@ -175,17 +183,19 @@ fn test_case_4() { ) .unwrap(); assert_eq!(expected_hash, actual_hash); - assert!(argon2i::verify( - &expected_hash, - &password, - &salt, - passes, - mem, - Some(secret_value), - Some(associated_data), - &mut actual_hash - ) - .is_ok()); + assert!( + argon2i::verify( + &expected_hash, + &password, + &salt, + passes, + mem, + Some(secret_value), + Some(associated_data), + &mut actual_hash + ) + .is_ok() + ); } #[test] @@ -212,17 +222,19 @@ fn test_case_5() { ) .unwrap(); assert_eq!(expected_hash, actual_hash); - assert!(argon2i::verify( - &expected_hash, - &password, - &salt, - passes, - mem, - Some(secret_value), - Some(associated_data), - &mut actual_hash - ) - .is_ok()); + assert!( + argon2i::verify( + &expected_hash, + &password, + &salt, + passes, + mem, + Some(secret_value), + Some(associated_data), + &mut actual_hash + ) + .is_ok() + ); } #[test] @@ -249,17 +261,19 @@ fn test_case_6() { ) .unwrap(); assert_eq!(expected_hash, actual_hash); - assert!(argon2i::verify( - &expected_hash, - &password, - &salt, - passes, - mem, - Some(secret_value), - Some(associated_data), - &mut actual_hash - ) - .is_ok()); + assert!( + argon2i::verify( + &expected_hash, + &password, + &salt, + passes, + mem, + Some(secret_value), + Some(associated_data), + &mut actual_hash + ) + .is_ok() + ); } #[test] @@ -286,17 +300,19 @@ fn test_case_7() { ) .unwrap(); assert_eq!(expected_hash, actual_hash); - assert!(argon2i::verify( - &expected_hash, - &password, - &salt, - passes, - mem, - Some(secret_value), - Some(associated_data), - &mut actual_hash - ) - .is_ok()); + assert!( + argon2i::verify( + &expected_hash, + &password, + &salt, + passes, + mem, + Some(secret_value), + Some(associated_data), + &mut actual_hash + ) + .is_ok() + ); } #[test] @@ -322,17 +338,19 @@ fn test_case_8() { ) .unwrap(); assert_eq!(expected_hash, actual_hash); - assert!(argon2i::verify( - &expected_hash, - &password, - &salt, - passes, - mem, - Some(secret_value), - Some(associated_data), - &mut actual_hash - ) - .is_ok()); + assert!( + argon2i::verify( + &expected_hash, + &password, + &salt, + passes, + mem, + Some(secret_value), + Some(associated_data), + &mut actual_hash + ) + .is_ok() + ); } #[test] @@ -358,17 +376,19 @@ fn test_case_9() { ) .unwrap(); assert_eq!(expected_hash, actual_hash); - assert!(argon2i::verify( - &expected_hash, - &password, - &salt, - passes, - mem, - Some(secret_value), - Some(associated_data), - &mut actual_hash - ) - .is_ok()); + assert!( + argon2i::verify( + &expected_hash, + &password, + &salt, + passes, + mem, + Some(secret_value), + Some(associated_data), + &mut actual_hash + ) + .is_ok() + ); } #[test] @@ -395,17 +415,19 @@ fn test_case_10() { ) .unwrap(); assert_eq!(expected_hash, actual_hash); - assert!(argon2i::verify( - &expected_hash, - &password, - &salt, - passes, - mem, - Some(secret_value), - Some(associated_data), - &mut actual_hash - ) - .is_ok()); + assert!( + argon2i::verify( + &expected_hash, + &password, + &salt, + passes, + mem, + Some(secret_value), + Some(associated_data), + &mut actual_hash + ) + .is_ok() + ); } #[test] @@ -432,17 +454,19 @@ fn test_case_11() { ) .unwrap(); assert_eq!(expected_hash, actual_hash); - assert!(argon2i::verify( - &expected_hash, - &password, - &salt, - passes, - mem, - Some(secret_value), - Some(associated_data), - &mut actual_hash - ) - .is_ok()); + assert!( + argon2i::verify( + &expected_hash, + &password, + &salt, + passes, + mem, + Some(secret_value), + Some(associated_data), + &mut actual_hash + ) + .is_ok() + ); } #[test] @@ -469,17 +493,19 @@ fn test_case_12() { ) .unwrap(); assert_eq!(expected_hash, actual_hash); - assert!(argon2i::verify( - &expected_hash, - &password, - &salt, - passes, - mem, - Some(secret_value), - Some(associated_data), - &mut actual_hash - ) - .is_ok()); + assert!( + argon2i::verify( + &expected_hash, + &password, + &salt, + passes, + mem, + Some(secret_value), + Some(associated_data), + &mut actual_hash + ) + .is_ok() + ); } #[test] @@ -506,17 +532,19 @@ fn test_case_13() { ) .unwrap(); assert_eq!(expected_hash, actual_hash); - assert!(argon2i::verify( - &expected_hash, - &password, - &salt, - passes, - mem, - Some(secret_value), - Some(associated_data), - &mut actual_hash - ) - .is_ok()); + assert!( + argon2i::verify( + &expected_hash, + &password, + &salt, + passes, + mem, + Some(secret_value), + Some(associated_data), + &mut actual_hash + ) + .is_ok() + ); } #[test] @@ -543,15 +571,17 @@ fn test_case_14() { ) .unwrap(); assert_eq!(expected_hash, actual_hash); - assert!(argon2i::verify( - &expected_hash, - &password, - &salt, - passes, - mem, - Some(secret_value), - Some(associated_data), - &mut actual_hash - ) - .is_ok()); + assert!( + argon2i::verify( + &expected_hash, + &password, + &salt, + passes, + mem, + Some(secret_value), + Some(associated_data), + &mut actual_hash + ) + .is_ok() + ); } diff --git a/tests/kdf/pynacl_argon2i.rs b/tests/kdf/pynacl_argon2i.rs index f2d15d6c2..04b9d0a6b 100644 --- a/tests/kdf/pynacl_argon2i.rs +++ b/tests/kdf/pynacl_argon2i.rs @@ -27,17 +27,19 @@ fn run_tests_from_json(path_to_vectors: &str) { for test in tests { let mut dst_out = vec![0u8; test.dgst_len]; - assert!(argon2i::verify( - &decode(&test.pwhash).unwrap(), - test.passwd.as_bytes(), - test.salt.as_bytes(), - test.iters, - test.maxmem, - None, - None, - &mut dst_out - ) - .is_ok()); + assert!( + argon2i::verify( + &decode(&test.pwhash).unwrap(), + test.passwd.as_bytes(), + test.salt.as_bytes(), + test.iters, + test.maxmem, + None, + None, + &mut dst_out + ) + .is_ok() + ); } } diff --git a/tests/kdf/ref_argon2i.rs b/tests/kdf/ref_argon2i.rs index e3b094c45..0dee53724 100644 --- a/tests/kdf/ref_argon2i.rs +++ b/tests/kdf/ref_argon2i.rs @@ -16,17 +16,19 @@ fn test_case_1() { hex::decode("1e14f98dce844e462a545ba81034494ce32ebba9a3f6a899ba83e98888e432b6").unwrap(); let mut actual = vec![0u8; expected_hash.len()]; - assert!(argon2i::verify( - &expected_hash, - &password, - &salt, - iterations, - memory, - Some(&secret), - Some(&ad), - &mut actual - ) - .is_ok()); + assert!( + argon2i::verify( + &expected_hash, + &password, + &salt, + iterations, + memory, + Some(&secret), + Some(&ad), + &mut actual + ) + .is_ok() + ); } #[test] @@ -43,17 +45,19 @@ fn test_case_2() { .unwrap(); let mut actual = vec![0u8; expected_hash.len()]; - assert!(argon2i::verify( - &expected_hash, - &password, - &salt, - iterations, - memory, - Some(&secret), - Some(&ad), - &mut actual - ) - .is_ok()); + assert!( + argon2i::verify( + &expected_hash, + &password, + &salt, + iterations, + memory, + Some(&secret), + Some(&ad), + &mut actual + ) + .is_ok() + ); } #[test] @@ -70,15 +74,17 @@ fn test_case_3() { .unwrap(); let mut actual = vec![0u8; expected_hash.len()]; - assert!(argon2i::verify( - &expected_hash, - &password, - &salt, - iterations, - memory, - Some(&secret), - Some(&ad), - &mut actual - ) - .is_ok()); + assert!( + argon2i::verify( + &expected_hash, + &password, + &salt, + iterations, + memory, + Some(&secret), + Some(&ad), + &mut actual + ) + .is_ok() + ); } diff --git a/tests/kdf/rfc_pbkdf2.rs b/tests/kdf/rfc_pbkdf2.rs index cc0909ace..eb963b430 100644 --- a/tests/kdf/rfc_pbkdf2.rs +++ b/tests/kdf/rfc_pbkdf2.rs @@ -9,7 +9,7 @@ mod rfc7914_test_vectors { #[test] fn test_case_1() { - let password_256 = sha256::Password::from_slice("passwd".as_bytes()).unwrap(); + let password_256 = sha256::Password::try_from("passwd".as_bytes()).unwrap(); let salt = "salt".as_bytes(); let iter = 1; let mut dk_out = [0u8; 64]; @@ -22,7 +22,7 @@ mod rfc7914_test_vectors { #[test] fn test_case_2() { - let password_256 = sha256::Password::from_slice("Password".as_bytes()).unwrap(); + let password_256 = sha256::Password::try_from("Password".as_bytes()).unwrap(); let salt = "NaCl".as_bytes(); let iter = 80000; let mut dk_out = [0u8; 64]; diff --git a/tests/kem/mlkem/c2sp_wycheproof.rs b/tests/kem/mlkem/c2sp_wycheproof.rs index e1a762626..3149f9b3d 100644 --- a/tests/kem/mlkem/c2sp_wycheproof.rs +++ b/tests/kem/mlkem/c2sp_wycheproof.rs @@ -1,8 +1,9 @@ // Test vectors taken at commit: https://github.com/C2SP/wycheproof/commit/fca0d3ba9f1286c3af57801ace39c633e29a88f1 -use orion::hazardous::kem::mlkem1024; +use orion::KP; use orion::hazardous::kem::mlkem512; use orion::hazardous::kem::mlkem768; +use orion::hazardous::kem::mlkem1024; use serde::{Deserialize, Serialize}; use std::{fs::File, io::BufReader}; @@ -66,8 +67,8 @@ fn mlkem_runner(path: &str) { let mut seed = [0u8; 64]; if test_group.parameterSet == "ML-KEM-512" { - let mut ek_expected = [0u8; mlkem512::MlKem512::EK_SIZE]; - let mut dk_expected = [0u8; mlkem512::MlKem512::DK_SIZE]; + let mut ek_expected = [0u8; mlkem512::EK_SIZE]; + let mut dk_expected = [0u8; mlkem512::DK_SIZE]; hex::decode_to_slice(test_vector.seed.as_ref().unwrap(), &mut seed).unwrap(); hex::decode_to_slice(test_vector.ek.as_ref().unwrap(), &mut ek_expected) .unwrap(); @@ -75,9 +76,9 @@ fn mlkem_runner(path: &str) { .unwrap(); let keypair = - mlkem512::KeyPair::try_from(&mlkem512::Seed::from_slice(&seed).unwrap()) + mlkem512::KeyPair::try_from(&mlkem512::Seed::try_from(&seed).unwrap()) .unwrap(); - let ek = mlkem512::EncapsulationKey::from_slice(&ek_expected).unwrap(); + let ek = mlkem512::EncapsulationKey::try_from(&ek_expected).unwrap(); let dk = mlkem512::DecapsulationKey::unchecked_from_slice(&dk_expected).unwrap(); @@ -88,8 +89,8 @@ fn mlkem_runner(path: &str) { } if test_group.parameterSet == "ML-KEM-768" { - let mut ek_expected = [0u8; mlkem768::MlKem768::EK_SIZE]; - let mut dk_expected = [0u8; mlkem768::MlKem768::DK_SIZE]; + let mut ek_expected = [0u8; mlkem768::EK_SIZE]; + let mut dk_expected = [0u8; mlkem768::DK_SIZE]; hex::decode_to_slice(test_vector.seed.as_ref().unwrap(), &mut seed).unwrap(); hex::decode_to_slice(test_vector.ek.as_ref().unwrap(), &mut ek_expected) .unwrap(); @@ -97,9 +98,9 @@ fn mlkem_runner(path: &str) { .unwrap(); let keypair = - mlkem768::KeyPair::try_from(&mlkem768::Seed::from_slice(&seed).unwrap()) + mlkem768::KeyPair::try_from(&mlkem768::Seed::try_from(&seed).unwrap()) .unwrap(); - let ek = mlkem768::EncapsulationKey::from_slice(&ek_expected).unwrap(); + let ek = mlkem768::EncapsulationKey::try_from(&ek_expected).unwrap(); let dk = mlkem768::DecapsulationKey::unchecked_from_slice(&dk_expected).unwrap(); @@ -110,8 +111,8 @@ fn mlkem_runner(path: &str) { } if test_group.parameterSet == "ML-KEM-1024" { - let mut ek_expected = [0u8; mlkem1024::MlKem1024::EK_SIZE]; - let mut dk_expected = [0u8; mlkem1024::MlKem1024::DK_SIZE]; + let mut ek_expected = [0u8; mlkem1024::EK_SIZE]; + let mut dk_expected = [0u8; mlkem1024::DK_SIZE]; hex::decode_to_slice(test_vector.seed.as_ref().unwrap(), &mut seed).unwrap(); hex::decode_to_slice(test_vector.ek.as_ref().unwrap(), &mut ek_expected) .unwrap(); @@ -119,9 +120,9 @@ fn mlkem_runner(path: &str) { .unwrap(); let keypair = - mlkem1024::KeyPair::try_from(&mlkem1024::Seed::from_slice(&seed).unwrap()) + mlkem1024::KeyPair::try_from(&mlkem1024::Seed::try_from(&seed).unwrap()) .unwrap(); - let ek = mlkem1024::EncapsulationKey::from_slice(&ek_expected).unwrap(); + let ek = mlkem1024::EncapsulationKey::try_from(&ek_expected).unwrap(); let dk = mlkem1024::DecapsulationKey::unchecked_from_slice(&dk_expected).unwrap(); @@ -142,24 +143,30 @@ fn mlkem_runner(path: &str) { match test_group.parameterSet.as_str() { "ML-KEM-512" => { - assert!(mlkem512::EncapsulationKey::from_slice( - &hex::decode(test_vector.ek.as_ref().unwrap()).unwrap() - ) - .is_err()); + assert!( + mlkem512::EncapsulationKey::try_from( + &hex::decode(test_vector.ek.as_ref().unwrap()).unwrap() + ) + .is_err() + ); } "ML-KEM-768" => { - assert!(mlkem768::EncapsulationKey::from_slice( - &hex::decode(test_vector.ek.as_ref().unwrap()).unwrap() - ) - .is_err()); + assert!( + mlkem768::EncapsulationKey::try_from( + &hex::decode(test_vector.ek.as_ref().unwrap()).unwrap() + ) + .is_err() + ); } "ML-KEM-1024" => { - assert!(mlkem1024::EncapsulationKey::from_slice( - &hex::decode(test_vector.ek.as_ref().unwrap()).unwrap() - ) - .is_err()); + assert!( + mlkem1024::EncapsulationKey::try_from( + &hex::decode(test_vector.ek.as_ref().unwrap()).unwrap() + ) + .is_err() + ); } _ => panic!("a test parameter set was unaccounted for"), } @@ -177,8 +184,8 @@ fn mlkem_runner(path: &str) { let mut shared_expected = [0u8; 32]; if test_group.parameterSet == "ML-KEM-512" { - let mut ek_expected = [0u8; mlkem512::MlKem512::EK_SIZE]; - let mut ct_expected = [0u8; mlkem512::MlKem512::CIPHERTEXT_SIZE]; + let mut ek_expected = [0u8; mlkem512::EK_SIZE]; + let mut ct_expected = [0u8; mlkem512::CIPHERTEXT_SIZE]; hex::decode_to_slice(test_vector.m.as_ref().unwrap(), &mut m).unwrap(); hex::decode_to_slice(test_vector.K.as_ref().unwrap(), &mut shared_expected) .unwrap(); @@ -187,19 +194,19 @@ fn mlkem_runner(path: &str) { hex::decode_to_slice(test_vector.c.as_ref().unwrap(), &mut ct_expected) .unwrap(); - let ek = mlkem512::EncapsulationKey::from_slice(&ek_expected).unwrap(); - let ciphertext = mlkem512::Ciphertext::from_slice(&ct_expected).unwrap(); + let ek = mlkem512::EncapsulationKey::try_from(&ek_expected).unwrap(); + let ciphertext = mlkem512::Ciphertext::try_from(&ct_expected).unwrap(); let (k_actual, c_acutal) = ek.encap_deterministic(&m).unwrap(); assert_eq!(ciphertext, c_acutal); - assert_eq!(&shared_expected, k_actual.unprotected_as_bytes()); + assert_eq!(&shared_expected, k_actual.unprotected_as_ref()); tests_run += 1; } if test_group.parameterSet == "ML-KEM-768" { - let mut ek_expected = [0u8; mlkem768::MlKem768::EK_SIZE]; - let mut ct_expected = [0u8; mlkem768::MlKem768::CIPHERTEXT_SIZE]; + let mut ek_expected = [0u8; mlkem768::EK_SIZE]; + let mut ct_expected = [0u8; mlkem768::CIPHERTEXT_SIZE]; hex::decode_to_slice(test_vector.m.as_ref().unwrap(), &mut m).unwrap(); hex::decode_to_slice(test_vector.K.as_ref().unwrap(), &mut shared_expected) .unwrap(); @@ -208,19 +215,19 @@ fn mlkem_runner(path: &str) { hex::decode_to_slice(test_vector.c.as_ref().unwrap(), &mut ct_expected) .unwrap(); - let ek = mlkem768::EncapsulationKey::from_slice(&ek_expected).unwrap(); - let ciphertext = mlkem768::Ciphertext::from_slice(&ct_expected).unwrap(); + let ek = mlkem768::EncapsulationKey::try_from(&ek_expected).unwrap(); + let ciphertext = mlkem768::Ciphertext::try_from(&ct_expected).unwrap(); let (k_actual, c_acutal) = ek.encap_deterministic(&m).unwrap(); assert_eq!(ciphertext, c_acutal); - assert_eq!(&shared_expected, k_actual.unprotected_as_bytes()); + assert_eq!(&shared_expected, k_actual.unprotected_as_ref()); tests_run += 1; } if test_group.parameterSet == "ML-KEM-1024" { - let mut ek_expected = [0u8; mlkem1024::MlKem1024::EK_SIZE]; - let mut ct_expected = [0u8; mlkem1024::MlKem1024::CIPHERTEXT_SIZE]; + let mut ek_expected = [0u8; mlkem1024::EK_SIZE]; + let mut ct_expected = [0u8; mlkem1024::CIPHERTEXT_SIZE]; hex::decode_to_slice(test_vector.m.as_ref().unwrap(), &mut m).unwrap(); hex::decode_to_slice(test_vector.K.as_ref().unwrap(), &mut shared_expected) .unwrap(); @@ -229,12 +236,12 @@ fn mlkem_runner(path: &str) { hex::decode_to_slice(test_vector.c.as_ref().unwrap(), &mut ct_expected) .unwrap(); - let ek = mlkem1024::EncapsulationKey::from_slice(&ek_expected).unwrap(); - let ciphertext = mlkem1024::Ciphertext::from_slice(&ct_expected).unwrap(); + let ek = mlkem1024::EncapsulationKey::try_from(&ek_expected).unwrap(); + let ciphertext = mlkem1024::Ciphertext::try_from(&ct_expected).unwrap(); let (k_actual, c_acutal) = ek.encap_deterministic(&m).unwrap(); assert_eq!(ciphertext, c_acutal); - assert_eq!(&shared_expected, k_actual.unprotected_as_bytes()); + assert_eq!(&shared_expected, k_actual.unprotected_as_ref()); tests_run += 1; } @@ -250,16 +257,20 @@ fn mlkem_runner(path: &str) { if test_vector.result == "invalid" { match test_vector.tcId { 2..=3 => { - assert!(mlkem512::Ciphertext::from_slice( - &hex::decode(test_vector.c.as_ref().unwrap()).unwrap() - ) - .is_err()); + assert!( + mlkem512::Ciphertext::try_from( + &hex::decode(test_vector.c.as_ref().unwrap()).unwrap() + ) + .is_err() + ); } 4..=7 => { - assert!(mlkem512::DecapsulationKey::unchecked_from_slice( - &hex::decode(test_vector.dk.as_ref().unwrap()).unwrap() - ) - .is_err()); + assert!( + mlkem512::DecapsulationKey::unchecked_from_slice( + &hex::decode(test_vector.dk.as_ref().unwrap()).unwrap() + ) + .is_err() + ); } _ => panic!("uncovered tcId - we need all for this test"), } @@ -269,8 +280,8 @@ fn mlkem_runner(path: &str) { } if test_vector.result == "valid" { - let mut dk_expected = [0u8; mlkem512::MlKem512::DK_SIZE]; - let mut ct_expected = [0u8; mlkem512::MlKem512::CIPHERTEXT_SIZE]; + let mut dk_expected = [0u8; mlkem512::DK_SIZE]; + let mut ct_expected = [0u8; mlkem512::CIPHERTEXT_SIZE]; hex::decode_to_slice(test_vector.dk.as_ref().unwrap(), &mut dk_expected) .unwrap(); hex::decode_to_slice(test_vector.c.as_ref().unwrap(), &mut ct_expected) @@ -278,7 +289,7 @@ fn mlkem_runner(path: &str) { let dk = mlkem512::DecapsulationKey::unchecked_from_slice(&dk_expected).unwrap(); - let ciphertext = mlkem512::Ciphertext::from_slice(&ct_expected).unwrap(); + let ciphertext = mlkem512::Ciphertext::try_from(&ct_expected).unwrap(); assert!(dk.decap(&ciphertext).is_ok()); tests_run += 1; } @@ -288,16 +299,20 @@ fn mlkem_runner(path: &str) { if test_vector.result == "invalid" { match test_vector.tcId { 2..=3 => { - assert!(mlkem768::Ciphertext::from_slice( - &hex::decode(test_vector.c.as_ref().unwrap()).unwrap() - ) - .is_err()); + assert!( + mlkem768::Ciphertext::try_from( + &hex::decode(test_vector.c.as_ref().unwrap()).unwrap() + ) + .is_err() + ); } 4..=7 => { - assert!(mlkem768::DecapsulationKey::unchecked_from_slice( - &hex::decode(test_vector.dk.as_ref().unwrap()).unwrap() - ) - .is_err()); + assert!( + mlkem768::DecapsulationKey::unchecked_from_slice( + &hex::decode(test_vector.dk.as_ref().unwrap()).unwrap() + ) + .is_err() + ); } _ => panic!("uncovered tcId - we need all for this test"), } @@ -307,8 +322,8 @@ fn mlkem_runner(path: &str) { } if test_vector.result == "valid" { - let mut dk_expected = [0u8; mlkem768::MlKem768::DK_SIZE]; - let mut ct_expected = [0u8; mlkem768::MlKem768::CIPHERTEXT_SIZE]; + let mut dk_expected = [0u8; mlkem768::DK_SIZE]; + let mut ct_expected = [0u8; mlkem768::CIPHERTEXT_SIZE]; hex::decode_to_slice(test_vector.dk.as_ref().unwrap(), &mut dk_expected) .unwrap(); hex::decode_to_slice(test_vector.c.as_ref().unwrap(), &mut ct_expected) @@ -316,7 +331,7 @@ fn mlkem_runner(path: &str) { let dk = mlkem768::DecapsulationKey::unchecked_from_slice(&dk_expected).unwrap(); - let ciphertext = mlkem768::Ciphertext::from_slice(&ct_expected).unwrap(); + let ciphertext = mlkem768::Ciphertext::try_from(&ct_expected).unwrap(); assert!(dk.decap(&ciphertext).is_ok()); tests_run += 1; } @@ -326,16 +341,20 @@ fn mlkem_runner(path: &str) { if test_vector.result == "invalid" { match test_vector.tcId { 2..=3 => { - assert!(mlkem1024::Ciphertext::from_slice( - &hex::decode(test_vector.c.as_ref().unwrap()).unwrap() - ) - .is_err()); + assert!( + mlkem1024::Ciphertext::try_from( + &hex::decode(test_vector.c.as_ref().unwrap()).unwrap() + ) + .is_err() + ); } 4..=7 => { - assert!(mlkem1024::DecapsulationKey::unchecked_from_slice( - &hex::decode(test_vector.dk.as_ref().unwrap()).unwrap() - ) - .is_err()); + assert!( + mlkem1024::DecapsulationKey::unchecked_from_slice( + &hex::decode(test_vector.dk.as_ref().unwrap()).unwrap() + ) + .is_err() + ); } _ => panic!("uncovered tcId - we need all for this test"), } @@ -345,8 +364,8 @@ fn mlkem_runner(path: &str) { } if test_vector.result == "valid" { - let mut dk_expected = [0u8; mlkem1024::MlKem1024::DK_SIZE]; - let mut ct_expected = [0u8; mlkem1024::MlKem1024::CIPHERTEXT_SIZE]; + let mut dk_expected = [0u8; mlkem1024::DK_SIZE]; + let mut ct_expected = [0u8; mlkem1024::CIPHERTEXT_SIZE]; hex::decode_to_slice(test_vector.dk.as_ref().unwrap(), &mut dk_expected) .unwrap(); hex::decode_to_slice(test_vector.c.as_ref().unwrap(), &mut ct_expected) @@ -354,7 +373,7 @@ fn mlkem_runner(path: &str) { let dk = mlkem1024::DecapsulationKey::unchecked_from_slice(&dk_expected) .unwrap(); - let ciphertext = mlkem1024::Ciphertext::from_slice(&ct_expected).unwrap(); + let ciphertext = mlkem1024::Ciphertext::try_from(&ct_expected).unwrap(); assert!(dk.decap(&ciphertext).is_ok()); tests_run += 1; } @@ -373,16 +392,20 @@ fn mlkem_runner(path: &str) { if test_vector.result == "invalid" { match test_vector.comment.as_ref().unwrap().as_str() { "Private key too short" | "Private key too long" => { - assert!(mlkem512::Seed::from_slice( - &hex::decode(test_vector.seed.as_ref().unwrap()).unwrap() - ) - .is_err()); + assert!( + mlkem512::Seed::try_from( + &hex::decode(test_vector.seed.as_ref().unwrap()).unwrap() + ) + .is_err() + ); } "Ciphertext too short" | "Ciphertext too long" => { - assert!(mlkem512::Ciphertext::from_slice( - &hex::decode(test_vector.c.as_ref().unwrap()).unwrap() - ) - .is_err()); + assert!( + mlkem512::Ciphertext::try_from( + &hex::decode(test_vector.c.as_ref().unwrap()).unwrap() + ) + .is_err() + ); } _ => panic!("a test parameter set was unaccounted for"), } @@ -393,8 +416,8 @@ fn mlkem_runner(path: &str) { assert!(test_vector.ek.is_some()); - let mut ek_expected = [0u8; mlkem512::MlKem512::EK_SIZE]; - let mut ct_expected = [0u8; mlkem512::MlKem512::CIPHERTEXT_SIZE]; + let mut ek_expected = [0u8; mlkem512::EK_SIZE]; + let mut ct_expected = [0u8; mlkem512::CIPHERTEXT_SIZE]; hex::decode_to_slice(test_vector.seed.as_ref().unwrap(), &mut seed).unwrap(); hex::decode_to_slice(test_vector.K.as_ref().unwrap(), &mut shared_expected) .unwrap(); @@ -404,15 +427,14 @@ fn mlkem_runner(path: &str) { .unwrap(); let keypair = - mlkem512::KeyPair::try_from(&mlkem512::Seed::from_slice(&seed).unwrap()) + mlkem512::KeyPair::try_from(&mlkem512::Seed::try_from(&seed).unwrap()) .unwrap(); - let ek = mlkem512::EncapsulationKey::from_slice(&ek_expected).unwrap(); + let ek = mlkem512::EncapsulationKey::try_from(&ek_expected).unwrap(); assert_eq!(keypair.public(), &ek); - let ciphertext = mlkem512::Ciphertext::from_slice(&ct_expected).unwrap(); - let k_actual = - mlkem512::MlKem512::decap(keypair.private(), &ciphertext).unwrap(); - assert_eq!(&shared_expected, k_actual.unprotected_as_bytes()); + let ciphertext = mlkem512::Ciphertext::try_from(&ct_expected).unwrap(); + let k_actual = keypair.decap(&ciphertext).unwrap(); + assert_eq!(&shared_expected, k_actual.unprotected_as_ref()); tests_run += 1; } @@ -421,16 +443,20 @@ fn mlkem_runner(path: &str) { if test_vector.result == "invalid" { match test_vector.comment.as_ref().unwrap().as_str() { "Private key too short" | "Private key too long" => { - assert!(mlkem768::Seed::from_slice( - &hex::decode(test_vector.seed.as_ref().unwrap()).unwrap() - ) - .is_err()); + assert!( + mlkem768::Seed::try_from( + &hex::decode(test_vector.seed.as_ref().unwrap()).unwrap() + ) + .is_err() + ); } "Ciphertext too short" | "Ciphertext too long" => { - assert!(mlkem768::Ciphertext::from_slice( - &hex::decode(test_vector.c.as_ref().unwrap()).unwrap() - ) - .is_err()); + assert!( + mlkem768::Ciphertext::try_from( + &hex::decode(test_vector.c.as_ref().unwrap()).unwrap() + ) + .is_err() + ); } _ => panic!("a test parameter set was unaccounted for"), } @@ -441,8 +467,8 @@ fn mlkem_runner(path: &str) { assert!(test_vector.ek.is_some()); - let mut ek_expected = [0u8; mlkem768::MlKem768::EK_SIZE]; - let mut ct_expected = [0u8; mlkem768::MlKem768::CIPHERTEXT_SIZE]; + let mut ek_expected = [0u8; mlkem768::EK_SIZE]; + let mut ct_expected = [0u8; mlkem768::CIPHERTEXT_SIZE]; hex::decode_to_slice(test_vector.seed.as_ref().unwrap(), &mut seed).unwrap(); hex::decode_to_slice(test_vector.K.as_ref().unwrap(), &mut shared_expected) .unwrap(); @@ -452,15 +478,14 @@ fn mlkem_runner(path: &str) { .unwrap(); let keypair = - mlkem768::KeyPair::try_from(&mlkem768::Seed::from_slice(&seed).unwrap()) + mlkem768::KeyPair::try_from(&mlkem768::Seed::try_from(&seed).unwrap()) .unwrap(); - let ek = mlkem768::EncapsulationKey::from_slice(&ek_expected).unwrap(); + let ek = mlkem768::EncapsulationKey::try_from(&ek_expected).unwrap(); assert_eq!(keypair.public(), &ek); - let ciphertext = mlkem768::Ciphertext::from_slice(&ct_expected).unwrap(); - let k_actual = - mlkem768::MlKem768::decap(keypair.private(), &ciphertext).unwrap(); - assert_eq!(&shared_expected, k_actual.unprotected_as_bytes()); + let ciphertext = mlkem768::Ciphertext::try_from(&ct_expected).unwrap(); + let k_actual = keypair.decap(&ciphertext).unwrap(); + assert_eq!(&shared_expected, k_actual.unprotected_as_ref()); tests_run += 1; } @@ -469,16 +494,20 @@ fn mlkem_runner(path: &str) { if test_vector.result == "invalid" { match test_vector.comment.as_ref().unwrap().as_str() { "Private key too short" | "Private key too long" => { - assert!(mlkem1024::Seed::from_slice( - &hex::decode(test_vector.seed.as_ref().unwrap()).unwrap() - ) - .is_err()); + assert!( + mlkem1024::Seed::try_from( + &hex::decode(test_vector.seed.as_ref().unwrap()).unwrap() + ) + .is_err() + ); } "Ciphertext too short" | "Ciphertext too long" => { - assert!(mlkem1024::Ciphertext::from_slice( - &hex::decode(test_vector.c.as_ref().unwrap()).unwrap() - ) - .is_err()); + assert!( + mlkem1024::Ciphertext::try_from( + &hex::decode(test_vector.c.as_ref().unwrap()).unwrap() + ) + .is_err() + ); } _ => panic!("a test parameter set was unaccounted for"), } @@ -489,8 +518,8 @@ fn mlkem_runner(path: &str) { assert!(test_vector.ek.is_some()); - let mut ek_expected = [0u8; mlkem1024::MlKem1024::EK_SIZE]; - let mut ct_expected = [0u8; mlkem1024::MlKem1024::CIPHERTEXT_SIZE]; + let mut ek_expected = [0u8; mlkem1024::EK_SIZE]; + let mut ct_expected = [0u8; mlkem1024::CIPHERTEXT_SIZE]; hex::decode_to_slice(test_vector.seed.as_ref().unwrap(), &mut seed).unwrap(); hex::decode_to_slice(test_vector.K.as_ref().unwrap(), &mut shared_expected) .unwrap(); @@ -500,15 +529,14 @@ fn mlkem_runner(path: &str) { .unwrap(); let keypair = - mlkem1024::KeyPair::try_from(&mlkem1024::Seed::from_slice(&seed).unwrap()) + mlkem1024::KeyPair::try_from(&mlkem1024::Seed::try_from(&seed).unwrap()) .unwrap(); - let ek = mlkem1024::EncapsulationKey::from_slice(&ek_expected).unwrap(); + let ek = mlkem1024::EncapsulationKey::try_from(&ek_expected).unwrap(); assert_eq!(keypair.public(), &ek); - let ciphertext = mlkem1024::Ciphertext::from_slice(&ct_expected).unwrap(); - let k_actual = - mlkem1024::MlKem1024::decap(keypair.private(), &ciphertext).unwrap(); - assert_eq!(&shared_expected, k_actual.unprotected_as_bytes()); + let ciphertext = mlkem1024::Ciphertext::try_from(&ct_expected).unwrap(); + let k_actual = keypair.decap(&ciphertext).unwrap(); + assert_eq!(&shared_expected, k_actual.unprotected_as_ref()); tests_run += 1; } diff --git a/tests/kem/mlkem/mod.rs b/tests/kem/mlkem/mod.rs index 9588839d5..7ac21f88a 100644 --- a/tests/kem/mlkem/mod.rs +++ b/tests/kem/mlkem/mod.rs @@ -1,5 +1,5 @@ use hex::decode; -use orion::hazardous::kem::{mlkem1024, mlkem512, mlkem768}; +use orion::hazardous::kem::{mlkem512, mlkem768, mlkem1024}; use std::{ fs::File, io::{BufRead, BufReader}, @@ -20,7 +20,7 @@ fn c2spcctv_mlkem_bad_encapsulation_keys_1024() { for line in lines.map_while(Result::ok) { let raw_ek_bytes = decode(line).unwrap(); - assert!(mlkem1024::EncapsulationKey::from_slice(&raw_ek_bytes).is_err()); + assert!(mlkem1024::EncapsulationKey::try_from(&raw_ek_bytes).is_err()); } } @@ -35,7 +35,7 @@ fn c2spcctv_mlkem_bad_encapsulation_keys_768() { for line in lines.map_while(Result::ok) { let raw_ek_bytes = decode(line).unwrap(); - assert!(mlkem768::EncapsulationKey::from_slice(&raw_ek_bytes).is_err()); + assert!(mlkem768::EncapsulationKey::try_from(&raw_ek_bytes).is_err()); } } @@ -50,6 +50,6 @@ fn c2spcctv_mlkem_bad_encapsulation_keys_512() { for line in lines.map_while(Result::ok) { let raw_ek_bytes = decode(line).unwrap(); - assert!(mlkem512::EncapsulationKey::from_slice(&raw_ek_bytes).is_err()); + assert!(mlkem512::EncapsulationKey::try_from(&raw_ek_bytes).is_err()); } } diff --git a/tests/kem/mlkem/nist_encap_decap.rs b/tests/kem/mlkem/nist_encap_decap.rs index 7038f7063..1ba31a1a4 100644 --- a/tests/kem/mlkem/nist_encap_decap.rs +++ b/tests/kem/mlkem/nist_encap_decap.rs @@ -1,9 +1,9 @@ // ml_kem_encapdecap_internalProjection.json // taken at commit: https://github.com/usnistgov/ACVP-Server/commit/203f667c26e10a1be89dfe8da7a54498fde2d848 -use orion::hazardous::kem::mlkem1024; use orion::hazardous::kem::mlkem512; use orion::hazardous::kem::mlkem768; +use orion::hazardous::kem::mlkem1024; use serde::{Deserialize, Serialize}; use std::{fs::File, io::BufReader}; @@ -61,49 +61,49 @@ fn mlkem_runner(path: &str) { let mut k = [0u8; 32]; let mut m = [0u8; 32]; - let mut ek_expected = [0u8; mlkem512::MlKem512::EK_SIZE]; - let mut dk_expected = [0u8; mlkem512::MlKem512::DK_SIZE]; - let mut ct_expected = [0u8; mlkem512::MlKem512::CIPHERTEXT_SIZE]; + let mut ek_expected = [0u8; mlkem512::EK_SIZE]; + let mut dk_expected = [0u8; mlkem512::DK_SIZE]; + let mut ct_expected = [0u8; mlkem512::CIPHERTEXT_SIZE]; hex::decode_to_slice(&test.k, &mut k).unwrap(); hex::decode_to_slice(test.m.as_ref().unwrap(), &mut m).unwrap(); hex::decode_to_slice(test.ek.as_ref().unwrap(), &mut ek_expected).unwrap(); hex::decode_to_slice(test.dk.as_ref().unwrap(), &mut dk_expected).unwrap(); hex::decode_to_slice(&test.c, &mut ct_expected).unwrap(); - let ek = mlkem512::EncapsulationKey::from_slice(&ek_expected).unwrap(); + let ek = mlkem512::EncapsulationKey::try_from(&ek_expected).unwrap(); let (k_actual, ct_actual) = ek.encap_deterministic(&m).unwrap(); - assert_eq!(k, k_actual.unprotected_as_bytes()); + assert_eq!(k, k_actual.unprotected_as_ref()); assert_eq!(ct_expected, ct_actual.as_ref()); let dk = mlkem512::DecapsulationKey::unchecked_from_slice(&dk_expected).unwrap(); let k_actual = dk - .decap(&mlkem512::Ciphertext::from_slice(&ct_expected).unwrap()) + .decap(&mlkem512::Ciphertext::try_from(&ct_expected).unwrap()) .unwrap(); - assert_eq!(k, k_actual.unprotected_as_bytes()); + assert_eq!(k, k_actual.unprotected_as_ref()); tests_run += 1; } } if test_group.function == "decapsulation" { - let mut ek_expected = [0u8; mlkem512::MlKem512::EK_SIZE]; - let mut dk_expected = [0u8; mlkem512::MlKem512::DK_SIZE]; + let mut ek_expected = [0u8; mlkem512::EK_SIZE]; + let mut dk_expected = [0u8; mlkem512::DK_SIZE]; hex::decode_to_slice(test_group.ek.as_ref().unwrap(), &mut ek_expected).unwrap(); hex::decode_to_slice(test_group.dk.as_ref().unwrap(), &mut dk_expected).unwrap(); for test in test_group.tests.iter() { let mut k = [0u8; 32]; - let mut ct_expected = [0u8; mlkem512::MlKem512::CIPHERTEXT_SIZE]; + let mut ct_expected = [0u8; mlkem512::CIPHERTEXT_SIZE]; hex::decode_to_slice(&test.k, &mut k).unwrap(); hex::decode_to_slice(&test.c, &mut ct_expected).unwrap(); let dk = mlkem512::DecapsulationKey::unchecked_from_slice(&dk_expected).unwrap(); let k_actual = dk - .decap(&mlkem512::Ciphertext::from_slice(&ct_expected).unwrap()) + .decap(&mlkem512::Ciphertext::try_from(&ct_expected).unwrap()) .unwrap(); - assert_eq!(k, k_actual.unprotected_as_bytes()); + assert_eq!(k, k_actual.unprotected_as_ref()); tests_run += 1; } @@ -118,49 +118,49 @@ fn mlkem_runner(path: &str) { let mut k = [0u8; 32]; let mut m = [0u8; 32]; - let mut ek_expected = [0u8; mlkem768::MlKem768::EK_SIZE]; - let mut dk_expected = [0u8; mlkem768::MlKem768::DK_SIZE]; - let mut ct_expected = [0u8; mlkem768::MlKem768::CIPHERTEXT_SIZE]; + let mut ek_expected = [0u8; mlkem768::EK_SIZE]; + let mut dk_expected = [0u8; mlkem768::DK_SIZE]; + let mut ct_expected = [0u8; mlkem768::CIPHERTEXT_SIZE]; hex::decode_to_slice(&test.k, &mut k).unwrap(); hex::decode_to_slice(test.m.as_ref().unwrap(), &mut m).unwrap(); hex::decode_to_slice(test.ek.as_ref().unwrap(), &mut ek_expected).unwrap(); hex::decode_to_slice(test.dk.as_ref().unwrap(), &mut dk_expected).unwrap(); hex::decode_to_slice(&test.c, &mut ct_expected).unwrap(); - let ek = mlkem768::EncapsulationKey::from_slice(&ek_expected).unwrap(); + let ek = mlkem768::EncapsulationKey::try_from(&ek_expected).unwrap(); let (k_actual, ct_actual) = ek.encap_deterministic(&m).unwrap(); - assert_eq!(k, k_actual.unprotected_as_bytes()); + assert_eq!(k, k_actual.unprotected_as_ref()); assert_eq!(ct_expected, ct_actual.as_ref()); let dk = mlkem768::DecapsulationKey::unchecked_from_slice(&dk_expected).unwrap(); let k_actual = dk - .decap(&mlkem768::Ciphertext::from_slice(&ct_expected).unwrap()) + .decap(&mlkem768::Ciphertext::try_from(&ct_expected).unwrap()) .unwrap(); - assert_eq!(k, k_actual.unprotected_as_bytes()); + assert_eq!(k, k_actual.unprotected_as_ref()); tests_run += 1; } } if test_group.function == "decapsulation" { - let mut ek_expected = [0u8; mlkem768::MlKem768::EK_SIZE]; - let mut dk_expected = [0u8; mlkem768::MlKem768::DK_SIZE]; + let mut ek_expected = [0u8; mlkem768::EK_SIZE]; + let mut dk_expected = [0u8; mlkem768::DK_SIZE]; hex::decode_to_slice(test_group.ek.as_ref().unwrap(), &mut ek_expected).unwrap(); hex::decode_to_slice(test_group.dk.as_ref().unwrap(), &mut dk_expected).unwrap(); for test in test_group.tests.iter() { let mut k = [0u8; 32]; - let mut ct_expected = [0u8; mlkem768::MlKem768::CIPHERTEXT_SIZE]; + let mut ct_expected = [0u8; mlkem768::CIPHERTEXT_SIZE]; hex::decode_to_slice(&test.k, &mut k).unwrap(); hex::decode_to_slice(&test.c, &mut ct_expected).unwrap(); let dk = mlkem768::DecapsulationKey::unchecked_from_slice(&dk_expected).unwrap(); let k_actual = dk - .decap(&mlkem768::Ciphertext::from_slice(&ct_expected).unwrap()) + .decap(&mlkem768::Ciphertext::try_from(&ct_expected).unwrap()) .unwrap(); - assert_eq!(k, k_actual.unprotected_as_bytes()); + assert_eq!(k, k_actual.unprotected_as_ref()); tests_run += 1; } @@ -176,49 +176,49 @@ fn mlkem_runner(path: &str) { let mut k = [0u8; 32]; let mut m = [0u8; 32]; - let mut ek_expected = [0u8; mlkem1024::MlKem1024::EK_SIZE]; - let mut dk_expected = [0u8; mlkem1024::MlKem1024::DK_SIZE]; - let mut ct_expected = [0u8; mlkem1024::MlKem1024::CIPHERTEXT_SIZE]; + let mut ek_expected = [0u8; mlkem1024::EK_SIZE]; + let mut dk_expected = [0u8; mlkem1024::DK_SIZE]; + let mut ct_expected = [0u8; mlkem1024::CIPHERTEXT_SIZE]; hex::decode_to_slice(&test.k, &mut k).unwrap(); hex::decode_to_slice(test.m.as_ref().unwrap(), &mut m).unwrap(); hex::decode_to_slice(test.ek.as_ref().unwrap(), &mut ek_expected).unwrap(); hex::decode_to_slice(test.dk.as_ref().unwrap(), &mut dk_expected).unwrap(); hex::decode_to_slice(&test.c, &mut ct_expected).unwrap(); - let ek = mlkem1024::EncapsulationKey::from_slice(&ek_expected).unwrap(); + let ek = mlkem1024::EncapsulationKey::try_from(&ek_expected).unwrap(); let (k_actual, ct_actual) = ek.encap_deterministic(&m).unwrap(); - assert_eq!(k, k_actual.unprotected_as_bytes()); + assert_eq!(k, k_actual.unprotected_as_ref()); assert_eq!(ct_expected, ct_actual.as_ref()); let dk = mlkem1024::DecapsulationKey::unchecked_from_slice(&dk_expected).unwrap(); let k_actual = dk - .decap(&mlkem1024::Ciphertext::from_slice(&ct_expected).unwrap()) + .decap(&mlkem1024::Ciphertext::try_from(&ct_expected).unwrap()) .unwrap(); - assert_eq!(k, k_actual.unprotected_as_bytes()); + assert_eq!(k, k_actual.unprotected_as_ref()); tests_run += 1; } } if test_group.function == "decapsulation" { - let mut ek_expected = [0u8; mlkem1024::MlKem1024::EK_SIZE]; - let mut dk_expected = [0u8; mlkem1024::MlKem1024::DK_SIZE]; + let mut ek_expected = [0u8; mlkem1024::EK_SIZE]; + let mut dk_expected = [0u8; mlkem1024::DK_SIZE]; hex::decode_to_slice(test_group.ek.as_ref().unwrap(), &mut ek_expected).unwrap(); hex::decode_to_slice(test_group.dk.as_ref().unwrap(), &mut dk_expected).unwrap(); for test in test_group.tests.iter() { let mut k = [0u8; 32]; - let mut ct_expected = [0u8; mlkem1024::MlKem1024::CIPHERTEXT_SIZE]; + let mut ct_expected = [0u8; mlkem1024::CIPHERTEXT_SIZE]; hex::decode_to_slice(&test.k, &mut k).unwrap(); hex::decode_to_slice(&test.c, &mut ct_expected).unwrap(); let dk = mlkem1024::DecapsulationKey::unchecked_from_slice(&dk_expected).unwrap(); let k_actual = dk - .decap(&mlkem1024::Ciphertext::from_slice(&ct_expected).unwrap()) + .decap(&mlkem1024::Ciphertext::try_from(&ct_expected).unwrap()) .unwrap(); - assert_eq!(k, k_actual.unprotected_as_bytes()); + assert_eq!(k, k_actual.unprotected_as_ref()); tests_run += 1; } diff --git a/tests/kem/mlkem/nist_keygen.rs b/tests/kem/mlkem/nist_keygen.rs index 938890795..121881670 100644 --- a/tests/kem/mlkem/nist_keygen.rs +++ b/tests/kem/mlkem/nist_keygen.rs @@ -1,9 +1,10 @@ // ml_kem_keygen_internalProjection.json // taken at commit: https://github.com/usnistgov/ACVP-Server/commit/203f667c26e10a1be89dfe8da7a54498fde2d848 -use orion::hazardous::kem::mlkem1024; +use orion::KP; use orion::hazardous::kem::mlkem512; use orion::hazardous::kem::mlkem768; +use orion::hazardous::kem::mlkem1024; use serde::{Deserialize, Serialize}; use std::{fs::File, io::BufReader}; @@ -49,8 +50,8 @@ fn mlkem_runner(path: &str) { for test in test_group.tests.iter() { let mut d = [0u8; 32]; let mut z = [0u8; 32]; - let mut ek_expected = [0u8; mlkem512::MlKem512::EK_SIZE]; - let mut dk_expected = [0u8; mlkem512::MlKem512::DK_SIZE]; + let mut ek_expected = [0u8; mlkem512::EK_SIZE]; + let mut dk_expected = [0u8; mlkem512::DK_SIZE]; hex::decode_to_slice(&test.z, &mut z).unwrap(); hex::decode_to_slice(&test.d, &mut d).unwrap(); hex::decode_to_slice(&test.ek, &mut ek_expected).unwrap(); @@ -59,7 +60,7 @@ fn mlkem_runner(path: &str) { let mut dz = d.to_vec(); dz.extend(&z); - let seed = mlkem512::Seed::from_slice(&dz).unwrap(); + let seed = mlkem512::Seed::try_from(&dz).unwrap(); let kp = mlkem512::KeyPair::try_from(&seed).unwrap(); assert_eq!(ek_expected, kp.public().as_ref()); @@ -72,8 +73,8 @@ fn mlkem_runner(path: &str) { for test in test_group.tests.iter() { let mut d = [0u8; 32]; let mut z = [0u8; 32]; - let mut ek_expected = [0u8; mlkem768::MlKem768::EK_SIZE]; - let mut dk_expected = [0u8; mlkem768::MlKem768::DK_SIZE]; + let mut ek_expected = [0u8; mlkem768::EK_SIZE]; + let mut dk_expected = [0u8; mlkem768::DK_SIZE]; hex::decode_to_slice(&test.z, &mut z).unwrap(); hex::decode_to_slice(&test.d, &mut d).unwrap(); hex::decode_to_slice(&test.ek, &mut ek_expected).unwrap(); @@ -82,7 +83,7 @@ fn mlkem_runner(path: &str) { let mut dz = d.to_vec(); dz.extend(&z); - let seed = mlkem768::Seed::from_slice(&dz).unwrap(); + let seed = mlkem768::Seed::try_from(&dz).unwrap(); let kp = mlkem768::KeyPair::try_from(&seed).unwrap(); assert_eq!(ek_expected, kp.public().as_ref()); @@ -96,8 +97,8 @@ fn mlkem_runner(path: &str) { for test in test_group.tests.iter() { let mut d = [0u8; 32]; let mut z = [0u8; 32]; - let mut ek_expected = [0u8; mlkem1024::MlKem1024::EK_SIZE]; - let mut dk_expected = [0u8; mlkem1024::MlKem1024::DK_SIZE]; + let mut ek_expected = [0u8; mlkem1024::EK_SIZE]; + let mut dk_expected = [0u8; mlkem1024::DK_SIZE]; hex::decode_to_slice(&test.z, &mut z).unwrap(); hex::decode_to_slice(&test.d, &mut d).unwrap(); hex::decode_to_slice(&test.ek, &mut ek_expected).unwrap(); @@ -106,7 +107,7 @@ fn mlkem_runner(path: &str) { let mut dz = d.to_vec(); dz.extend(&z); - let seed = mlkem1024::Seed::from_slice(&dz).unwrap(); + let seed = mlkem1024::Seed::try_from(&dz).unwrap(); let kp = mlkem1024::KeyPair::try_from(&seed).unwrap(); assert_eq!(ek_expected, kp.public().as_ref()); diff --git a/tests/kem/xwing_draft.rs b/tests/kem/xwing_draft.rs index f62457dbb..79438d96c 100644 --- a/tests/kem/xwing_draft.rs +++ b/tests/kem/xwing_draft.rs @@ -1,3 +1,4 @@ +use orion::KP; use orion::hazardous::kem::xwing::*; #[test] @@ -17,16 +18,18 @@ pub fn draft_06_testvector_1() { hex::decode("d2df0522128f09dd8e2c92b1e905c793d8f57a54c3da25861f10bf4ca613e384").unwrap(); assert_eq!(seed, sk); - let dk = Seed::from_slice(&seed).unwrap(); - let kp = KeyPair::generate_deterministic(&dk).unwrap(); + let dk = DecapsulationKey::try_from(&seed).unwrap(); + assert_eq!(&sk, dk.unprotected_as_ref()); + let kp = KeyPair::try_from(&dk).unwrap(); assert_eq!(&pk, kp.public().as_ref()); - let (ss_actual, ct_actual) = XWing::encap_deterministic(kp.public(), &eseed).unwrap(); - assert_eq!(&ss, ss_actual.unprotected_as_bytes()); + let (ss_actual, ct_actual) = kp.public().encap_deterministic(&eseed).unwrap(); + assert_eq!(&ss, ss_actual.unprotected_as_ref()); assert_eq!(&ct, ct_actual.as_ref()); - let ss_roundtrip = XWing::decap(kp.private(), &ct_actual).unwrap(); - assert_eq!(&ss, ss_roundtrip.unprotected_as_bytes()); + let ss_roundtrip = kp.decap(&ct_actual).unwrap(); + assert_eq!(&ss, ss_roundtrip.unprotected_as_ref()); + assert_eq!(&ss, dk.decap(&ct_actual).unwrap().unprotected_as_ref()); } #[test] @@ -51,16 +54,18 @@ pub fn draft_06_testvector_2() { hex::decode("f2e86241c64d60f6649fbc6c5b7d17180b780a3f34355e64a85749949c45f150").unwrap(); assert_eq!(seed, sk); - let dk = Seed::from_slice(&seed).unwrap(); - let kp = KeyPair::generate_deterministic(&dk).unwrap(); + let dk = DecapsulationKey::try_from(&seed).unwrap(); + assert_eq!(&sk, dk.unprotected_as_ref()); + let kp = KeyPair::try_from(&dk).unwrap(); assert_eq!(&pk, kp.public().as_ref()); - let (ss_actual, ct_actual) = XWing::encap_deterministic(kp.public(), &eseed).unwrap(); - assert_eq!(&ss, ss_actual.unprotected_as_bytes()); + let (ss_actual, ct_actual) = kp.public().encap_deterministic(&eseed).unwrap(); + assert_eq!(&ss, ss_actual.unprotected_as_ref()); assert_eq!(&ct, ct_actual.as_ref()); - let ss_roundtrip = XWing::decap(kp.private(), &ct_actual).unwrap(); - assert_eq!(&ss, ss_roundtrip.unprotected_as_bytes()); + let ss_roundtrip = kp.decap(&ct_actual).unwrap(); + assert_eq!(&ss, ss_roundtrip.unprotected_as_ref()); + assert_eq!(&ss, dk.decap(&ct_actual).unwrap().unprotected_as_ref()); } #[test] @@ -86,14 +91,16 @@ pub fn draft_06_testvector_3() { hex::decode("953f7f4e8c5b5049bdc771d1dffada0dd961477d1a2ae0988baa7ea6898d893f").unwrap(); assert_eq!(seed, sk); - let dk = Seed::from_slice(&seed).unwrap(); - let kp = KeyPair::generate_deterministic(&dk).unwrap(); + let dk = DecapsulationKey::try_from(&seed).unwrap(); + assert_eq!(&sk, dk.unprotected_as_ref()); + let kp = KeyPair::try_from(&dk).unwrap(); assert_eq!(&pk, kp.public().as_ref()); - let (ss_actual, ct_actual) = XWing::encap_deterministic(kp.public(), &eseed).unwrap(); - assert_eq!(&ss, ss_actual.unprotected_as_bytes()); + let (ss_actual, ct_actual) = kp.public().encap_deterministic(&eseed).unwrap(); + assert_eq!(&ss, ss_actual.unprotected_as_ref()); assert_eq!(&ct, ct_actual.as_ref()); - let ss_roundtrip = XWing::decap(kp.private(), &ct_actual).unwrap(); - assert_eq!(&ss, ss_roundtrip.unprotected_as_bytes()); + let ss_roundtrip = kp.decap(&ct_actual).unwrap(); + assert_eq!(&ss, ss_roundtrip.unprotected_as_ref()); + assert_eq!(&ss, dk.decap(&ct_actual).unwrap().unprotected_as_ref()); } diff --git a/tests/mac/boringssl_poly1305.rs b/tests/mac/boringssl_poly1305.rs index 8981736a6..cccf7fa86 100644 --- a/tests/mac/boringssl_poly1305.rs +++ b/tests/mac/boringssl_poly1305.rs @@ -1,8 +1,8 @@ // Testing against BoringSSL test vector from [boringssl](https://boringssl.googlesource.com/boringssl/+/master/crypto/poly1305/poly1305_tests.txt). // Pulled at commit (master): 0f5ecd3a854546d943104e1f7421e489b7f4d5aa -use crate::mac::poly1305_test_runner; use crate::TestCaseReader; +use crate::mac::poly1305_test_runner; #[test] fn test_boringssl_poly1305() { diff --git a/tests/mac/mod.rs b/tests/mac/mod.rs index 116e69249..ada5e5a5c 100644 --- a/tests/mac/mod.rs +++ b/tests/mac/mod.rs @@ -21,12 +21,12 @@ macro_rules! impl_hmac_test_runner (($name:ident, $hmac:ident, $hmac_tag:ident, None => $sha2_outsize, }; - let key = $hmac_sk::from_slice(secret_key).unwrap(); + let key = $hmac_sk::try_from(secret_key).unwrap(); // Only use verify() on OUTSIZE length tags since this is // the amount that Tag requires. if len == $sha2_outsize { - let expected_tag = $hmac_tag::from_slice(expected).unwrap(); + let expected_tag = $hmac_tag::try_from(expected).unwrap(); let res = $hmac::verify(&expected_tag, &key, data); if valid_result { assert!(res.is_ok()); @@ -38,9 +38,9 @@ macro_rules! impl_hmac_test_runner (($name:ident, $hmac:ident, $hmac_tag:ident, ctx.update(data).unwrap(); let actual = ctx.finalize().unwrap(); if valid_result { - assert_eq!(expected, actual.unprotected_as_bytes()[..len].as_ref()); + assert_eq!(expected, actual.unprotected_as_ref()[..len].as_ref()); } else { - assert_ne!(expected, actual.unprotected_as_bytes()[..len].as_ref()); + assert_ne!(expected, actual.unprotected_as_ref()[..len].as_ref()); } } } @@ -80,7 +80,7 @@ impl_hmac_test_runner!( ); fn poly1305_test_runner(key: &[u8], input: &[u8], output: &[u8]) { - let sk = OneTimeKey::from_slice(key).unwrap(); + let sk = OneTimeKey::try_from(key).unwrap(); let mut state = poly1305::Poly1305::new(&sk); state.update(input).unwrap(); @@ -90,5 +90,5 @@ fn poly1305_test_runner(key: &[u8], input: &[u8], output: &[u8]) { assert_eq!(tag_stream, output); assert_eq!(tag_one_shot, output); - assert!(poly1305::Poly1305::verify(&Tag::from_slice(output).unwrap(), &sk, input).is_ok()); + assert!(poly1305::Poly1305::verify(&Tag::try_from(output).unwrap(), &sk, input).is_ok()); } diff --git a/tests/stream/mod.rs b/tests/stream/mod.rs index bbd8b4d85..e73c78243 100644 --- a/tests/stream/mod.rs +++ b/tests/stream/mod.rs @@ -16,18 +16,18 @@ pub fn chacha_test_runner( output: &[u8], ) { if key.len() != CHACHA_KEYSIZE { - assert!(SecretKey::from_slice(key).is_err()); + assert!(SecretKey::try_from(key).is_err()); return; } if input.is_empty() || output.is_empty() { return; } - let sk = SecretKey::from_slice(key).unwrap(); + let sk = SecretKey::try_from(key).unwrap(); // Selecting variant based on nonce size if nonce.len() == IETF_CHACHA_NONCESIZE { - let n = chacha20::Nonce::from_slice(nonce).unwrap(); + let n = chacha20::Nonce::try_from(nonce).unwrap(); StreamCipherTestRunner( chacha20::encrypt, chacha20::decrypt, @@ -38,7 +38,7 @@ pub fn chacha_test_runner( Some(output), ); } else if nonce.len() == XCHACHA_NONCESIZE { - let n = xchacha20::Nonce::from_slice(nonce).unwrap(); + let n = xchacha20::Nonce::try_from(nonce).unwrap(); StreamCipherTestRunner( xchacha20::encrypt, xchacha20::decrypt, @@ -49,7 +49,7 @@ pub fn chacha_test_runner( Some(output), ); } else { - assert!(chacha20::Nonce::from_slice(nonce).is_err()); - assert!(xchacha20::Nonce::from_slice(nonce).is_err()); + assert!(chacha20::Nonce::try_from(nonce).is_err()); + assert!(xchacha20::Nonce::try_from(nonce).is_err()); } }