diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1b2eb25..becadf0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,7 +26,7 @@ jobs: - --features serde toolchain: - stable - - 1.85.0 + - 1.87.0 name: test env: PKCS11_MODULE: /usr/lib/softhsm/libsofthsm2.so @@ -95,7 +95,7 @@ jobs: matrix: toolchain: - stable - - 1.85.0 + - 1.87.0 name: test simple_login command-line example steps: - name: install expect @@ -118,7 +118,7 @@ jobs: matrix: toolchain: - stable - - 1.85.0 + - 1.87.0 name: test digital_locker command-line example steps: - name: install expect diff --git a/CHANGELOG.md b/CHANGELOG.md index cab9d58..0306bec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 4.1.0-pre.2 (March 26, 2026) +* Upgraded ml-kem from 0.2 to 0.3.0-rc.0 +* Increased MSRV to 1.87 + ## 4.1.0-pre.1 (November 17, 2025) * Added ml-kem re-export behind the kem feature diff --git a/Cargo.toml b/Cargo.toml index 549a193..a134888 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,8 @@ license = "Apache-2.0 OR MIT" name = "opaque-ke" readme = "README.md" repository = "https://github.com/facebook/opaque-ke" -rust-version = "1.85" -version = "4.1.0-pre.1" +rust-version = "1.87" +version = "4.1.0-pre.2" [features] argon2 = ["dep:argon2"] @@ -18,7 +18,7 @@ curve25519 = ["dep:curve25519-dalek"] default = ["ristretto255", "serde"] ecdsa = ["dep:ecdsa", "dep:rfc6979"] ed25519 = ["dep:curve25519-dalek", "dep:ed25519-dalek"] -kem = ["dep:ml-kem"] +kem = ["dep:ml-kem", "dep:rand_core_10"] ristretto255 = ["dep:curve25519-dalek", "voprf/ristretto255-ciphersuite"] serde = [ "dep:serde", @@ -54,10 +54,11 @@ elliptic-curve = { version = "0.13", features = ["hash2curve", "sec1"] } generic-array = "=0.14.7" # pinned to avoid deprecation warnings hkdf = "0.12" hmac = "0.12" -ml-kem = { version = "0.2", default-features = false, features = [ +ml-kem = { version = "0.3.0-rc.0", default-features = false, features = [ "zeroize", ], optional = true } rand = { version = "0.8", default-features = false } +rand_core_10 = { package = "rand_core", version = "0.10", default-features = false, optional = true } rfc6979 = { version = "0.4", optional = true } serde = { version = "1", default-features = false, features = [ "derive", @@ -73,7 +74,7 @@ getrandom = { version = "0.2", features = ["js"], optional = true } anyhow = "1" bincode = "1" chacha20poly1305 = "0.10" -criterion = "0.7" +criterion = "0.8" cryptoki = "0.9" elliptic-curve = { version = "0.13", features = ["alloc", "pkcs8"] } hex = "0.4" diff --git a/README.md b/README.md index 3fcb390..d24e595 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,12 @@ Installation Add the following line to the dependencies of your `Cargo.toml`: ``` -opaque-ke = "4.1.0-pre.1" +opaque-ke = "4.1.0-pre.2" ``` ### Minimum Supported Rust Version -Rust **1.85** or higher. +Rust **1.87** or higher. Audit ----- diff --git a/src/key_exchange/tripledh_kem.rs b/src/key_exchange/tripledh_kem.rs index ef7553c..099cbeb 100644 --- a/src/key_exchange/tripledh_kem.rs +++ b/src/key_exchange/tripledh_kem.rs @@ -18,7 +18,6 @@ //! the generic `ml-kem` abstractions into the existing OPAQUE key-exchange //! pipeline. -use core::convert::TryFrom; use core::fmt::Debug; use core::marker::PhantomData; use core::ops::Add; @@ -29,9 +28,11 @@ use digest::{Digest, Output}; use generic_array::sequence::Concat; use generic_array::typenum::{IsLess, Le, NonZero, Sum, U256}; use generic_array::{ArrayLength, GenericArray}; -use ml_kem::kem::{Decapsulate, Encapsulate}; -use ml_kem::{ - Ciphertext as MlKemCiphertext, Encoded, EncodedSizeUser, KemCore, SharedKey as MlKemSharedKey, +#[allow(deprecated)] +use ml_kem::ExpandedKeyEncoding; +use ml_kem::kem::{ + Ciphertext as MlKemCiphertext, Decapsulate, Encapsulate, Kem as MlKemTrait, KeyExport, + KeySizeUser, TryKeyInit, }; use rand::{CryptoRng, RngCore}; use subtle::{ConstantTimeEq, CtOption}; @@ -44,7 +45,7 @@ use super::{ SerializedIdentifiers, }; use crate::ciphersuite::{CipherSuite, KeGroup}; -use crate::errors::{InternalError, ProtocolError}; +use crate::errors::ProtocolError; use crate::hash::{Hash, OutputSize, ProxyHash}; use crate::key_exchange::group::Group; use crate::keypair::{PrivateKey, PublicKey}; @@ -115,16 +116,42 @@ pub trait KemCoreWrapper { ) -> Result, ProtocolError>; } -type RcEncapsulationKeyLen = <::EncapsulationKey as EncodedSizeUser>::EncodedSize; -type RcDecapsulationKeyLen = <::DecapsulationKey as EncodedSizeUser>::EncodedSize; -type RcCiphertextLen = ::CiphertextSize; -type RcSharedSecretLen = ::SharedKeySize; +/// Adapter to bridge `rand 0.8` (`rand_core 0.6`) RNGs to `rand_core 0.10` +/// which is required by `ml-kem 0.3.x`. +struct RngCompat<'a, R>(&'a mut R); +impl rand_core_10::TryRng for RngCompat<'_, R> { + type Error = core::convert::Infallible; + + fn try_next_u32(&mut self) -> Result { + Ok(self.0.next_u32()) + } + + fn try_next_u64(&mut self) -> Result { + Ok(self.0.next_u64()) + } + + fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> { + self.0.fill_bytes(dst); + Ok(()) + } +} + +impl rand_core_10::TryCryptoRng for RngCompat<'_, R> {} + +type RcEncapsulationKeyLen = <::EncapsulationKey as KeySizeUser>::KeySize; +#[allow(deprecated)] +type RcDecapsulationKeyLen = + <::DecapsulationKey as ExpandedKeyEncoding>::EncodedSize; +type RcCiphertextLen = ::CiphertextSize; +type RcSharedSecretLen = ::SharedKeySize; + +#[allow(deprecated)] impl KemCoreWrapper for K where - K: KemCore, - K::EncapsulationKey: Encapsulate, MlKemSharedKey> + Clone, - K::DecapsulationKey: Decapsulate, MlKemSharedKey> + Clone + ZeroizeOnDrop, + K: MlKemTrait, + K::EncapsulationKey: Encapsulate + KeyExport + TryKeyInit + Clone, + K::DecapsulationKey: Decapsulate + ExpandedKeyEncoding + Clone + ZeroizeOnDrop, RcEncapsulationKeyLen: ArrayLength, RcDecapsulationKeyLen: ArrayLength, RcCiphertextLen: ArrayLength, @@ -140,13 +167,13 @@ where fn generate( rng: &mut R, ) -> Result<(Self::DecapsulationKey, Self::EncapsulationKey), ProtocolError> { - Ok(K::generate(rng)) + Ok(K::generate_keypair_from_rng(&mut RngCompat(rng))) } fn serialize_encapsulation_key( key: &Self::EncapsulationKey, ) -> GenericArray { - GenericArray::clone_from_slice(key.as_bytes().as_slice()) + GenericArray::clone_from_slice(key.to_bytes().as_slice()) } fn deserialize_encapsulation_key( @@ -154,15 +181,15 @@ where ) -> Result { let bytes: GenericArray> = input.take_array("kem encapsulation key")?; - let encoded = Encoded::::try_from(bytes.as_slice()) + let key = ml_kem::array::Array::try_from(bytes.as_slice()) .map_err(|_| ProtocolError::SerializationError)?; - Ok(K::EncapsulationKey::from_bytes(&encoded)) + TryKeyInit::new(&key).map_err(|_| ProtocolError::SerializationError) } fn serialize_decapsulation_key( key: &Self::DecapsulationKey, ) -> GenericArray { - GenericArray::clone_from_slice(key.as_bytes().as_slice()) + GenericArray::clone_from_slice(key.to_expanded_bytes().as_slice()) } fn deserialize_decapsulation_key( @@ -170,9 +197,10 @@ where ) -> Result { let bytes: GenericArray> = input.take_array("kem decapsulation key")?; - let encoded = Encoded::::try_from(bytes.as_slice()) + let key = ml_kem::array::Array::try_from(bytes.as_slice()) .map_err(|_| ProtocolError::SerializationError)?; - Ok(K::DecapsulationKey::from_bytes(&encoded)) + K::DecapsulationKey::from_expanded_bytes(&key) + .map_err(|_| ProtocolError::SerializationError) } fn encapsulate( @@ -185,14 +213,11 @@ where ), ProtocolError, > { - key.encapsulate(rng) - .map(|(ciphertext, shared)| { - ( - GenericArray::clone_from_slice(ciphertext.as_slice()), - GenericArray::clone_from_slice(shared.as_slice()), - ) - }) - .map_err(|_| ProtocolError::LibraryError(InternalError::KemError)) + let (ciphertext, shared) = key.encapsulate_with_rng(&mut RngCompat(rng)); + Ok(( + GenericArray::clone_from_slice(ciphertext.as_slice()), + GenericArray::clone_from_slice(shared.as_slice()), + )) } fn decapsulate( @@ -201,9 +226,8 @@ where ) -> Result, ProtocolError> { let ciphertext = MlKemCiphertext::::try_from(encapsulated_key.as_slice()) .map_err(|_| ProtocolError::SerializationError)?; - key.decapsulate(&ciphertext) - .map(|shared| GenericArray::clone_from_slice(shared.as_slice())) - .map_err(|_| ProtocolError::LibraryError(InternalError::KemError)) + let shared = key.decapsulate(&ciphertext); + Ok(GenericArray::clone_from_slice(shared.as_slice())) } } /// Triple Diffie-Hellman-style key exchange that offloads the second hop to a diff --git a/src/lib.rs b/src/lib.rs index 0807453..f9cd29c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,7 @@ //! //! ### Minimum Supported Rust Version //! -//! Rust **1.85** or higher. +//! Rust **1.87** or higher. //! //! # Overview //! @@ -1383,9 +1383,10 @@ mod tests; #[cfg(feature = "argon2")] pub use argon2; +pub use generic_array; #[cfg(feature = "kem")] pub use ml_kem; -pub use {generic_array, rand}; +pub use rand; pub use crate::ciphersuite::CipherSuite; #[cfg(feature = "curve25519")]