From 1fccbd69870cfdab00e8c5b876f285e3be12f5da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Thu, 8 Jan 2026 16:55:57 +0100 Subject: [PATCH 01/23] restructure modules --- src/abe.rs | 75 ++++++++ src/{ => abe}/api.rs | 30 ++- src/{ => abe}/core/mod.rs | 24 ++- src/{ => abe}/core/primitives.rs | 22 ++- src/{ => abe}/core/serialization/mod.rs | 17 +- src/{ => abe}/core/tests.rs | 13 +- src/{ => abe}/encrypted_header.rs | 7 +- src/abe/error.rs | 56 ++++++ src/{abe_policy/mod.rs => abe/policy.rs} | 3 +- .../policy}/access_policy.rs | 2 +- .../policy}/access_structure.rs | 6 +- src/{abe_policy => abe/policy}/attribute.rs | 0 src/{abe_policy => abe/policy}/dimension.rs | 0 src/{abe_policy => abe/policy}/rights.rs | 0 src/{abe_policy => abe/policy}/tests.rs | 15 +- src/core/kem.rs | 10 - src/core/nike.rs | 14 -- src/lib.rs | 26 +-- src/providers.rs | 3 + src/{ => providers}/ae.rs | 9 +- src/providers/kem.rs | 39 ++++ src/{core => providers}/kem/mlkem.rs | 16 +- src/providers/nike.rs | 54 ++++++ src/{core => providers}/nike/p256.rs | 53 +++--- src/{core => providers}/nike/r25519.rs | 31 ++- src/test_utils/mod.rs | 16 +- src/traits.rs | 176 +++--------------- 27 files changed, 389 insertions(+), 328 deletions(-) create mode 100644 src/abe.rs rename src/{ => abe}/api.rs (95%) rename src/{ => abe}/core/mod.rs (98%) rename src/{ => abe}/core/primitives.rs (98%) rename src/{ => abe}/core/serialization/mod.rs (97%) rename src/{ => abe}/core/tests.rs (98%) rename src/{ => abe}/encrypted_header.rs (98%) create mode 100644 src/abe/error.rs rename src/{abe_policy/mod.rs => abe/policy.rs} (99%) rename src/{abe_policy => abe/policy}/access_policy.rs (99%) rename src/{abe_policy => abe/policy}/access_structure.rs (99%) rename src/{abe_policy => abe/policy}/attribute.rs (100%) rename src/{abe_policy => abe/policy}/dimension.rs (100%) rename src/{abe_policy => abe/policy}/rights.rs (100%) rename src/{abe_policy => abe/policy}/tests.rs (96%) delete mode 100644 src/core/kem.rs delete mode 100644 src/core/nike.rs create mode 100644 src/providers.rs rename src/{ => providers}/ae.rs (81%) create mode 100644 src/providers/kem.rs rename src/{core => providers}/kem/mlkem.rs (95%) create mode 100644 src/providers/nike.rs rename src/{core => providers}/nike/p256.rs (90%) rename src/{core => providers}/nike/r25519.rs (95%) diff --git a/src/abe.rs b/src/abe.rs new file mode 100644 index 00000000..3e18b2f2 --- /dev/null +++ b/src/abe.rs @@ -0,0 +1,75 @@ +mod api; +mod core; +mod policy; + +pub mod encrypted_header; + +pub use api::Covercrypt; +pub use core::{MasterPublicKey, MasterSecretKey, UserSecretKey}; +pub use policy::{ + AccessPolicy, AccessStructure, Attribute, Dimension, EncryptionStatus, QualifiedAttribute, + SecurityMode, +}; + +#[cfg(any(test, feature = "test-utils"))] +pub use policy::gen_structure; + +use crate::traits::AE; +use cosmian_crypto_core::{reexport::zeroize::Zeroizing, Secret}; + +pub trait KemAc { + type EncapsulationKey; + type DecapsulationKey; + type Encapsulation; + type Error: std::error::Error; + + /// Generates a new encapsulation for the given access policy. + /// + /// # Error + /// + /// Returns an error if the access policy is not valid. + fn encaps( + &self, + ek: &Self::EncapsulationKey, + ap: &AccessPolicy, + ) -> Result<(Secret, Self::Encapsulation), Self::Error>; + + /// Attempts opening the given encapsulation with the given key. + /// + /// Returns the encapsulated secret upon success or `None` if this key was + /// not authorized to open this encapsulation. + fn decaps( + &self, + dk: &Self::DecapsulationKey, + enc: &Self::Encapsulation, + ) -> Result>, Self::Error>; +} + +pub trait PkeAc> { + type EncryptionKey; + type DecryptionKey; + type Ciphertext; + type Error: std::error::Error; + + /// Encrypts the given plaintext under the given access policy. + /// + /// # Error + /// + /// Returns an error if the access policy is not valid. + fn encrypt( + &self, + ek: &Self::EncryptionKey, + ap: &AccessPolicy, + ptx: &[u8], + ) -> Result; + + /// Attempts decrypting the given ciphertext with the given key. + /// + /// Returns the plaintext upon success, or `None` if this key was not + /// authorized to decrypt this ciphertext. + fn decrypt( + &self, + dk: &Self::DecryptionKey, + ctx: &Self::Ciphertext, + ) -> Result>>, Self::Error>; +} diff --git a/src/api.rs b/src/abe/api.rs similarity index 95% rename from src/api.rs rename to src/abe/api.rs index c32776ba..b4c6aeee 100644 --- a/src/api.rs +++ b/src/abe/api.rs @@ -1,23 +1,21 @@ -use std::sync::{Mutex, MutexGuard}; - +use crate::{ + abe::{ + core::{ + primitives::{self, full_decaps, prune, refresh, rekey, setup, update_msk, usk_keygen}, + MasterPublicKey, MasterSecretKey, UserSecretKey, XEnc, MIN_TRACING_LEVEL, + SHARED_SECRET_LENGTH, + }, + policy::AccessPolicy, + KemAc, PkeAc, + }, + traits::AE, + Error, +}; use cosmian_crypto_core::{ reexport::{rand_core::SeedableRng, zeroize::Zeroizing}, CsRng, Secret, SymmetricKey, }; - -use super::{ - core::primitives::{prune, update_msk, usk_keygen}, - core::MIN_TRACING_LEVEL, - traits::AE, -}; -use crate::{ - core::{ - primitives::{self, full_decaps, refresh, rekey, setup}, - MasterPublicKey, MasterSecretKey, UserSecretKey, XEnc, SHARED_SECRET_LENGTH, - }, - traits::{KemAc, PkeAc}, - AccessPolicy, Error, -}; +use std::sync::{Mutex, MutexGuard}; #[derive(Debug)] pub struct Covercrypt { diff --git a/src/core/mod.rs b/src/abe/core/mod.rs similarity index 98% rename from src/core/mod.rs rename to src/abe/core/mod.rs index 95c13866..c86ab0ae 100644 --- a/src/core/mod.rs +++ b/src/abe/core/mod.rs @@ -1,23 +1,21 @@ #![allow(non_snake_case)] +use crate::{ + abe::policy::{AccessStructure, EncryptionStatus, Right, SecurityMode}, + data_struct::{RevisionMap, RevisionVec}, + providers::{ + kem::{Kem, MlKem}, + nike::{ElGamal, Nike}, + }, + traits::Zero, + Error, +}; +use cosmian_crypto_core::{reexport::rand_core::CryptoRngCore, Sampling, SymmetricKey}; use std::{ collections::{HashMap, HashSet, LinkedList}, hash::Hash, }; -use cosmian_crypto_core::{reexport::rand_core::CryptoRngCore, SymmetricKey}; -use kem::MlKem; -use nike::ElGamal; - -use crate::{ - abe_policy::{AccessStructure, EncryptionStatus, Right}, - data_struct::{RevisionMap, RevisionVec}, - traits::{Kem, Nike, Sampling, Zero}, - Error, SecurityMode, -}; - -mod kem; -mod nike; mod serialization; #[cfg(test)] diff --git a/src/core/primitives.rs b/src/abe/core/primitives.rs similarity index 98% rename from src/core/primitives.rs rename to src/abe/core/primitives.rs index dae4acc3..7dbcbf80 100644 --- a/src/core/primitives.rs +++ b/src/abe/core/primitives.rs @@ -14,14 +14,20 @@ use cosmian_crypto_core::{ }; use crate::{ - abe_policy::{AccessStructure, EncryptionStatus, Right, SecurityMode}, - core::{ - kem::MlKem, nike::ElGamal, KmacSignature, MasterPublicKey, MasterSecretKey, RightPublicKey, - RightSecretKey, TracingSecretKey, UserId, UserSecretKey, XEnc, MIN_TRACING_LEVEL, - SHARED_SECRET_LENGTH, SIGNATURE_LENGTH, SIGNING_KEY_LENGTH, TAG_LENGTH, + abe::{ + core::{ + KmacSignature, MasterPublicKey, MasterSecretKey, RightPublicKey, RightSecretKey, + TracingSecretKey, UserId, UserSecretKey, XEnc, MIN_TRACING_LEVEL, SHARED_SECRET_LENGTH, + SIGNATURE_LENGTH, SIGNING_KEY_LENGTH, TAG_LENGTH, + }, + policy::{AccessStructure, EncryptionStatus, Right, SecurityMode}, }, data_struct::{RevisionMap, RevisionVec}, - traits::{Kem, Nike, Sampling}, + providers::{ + kem::{Kem, MlKem}, + nike::{ElGamal, Nike}, + }, + traits::Seedable, Error, }; @@ -83,7 +89,9 @@ fn verify(msk: &MasterSecretKey, usk: &UserSecretKey) -> Result<(), Error> { } fn G_hash(seed: &Secret) -> Result<::SecretKey, Error> { - Ok(<::SecretKey as Sampling>::hash(&**seed)) + Ok(<::SecretKey as Seedable< + SHARED_SECRET_LENGTH, + >>::from_seed(seed)) } fn H_hash( diff --git a/src/core/serialization/mod.rs b/src/abe/core/serialization/mod.rs similarity index 97% rename from src/core/serialization/mod.rs rename to src/abe/core/serialization/mod.rs index aaa16fdc..995a868b 100644 --- a/src/core/serialization/mod.rs +++ b/src/abe/core/serialization/mod.rs @@ -3,7 +3,7 @@ use cosmian_crypto_core::bytes_ser_de::{Deserializer, Serializable, Serializer}; use crate::{ - core::{ + abe::core::{ MasterPublicKey, MasterSecretKey, RightPublicKey, RightSecretKey, TracingPublicKey, TracingSecretKey, UserId, UserSecretKey, XEnc, }, @@ -291,15 +291,16 @@ mod tests { }; use crate::{ - abe_policy::{EncryptionStatus, Right}, - api::Covercrypt, - core::{ - primitives::{encaps, rekey, setup, update_msk, usk_keygen}, - MIN_TRACING_LEVEL, + abe::{ + api::Covercrypt, + core::{ + primitives::{encaps, rekey, setup, update_msk, usk_keygen}, + MIN_TRACING_LEVEL, + }, + policy::{AccessPolicy, EncryptionStatus, Right, SecurityMode}, + KemAc, }, test_utils::cc_keygen, - traits::KemAc, - AccessPolicy, SecurityMode, }; #[test] diff --git a/src/core/tests.rs b/src/abe/core/tests.rs similarity index 98% rename from src/core/tests.rs rename to src/abe/core/tests.rs index e038938b..581fd3e1 100644 --- a/src/core/tests.rs +++ b/src/abe/core/tests.rs @@ -3,14 +3,15 @@ use std::collections::{HashMap, HashSet}; use cosmian_crypto_core::{reexport::rand_core::SeedableRng, Aes256Gcm, CsRng}; use crate::{ - abe_policy::{AccessPolicy, EncryptionStatus, Right}, - api::Covercrypt, - core::{ - primitives::{decaps, encaps, refresh, rekey, update_msk}, - SecurityMode, + abe::{ + core::{ + primitives::{decaps, encaps, refresh, rekey, update_msk}, + SecurityMode, + }, + policy::{AccessPolicy, EncryptionStatus, Right}, + Covercrypt, KemAc, PkeAc, }, test_utils::cc_keygen, - traits::{KemAc, PkeAc}, }; use super::{ diff --git a/src/encrypted_header.rs b/src/abe/encrypted_header.rs similarity index 98% rename from src/encrypted_header.rs rename to src/abe/encrypted_header.rs index 4afc1ce5..aa6a0a34 100644 --- a/src/encrypted_header.rs +++ b/src/abe/encrypted_header.rs @@ -4,8 +4,11 @@ use cosmian_crypto_core::{ }; use crate::{ - abe_policy::AccessPolicy, api::Covercrypt, core::SHARED_SECRET_LENGTH, traits::KemAc, Error, - MasterPublicKey, UserSecretKey, XEnc, + abe::{ + core::{XEnc, SHARED_SECRET_LENGTH}, + AccessPolicy, Covercrypt, KemAc, MasterPublicKey, UserSecretKey, + }, + Error, }; /// Encrypted header holding a `Covercrypt` encapsulation of a 256-byte secret, and metadata diff --git a/src/abe/error.rs b/src/abe/error.rs new file mode 100644 index 00000000..191b08c9 --- /dev/null +++ b/src/abe/error.rs @@ -0,0 +1,56 @@ +//! Error type for the crate. + +use core::{fmt::Display, num::TryFromIntError}; + +use cosmian_crypto_core::CryptoCoreError; + +#[derive(Debug)] +pub enum Error { + Kem(String), + CryptoCoreError(CryptoCoreError), + KeyError(String), + AttributeNotFound(String), + ExistingDimension(String), + OperationNotPermitted(String), + InvalidBooleanExpression(String), + InvalidAttribute(String), + DimensionNotFound(String), + ConversionFailed(String), + Tracing(String), +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Kem(err) => write!(f, "Kyber error: {err}"), + Self::CryptoCoreError(err) => write!(f, "CryptoCore error{err}"), + Self::KeyError(err) => write!(f, "{err}"), + Self::AttributeNotFound(err) => write!(f, "attribute not found: {err}"), + Self::ExistingDimension(dimension) => { + write!(f, "dimension {dimension} already exists") + } + Self::InvalidBooleanExpression(expr_str) => { + write!(f, "invalid boolean expression: {expr_str}") + } + Self::InvalidAttribute(attr) => write!(f, "invalid attribute: {attr}"), + Self::DimensionNotFound(dim_str) => write!(f, "cannot find dimension: {dim_str}"), + Self::ConversionFailed(err) => write!(f, "Conversion failed: {err}"), + Self::OperationNotPermitted(err) => write!(f, "Operation not permitted: {err}"), + Self::Tracing(err) => write!(f, "tracing error: {err}"), + } + } +} + +impl From for Error { + fn from(e: TryFromIntError) -> Self { + Self::ConversionFailed(e.to_string()) + } +} + +impl From for Error { + fn from(e: CryptoCoreError) -> Self { + Self::CryptoCoreError(e) + } +} + +impl std::error::Error for Error {} diff --git a/src/abe_policy/mod.rs b/src/abe/policy.rs similarity index 99% rename from src/abe_policy/mod.rs rename to src/abe/policy.rs index a54e23b9..d9b8b647 100644 --- a/src/abe_policy/mod.rs +++ b/src/abe/policy.rs @@ -10,13 +10,14 @@ mod tests; pub use access_policy::AccessPolicy; pub use access_structure::AccessStructure; pub use attribute::{EncryptionStatus, QualifiedAttribute, SecurityMode}; -use cosmian_crypto_core::bytes_ser_de::Serializable; pub use dimension::{Attribute, Dimension}; pub use rights::Right; + #[cfg(any(test, feature = "test-utils"))] pub use tests::gen_structure; use crate::Error; +use cosmian_crypto_core::bytes_ser_de::Serializable; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Version { diff --git a/src/abe_policy/access_policy.rs b/src/abe/policy/access_policy.rs similarity index 99% rename from src/abe_policy/access_policy.rs rename to src/abe/policy/access_policy.rs index 4d85acad..57ce4fe8 100644 --- a/src/abe_policy/access_policy.rs +++ b/src/abe/policy/access_policy.rs @@ -10,7 +10,7 @@ use std::{ ops::{BitAnd, BitOr}, }; -use crate::{abe_policy::QualifiedAttribute, Error}; +use crate::{abe::policy::QualifiedAttribute, Error}; /// An access policy is a boolean expression of qualified attributes. #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/src/abe_policy/access_structure.rs b/src/abe/policy/access_structure.rs similarity index 99% rename from src/abe_policy/access_structure.rs rename to src/abe/policy/access_structure.rs index b206eafe..6223e0bf 100644 --- a/src/abe_policy/access_structure.rs +++ b/src/abe/policy/access_structure.rs @@ -1,7 +1,7 @@ use std::collections::{hash_map::Entry, HashMap, HashSet}; use crate::{ - abe_policy::{ + abe::policy::{ attribute::SecurityMode, AccessPolicy, Attribute, Dimension, EncryptionStatus, QualifiedAttribute, Right, Version, }, @@ -377,7 +377,7 @@ mod serialization { #[test] fn test_access_structure_serialization() { - use crate::abe_policy::gen_structure; + use crate::abe::gen_structure; use cosmian_crypto_core::bytes_ser_de::test_serialization; let mut structure = AccessStructure::new(); @@ -389,7 +389,7 @@ mod serialization { #[cfg(test)] mod tests { use super::*; - use crate::abe_policy::gen_structure; + use crate::abe::gen_structure; #[test] fn test_combine() { diff --git a/src/abe_policy/attribute.rs b/src/abe/policy/attribute.rs similarity index 100% rename from src/abe_policy/attribute.rs rename to src/abe/policy/attribute.rs diff --git a/src/abe_policy/dimension.rs b/src/abe/policy/dimension.rs similarity index 100% rename from src/abe_policy/dimension.rs rename to src/abe/policy/dimension.rs diff --git a/src/abe_policy/rights.rs b/src/abe/policy/rights.rs similarity index 100% rename from src/abe_policy/rights.rs rename to src/abe/policy/rights.rs diff --git a/src/abe_policy/tests.rs b/src/abe/policy/tests.rs similarity index 96% rename from src/abe_policy/tests.rs rename to src/abe/policy/tests.rs index 02289a57..4a96166c 100644 --- a/src/abe_policy/tests.rs +++ b/src/abe/policy/tests.rs @@ -1,10 +1,13 @@ -use crate::{abe_policy::AccessStructure, Error, SecurityMode}; +use crate::{ + abe::policy::{AccessStructure, SecurityMode}, + Error, +}; pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), Error> { policy.add_hierarchy("SEC".to_string())?; policy.add_attribute( - crate::abe_policy::QualifiedAttribute { + crate::abe::policy::QualifiedAttribute { dimension: "SEC".to_string(), name: "LOW".to_string(), }, @@ -12,7 +15,7 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), None, )?; policy.add_attribute( - crate::abe_policy::QualifiedAttribute { + crate::abe::policy::QualifiedAttribute { dimension: "SEC".to_string(), name: "MED".to_string(), }, @@ -20,7 +23,7 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), Some("LOW"), )?; policy.add_attribute( - crate::abe_policy::QualifiedAttribute { + crate::abe::policy::QualifiedAttribute { dimension: "SEC".to_string(), name: "TOP".to_string(), }, @@ -39,7 +42,7 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), .into_iter() .try_for_each(|(attribute, mode)| { policy.add_attribute( - crate::abe_policy::QualifiedAttribute { + crate::abe::policy::QualifiedAttribute { dimension: "DPT".to_string(), name: attribute.to_string(), }, @@ -60,7 +63,7 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), .into_iter() .try_for_each(|(attribute, mode)| { policy.add_attribute( - crate::abe_policy::QualifiedAttribute { + crate::abe::policy::QualifiedAttribute { dimension: "CTR".to_string(), name: attribute.to_string(), }, diff --git a/src/core/kem.rs b/src/core/kem.rs deleted file mode 100644 index f798da4a..00000000 --- a/src/core/kem.rs +++ /dev/null @@ -1,10 +0,0 @@ -#[cfg(all(feature = "mlkem-512", feature = "mlkem-768"))] -compile_error!("only one MLKEM version can be chosen at a time"); - -pub mod mlkem; - -#[cfg(feature = "mlkem-512")] -pub use mlkem::MlKem512 as MlKem; - -#[cfg(feature = "mlkem-768")] -pub use mlkem::MlKem768 as MlKem; diff --git a/src/core/nike.rs b/src/core/nike.rs deleted file mode 100644 index 6ad18b96..00000000 --- a/src/core/nike.rs +++ /dev/null @@ -1,14 +0,0 @@ -#[cfg(all(feature = "curve25519", feature = "p-256"))] -compile_error!("only one elliptic curve can be chosen at a time"); - -#[cfg(feature = "curve25519")] -mod r25519; - -#[cfg(feature = "curve25519")] -pub use r25519::R25519 as ElGamal; - -#[cfg(feature = "p-256")] -mod p256; - -#[cfg(feature = "p-256")] -pub use p256::P256 as ElGamal; diff --git a/src/lib.rs b/src/lib.rs index 406a1448..b6f2645b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,29 +8,15 @@ //! the DDH and LWE", T. Brézot, P. de Perthuis and D. Pointcheval 2023. //! [2] "A Proposal for an ISO Standard for Public Key Encryption (version 2.1)", Shoup 2001. -mod error; +#![allow(dead_code)] -mod abe_policy; -mod ae; -mod core; mod data_struct; -mod encrypted_header; - -pub mod api; -pub mod traits; - -pub use abe_policy::{AccessStructure, QualifiedAttribute, SecurityMode}; +mod error; +mod providers; +mod traits; #[cfg(any(test, feature = "test-utils"))] -pub mod test_utils; - -#[cfg(feature = "test-utils")] -pub use abe_policy::gen_structure; - -#[cfg(feature = "test-utils")] -pub use test_utils::cc_keygen; +mod test_utils; -pub use self::core::{MasterPublicKey, MasterSecretKey, UserSecretKey, XEnc}; -pub use abe_policy::AccessPolicy; -pub use encrypted_header::{CleartextHeader, EncryptedHeader}; +pub mod abe; pub use error::Error; diff --git a/src/providers.rs b/src/providers.rs new file mode 100644 index 00000000..a30f9bb4 --- /dev/null +++ b/src/providers.rs @@ -0,0 +1,3 @@ +mod ae; +pub(crate) mod kem; +pub(crate) mod nike; diff --git a/src/ae.rs b/src/providers/ae.rs similarity index 81% rename from src/ae.rs rename to src/providers/ae.rs index 90c488c1..00451927 100644 --- a/src/ae.rs +++ b/src/providers/ae.rs @@ -1,6 +1,7 @@ use cosmian_crypto_core::{ reexport::{rand_core::CryptoRngCore, zeroize::Zeroizing}, - Aes256Gcm, Dem, FixedSizeCBytes, Instantiable, Nonce, RandomFixedSizeCBytes, SymmetricKey, + Aes256Gcm, CryptoCoreError, Dem, FixedSizeCBytes, Instantiable, Nonce, RandomFixedSizeCBytes, + SymmetricKey, }; use crate::{traits::AE, Error}; @@ -23,14 +24,12 @@ impl AE<{ Self::KEY_LENGTH }> for Aes256Gcm { ctx: &[u8], ) -> Result>, Error> { if ctx.len() < Self::NONCE_LENGTH { - return Err(Error::CryptoCoreError( - cosmian_crypto_core::CryptoCoreError::DecryptionError, - )); + return Err(Error::CryptoCoreError(CryptoCoreError::DecryptionError)); } let nonce = Nonce::try_from_slice(&ctx[..Self::NONCE_LENGTH])?; Self::new(key) .decrypt(&nonce, &ctx[Self::NONCE_LENGTH..], None) - .map_err(Error::CryptoCoreError) .map(Zeroizing::new) + .map_err(Error::CryptoCoreError) } } diff --git a/src/providers/kem.rs b/src/providers/kem.rs new file mode 100644 index 00000000..0290ba76 --- /dev/null +++ b/src/providers/kem.rs @@ -0,0 +1,39 @@ +#[cfg(all(feature = "mlkem-512", feature = "mlkem-768"))] +compile_error!("only one MLKEM version can be chosen at a time"); + +use cosmian_crypto_core::reexport::rand_core::CryptoRngCore; + +pub mod mlkem; + +#[cfg(feature = "mlkem-512")] +pub use mlkem::MlKem512 as MlKem; + +#[cfg(feature = "mlkem-768")] +pub use mlkem::MlKem768 as MlKem; + +pub trait Kem { + type EncapsulationKey; + type DecapsulationKey; + type SessionKey; + type Encapsulation; + type Error: std::error::Error; + + /// Generates a new random keypair. + fn keygen( + rng: &mut impl CryptoRngCore, + ) -> Result<(Self::DecapsulationKey, Self::EncapsulationKey), Self::Error>; + + /// Generates an encapsulation of a random session key, and returns both the + /// key and its encapsulation. + fn enc( + ek: &Self::EncapsulationKey, + rng: &mut impl CryptoRngCore, + ) -> Result<(Self::SessionKey, Self::Encapsulation), Self::Error>; + + /// Attempts opening the given encapsulation. Upon failure to decapsulate, + /// returns a random session key. + fn dec( + dk: &Self::DecapsulationKey, + enc: &Self::Encapsulation, + ) -> Result; +} diff --git a/src/core/kem/mlkem.rs b/src/providers/kem/mlkem.rs similarity index 95% rename from src/core/kem/mlkem.rs rename to src/providers/kem/mlkem.rs index d3817919..854011e9 100644 --- a/src/core/kem/mlkem.rs +++ b/src/providers/kem/mlkem.rs @@ -1,8 +1,8 @@ -use crate::{core::SHARED_SECRET_LENGTH, traits::Kem, Error}; +use crate::{providers::kem::Kem, Error}; use cosmian_crypto_core::{ bytes_ser_de::{Deserializer, Serializable, Serializer}, reexport::{rand_core::CryptoRngCore, zeroize::Zeroize}, - Secret, + CryptoCoreError, Secret, }; use ml_kem::{ array::Array, @@ -10,13 +10,15 @@ use ml_kem::{ EncodedSizeUser, KemCore, }; +const SHARED_SECRET_LENGTH: usize = 32; + macro_rules! make_mlkem { ($base: ident, $ek: ident, $ek_len: literal, $dk: ident, $dk_len: literal, $enc: ident, $enc_len:literal) => { #[derive(Debug, PartialEq, Clone)] pub struct $ek(Box<::EncapsulationKey>); impl Serializable for $ek { - type Error = Error; + type Error = CryptoCoreError; fn length(&self) -> usize { $ek_len @@ -48,7 +50,7 @@ macro_rules! make_mlkem { } impl Serializable for $dk { - type Error = Error; + type Error = CryptoCoreError; fn length(&self) -> usize { $dk_len @@ -73,14 +75,14 @@ macro_rules! make_mlkem { pub struct $enc(Box::CiphertextSize>>); impl Serializable for $enc { - type Error = Error; + type Error = CryptoCoreError; fn length(&self) -> usize { $enc_len } fn write(&self, ser: &mut Serializer) -> Result { - Ok(ser.write_array(&self.0)?) + ser.write_array(&self.0) } fn read(de: &mut Deserializer) -> Result { @@ -97,9 +99,7 @@ macro_rules! make_mlkem { type EncapsulationKey = $ek; type DecapsulationKey = $dk; type SessionKey = Secret; - type Encapsulation = $enc; - type Error = Error; fn keygen( diff --git a/src/providers/nike.rs b/src/providers/nike.rs new file mode 100644 index 00000000..2a9f4830 --- /dev/null +++ b/src/providers/nike.rs @@ -0,0 +1,54 @@ +#[cfg(all(feature = "curve25519", feature = "p-256"))] +compile_error!("only one elliptic curve can be chosen at a time"); + +use crate::traits::{Group, Ring}; +use cosmian_crypto_core::{reexport::rand_core::CryptoRngCore, Sampling}; +use std::ops::{Add, Div, Mul, Sub}; + +#[cfg(all(feature = "curve25519", not(feature = "p-256")))] +mod r25519; + +#[cfg(all(feature = "curve25519", not(feature = "p-256")))] +pub use r25519::R25519 as ElGamal; + +#[cfg(all(feature = "p-256", not(feature = "curve25519")))] +mod p256; + +#[cfg(all(feature = "p-256", not(feature = "curve25519")))] +pub use p256::P256 as ElGamal; + +pub trait Nike { + type SecretKey: Sampling; + type PublicKey: for<'a> From<&'a Self::SecretKey>; + type SessionKey; + type Error: std::error::Error; + + /// Generates a new random keypair. + fn keygen( + rng: &mut impl CryptoRngCore, + ) -> Result<(Self::SecretKey, Self::PublicKey), Self::Error>; + + /// Generates the session key associated to the given keypair. + fn session_key( + sk: &Self::SecretKey, + pk: &Self::PublicKey, + ) -> Result; +} + +pub trait KeyHomomorphicNike: Nike +where + Self::PublicKey: Group, + Self::SecretKey: Ring, + Self::PublicKey: Mul, + for<'a> Self::PublicKey: Mul<&'a Self::SecretKey, Output = Self::PublicKey>, + for<'a, 'b> &'a Self::PublicKey: Add<&'b Self::PublicKey, Output = Self::PublicKey>, + for<'a, 'b> &'a Self::PublicKey: Sub<&'b Self::PublicKey, Output = Self::PublicKey>, + for<'a, 'b> &'a Self::SecretKey: Add<&'b Self::SecretKey, Output = Self::SecretKey>, + for<'a, 'b> &'a Self::SecretKey: Sub<&'b Self::SecretKey, Output = Self::SecretKey>, + for<'a, 'b> &'a Self::SecretKey: Mul<&'b Self::SecretKey, Output = Self::SecretKey>, + for<'a, 'b> &'a Self::SecretKey: Div< + &'b Self::SecretKey, + Output = Result::DivError>, + >, +{ +} diff --git a/src/core/nike/p256.rs b/src/providers/nike/p256.rs similarity index 90% rename from src/core/nike/p256.rs rename to src/providers/nike/p256.rs index ac7ca4a8..5b74c2af 100644 --- a/src/core/nike/p256.rs +++ b/src/providers/nike/p256.rs @@ -1,36 +1,25 @@ -use std::hash::Hash; -use std::iter::Sum; -use std::ops::Add; -use std::ops::AddAssign; -use std::ops::Div; -use std::ops::Mul; -use std::ops::MulAssign; -use std::ops::Sub; -use std::ops::SubAssign; - -use cosmian_crypto_core::bytes_ser_de::Deserializer; -use cosmian_crypto_core::bytes_ser_de::Serializable; -use cosmian_crypto_core::bytes_ser_de::Serializer; -use cosmian_crypto_core::reexport::tiny_keccak::Hasher; -use cosmian_crypto_core::reexport::tiny_keccak::Sha3; -use cosmian_crypto_core::reexport::zeroize::Zeroize; -use cosmian_crypto_core::CryptoCoreError; -use elliptic_curve::group::GroupEncoding; -use elliptic_curve::rand_core::CryptoRngCore; -use elliptic_curve::Field; -use elliptic_curve::PrimeField; +use crate::{ + providers::nike::{KeyHomomorphicNike, Nike}, + traits::{Group, One, Ring, Seedable, Zero}, + Error, +}; +use cosmian_crypto_core::{ + bytes_ser_de::{Deserializer, Serializable, Serializer}, + reexport::{ + tiny_keccak::{Hasher, Sha3}, + zeroize::Zeroize, + }, + CryptoCoreError, Sampling, +}; +use elliptic_curve::{group::GroupEncoding, rand_core::CryptoRngCore, Field, PrimeField}; use p256::{ProjectivePoint, Scalar}; +use std::{ + hash::Hash, + iter::Sum, + ops::{Add, AddAssign, Div, Mul, MulAssign, Sub, SubAssign}, +}; use subtle::ConstantTimeEq; -use crate::traits::Group; -use crate::traits::KeyHomomorphicNike; -use crate::traits::Nike; -use crate::traits::One; -use crate::traits::Ring; -use crate::traits::Sampling; -use crate::traits::Zero; -use crate::Error; - #[derive(Clone, Debug, PartialEq, Eq)] pub struct P256Point(ProjectivePoint); @@ -328,8 +317,10 @@ impl Sampling for P256Scalar { fn random(rng: &mut impl CryptoRngCore) -> Self { Self(Scalar::random(rng)) } +} - fn hash(seed: &[u8]) -> Self { +impl Seedable<32> for P256Scalar { + fn from_seed(seed: &[u8; 32]) -> Self { let mut i = 0u32; loop { let mut hasher = Sha3::v256(); diff --git a/src/core/nike/r25519.rs b/src/providers/nike/r25519.rs similarity index 95% rename from src/core/nike/r25519.rs rename to src/providers/nike/r25519.rs index be87bbaa..12d45a6c 100644 --- a/src/core/nike/r25519.rs +++ b/src/providers/nike/r25519.rs @@ -1,27 +1,20 @@ -use std::iter::Sum; -use std::ops::Add; -use std::ops::AddAssign; -use std::ops::Deref; -use std::ops::Div; -use std::ops::Mul; -use std::ops::MulAssign; -use std::ops::Sub; -use std::ops::SubAssign; - -use cosmian_crypto_core::bytes_ser_de::Deserializer; +use crate::{ + providers::nike::{KeyHomomorphicNike, Nike}, + traits::{Group, One, Ring, Seedable, Zero}, + Error, +}; use cosmian_crypto_core::{ - bytes_ser_de::{Serializable, Serializer}, + bytes_ser_de::{Deserializer, Serializable, Serializer}, reexport::{ rand_core::CryptoRngCore, tiny_keccak::{Hasher, Sha3}, zeroize::Zeroize, }, - CryptoCoreError, R25519PrivateKey as Scalar, R25519PublicKey as EcPoint, + CryptoCoreError, R25519PrivateKey as Scalar, R25519PublicKey as EcPoint, Sampling, }; - -use crate::{ - traits::{Group, KeyHomomorphicNike, Nike, One, Ring, Sampling, Zero}, - Error, +use std::{ + iter::Sum, + ops::{Add, AddAssign, Deref, Div, Mul, MulAssign, Sub, SubAssign}, }; #[derive(Clone, Debug, PartialEq, Eq)] @@ -305,8 +298,10 @@ impl Sampling for R25519Scalar { fn random(rng: &mut impl CryptoRngCore) -> Self { Self(Scalar::new(rng)) } +} - fn hash(seed: &[u8]) -> Self { +impl Seedable<32> for R25519Scalar { + fn from_seed(seed: &[u8; 32]) -> Self { let mut hasher = Sha3::v512(); let mut bytes = [0; 512 / 8]; hasher.update(seed); diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs index 8ad782d2..c177fb01 100644 --- a/src/test_utils/mod.rs +++ b/src/test_utils/mod.rs @@ -1,6 +1,8 @@ -use crate::{abe_policy::gen_structure, api::Covercrypt, Error, MasterPublicKey, MasterSecretKey}; - -//pub mod non_regression; +use crate::{ + abe::gen_structure, + abe::{Covercrypt, MasterPublicKey, MasterSecretKey}, + Error, +}; /// Creates the test access structure. pub fn cc_keygen( @@ -17,11 +19,9 @@ pub fn cc_keygen( mod tests { use super::*; - use crate::{ - abe_policy::{AccessPolicy, QualifiedAttribute}, - api::Covercrypt, - traits::KemAc, - EncryptedHeader, SecurityMode, + use crate::abe::{ + encrypted_header::EncryptedHeader, AccessPolicy, Covercrypt, KemAc, QualifiedAttribute, + SecurityMode, }; #[test] diff --git a/src/traits.rs b/src/traits.rs index 6cb9604d..d05a7366 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,141 +1,8 @@ use cosmian_crypto_core::{ reexport::{rand_core::CryptoRngCore, zeroize::Zeroizing}, - Secret, SymmetricKey, + SymmetricKey, }; -use std::ops::Add; -use std::ops::AddAssign; -use std::ops::Div; -use std::ops::Mul; -use std::ops::MulAssign; -use std::ops::Sub; -use std::ops::SubAssign; - -use crate::AccessPolicy; - -pub trait KemAc { - type EncapsulationKey; - type DecapsulationKey; - type Encapsulation; - type Error: std::error::Error; - - /// Generates a new encapsulation for the given access policy. - /// - /// # Error - /// - /// Returns an error if the access policy is not valid. - fn encaps( - &self, - ek: &Self::EncapsulationKey, - ap: &AccessPolicy, - ) -> Result<(Secret, Self::Encapsulation), Self::Error>; - - /// Attempts opening the given encapsulation with the given key. Returns the encapsulated - /// secret upon success or `None` if this key was not authorized to open this encapsulation. - fn decaps( - &self, - dk: &Self::DecapsulationKey, - enc: &Self::Encapsulation, - ) -> Result>, Self::Error>; -} - -pub trait AE { - type Error: std::error::Error; - - /// Encrypts the given plaintext using the given key. - fn encrypt( - rng: &mut impl CryptoRngCore, - key: &SymmetricKey, - ptx: &[u8], - ) -> Result, Self::Error>; - - /// Decrypts the given ciphertext using the given key. - /// - /// # Error - /// - /// Returns an error if the integrity of the ciphertext could not be verified. - fn decrypt( - key: &SymmetricKey, - ctx: &[u8], - ) -> Result>, Self::Error>; -} - -pub trait PkeAc> { - type EncryptionKey; - type DecryptionKey; - type Ciphertext; - type Error: std::error::Error; - - /// Encrypts the given plaintext under the given access policy. - /// - /// # Error - /// - /// Returns an error if the access policy is not valid. - fn encrypt( - &self, - ek: &Self::EncryptionKey, - ap: &AccessPolicy, - ptx: &[u8], - ) -> Result; - - /// Attempts decrypting the given ciphertext with the given key. Returns the - /// plaintext upon success, or `None` if this key was not authorized to - /// decrypt this ciphertext. - fn decrypt( - &self, - dk: &Self::DecryptionKey, - ctx: &Self::Ciphertext, - ) -> Result>>, Self::Error>; -} - -pub trait Kem { - type EncapsulationKey; - type DecapsulationKey; - type SessionKey; - type Encapsulation; - type Error: std::error::Error; - - /// Generates a new random keypair. - fn keygen( - rng: &mut impl CryptoRngCore, - ) -> Result<(Self::DecapsulationKey, Self::EncapsulationKey), Self::Error>; - - /// Generates an encapsulation of a random session key, and returns both the - /// key and its encapsulation. - fn enc( - ek: &Self::EncapsulationKey, - rng: &mut impl CryptoRngCore, - ) -> Result<(Self::SessionKey, Self::Encapsulation), Self::Error>; - - /// Attempts opening the given encapsulation. Upon failure to decapsulate, - /// returns a random session key. - fn dec( - dk: &Self::DecapsulationKey, - enc: &Self::Encapsulation, - ) -> Result; -} - -pub trait Nike { - type SecretKey: Sampling; - type PublicKey: for<'a> From<&'a Self::SecretKey>; - type SessionKey; - type Error: std::error::Error; - - /// Generates a new random keypair. - fn keygen( - rng: &mut impl CryptoRngCore, - ) -> Result<(Self::SecretKey, Self::PublicKey), Self::Error>; - - /// Generates the session key associated to the given keypair. - fn session_key( - sk: &Self::SecretKey, - pk: &Self::PublicKey, - ) -> Result; -} - -pub trait Sampling { - fn random(rng: &mut impl CryptoRngCore) -> Self; - fn hash(seed: &[u8]) -> Self; -} +use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Sub, SubAssign}; pub trait Zero { fn zero() -> Self; @@ -147,6 +14,10 @@ pub trait One { fn is_one(&self) -> bool; } +pub trait Seedable { + fn from_seed(seed: &[u8; LENGTH]) -> Self; +} + pub trait Group: Sized + Zero @@ -179,20 +50,23 @@ where type DivError; } -pub trait KeyHomomorphicNike: Nike -where - Self::PublicKey: Group, - Self::SecretKey: Ring, - Self::PublicKey: Mul, - for<'a> Self::PublicKey: Mul<&'a Self::SecretKey, Output = Self::PublicKey>, - for<'a, 'b> &'a Self::PublicKey: Add<&'b Self::PublicKey, Output = Self::PublicKey>, - for<'a, 'b> &'a Self::PublicKey: Sub<&'b Self::PublicKey, Output = Self::PublicKey>, - for<'a, 'b> &'a Self::SecretKey: Add<&'b Self::SecretKey, Output = Self::SecretKey>, - for<'a, 'b> &'a Self::SecretKey: Sub<&'b Self::SecretKey, Output = Self::SecretKey>, - for<'a, 'b> &'a Self::SecretKey: Mul<&'b Self::SecretKey, Output = Self::SecretKey>, - for<'a, 'b> &'a Self::SecretKey: Div< - &'b Self::SecretKey, - Output = Result::DivError>, - >, -{ +pub trait AE { + type Error: std::error::Error; + + /// Encrypts the given plaintext using the given key. + fn encrypt( + rng: &mut impl CryptoRngCore, + key: &SymmetricKey, + ptx: &[u8], + ) -> Result, Self::Error>; + + /// Decrypts the given ciphertext using the given key. + /// + /// # Error + /// + /// Returns an error if the integrity of the ciphertext could not be verified. + fn decrypt( + key: &SymmetricKey, + ctx: &[u8], + ) -> Result>, Self::Error>; } From 50b43a0de4c417842025b6c2d694ffa32d311d74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Thu, 15 Jan 2026 19:21:05 +0100 Subject: [PATCH 02/23] WIP: implement wrapper around Covercrypt --- Cargo.toml | 2 +- src/abe.rs | 9 +- src/abe/api.rs | 34 +-- src/abe/core/mod.rs | 92 ++++---- src/abe/core/primitives.rs | 157 ++++++++------ src/base.rs | 248 ++++++++++++++++++++++ src/lib.rs | 7 +- src/providers.rs | 14 +- src/providers/ae.rs | 35 --- src/providers/kem.rs | 29 --- src/providers/kem/mlkem.rs | 17 +- src/providers/nike.rs | 52 +---- src/providers/nike/r25519.rs | 398 ----------------------------------- src/traits.rs | 71 ------- 14 files changed, 440 insertions(+), 725 deletions(-) create mode 100644 src/base.rs delete mode 100644 src/providers/ae.rs delete mode 100644 src/providers/nike/r25519.rs diff --git a/Cargo.toml b/Cargo.toml index ce0c398f..2dc300f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ curve25519 = ["cosmian_crypto_core/curve25519"] test-utils = [] [dependencies] -cosmian_crypto_core = { version = "10.3", default-features = false, features = [ +cosmian_crypto_core = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/generic-pke-implementation", default-features = false, features = [ "aes", "ser", "sha3" diff --git a/src/abe.rs b/src/abe.rs index 3e18b2f2..41b31d04 100644 --- a/src/abe.rs +++ b/src/abe.rs @@ -1,11 +1,11 @@ mod api; -mod core; +pub mod core; mod policy; pub mod encrypted_header; pub use api::Covercrypt; -pub use core::{MasterPublicKey, MasterSecretKey, UserSecretKey}; +pub use core::{MasterPublicKey, MasterSecretKey, UserSecretKey, XEnc}; pub use policy::{ AccessPolicy, AccessStructure, Attribute, Dimension, EncryptionStatus, QualifiedAttribute, SecurityMode, @@ -14,8 +14,7 @@ pub use policy::{ #[cfg(any(test, feature = "test-utils"))] pub use policy::gen_structure; -use crate::traits::AE; -use cosmian_crypto_core::{reexport::zeroize::Zeroizing, Secret}; +use cosmian_crypto_core::{traits::AE, Secret}; pub trait KemAc { type EncapsulationKey; @@ -71,5 +70,5 @@ pub trait PkeAc> { &self, dk: &Self::DecryptionKey, ctx: &Self::Ciphertext, - ) -> Result>>, Self::Error>; + ) -> Result, Self::Error>; } diff --git a/src/abe/api.rs b/src/abe/api.rs index b4c6aeee..392fb152 100644 --- a/src/abe/api.rs +++ b/src/abe/api.rs @@ -1,31 +1,31 @@ use crate::{ abe::{ core::{ - primitives::{self, full_decaps, prune, refresh, rekey, setup, update_msk, usk_keygen}, + primitives::{ + self, master_decaps, prune, refresh, rekey, setup, update_msk, usk_keygen, + }, MasterPublicKey, MasterSecretKey, UserSecretKey, XEnc, MIN_TRACING_LEVEL, SHARED_SECRET_LENGTH, }, policy::AccessPolicy, KemAc, PkeAc, }, - traits::AE, Error, }; use cosmian_crypto_core::{ - reexport::{rand_core::SeedableRng, zeroize::Zeroizing}, - CsRng, Secret, SymmetricKey, + reexport::rand_core::SeedableRng, traits::AE, CsRng, Secret, SymmetricKey, }; -use std::sync::{Mutex, MutexGuard}; +use std::sync::{Arc, Mutex, MutexGuard}; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Covercrypt { - rng: Mutex, + rng: Arc>, } impl Default for Covercrypt { fn default() -> Self { Self { - rng: Mutex::new(CsRng::from_entropy()), + rng: Arc::new(Mutex::new(CsRng::from_entropy())), } } } @@ -156,7 +156,7 @@ impl Covercrypt { mpk: &MasterPublicKey, encapsulation: &XEnc, ) -> Result<(Secret<32>, XEnc), Error> { - let (_ss, rights) = full_decaps(msk, encapsulation)?; + let (_ss, rights) = master_decaps(msk, encapsulation, true)?; primitives::encaps( &mut *self.rng.lock().expect("Mutex lock failed!"), mpk, @@ -192,12 +192,13 @@ impl KemAc for Covercrypt { } } -impl> PkeAc - for Covercrypt +impl> PkeAc for Covercrypt +where + Error: From, { type EncryptionKey = MasterPublicKey; type DecryptionKey = UserSecretKey; - type Ciphertext = (XEnc, Vec); + type Ciphertext = (XEnc, E::Ciphertext); type Error = Error; fn encrypt( @@ -210,19 +211,20 @@ impl> PkeAc::derive(&seed, b"Covercrypt AE key")?; + let ctx = E::encrypt(&key, ptx, &mut *rng)?; + Ok((enc, ctx)) } fn decrypt( &self, usk: &Self::DecryptionKey, ctx: &Self::Ciphertext, - ) -> Result>>, Self::Error> { + ) -> Result, Self::Error> { self.decaps(usk, &ctx.0)? .map(|seed| { let key = SymmetricKey::derive(&seed, b"Covercrypt AE key")?; - E::decrypt(&key, &ctx.1) + E::decrypt(&key, &ctx.1).map_err(Self::Error::from) }) .transpose() } diff --git a/src/abe/core/mod.rs b/src/abe/core/mod.rs index c86ab0ae..b7b6aa4c 100644 --- a/src/abe/core/mod.rs +++ b/src/abe/core/mod.rs @@ -3,14 +3,14 @@ use crate::{ abe::policy::{AccessStructure, EncryptionStatus, Right, SecurityMode}, data_struct::{RevisionMap, RevisionVec}, - providers::{ - kem::{Kem, MlKem}, - nike::{ElGamal, Nike}, - }, - traits::Zero, + providers::{ElGamal, MlKem}, Error, }; -use cosmian_crypto_core::{reexport::rand_core::CryptoRngCore, Sampling, SymmetricKey}; +use cosmian_crypto_core::{ + reexport::rand_core::CryptoRngCore, + traits::{Sampling, Zero, KEM, NIKE}, + SymmetricKey, +}; use std::{ collections::{HashMap, HashSet, LinkedList}, hash::Hash, @@ -57,14 +57,14 @@ pub const MIN_TRACING_LEVEL: usize = 1; #[derive(Clone, Debug, PartialEq)] enum RightSecretKey { PreQuantum { - sk: ::SecretKey, + sk: ::SecretKey, }, PostQuantum { - dk: ::DecapsulationKey, + dk: >::DecapsulationKey, }, Hybridized { - sk: ::SecretKey, - dk: ::DecapsulationKey, + sk: ::SecretKey, + dk: >::DecapsulationKey, }, } @@ -74,7 +74,7 @@ impl RightSecretKey { fn random(rng: &mut impl CryptoRngCore, security_mode: SecurityMode) -> Result { match security_mode { SecurityMode::PreQuantum => { - let sk = ::SecretKey::random(rng); + let sk = ::SecretKey::random(rng); Ok(Self::PreQuantum { sk }) } SecurityMode::PostQuantum => { @@ -82,7 +82,7 @@ impl RightSecretKey { Ok(Self::PostQuantum { dk }) } SecurityMode::Hybridized => { - let sk = ::SecretKey::random(rng); + let sk = ::SecretKey::random(rng); let (dk, _) = MlKem::keygen(rng)?; Ok(Self::Hybridized { sk, dk }) } @@ -91,7 +91,7 @@ impl RightSecretKey { /// Generates the associated right public key. #[must_use] - fn cpk(&self, h: &::PublicKey) -> RightPublicKey { + fn cpk(&self, h: &::PublicKey) -> RightPublicKey { match self { Self::Hybridized { sk, dk } => RightPublicKey::Hybridized { H: h * sk, @@ -122,20 +122,20 @@ impl RightSecretKey { (Self::Hybridized { dk, .. }, SecurityMode::PostQuantum) => Self::PostQuantum { dk }, (Self::Hybridized { sk, dk }, SecurityMode::Hybridized) => Self::Hybridized { sk, dk }, (Self::PostQuantum { .. }, SecurityMode::PreQuantum) => Self::PostQuantum { - dk: ::keygen(rng)?.0, + dk: >::keygen(rng)?.0, }, (Self::PostQuantum { dk }, SecurityMode::PostQuantum) => Self::PostQuantum { dk }, (Self::PostQuantum { dk }, SecurityMode::Hybridized) => Self::Hybridized { - sk: ::keygen(rng)?.0, + sk: ::keygen(rng)?.0, dk, }, (Self::PreQuantum { sk }, SecurityMode::PreQuantum) => Self::PreQuantum { sk }, (Self::PreQuantum { .. }, SecurityMode::PostQuantum) => Self::PostQuantum { - dk: ::keygen(rng)?.0, + dk: >::keygen(rng)?.0, }, (Self::PreQuantum { sk }, SecurityMode::Hybridized) => Self::Hybridized { sk, - dk: ::keygen(rng)?.0, + dk: >::keygen(rng)?.0, }, }) } @@ -149,14 +149,14 @@ impl RightSecretKey { #[derive(Clone, Debug, PartialEq)] enum RightPublicKey { PreQuantum { - H: ::PublicKey, + H: ::PublicKey, }, PostQuantum { - ek: ::EncapsulationKey, + ek: >::EncapsulationKey, }, Hybridized { - H: ::PublicKey, - ek: ::EncapsulationKey, + H: ::PublicKey, + ek: >::EncapsulationKey, }, } @@ -173,7 +173,7 @@ impl RightPublicKey { /// Covercrypt user IDs are used to make user keys unique and traceable. #[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] -struct UserId(LinkedList<::SecretKey>); +struct UserId(LinkedList<::SecretKey>); impl UserId { /// Returns the tracing level of the USK. @@ -181,7 +181,7 @@ impl UserId { self.0.len() - 1 } - fn iter(&self) -> impl Iterator::SecretKey> { + fn iter(&self) -> impl Iterator::SecretKey> { self.0.iter() } } @@ -202,16 +202,16 @@ impl UserId { /// - the set of known user IDs. #[derive(Debug, PartialEq, Eq)] struct TracingSecretKey { - s: ::SecretKey, - tracers: LinkedList<(::SecretKey, ::PublicKey)>, + s: ::SecretKey, + tracers: LinkedList<(::SecretKey, ::PublicKey)>, users: HashSet, } impl TracingSecretKey { fn new_with_level(level: usize, rng: &mut impl CryptoRngCore) -> Result { - let s = ::SecretKey::random(rng); + let s = ::SecretKey::random(rng); let tracers = (0..=level) - .map(|_| ElGamal::keygen(rng)) + .map(|_| ::keygen(rng)) .collect::>()?; let users = HashSet::new(); @@ -225,14 +225,14 @@ impl TracingSecretKey { /// Generates a new tracer. Returns the associated trap. fn _increase_tracing(&mut self, rng: &mut impl CryptoRngCore) -> Result<(), Error> { - self.tracers.push_back(ElGamal::keygen(rng)?); + self.tracers.push_back(::keygen(rng)?); Ok(()) } /// Drops the oldest tracer and returns it. fn _decrease_tracing( &mut self, - ) -> Result<(::SecretKey, ::PublicKey), Error> { + ) -> Result<(::SecretKey, ::PublicKey), Error> { if self.tracing_level() == MIN_TRACING_LEVEL { Err(Error::OperationNotPermitted(format!( "tracing level cannot be lower than {MIN_TRACING_LEVEL}" @@ -287,7 +287,7 @@ impl TracingSecretKey { } /// Returns the binding points. - fn binding_point(&self) -> ::PublicKey { + fn binding_point(&self) -> ::PublicKey { (&self.s).into() } @@ -299,7 +299,7 @@ impl TracingSecretKey { .tracers .iter() .take(self.tracers.len() - 1) - .map(|_| ::SecretKey::random(rng)) + .map(|_| ::SecretKey::random(rng)) .collect::>(); let last_marker = ((&self.s @@ -308,7 +308,7 @@ impl TracingSecretKey { .iter() .zip(markers.iter()) .map(|((sk_i, _), a_i)| sk_i * a_i) - .fold(::SecretKey::zero(), |acc, x_i| acc + x_i)) + .fold(::SecretKey::zero(), |acc, x_i| acc + x_i)) / last_tracer)?; markers.push_back(last_marker); @@ -356,8 +356,8 @@ impl TracingSecretKey { } /// Covercrypt tracing public key. -#[derive(Debug, PartialEq, Eq, Default)] -struct TracingPublicKey(LinkedList<::PublicKey>); +#[derive(Debug, Clone, PartialEq, Eq, Default)] +struct TracingPublicKey(LinkedList<::PublicKey>); impl TracingPublicKey { /// Returns the tracing level tracing of this key. @@ -401,7 +401,7 @@ impl MasterSecretKey { }) } - fn tracing_points(&self) -> impl IntoIterator::PublicKey> { + fn tracing_points(&self) -> impl IntoIterator::PublicKey> { self.tsk.tracers.iter().map(|(_, P)| P) } @@ -434,7 +434,7 @@ impl MasterSecretKey { /// - the tracing public key; /// - the public keys for each right in Omega; /// - the access structure. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct MasterPublicKey { tpk: TracingPublicKey, encryption_keys: HashMap, @@ -449,7 +449,7 @@ impl MasterPublicKey { /// Generates traps for the given scalar. // TODO: find a better concept. - fn set_traps(&self, r: &::SecretKey) -> Vec<::PublicKey> { + fn set_traps(&self, r: &::SecretKey) -> Vec<::PublicKey> { self.tpk.0.iter().map(|Pi| Pi * r).collect() } @@ -506,7 +506,7 @@ impl MasterPublicKey { #[derive(Clone, Debug, PartialEq)] pub struct UserSecretKey { id: UserId, - ps: Vec<::PublicKey>, + ps: Vec<::PublicKey>, secrets: RevisionVec, signature: Option, } @@ -522,7 +522,7 @@ impl UserSecretKey { self.secrets.len() } - fn tracing_points(&self) -> &[::PublicKey] { + fn tracing_points(&self) -> &[::PublicKey] { &self.ps } } @@ -539,17 +539,23 @@ impl UserSecretKey { pub enum XEnc { PreQuantum { tag: Tag, - c: Vec<::PublicKey>, + c: Vec<::PublicKey>, encapsulations: Vec<[u8; SHARED_SECRET_LENGTH]>, }, PostQuantum { tag: Tag, - encapsulations: Vec<(::Encapsulation, [u8; SHARED_SECRET_LENGTH])>, + encapsulations: Vec<( + >::Encapsulation, + [u8; SHARED_SECRET_LENGTH], + )>, }, Hybridized { tag: Tag, - c: Vec<::PublicKey>, - encapsulations: Vec<(::Encapsulation, [u8; SHARED_SECRET_LENGTH])>, + c: Vec<::PublicKey>, + encapsulations: Vec<( + >::Encapsulation, + [u8; SHARED_SECRET_LENGTH], + )>, }, } diff --git a/src/abe/core/primitives.rs b/src/abe/core/primitives.rs index 7dbcbf80..8eb91d41 100644 --- a/src/abe/core/primitives.rs +++ b/src/abe/core/primitives.rs @@ -1,18 +1,3 @@ -use std::{ - collections::{HashMap, HashSet, LinkedList}, - mem::take, -}; - -use cosmian_crypto_core::{ - bytes_ser_de::Serializable, - reexport::{ - rand_core::{CryptoRngCore, RngCore}, - tiny_keccak::{Hasher, Kmac, Sha3}, - zeroize::Zeroize, - }, - RandomFixedSizeCBytes, Secret, SymmetricKey, -}; - use crate::{ abe::{ core::{ @@ -23,13 +8,23 @@ use crate::{ policy::{AccessStructure, EncryptionStatus, Right, SecurityMode}, }, data_struct::{RevisionMap, RevisionVec}, - providers::{ - kem::{Kem, MlKem}, - nike::{ElGamal, Nike}, - }, - traits::Seedable, + providers::{ElGamal, MlKem}, Error, }; +use cosmian_crypto_core::{ + bytes_ser_de::Serializable, + reexport::{ + rand_core::{CryptoRngCore, RngCore}, + tiny_keccak::{Hasher, Kmac, Sha3}, + zeroize::Zeroize, + }, + traits::{Seedable, KEM, NIKE}, + RandomFixedSizeCBytes, Secret, SymmetricKey, +}; +use std::{ + collections::{HashMap, HashSet, LinkedList}, + mem::take, +}; fn xor_2(lhs: &[u8; LENGTH], rhs: &[u8; LENGTH]) -> [u8; LENGTH] { let mut out = [0; LENGTH]; @@ -88,15 +83,15 @@ fn verify(msk: &MasterSecretKey, usk: &UserSecretKey) -> Result<(), Error> { } } -fn G_hash(seed: &Secret) -> Result<::SecretKey, Error> { - Ok(<::SecretKey as Seedable< +fn G_hash(seed: &Secret) -> Result<::SecretKey, Error> { + Ok(<::SecretKey as Seedable< SHARED_SECRET_LENGTH, >>::from_seed(seed)) } fn H_hash( - K1: Option<&::PublicKey>, - K2: Option<&Secret>, + K1: Option<&::PublicKey>, + K2: Option<&SymmetricKey>, T: &Secret, ) -> Result, Error> { // Additional check to enforce the constraint on the SHARED_SECRET_LENGTH @@ -136,8 +131,10 @@ fn J_hash( } fn generate_T<'a>( - c: Option<&[::PublicKey]>, - encapsulations: Option::Encapsulation>>, + c: Option<&[::PublicKey]>, + encapsulations: Option< + impl IntoIterator>::Encapsulation>, + >, ) -> Result, Error> { let mut hasher = Sha3::v256(); let mut T = Secret::new(); @@ -220,12 +217,12 @@ pub fn usk_keygen( /// marker c, ElGamal random r and subkeys. fn h_encaps<'a>( S: Secret, - c: Vec<::PublicKey>, - r: ::SecretKey, + c: Vec<::PublicKey>, + r: ::SecretKey, subkeys: impl IntoIterator< Item = ( - &'a ::PublicKey, - &'a ::EncapsulationKey, + &'a ::PublicKey, + &'a >::EncapsulationKey, ), >, rng: &mut impl CryptoRngCore, @@ -233,7 +230,7 @@ fn h_encaps<'a>( let encs = subkeys .into_iter() .map(|(H, ek)| { - let K1 = ElGamal::session_key(&r, H)?; + let K1 = ElGamal::shared_secret(&r, H)?; let (K2, E) = MlKem::enc(ek, rng)?; Ok((K1, K2, E)) }) @@ -268,7 +265,7 @@ fn h_encaps<'a>( /// subkeys. fn post_quantum_encaps<'a>( S: Secret, - subkeys: impl IntoIterator::EncapsulationKey>, + subkeys: impl IntoIterator>::EncapsulationKey>, rng: &mut impl CryptoRngCore, ) -> Result<(Secret, XEnc), Error> { let encs = subkeys @@ -303,16 +300,16 @@ fn post_quantum_encaps<'a>( /// marker c, ElGamal random r and subkeys. fn pre_quantum_encaps<'a>( S: Secret, - c: Vec<::PublicKey>, - r: ::SecretKey, - subkeys: impl IntoIterator::PublicKey>, + c: Vec<::PublicKey>, + r: ::SecretKey, + subkeys: impl IntoIterator::PublicKey>, ) -> Result<(Secret, XEnc), Error> { let T = generate_T(Some(&c), None::>)?; let encapsulations = subkeys .into_iter() .map(|H| -> Result<_, _> { - let K1 = ElGamal::session_key(&r, H)?; + let K1 = ElGamal::shared_secret(&r, H)?; let F = xor_2(&S, &*H_hash(Some(&K1), None, &T)?); Ok(F) }) @@ -398,16 +395,16 @@ pub fn encaps( #[allow(clippy::too_many_arguments)] fn attempt_pre_quantum_decaps<'a>( secret: &RightSecretKey, - A: &::PublicKey, + A: &::PublicKey, U: &Secret, T: &Secret, F: &[u8; 32], - c: &[::PublicKey], + c: &[::PublicKey], tag: &[u8; TAG_LENGTH], - tracing_points: impl IntoIterator::PublicKey>, + tracing_points: impl IntoIterator::PublicKey>, ) -> Result>, Error> { if let RightSecretKey::PreQuantum { sk } = secret { - let mut K1 = ElGamal::session_key(sk, A)?; + let mut K1 = ElGamal::shared_secret(sk, A)?; let S = xor_in_place(H_hash(Some(&K1), None, T)?, F); K1.zeroize(); let (tag_ij, ss) = J_hash(&S, U); @@ -429,17 +426,17 @@ fn attempt_pre_quantum_decaps<'a>( #[allow(clippy::too_many_arguments)] fn attempt_hybridized_decaps<'a>( secret: &RightSecretKey, - A: &::PublicKey, + A: &::PublicKey, U: &Secret, T: &Secret, - E: &::Encapsulation, + E: &>::Encapsulation, F: &[u8; 32], - c: &[::PublicKey], + c: &[::PublicKey], tag: &[u8; TAG_LENGTH], - tracing_points: impl IntoIterator::PublicKey>, + tracing_points: impl IntoIterator::PublicKey>, ) -> Result>, Error> { if let RightSecretKey::Hybridized { sk, dk } = secret { - let mut K1 = ElGamal::session_key(sk, A)?; + let mut K1 = ElGamal::shared_secret(sk, A)?; let K2 = MlKem::dec(dk, E)?; let S_ij = xor_in_place(H_hash(Some(&K1), Some(&K2), T)?, F); let (tag_ij, ss) = J_hash(&S_ij, U); @@ -463,7 +460,7 @@ fn attempt_post_quantum_decaps( secret: &RightSecretKey, U: &Secret, T: &Secret, - E: &::Encapsulation, + E: &>::Encapsulation, F: &[u8; 32], tag: &[u8; TAG_LENGTH], ) -> Result>, Error> { @@ -487,20 +484,23 @@ pub fn decaps( ) -> Result>, Error> { fn generate_tracing_closure( usk: &UserSecretKey, - c: &[::PublicKey], - ) -> ::PublicKey { + c: &[::PublicKey], + ) -> ::PublicKey { usk.id .iter() .zip(c.iter()) .map(|(marker, trap)| trap * marker) - .sum::<::PublicKey>() + .sum::<::PublicKey>() } fn partial_post_quantum_decaps( rng: &mut impl CryptoRngCore, usk: &UserSecretKey, tag: &[u8; TAG_LENGTH], - encs: &[(::Encapsulation, [u8; SHARED_SECRET_LENGTH])], + encs: &[( + >::Encapsulation, + [u8; SHARED_SECRET_LENGTH], + )], ) -> Result>, Error> { let T = generate_T(None, Some(encs.iter().map(|(E, _)| E)))?; let U = generate_U(&T, encs.iter().map(|(_, F)| F)); @@ -530,9 +530,12 @@ pub fn decaps( fn partial_hybridized_decaps( rng: &mut impl CryptoRngCore, usk: &UserSecretKey, - c: &[::PublicKey], + c: &[::PublicKey], tag: &[u8; TAG_LENGTH], - encs: &[(::Encapsulation, [u8; SHARED_SECRET_LENGTH])], + encs: &[( + >::Encapsulation, + [u8; SHARED_SECRET_LENGTH], + )], ) -> Result>, Error> { let A = generate_tracing_closure(usk, c); let T = generate_T(Some(c), Some(encs.iter().map(|(E, _)| E)))?; @@ -573,7 +576,7 @@ pub fn decaps( fn partial_pre_quantum_decaps( rng: &mut impl CryptoRngCore, usk: &UserSecretKey, - c: &[::PublicKey], + c: &[::PublicKey], tag: &[u8; TAG_LENGTH], encs: &Vec<[u8; SHARED_SECRET_LENGTH]>, ) -> Result>, Error> { @@ -632,9 +635,10 @@ pub fn decaps( /// Recover the encapsulated shared secret and set of rights used in the /// encapsulation. -pub fn full_decaps( +pub fn master_decaps( msk: &MasterSecretKey, encapsulation: &XEnc, + full: bool, ) -> Result<(Secret, HashSet), Error> { /// Opens the given encapsulation with the provided secrets. Returns both /// the encapsulated secret and the right associated to the first secret @@ -656,8 +660,8 @@ pub fn full_decaps( fn generate_tracing_closure( msk: &MasterSecretKey, - c: &[::PublicKey], - ) -> Result<::PublicKey, Error> { + c: &[::PublicKey], + ) -> Result<::PublicKey, Error> { let c_0 = c .first() .ok_or_else(|| Error::Kem("invalid encapsulation: C is empty".to_string()))?; @@ -671,11 +675,12 @@ pub fn full_decaps( Ok(c_0 * &(&msk.tsk.s / t_0)?) } - fn full_pre_quantum_decapsulation( + fn pre_quantum_decapsulation( msk: &MasterSecretKey, tag: &[u8; TAG_LENGTH], - c: &[::PublicKey], + c: &[::PublicKey], encapsulations: &[[u8; 32]], + full: bool, ) -> Result<(Secret, HashSet), Error> { let A = generate_tracing_closure(msk, c)?; let T = generate_T(Some(c), None::>)?; @@ -704,6 +709,10 @@ pub fn full_decaps( secrets.remove(&right); enc_ss = Some(ss); rights.insert(right); + + if !full { + break; + } } enc_ss @@ -713,10 +722,14 @@ pub fn full_decaps( .ok_or_else(|| Error::Kem("empty encapsulation".to_string())) } - fn full_post_quantum_decapsulation( + fn post_quantum_decapsulation( msk: &MasterSecretKey, tag: &[u8; TAG_LENGTH], - encapsulations: &[(::Encapsulation, [u8; SHARED_SECRET_LENGTH])], + encapsulations: &[( + >::Encapsulation, + [u8; SHARED_SECRET_LENGTH], + )], + full: bool, ) -> Result<(Secret, HashSet), Error> { let T = generate_T(None, Some(encapsulations.iter().map(|(E, _)| E)))?; let U = generate_U(&T, encapsulations.iter().map(|(_, F)| F)); @@ -742,6 +755,10 @@ pub fn full_decaps( secrets.remove(&right); enc_ss = Some(ss); rights.insert(right); + + if !full { + break; + } } enc_ss @@ -751,11 +768,15 @@ pub fn full_decaps( .ok_or_else(|| Error::Kem("empty encapsulation".to_string())) } - fn full_hybrid_decapsulation( + fn hybrid_decapsulation( msk: &MasterSecretKey, tag: &[u8; TAG_LENGTH], - c: &[::PublicKey], - encapsulations: &[(::Encapsulation, [u8; SHARED_SECRET_LENGTH])], + c: &[::PublicKey], + encapsulations: &[( + >::Encapsulation, + [u8; SHARED_SECRET_LENGTH], + )], + full: bool, ) -> Result<(Secret, HashSet), Error> { let A = generate_tracing_closure(msk, c)?; let T = generate_T(Some(c), Some(encapsulations.iter().map(|(E, _)| E)))?; @@ -783,6 +804,10 @@ pub fn full_decaps( secrets.remove(&right); enc_ss = Some(ss); rights.insert(right); + + if !full { + break; + } } enc_ss @@ -797,16 +822,16 @@ pub fn full_decaps( tag, c, encapsulations, - } => full_pre_quantum_decapsulation(msk, tag, c, encapsulations), + } => pre_quantum_decapsulation(msk, tag, c, encapsulations, full), XEnc::PostQuantum { tag, encapsulations, - } => full_post_quantum_decapsulation(msk, tag, encapsulations), + } => post_quantum_decapsulation(msk, tag, encapsulations, full), XEnc::Hybridized { tag, c, encapsulations, - } => full_hybrid_decapsulation(msk, tag, c, encapsulations), + } => hybrid_decapsulation(msk, tag, c, encapsulations, full), } } diff --git a/src/base.rs b/src/base.rs new file mode 100644 index 00000000..cf2a12a0 --- /dev/null +++ b/src/base.rs @@ -0,0 +1,248 @@ +use super::*; +use crate::{ + abe::{AccessPolicy, KemAc}, + providers::{MlKem, PreQuantumKem}, +}; +use cosmian_crypto_core::{reexport::rand_core::CryptoRngCore, traits::KEM, SymmetricKey}; + +#[derive(Debug)] +pub enum AbeDKey { + Master(abe::Covercrypt, abe::MasterSecretKey), + User(abe::Covercrypt, abe::UserSecretKey), +} + +#[derive(Debug)] +pub enum DKey { + AbeScheme(AbeDKey), + PreQuantum(>::DecapsulationKey), + PostQuantum(>::DecapsulationKey), +} + +impl DKey { + pub fn access_structure(&mut self) -> Result<&mut abe::AccessStructure, Error> { + match self { + DKey::AbeScheme(AbeDKey::Master(_, msk)) => Ok(&mut msk.access_structure), + _ => Err(Error::KeyError( + "no access structure associated to non-ABE key type".to_string(), + )), + } + } + + pub fn update_msk(&mut self) -> Result { + match self { + DKey::AbeScheme(AbeDKey::Master(cc, msk)) => { + let mpk = cc.update_msk(msk)?; + Ok(EKey::AbeScheme(cc.clone(), mpk, None)) + } + _ => Err(Error::KeyError( + "cannot update non ABE master keys".to_string(), + )), + } + } + + pub fn rekey(&mut self, ap: &abe::AccessPolicy) -> Result { + match self { + DKey::AbeScheme(AbeDKey::Master(cc, msk)) => { + let mpk = cc.rekey(msk, ap)?; + Ok(EKey::AbeScheme(cc.clone(), mpk, None)) + } + _ => Err(Error::KeyError( + "cannot rekey non ABE master keys".to_string(), + )), + } + } + + pub fn prune_master_key(&mut self, ap: &abe::AccessPolicy) -> Result { + match self { + DKey::AbeScheme(AbeDKey::Master(cc, msk)) => { + let mpk = cc.prune_master_secret_key(msk, ap)?; + Ok(EKey::AbeScheme(cc.clone(), mpk, None)) + } + _ => Err(Error::KeyError( + "cannot prune non ABE master keys".to_string(), + )), + } + } + + pub fn generate_user_secret_key(&mut self, ap: &abe::AccessPolicy) -> Result { + match self { + DKey::AbeScheme(AbeDKey::Master(cc, msk)) => { + let usk = cc.generate_user_secret_key(msk, ap)?; + Ok(DKey::AbeScheme(AbeDKey::User(cc.clone(), usk))) + } + _ => Err(Error::KeyError( + "cannot prune non ABE master keys".to_string(), + )), + } + } + + pub fn refresh_user_secret_key( + &mut self, + usk: &mut DKey, + keep_old_secrets: bool, + ) -> Result<(), Error> { + match (self, usk) { + (DKey::AbeScheme(AbeDKey::Master(cc, msk)), DKey::AbeScheme(AbeDKey::User(_, usk))) => { + cc.refresh_usk(msk, usk, keep_old_secrets) + } + _ => Err(Error::KeyError( + "cannot refresh user secret key: invalid key types".to_string(), + )), + } + } + + pub fn recaps( + &mut self, + mpk: &EKey, + enc: &Enc, + ) -> Result<(SymmetricKey<{ ConfigurableKEM::KEY_LENGTH }>, Enc), Error> { + match (self, mpk, enc) { + ( + DKey::AbeScheme(AbeDKey::Master(cc, msk)), + EKey::AbeScheme(_, mpk, _), + Enc::AbeScheme(enc), + ) => { + let (ss, enc) = cc.recaps(msk, mpk, enc)?; + Ok((SymmetricKey::from(ss), Enc::AbeScheme(enc))) + } + _ => Err(Error::KeyError( + "cannot refresh user secret key: invalid key types".to_string(), + )), + } + } +} + +#[derive(Debug, Clone)] +pub enum EKey { + AbeScheme(abe::Covercrypt, abe::MasterPublicKey, Option), + PreQuantum(>::EncapsulationKey), + PostQuantum(>::EncapsulationKey), +} + +impl EKey { + /// Sets the encapsulation key to use the provided access polity. + pub fn set_access_policy(&mut self, access_policy: abe::AccessPolicy) -> Result<(), Error> { + match self { + Self::AbeScheme(_, _, ap) => { + *ap = Some(access_policy); + Ok(()) + } + _ => Err(Error::KeyError( + "cannot set access policy for non-ABE encapsulation keys".to_string(), + )), + } + } +} + +#[derive(Debug, Clone)] +pub enum Enc { + AbeScheme(abe::XEnc), + PreQuantum(>::Encapsulation), + PostQuantum(>::Encapsulation), +} + +#[derive(Debug, Clone)] +pub enum Configuration { + AbeScheme, + PreQuantum, + PostQuantum, +} + +impl Configuration { + pub fn keygen(&self, rng: &mut impl CryptoRngCore) -> Result<(DKey, EKey), Error> { + match self { + Self::AbeScheme => { + let cc = abe::Covercrypt::default(); + let (msk, mpk) = cc.setup()?; + Ok(( + DKey::AbeScheme(AbeDKey::Master(cc.clone(), msk)), + EKey::AbeScheme(cc, mpk, None), + )) + } + Self::PreQuantum => { + let (dk, ek) = PreQuantumKem::keygen(rng)?; + Ok((DKey::PreQuantum(dk), EKey::PreQuantum(ek))) + } + Self::PostQuantum => { + let (dk, ek) = MlKem::keygen(rng)?; + Ok((DKey::PostQuantum(dk), EKey::PostQuantum(ek))) + } + } + } +} + +#[derive(Debug, Clone)] +pub struct ConfigurableKEM; + +impl KEM<32> for ConfigurableKEM { + type Encapsulation = Enc; + + type EncapsulationKey = EKey; + + type DecapsulationKey = DKey; + + type Error = Error; + + fn keygen( + _rng: &mut impl CryptoRngCore, + ) -> Result<(Self::DecapsulationKey, Self::EncapsulationKey), Self::Error> { + Err(Error::Kem( + "key generation is not implemented for ConfigurableKEM, use the KEMConfiguration instead" + .to_string(), + )) + } + + fn enc( + ek: &Self::EncapsulationKey, + rng: &mut impl CryptoRngCore, + ) -> Result<(SymmetricKey<32>, Self::Encapsulation), Self::Error> { + match ek { + EKey::AbeScheme(_, _, None) => Err(Error::Kem( + "access policy must be provided for encapsulation".to_string(), + )), + EKey::AbeScheme(cc, mpk, Some(ap)) => cc + .encaps(mpk, ap) + .map(|(key, enc)| (SymmetricKey::from(key), Enc::AbeScheme(enc))), + EKey::PreQuantum(ek) => { + let (key, enc) = + PreQuantumKem::enc(ek, rng).map_err(|e| Error::Kem(e.to_string()))?; + Ok((key, Enc::PreQuantum(enc))) + } + EKey::PostQuantum(ek) => { + let (key, enc) = MlKem::enc(ek, rng).map_err(|e| Error::Kem(e.to_string()))?; + Ok((key, Enc::PostQuantum(enc))) + } + } + } + + fn dec( + dk: &Self::DecapsulationKey, + enc: &Self::Encapsulation, + ) -> Result, Self::Error> { + match (dk, enc) { + (DKey::AbeScheme(dk), Enc::AbeScheme(xenc)) => match dk { + AbeDKey::Master(_cc, msk) => { + let (ss, _) = crate::abe::core::primitives::master_decaps(msk, xenc, false)?; + Ok(SymmetricKey::from(ss)) + } + AbeDKey::User(cc, usk) => cc.decaps(usk, xenc).and_then(|res| { + let ss = res.ok_or_else(|| { + Error::OperationNotPermitted( + "user key does not have the required access right".to_string(), + ) + })?; + Ok(SymmetricKey::from(ss)) + }), + }, + (DKey::PreQuantum(dk), Enc::PreQuantum(enc)) => { + PreQuantumKem::dec(dk, enc).map_err(|e| Error::Kem(e.to_string())) + } + (DKey::PostQuantum(dk), Enc::PostQuantum(enc)) => { + MlKem::dec(dk, enc).map_err(|e| Error::Kem(e.to_string())) + } + _ => Err(Error::KeyError( + "cannot proceed with decapsulation: incompatible types".to_string(), + )), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index b6f2645b..189c5720 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,8 +8,7 @@ //! the DDH and LWE", T. Brézot, P. de Perthuis and D. Pointcheval 2023. //! [2] "A Proposal for an ISO Standard for Public Key Encryption (version 2.1)", Shoup 2001. -#![allow(dead_code)] - +mod abe; mod data_struct; mod error; mod providers; @@ -18,5 +17,7 @@ mod traits; #[cfg(any(test, feature = "test-utils"))] mod test_utils; -pub mod abe; +pub mod base; + +pub use abe::*; pub use error::Error; diff --git a/src/providers.rs b/src/providers.rs index a30f9bb4..5343cacb 100644 --- a/src/providers.rs +++ b/src/providers.rs @@ -1,3 +1,11 @@ -mod ae; -pub(crate) mod kem; -pub(crate) mod nike; +use cosmian_crypto_core::{kdf::Kdf as ShakeKDF, traits::cyclic_group_to_kem::GenericKem}; + +mod kem; +mod nike; + +pub use kem::MlKem; +pub use nike::ElGamal; + +pub const PRE_QUANTUM_KEM_KEY_LENGTH: usize = 32; + +pub type PreQuantumKem = GenericKem; diff --git a/src/providers/ae.rs b/src/providers/ae.rs deleted file mode 100644 index 00451927..00000000 --- a/src/providers/ae.rs +++ /dev/null @@ -1,35 +0,0 @@ -use cosmian_crypto_core::{ - reexport::{rand_core::CryptoRngCore, zeroize::Zeroizing}, - Aes256Gcm, CryptoCoreError, Dem, FixedSizeCBytes, Instantiable, Nonce, RandomFixedSizeCBytes, - SymmetricKey, -}; - -use crate::{traits::AE, Error}; - -impl AE<{ Self::KEY_LENGTH }> for Aes256Gcm { - type Error = Error; - - fn encrypt( - rng: &mut impl CryptoRngCore, - key: &SymmetricKey<{ Self::KEY_LENGTH }>, - ptx: &[u8], - ) -> Result, Error> { - let nonce = Nonce::<{ Self::NONCE_LENGTH }>::new(&mut *rng); - let ciphertext = Self::new(key).encrypt(&nonce, ptx, None)?; - Ok([nonce.as_bytes(), &ciphertext].concat()) - } - - fn decrypt( - key: &SymmetricKey<{ Self::KEY_LENGTH }>, - ctx: &[u8], - ) -> Result>, Error> { - if ctx.len() < Self::NONCE_LENGTH { - return Err(Error::CryptoCoreError(CryptoCoreError::DecryptionError)); - } - let nonce = Nonce::try_from_slice(&ctx[..Self::NONCE_LENGTH])?; - Self::new(key) - .decrypt(&nonce, &ctx[Self::NONCE_LENGTH..], None) - .map(Zeroizing::new) - .map_err(Error::CryptoCoreError) - } -} diff --git a/src/providers/kem.rs b/src/providers/kem.rs index 0290ba76..f798da4a 100644 --- a/src/providers/kem.rs +++ b/src/providers/kem.rs @@ -1,8 +1,6 @@ #[cfg(all(feature = "mlkem-512", feature = "mlkem-768"))] compile_error!("only one MLKEM version can be chosen at a time"); -use cosmian_crypto_core::reexport::rand_core::CryptoRngCore; - pub mod mlkem; #[cfg(feature = "mlkem-512")] @@ -10,30 +8,3 @@ pub use mlkem::MlKem512 as MlKem; #[cfg(feature = "mlkem-768")] pub use mlkem::MlKem768 as MlKem; - -pub trait Kem { - type EncapsulationKey; - type DecapsulationKey; - type SessionKey; - type Encapsulation; - type Error: std::error::Error; - - /// Generates a new random keypair. - fn keygen( - rng: &mut impl CryptoRngCore, - ) -> Result<(Self::DecapsulationKey, Self::EncapsulationKey), Self::Error>; - - /// Generates an encapsulation of a random session key, and returns both the - /// key and its encapsulation. - fn enc( - ek: &Self::EncapsulationKey, - rng: &mut impl CryptoRngCore, - ) -> Result<(Self::SessionKey, Self::Encapsulation), Self::Error>; - - /// Attempts opening the given encapsulation. Upon failure to decapsulate, - /// returns a random session key. - fn dec( - dk: &Self::DecapsulationKey, - enc: &Self::Encapsulation, - ) -> Result; -} diff --git a/src/providers/kem/mlkem.rs b/src/providers/kem/mlkem.rs index 854011e9..89a252df 100644 --- a/src/providers/kem/mlkem.rs +++ b/src/providers/kem/mlkem.rs @@ -1,8 +1,9 @@ -use crate::{providers::kem::Kem, Error}; +use crate::Error; use cosmian_crypto_core::{ bytes_ser_de::{Deserializer, Serializable, Serializer}, reexport::{rand_core::CryptoRngCore, zeroize::Zeroize}, - CryptoCoreError, Secret, + traits::KEM, + CryptoCoreError, Secret, SymmetricKey, }; use ml_kem::{ array::Array, @@ -95,10 +96,9 @@ macro_rules! make_mlkem { pub struct $base; - impl Kem for $base { + impl KEM for $base { type EncapsulationKey = $ek; type DecapsulationKey = $dk; - type SessionKey = Secret; type Encapsulation = $enc; type Error = Error; @@ -112,23 +112,24 @@ macro_rules! make_mlkem { fn enc( ek: &Self::EncapsulationKey, rng: &mut impl CryptoRngCore, - ) -> Result<(Self::SessionKey, Self::Encapsulation), Self::Error> { + ) -> Result<(SymmetricKey, Self::Encapsulation), Self::Error> + { let (enc, mut ss) = ek.0.encapsulate(rng) .map_err(|e| Error::Kem(format!("{:?}", e)))?; let ss = Secret::from_unprotected_bytes(ss.as_mut()); - Ok((ss, $enc(Box::new(enc)))) + Ok((ss.into(), $enc(Box::new(enc)))) } fn dec( dk: &Self::DecapsulationKey, enc: &Self::Encapsulation, - ) -> Result { + ) -> Result, Self::Error> { let mut ss = dk.0.decapsulate(&enc.0) .map_err(|e| Self::Error::Kem(format!("{e:?}")))?; let ss = Secret::from_unprotected_bytes(ss.as_mut()); - Ok(ss) + Ok(ss.into()) } } }; diff --git a/src/providers/nike.rs b/src/providers/nike.rs index 2a9f4830..2fab9f81 100644 --- a/src/providers/nike.rs +++ b/src/providers/nike.rs @@ -1,54 +1,12 @@ #[cfg(all(feature = "curve25519", feature = "p-256"))] compile_error!("only one elliptic curve can be chosen at a time"); -use crate::traits::{Group, Ring}; -use cosmian_crypto_core::{reexport::rand_core::CryptoRngCore, Sampling}; -use std::ops::{Add, Div, Mul, Sub}; +// #[cfg(all(feature = "curve25519", not(feature = "p-256")))] +#[cfg(feature = "curve25519")] +pub use cosmian_crypto_core::R25519 as ElGamal; -#[cfg(all(feature = "curve25519", not(feature = "p-256")))] -mod r25519; - -#[cfg(all(feature = "curve25519", not(feature = "p-256")))] -pub use r25519::R25519 as ElGamal; - -#[cfg(all(feature = "p-256", not(feature = "curve25519")))] +#[cfg(feature = "p-256")] mod p256; -#[cfg(all(feature = "p-256", not(feature = "curve25519")))] +#[cfg(feature = "p-256")] pub use p256::P256 as ElGamal; - -pub trait Nike { - type SecretKey: Sampling; - type PublicKey: for<'a> From<&'a Self::SecretKey>; - type SessionKey; - type Error: std::error::Error; - - /// Generates a new random keypair. - fn keygen( - rng: &mut impl CryptoRngCore, - ) -> Result<(Self::SecretKey, Self::PublicKey), Self::Error>; - - /// Generates the session key associated to the given keypair. - fn session_key( - sk: &Self::SecretKey, - pk: &Self::PublicKey, - ) -> Result; -} - -pub trait KeyHomomorphicNike: Nike -where - Self::PublicKey: Group, - Self::SecretKey: Ring, - Self::PublicKey: Mul, - for<'a> Self::PublicKey: Mul<&'a Self::SecretKey, Output = Self::PublicKey>, - for<'a, 'b> &'a Self::PublicKey: Add<&'b Self::PublicKey, Output = Self::PublicKey>, - for<'a, 'b> &'a Self::PublicKey: Sub<&'b Self::PublicKey, Output = Self::PublicKey>, - for<'a, 'b> &'a Self::SecretKey: Add<&'b Self::SecretKey, Output = Self::SecretKey>, - for<'a, 'b> &'a Self::SecretKey: Sub<&'b Self::SecretKey, Output = Self::SecretKey>, - for<'a, 'b> &'a Self::SecretKey: Mul<&'b Self::SecretKey, Output = Self::SecretKey>, - for<'a, 'b> &'a Self::SecretKey: Div< - &'b Self::SecretKey, - Output = Result::DivError>, - >, -{ -} diff --git a/src/providers/nike/r25519.rs b/src/providers/nike/r25519.rs deleted file mode 100644 index 12d45a6c..00000000 --- a/src/providers/nike/r25519.rs +++ /dev/null @@ -1,398 +0,0 @@ -use crate::{ - providers::nike::{KeyHomomorphicNike, Nike}, - traits::{Group, One, Ring, Seedable, Zero}, - Error, -}; -use cosmian_crypto_core::{ - bytes_ser_de::{Deserializer, Serializable, Serializer}, - reexport::{ - rand_core::CryptoRngCore, - tiny_keccak::{Hasher, Sha3}, - zeroize::Zeroize, - }, - CryptoCoreError, R25519PrivateKey as Scalar, R25519PublicKey as EcPoint, Sampling, -}; -use std::{ - iter::Sum, - ops::{Add, AddAssign, Deref, Div, Mul, MulAssign, Sub, SubAssign}, -}; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct R25519Point(EcPoint); - -impl Zero for R25519Point { - fn zero() -> Self { - Self(EcPoint::identity()) - } - - fn is_zero(&self) -> bool { - self == &Self::zero() - } -} - -// TODO: for some reason, the derive macro cannot be used. -impl Zeroize for R25519Point { - fn zeroize(&mut self) { - self.0.zeroize(); - } -} - -impl Add for R25519Point { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - Self(self.0 + &rhs.0) - } -} - -impl Add<&R25519Point> for R25519Point { - type Output = Self; - - fn add(self, rhs: &R25519Point) -> Self::Output { - Self(self.0 + &rhs.0) - } -} - -impl Add<&R25519Point> for &R25519Point { - type Output = R25519Point; - - fn add(self, rhs: &R25519Point) -> Self::Output { - R25519Point(&self.0 + &rhs.0) - } -} - -impl AddAssign for R25519Point { - fn add_assign(&mut self, rhs: Self) { - self.0 = &self.0 + &rhs.0; - } -} - -impl Sub for R25519Point { - type Output = Self; - - fn sub(self, rhs: Self) -> Self::Output { - Self(&self.0 - &rhs.0) - } -} - -impl SubAssign for R25519Point { - fn sub_assign(&mut self, rhs: Self) { - self.0 = &self.0 - &rhs.0 - } -} - -impl Sub<&R25519Point> for R25519Point { - type Output = Self; - - fn sub(self, rhs: &R25519Point) -> Self::Output { - Self(&self.0 - &rhs.0) - } -} - -impl Sub<&R25519Point> for &R25519Point { - type Output = R25519Point; - - fn sub(self, rhs: &R25519Point) -> Self::Output { - R25519Point(&self.0 - &rhs.0) - } -} - -impl Group for R25519Point {} - -impl Serializable for R25519Point { - type Error = Error; - - fn length(&self) -> usize { - self.0.length() - } - - fn write(&self, ser: &mut Serializer) -> Result { - self.0.write(ser).map_err(Self::Error::from) - } - - fn read(de: &mut Deserializer) -> Result { - de.read().map(Self).map_err(Self::Error::from) - } -} - -impl Sum for R25519Point { - fn sum>(iter: I) -> Self { - iter.fold(Self::zero(), |a, p| a + p) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct R25519Scalar(Scalar); - -impl Deref for R25519Scalar { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - self.0.as_bytes() - } -} - -impl Zero for R25519Scalar { - fn zero() -> Self { - Self(Scalar::zero()) - } - - fn is_zero(&self) -> bool { - self == &Self::zero() - } -} - -impl One for R25519Scalar { - fn one() -> Self { - Self(Scalar::one()) - } - - fn is_one(&self) -> bool { - self == &Self::one() - } -} - -impl Add for R25519Scalar { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - Self(self.0 + rhs.0) - } -} - -impl AddAssign for R25519Scalar { - fn add_assign(&mut self, rhs: Self) { - self.0 = &self.0 + &rhs.0; - } -} - -impl Add<&R25519Scalar> for R25519Scalar { - type Output = Self; - - fn add(self, rhs: &R25519Scalar) -> Self::Output { - Self(&self.0 + &rhs.0) - } -} - -impl Add<&R25519Scalar> for &R25519Scalar { - type Output = R25519Scalar; - - fn add(self, rhs: &R25519Scalar) -> Self::Output { - R25519Scalar(&self.0 + &rhs.0) - } -} - -impl Sub for R25519Scalar { - type Output = Self; - - fn sub(self, rhs: Self) -> Self::Output { - Self(&self.0 - &rhs.0) - } -} - -impl SubAssign for R25519Scalar { - fn sub_assign(&mut self, rhs: Self) { - self.0 = &self.0 - &rhs.0 - } -} - -impl Sub<&R25519Scalar> for R25519Scalar { - type Output = Self; - - fn sub(self, rhs: &R25519Scalar) -> Self::Output { - Self(&self.0 - &rhs.0) - } -} - -impl Sub<&R25519Scalar> for &R25519Scalar { - type Output = R25519Scalar; - - fn sub(self, rhs: &R25519Scalar) -> Self::Output { - R25519Scalar(&self.0 - &rhs.0) - } -} - -impl Mul for R25519Scalar { - type Output = Self; - - fn mul(self, rhs: Self) -> Self::Output { - Self(&self.0 * &rhs.0) - } -} - -impl MulAssign for R25519Scalar { - fn mul_assign(&mut self, rhs: Self) { - self.0 = &self.0 * &rhs.0 - } -} - -impl Mul<&R25519Scalar> for R25519Scalar { - type Output = Self; - - fn mul(self, rhs: &R25519Scalar) -> Self::Output { - Self(&self.0 * &rhs.0) - } -} - -impl Mul<&R25519Scalar> for &R25519Scalar { - type Output = R25519Scalar; - - fn mul(self, rhs: &R25519Scalar) -> Self::Output { - R25519Scalar(&self.0 * &rhs.0) - } -} - -impl Div for R25519Scalar { - type Output = Result; - - fn div(self, rhs: Self) -> Self::Output { - &self / &rhs - } -} - -impl Div<&R25519Scalar> for R25519Scalar { - type Output = Result; - - fn div(self, rhs: &R25519Scalar) -> Self::Output { - &self / rhs - } -} - -impl Div<&R25519Scalar> for &R25519Scalar { - type Output = Result; - - fn div(self, rhs: &R25519Scalar) -> Self::Output { - (&self.0 / &rhs.0).map(R25519Scalar) - } -} - -impl Sum for R25519Scalar { - fn sum>(iter: I) -> Self { - iter.fold(Self::zero(), |a, s| a + s) - } -} - -impl Group for R25519Scalar {} - -impl Ring for R25519Scalar { - type DivError = CryptoCoreError; -} - -impl Serializable for R25519Scalar { - type Error = Error; - - fn length(&self) -> usize { - self.0.length() - } - - fn write(&self, ser: &mut Serializer) -> Result { - self.0.write(ser).map_err(Self::Error::from) - } - - fn read(de: &mut Deserializer) -> Result { - de.read().map(Self).map_err(Self::Error::from) - } -} - -impl Sampling for R25519Scalar { - fn random(rng: &mut impl CryptoRngCore) -> Self { - Self(Scalar::new(rng)) - } -} - -impl Seedable<32> for R25519Scalar { - fn from_seed(seed: &[u8; 32]) -> Self { - let mut hasher = Sha3::v512(); - let mut bytes = [0; 512 / 8]; - hasher.update(seed); - hasher.finalize(&mut bytes); - let s = Self(Scalar::from_raw_bytes(&bytes)); - bytes.zeroize(); - s - } -} - -impl From<&R25519Scalar> for R25519Point { - fn from(s: &R25519Scalar) -> Self { - Self(EcPoint::from(&s.0)) - } -} - -impl Mul for R25519Point { - type Output = Self; - - fn mul(self, rhs: R25519Scalar) -> Self::Output { - Self(&self.0 * &rhs.0) - } -} - -impl MulAssign for R25519Point { - fn mul_assign(&mut self, rhs: R25519Scalar) { - self.0 = &self.0 * &rhs.0 - } -} - -impl Mul<&R25519Scalar> for R25519Point { - type Output = Self; - - fn mul(self, rhs: &R25519Scalar) -> Self::Output { - Self(&self.0 * &rhs.0) - } -} - -impl Mul<&R25519Scalar> for &R25519Point { - type Output = R25519Point; - - fn mul(self, rhs: &R25519Scalar) -> Self::Output { - R25519Point(&self.0 * &rhs.0) - } -} - -pub struct R25519; - -impl Nike for R25519 { - type SecretKey = R25519Scalar; - type PublicKey = R25519Point; - type SessionKey = R25519Point; - type Error = Error; - - fn keygen( - rng: &mut impl CryptoRngCore, - ) -> Result<(Self::SecretKey, Self::PublicKey), Self::Error> { - let sk = Self::SecretKey::random(rng); - let pk = Self::PublicKey::from(&sk); - Ok((sk, pk)) - } - - fn session_key( - sk: &Self::SecretKey, - pk: &Self::PublicKey, - ) -> Result { - Ok(pk * sk) - } -} - -impl KeyHomomorphicNike for R25519 {} - -#[cfg(test)] -mod tests { - use cosmian_crypto_core::{ - bytes_ser_de::test_serialization, reexport::rand_core::SeedableRng, CsRng, - }; - - use super::*; - - #[test] - fn test_r25519() { - let mut rng = CsRng::from_entropy(); - let (sk1, pk1) = R25519::keygen(&mut rng).unwrap(); - let (sk2, pk2) = R25519::keygen(&mut rng).unwrap(); - test_serialization(&sk1).unwrap(); - test_serialization(&pk1).unwrap(); - test_serialization(&sk2).unwrap(); - test_serialization(&pk2).unwrap(); - let ss1 = R25519::session_key(&sk1, &pk2).unwrap(); - let ss2 = R25519::session_key(&sk2, &pk1).unwrap(); - assert_eq!(ss1, ss2); - } -} diff --git a/src/traits.rs b/src/traits.rs index d05a7366..8b137891 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,72 +1 @@ -use cosmian_crypto_core::{ - reexport::{rand_core::CryptoRngCore, zeroize::Zeroizing}, - SymmetricKey, -}; -use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Sub, SubAssign}; -pub trait Zero { - fn zero() -> Self; - fn is_zero(&self) -> bool; -} - -pub trait One { - fn one() -> Self; - fn is_one(&self) -> bool; -} - -pub trait Seedable { - fn from_seed(seed: &[u8; LENGTH]) -> Self; -} - -pub trait Group: - Sized - + Zero - + Add - + AddAssign - + Sub - + SubAssign - + for<'a> Add<&'a Self, Output = Self> - + for<'a> Sub<&'a Self, Output = Self> -where - for<'a, 'b> &'a Self: Add<&'b Self, Output = Self>, - for<'a, 'b> &'a Self: Sub<&'b Self, Output = Self>, -{ -} - -pub trait Ring: - Group - + Zero - + Mul - + MulAssign - + Div> - + for<'a> Mul<&'a Self, Output = Self> - + for<'a> Div<&'a Self, Output = Result> -where - for<'a, 'b> &'a Self: Add<&'b Self, Output = Self>, - for<'a, 'b> &'a Self: Sub<&'b Self, Output = Self>, - for<'a, 'b> &'a Self: Mul<&'b Self, Output = Self>, - for<'a, 'b> &'a Self: Div<&'b Self, Output = Result>, -{ - type DivError; -} - -pub trait AE { - type Error: std::error::Error; - - /// Encrypts the given plaintext using the given key. - fn encrypt( - rng: &mut impl CryptoRngCore, - key: &SymmetricKey, - ptx: &[u8], - ) -> Result, Self::Error>; - - /// Decrypts the given ciphertext using the given key. - /// - /// # Error - /// - /// Returns an error if the integrity of the ciphertext could not be verified. - fn decrypt( - key: &SymmetricKey, - ctx: &[u8], - ) -> Result>, Self::Error>; -} From 3c8e121c4f882fd3a9cd9b6e07c51448977e0555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Fri, 16 Jan 2026 15:22:27 +0100 Subject: [PATCH 03/23] use crypto_core providers --- Cargo.toml | 11 +- src/base.rs | 40 ++-- src/providers/nike.rs | 8 +- src/providers/nike/p256.rs | 425 ------------------------------------- 4 files changed, 30 insertions(+), 454 deletions(-) delete mode 100644 src/providers/nike/p256.rs diff --git a/Cargo.toml b/Cargo.toml index 2dc300f0..f5086160 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,22 +41,21 @@ required-features = ["test-utils"] default = ["mlkem-512", "curve25519"] mlkem-512 = [] mlkem-768 = [] -p-256 = ["elliptic-curve", "p256", "subtle"] -curve25519 = ["cosmian_crypto_core/curve25519"] +p-256 = ["cosmian_rust_nist_ec_provider"] +curve25519 = ["cosmian_rust_curve25519_provider"] test-utils = [] [dependencies] -cosmian_crypto_core = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/generic-pke-implementation", default-features = false, features = [ +cosmian_rust_nist_ec_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/restructure-repo", optional = true } +cosmian_rust_curve25519_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/restructure-repo", optional = true } +cosmian_crypto_core = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/restructure-repo", default-features = false, features = [ "aes", "ser", "sha3" ] } -elliptic-curve = { version = "0.13", optional = true } ml-kem = { version = "0.2", features = ["zeroize"] } -p256 = { version = "0.13", optional = true } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["preserve_order"] } -subtle = { version = "2.6", optional = true } [dev-dependencies] criterion = { version = "0.7", features = [ diff --git a/src/base.rs b/src/base.rs index cf2a12a0..c236ec40 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1,14 +1,18 @@ -use super::*; use crate::{ abe::{AccessPolicy, KemAc}, providers::{MlKem, PreQuantumKem}, + AccessStructure, Covercrypt, Error, MasterPublicKey, MasterSecretKey, UserSecretKey, XEnc, +}; +use cosmian_crypto_core::{ + reexport::rand_core::CryptoRngCore, + traits::{kem_to_pke::GenericPKE, KEM}, + Aes256Gcm, SymmetricKey, }; -use cosmian_crypto_core::{reexport::rand_core::CryptoRngCore, traits::KEM, SymmetricKey}; #[derive(Debug)] pub enum AbeDKey { - Master(abe::Covercrypt, abe::MasterSecretKey), - User(abe::Covercrypt, abe::UserSecretKey), + Master(Covercrypt, MasterSecretKey), + User(Covercrypt, UserSecretKey), } #[derive(Debug)] @@ -19,7 +23,7 @@ pub enum DKey { } impl DKey { - pub fn access_structure(&mut self) -> Result<&mut abe::AccessStructure, Error> { + pub fn access_structure(&mut self) -> Result<&mut AccessStructure, Error> { match self { DKey::AbeScheme(AbeDKey::Master(_, msk)) => Ok(&mut msk.access_structure), _ => Err(Error::KeyError( @@ -35,43 +39,43 @@ impl DKey { Ok(EKey::AbeScheme(cc.clone(), mpk, None)) } _ => Err(Error::KeyError( - "cannot update non ABE master keys".to_string(), + "cannot update non ABE master key".to_string(), )), } } - pub fn rekey(&mut self, ap: &abe::AccessPolicy) -> Result { + pub fn rekey(&mut self, ap: &AccessPolicy) -> Result { match self { DKey::AbeScheme(AbeDKey::Master(cc, msk)) => { let mpk = cc.rekey(msk, ap)?; Ok(EKey::AbeScheme(cc.clone(), mpk, None)) } _ => Err(Error::KeyError( - "cannot rekey non ABE master keys".to_string(), + "cannot re-key non ABE master key".to_string(), )), } } - pub fn prune_master_key(&mut self, ap: &abe::AccessPolicy) -> Result { + pub fn prune_master_key(&mut self, ap: &AccessPolicy) -> Result { match self { DKey::AbeScheme(AbeDKey::Master(cc, msk)) => { let mpk = cc.prune_master_secret_key(msk, ap)?; Ok(EKey::AbeScheme(cc.clone(), mpk, None)) } _ => Err(Error::KeyError( - "cannot prune non ABE master keys".to_string(), + "cannot prune non ABE master key".to_string(), )), } } - pub fn generate_user_secret_key(&mut self, ap: &abe::AccessPolicy) -> Result { + pub fn generate_user_secret_key(&mut self, ap: &AccessPolicy) -> Result { match self { DKey::AbeScheme(AbeDKey::Master(cc, msk)) => { let usk = cc.generate_user_secret_key(msk, ap)?; Ok(DKey::AbeScheme(AbeDKey::User(cc.clone(), usk))) } _ => Err(Error::KeyError( - "cannot prune non ABE master keys".to_string(), + "cannot generate user secret key using a non ABE master key".to_string(), )), } } @@ -106,7 +110,7 @@ impl DKey { Ok((SymmetricKey::from(ss), Enc::AbeScheme(enc))) } _ => Err(Error::KeyError( - "cannot refresh user secret key: invalid key types".to_string(), + "cannot re-encapsulate: invalid object types".to_string(), )), } } @@ -114,14 +118,14 @@ impl DKey { #[derive(Debug, Clone)] pub enum EKey { - AbeScheme(abe::Covercrypt, abe::MasterPublicKey, Option), + AbeScheme(Covercrypt, MasterPublicKey, Option), PreQuantum(>::EncapsulationKey), PostQuantum(>::EncapsulationKey), } impl EKey { /// Sets the encapsulation key to use the provided access polity. - pub fn set_access_policy(&mut self, access_policy: abe::AccessPolicy) -> Result<(), Error> { + pub fn set_access_policy(&mut self, access_policy: AccessPolicy) -> Result<(), Error> { match self { Self::AbeScheme(_, _, ap) => { *ap = Some(access_policy); @@ -136,7 +140,7 @@ impl EKey { #[derive(Debug, Clone)] pub enum Enc { - AbeScheme(abe::XEnc), + AbeScheme(XEnc), PreQuantum(>::Encapsulation), PostQuantum(>::Encapsulation), } @@ -152,7 +156,7 @@ impl Configuration { pub fn keygen(&self, rng: &mut impl CryptoRngCore) -> Result<(DKey, EKey), Error> { match self { Self::AbeScheme => { - let cc = abe::Covercrypt::default(); + let cc = Covercrypt::default(); let (msk, mpk) = cc.setup()?; Ok(( DKey::AbeScheme(AbeDKey::Master(cc.clone(), msk)), @@ -246,3 +250,5 @@ impl KEM<32> for ConfigurableKEM { } } } + +pub type ConfigurablePKE = GenericPKE<{ ConfigurableKEM::KEY_LENGTH }, ConfigurableKEM, Aes256Gcm>; diff --git a/src/providers/nike.rs b/src/providers/nike.rs index 2fab9f81..86dfdeca 100644 --- a/src/providers/nike.rs +++ b/src/providers/nike.rs @@ -1,12 +1,8 @@ #[cfg(all(feature = "curve25519", feature = "p-256"))] compile_error!("only one elliptic curve can be chosen at a time"); -// #[cfg(all(feature = "curve25519", not(feature = "p-256")))] #[cfg(feature = "curve25519")] -pub use cosmian_crypto_core::R25519 as ElGamal; +pub use cosmian_rust_curve25519_provider::R25519 as ElGamal; #[cfg(feature = "p-256")] -mod p256; - -#[cfg(feature = "p-256")] -pub use p256::P256 as ElGamal; +pub use cosmian_rust_nist_ec_provider::P256 as ElGamal; diff --git a/src/providers/nike/p256.rs b/src/providers/nike/p256.rs deleted file mode 100644 index 5b74c2af..00000000 --- a/src/providers/nike/p256.rs +++ /dev/null @@ -1,425 +0,0 @@ -use crate::{ - providers::nike::{KeyHomomorphicNike, Nike}, - traits::{Group, One, Ring, Seedable, Zero}, - Error, -}; -use cosmian_crypto_core::{ - bytes_ser_de::{Deserializer, Serializable, Serializer}, - reexport::{ - tiny_keccak::{Hasher, Sha3}, - zeroize::Zeroize, - }, - CryptoCoreError, Sampling, -}; -use elliptic_curve::{group::GroupEncoding, rand_core::CryptoRngCore, Field, PrimeField}; -use p256::{ProjectivePoint, Scalar}; -use std::{ - hash::Hash, - iter::Sum, - ops::{Add, AddAssign, Div, Mul, MulAssign, Sub, SubAssign}, -}; -use subtle::ConstantTimeEq; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct P256Point(ProjectivePoint); - -impl Zero for P256Point { - fn zero() -> Self { - Self(ProjectivePoint::IDENTITY) - } - - fn is_zero(&self) -> bool { - self.0.ct_eq(&ProjectivePoint::IDENTITY).into() - } -} - -impl Zeroize for P256Point { - fn zeroize(&mut self) { - self.0.zeroize() - } -} - -impl Add for P256Point { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - &self + &rhs - } -} - -impl Add<&P256Point> for P256Point { - type Output = Self; - - fn add(self, rhs: &P256Point) -> Self::Output { - &self + rhs - } -} - -impl Add<&P256Point> for &P256Point { - type Output = P256Point; - - fn add(self, rhs: &P256Point) -> Self::Output { - P256Point(self.0 + rhs.0) - } -} - -impl AddAssign for P256Point { - fn add_assign(&mut self, rhs: Self) { - self.0 = self.0 + rhs.0; - } -} - -impl Sub for P256Point { - type Output = Self; - - fn sub(self, rhs: Self) -> Self::Output { - &self - &rhs - } -} - -impl SubAssign for P256Point { - fn sub_assign(&mut self, rhs: Self) { - self.0 = self.0 - rhs.0 - } -} - -impl Sub<&P256Point> for P256Point { - type Output = Self; - - fn sub(self, rhs: &P256Point) -> Self::Output { - &self - rhs - } -} - -impl Sub<&P256Point> for &P256Point { - type Output = P256Point; - - fn sub(self, rhs: &P256Point) -> Self::Output { - P256Point(self.0 - rhs.0) - } -} - -impl Group for P256Point {} - -impl Serializable for P256Point { - type Error = CryptoCoreError; - - fn length(&self) -> usize { - 33 - } - - fn write(&self, ser: &mut Serializer) -> Result { - ser.write_array(&self.0.to_bytes()) - } - - fn read(de: &mut Deserializer) -> Result { - let bytes = de.read_array::<33>()?; - let point = ProjectivePoint::from_bytes(&bytes.into()) - .into_option() - .ok_or_else(|| { - CryptoCoreError::GenericDeserializationError("cannot deserialize point".to_string()) - })?; - Ok(Self(point)) - } -} - -impl Sum for P256Point { - fn sum>(iter: I) -> Self { - iter.fold(Self::zero(), |a, p| a + p) - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct P256Scalar(Scalar); - -impl Hash for P256Scalar { - fn hash(&self, state: &mut H) { - state.write(&self.0.to_bytes()); - } -} - -impl Zero for P256Scalar { - fn zero() -> Self { - Self(Scalar::ZERO) - } - - fn is_zero(&self) -> bool { - self.0.ct_eq(&Scalar::ZERO).into() - } -} - -impl One for P256Scalar { - fn one() -> Self { - Self(Scalar::ONE) - } - - fn is_one(&self) -> bool { - self.0.ct_eq(&Scalar::ONE).into() - } -} - -impl Add for P256Scalar { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - &self + &rhs - } -} - -impl AddAssign for P256Scalar { - fn add_assign(&mut self, rhs: Self) { - self.0 = self.0 + rhs.0; - } -} - -impl Add<&P256Scalar> for P256Scalar { - type Output = Self; - - fn add(self, rhs: &P256Scalar) -> Self::Output { - &self + rhs - } -} - -impl Add<&P256Scalar> for &P256Scalar { - type Output = P256Scalar; - - fn add(self, rhs: &P256Scalar) -> Self::Output { - P256Scalar(self.0 + rhs.0) - } -} - -impl Sub for P256Scalar { - type Output = Self; - - fn sub(self, rhs: Self) -> Self::Output { - &self - &rhs - } -} - -impl SubAssign for P256Scalar { - fn sub_assign(&mut self, rhs: Self) { - self.0 = self.0 - rhs.0 - } -} - -impl Sub<&P256Scalar> for P256Scalar { - type Output = Self; - - fn sub(self, rhs: &P256Scalar) -> Self::Output { - &self - rhs - } -} - -impl Sub<&P256Scalar> for &P256Scalar { - type Output = P256Scalar; - - fn sub(self, rhs: &P256Scalar) -> Self::Output { - P256Scalar(self.0 - rhs.0) - } -} - -impl Mul for P256Scalar { - type Output = Self; - - fn mul(self, rhs: Self) -> Self::Output { - &self * &rhs - } -} - -impl MulAssign for P256Scalar { - fn mul_assign(&mut self, rhs: Self) { - self.0 = self.0 * rhs.0 - } -} - -impl Mul<&P256Scalar> for P256Scalar { - type Output = Self; - - fn mul(self, rhs: &P256Scalar) -> Self::Output { - &self * rhs - } -} - -impl Mul<&P256Scalar> for &P256Scalar { - type Output = P256Scalar; - - fn mul(self, rhs: &P256Scalar) -> Self::Output { - P256Scalar(self.0 * rhs.0) - } -} - -impl Div for P256Scalar { - type Output = Result; - - fn div(self, rhs: Self) -> Self::Output { - &self / &rhs - } -} - -impl Div<&P256Scalar> for P256Scalar { - type Output = Result; - - fn div(self, rhs: &P256Scalar) -> Self::Output { - &self / rhs - } -} - -impl Div<&P256Scalar> for &P256Scalar { - type Output = Result; - - fn div(self, rhs: &P256Scalar) -> Self::Output { - rhs.0 - .invert() - .map(|rhs| self.0 * rhs) - .map(P256Scalar) - .into_option() - .ok_or_else(|| Error::OperationNotPermitted("Division by zero".to_string())) - } -} - -impl Sum for P256Scalar { - fn sum>(iter: I) -> Self { - iter.fold(Self::zero(), |a, s| a + s) - } -} - -impl Group for P256Scalar {} - -impl Ring for P256Scalar { - type DivError = Error; -} - -impl Serializable for P256Scalar { - type Error = CryptoCoreError; - - fn length(&self) -> usize { - 32 - } - - fn write(&self, ser: &mut Serializer) -> Result { - ser.write_array(&self.0.to_bytes()) - } - - fn read(de: &mut Deserializer) -> Result { - let bytes = de.read_array::<32>()?; - let scalar = Scalar::from_repr(bytes.into()) - .into_option() - .ok_or_else(|| { - CryptoCoreError::GenericDeserializationError( - "cannot deserialize scalar".to_string(), - ) - })?; - Ok(Self(scalar)) - } -} - -impl Sampling for P256Scalar { - fn random(rng: &mut impl CryptoRngCore) -> Self { - Self(Scalar::random(rng)) - } -} - -impl Seedable<32> for P256Scalar { - fn from_seed(seed: &[u8; 32]) -> Self { - let mut i = 0u32; - loop { - let mut hasher = Sha3::v256(); - let mut bytes = [0; 32]; - hasher.update(seed); - hasher.update(&i.to_be_bytes()); - hasher.finalize(&mut bytes); - let s = Self::deserialize(&bytes); - bytes.zeroize(); - if let Ok(s) = s { - return s; - } else { - i += 1; - } - } - } -} - -impl From<&P256Scalar> for P256Point { - fn from(s: &P256Scalar) -> Self { - P256Point(ProjectivePoint::GENERATOR * s.0) - } -} - -impl Mul for P256Point { - type Output = Self; - - fn mul(self, rhs: P256Scalar) -> Self::Output { - &self * &rhs - } -} - -impl MulAssign for P256Point { - fn mul_assign(&mut self, rhs: P256Scalar) { - self.0 = self.0 * rhs.0 - } -} - -impl Mul<&P256Scalar> for P256Point { - type Output = Self; - - fn mul(self, rhs: &P256Scalar) -> Self::Output { - &self * rhs - } -} - -impl Mul<&P256Scalar> for &P256Point { - type Output = P256Point; - - fn mul(self, rhs: &P256Scalar) -> Self::Output { - P256Point(self.0 * rhs.0) - } -} - -pub struct P256; - -impl Nike for P256 { - type SecretKey = P256Scalar; - type PublicKey = P256Point; - type SessionKey = P256Point; - type Error = Error; - - fn keygen( - rng: &mut impl CryptoRngCore, - ) -> Result<(Self::SecretKey, Self::PublicKey), Self::Error> { - let sk = Self::SecretKey::random(rng); - let pk = Self::PublicKey::from(&sk); - Ok((sk, pk)) - } - - fn session_key( - sk: &Self::SecretKey, - pk: &Self::PublicKey, - ) -> Result { - Ok(pk * sk) - } -} - -impl KeyHomomorphicNike for P256 {} - -#[cfg(test)] -mod tests { - use cosmian_crypto_core::{ - bytes_ser_de::test_serialization, reexport::rand_core::SeedableRng, CsRng, - }; - - use super::*; - - #[test] - fn test_p256() { - let mut rng = CsRng::from_entropy(); - let (sk1, pk1) = P256::keygen(&mut rng).unwrap(); - let (sk2, pk2) = P256::keygen(&mut rng).unwrap(); - test_serialization(&sk1).unwrap(); - test_serialization(&pk1).unwrap(); - test_serialization(&sk2).unwrap(); - test_serialization(&pk2).unwrap(); - let ss1 = P256::session_key(&sk1, &pk2).unwrap(); - let ss2 = P256::session_key(&sk2, &pk1).unwrap(); - assert_eq!(ss1, ss2); - } -} From e3162a23e768fdb2a5c56a94fa5341ab63da7d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Fri, 16 Jan 2026 15:42:43 +0100 Subject: [PATCH 04/23] fix lack of ZeroizeOnDrop on MlKem implementation --- src/abe/core/primitives.rs | 10 +++------- src/providers/kem/mlkem.rs | 8 +++++++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/abe/core/primitives.rs b/src/abe/core/primitives.rs index 8eb91d41..73c1bfa1 100644 --- a/src/abe/core/primitives.rs +++ b/src/abe/core/primitives.rs @@ -16,7 +16,6 @@ use cosmian_crypto_core::{ reexport::{ rand_core::{CryptoRngCore, RngCore}, tiny_keccak::{Hasher, Kmac, Sha3}, - zeroize::Zeroize, }, traits::{Seedable, KEM, NIKE}, RandomFixedSizeCBytes, Secret, SymmetricKey, @@ -240,9 +239,8 @@ fn h_encaps<'a>( let encapsulations = encs .into_iter() - .map(|(mut K1, K2, E)| -> Result<_, _> { + .map(|(K1, K2, E)| -> Result<_, _> { let F = xor_2(&S, &*H_hash(Some(&K1), Some(&K2), &T)?); - K1.zeroize(); Ok((E, F)) }) .collect::, Error>>()?; @@ -404,9 +402,8 @@ fn attempt_pre_quantum_decaps<'a>( tracing_points: impl IntoIterator::PublicKey>, ) -> Result>, Error> { if let RightSecretKey::PreQuantum { sk } = secret { - let mut K1 = ElGamal::shared_secret(sk, A)?; + let K1 = ElGamal::shared_secret(sk, A)?; let S = xor_in_place(H_hash(Some(&K1), None, T)?, F); - K1.zeroize(); let (tag_ij, ss) = J_hash(&S, U); if tag == &tag_ij { // Fujisaki-Okamoto @@ -436,7 +433,7 @@ fn attempt_hybridized_decaps<'a>( tracing_points: impl IntoIterator::PublicKey>, ) -> Result>, Error> { if let RightSecretKey::Hybridized { sk, dk } = secret { - let mut K1 = ElGamal::shared_secret(sk, A)?; + let K1 = ElGamal::shared_secret(sk, A)?; let K2 = MlKem::dec(dk, E)?; let S_ij = xor_in_place(H_hash(Some(&K1), Some(&K2), T)?, F); let (tag_ij, ss) = J_hash(&S_ij, U); @@ -448,7 +445,6 @@ fn attempt_hybridized_decaps<'a>( .map(|P| P * &r) .collect::>(); if c == c_ij { - K1.zeroize(); return Ok(Some(ss)); } } diff --git a/src/providers/kem/mlkem.rs b/src/providers/kem/mlkem.rs index 89a252df..19818e0e 100644 --- a/src/providers/kem/mlkem.rs +++ b/src/providers/kem/mlkem.rs @@ -1,7 +1,10 @@ use crate::Error; use cosmian_crypto_core::{ bytes_ser_de::{Deserializer, Serializable, Serializer}, - reexport::{rand_core::CryptoRngCore, zeroize::Zeroize}, + reexport::{ + rand_core::CryptoRngCore, + zeroize::{Zeroize, ZeroizeOnDrop}, + }, traits::KEM, CryptoCoreError, Secret, SymmetricKey, }; @@ -43,6 +46,9 @@ macro_rules! make_mlkem { #[derive(Debug, Clone, PartialEq)] pub struct $dk(Box<::DecapsulationKey>); + // DecapsulationKey implements ZeroizeOnDrop. + impl ZeroizeOnDrop for $dk {} + #[allow(dead_code)] impl $dk { pub fn ek(&self) -> $ek { From b65a74c20c154d748d39a13cea6ba449eb78f894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Fri, 16 Jan 2026 15:52:00 +0100 Subject: [PATCH 05/23] fix: add ZeroizeOnDrop for abe::DKey --- Cargo.toml | 1 + src/abe/core/mod.rs | 8 +++++++- src/base.rs | 7 +++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f5086160..e4764d00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ cosmian_crypto_core = { git = "https://github.com/Cosmian/crypto_core.git", bran ml-kem = { version = "0.2", features = ["zeroize"] } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["preserve_order"] } +zeroize = "1.8" [dev-dependencies] criterion = { version = "0.7", features = [ diff --git a/src/abe/core/mod.rs b/src/abe/core/mod.rs index b7b6aa4c..639a38f3 100644 --- a/src/abe/core/mod.rs +++ b/src/abe/core/mod.rs @@ -7,7 +7,7 @@ use crate::{ Error, }; use cosmian_crypto_core::{ - reexport::rand_core::CryptoRngCore, + reexport::{rand_core::CryptoRngCore, zeroize::ZeroizeOnDrop}, traits::{Sampling, Zero, KEM, NIKE}, SymmetricKey, }; @@ -382,6 +382,9 @@ pub struct MasterSecretKey { pub access_structure: AccessStructure, } +// All secret keys are zeroized on drop. +impl ZeroizeOnDrop for MasterSecretKey {} + impl MasterSecretKey { /// Returns the most recent secret key associated to each given right. /// @@ -511,6 +514,9 @@ pub struct UserSecretKey { signature: Option, } +// All secret keys are zeroized on drop. +impl ZeroizeOnDrop for UserSecretKey {} + impl UserSecretKey { /// Returns the tracing level of this user secret key. pub fn tracing_level(&self) -> usize { diff --git a/src/base.rs b/src/base.rs index c236ec40..ce6c8e4e 100644 --- a/src/base.rs +++ b/src/base.rs @@ -4,7 +4,7 @@ use crate::{ AccessStructure, Covercrypt, Error, MasterPublicKey, MasterSecretKey, UserSecretKey, XEnc, }; use cosmian_crypto_core::{ - reexport::rand_core::CryptoRngCore, + reexport::{rand_core::CryptoRngCore, zeroize::ZeroizeOnDrop}, traits::{kem_to_pke::GenericPKE, KEM}, Aes256Gcm, SymmetricKey, }; @@ -15,7 +15,10 @@ pub enum AbeDKey { User(Covercrypt, UserSecretKey), } -#[derive(Debug)] +// All secret keys are zeroized on drop. +impl ZeroizeOnDrop for AbeDKey {} + +#[derive(Debug, ZeroizeOnDrop)] pub enum DKey { AbeScheme(AbeDKey), PreQuantum(>::DecapsulationKey), From 8c452f51124755046204390ba2d0aa89cafa1077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Fri, 16 Jan 2026 16:17:45 +0100 Subject: [PATCH 06/23] add test for the ABE variant of the ConfigurableKEM --- src/base.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/base.rs b/src/base.rs index ce6c8e4e..5f4d0b2c 100644 --- a/src/base.rs +++ b/src/base.rs @@ -6,7 +6,7 @@ use crate::{ use cosmian_crypto_core::{ reexport::{rand_core::CryptoRngCore, zeroize::ZeroizeOnDrop}, traits::{kem_to_pke::GenericPKE, KEM}, - Aes256Gcm, SymmetricKey, + SymmetricKey, }; #[derive(Debug)] @@ -254,4 +254,49 @@ impl KEM<32> for ConfigurableKEM { } } -pub type ConfigurablePKE = GenericPKE<{ ConfigurableKEM::KEY_LENGTH }, ConfigurableKEM, Aes256Gcm>; +pub type ConfigurablePKE = GenericPKE<{ ConfigurableKEM::KEY_LENGTH }, ConfigurableKEM, AE>; + +#[cfg(test)] +mod tests { + use crate::test_utils::cc_keygen; + + use super::*; + use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng}; + + #[test] + fn test_abe_kem() { + let mut rng = CsRng::from_entropy(); + let config = Configuration::AbeScheme; + let (mut msk, _) = config.keygen(&mut rng).unwrap(); + + // Load the test access structure used in other tests. + let access_structure = msk.access_structure().unwrap(); + let (_msk, _) = cc_keygen(&Covercrypt::default(), true).unwrap(); + *access_structure = _msk.access_structure.clone(); + let mut mpk = msk.update_msk().unwrap(); + + let user_ap = AccessPolicy::parse("(DPT::MKG || DPT::FIN) && SEC::TOP").unwrap(); + let ok_ap = AccessPolicy::parse("DPT::MKG && SEC::TOP").unwrap(); + let ko_ap = AccessPolicy::parse("DPT::DEV").unwrap(); + + let usk = msk.generate_user_secret_key(&user_ap).unwrap(); + + // Check user *can* decrypt the OK access policy. + mpk.set_access_policy(ok_ap).unwrap(); + let (key, enc) = ConfigurableKEM::enc(&mpk, &mut rng).unwrap(); + let key_ = ConfigurableKEM::dec(&usk, &enc).unwrap(); + assert_eq!(key, key_); + + // Check user *cannot* decrypt the KO access policy. + mpk.set_access_policy(ko_ap).unwrap(); + let (_key, enc) = ConfigurableKEM::enc(&mpk, &mut rng).unwrap(); + let res = ConfigurableKEM::dec(&usk, &enc); + assert!(res.is_err()); + match res { + Err(Error::OperationNotPermitted(msg)) => { + assert_eq!(&msg, "user key does not have the required access right") + } + _ => panic!("incorrect error returned"), + } + } +} From a5a5191b274a3a116d2cdc9fe85d431e2bf9282d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Mon, 19 Jan 2026 16:14:37 +0100 Subject: [PATCH 07/23] revert naming breaking change --- src/abe.rs | 6 ++-- src/abe/core/mod.rs | 58 +++++++++++++++--------------- src/abe/core/primitives.rs | 10 +++--- src/abe/core/serialization/mod.rs | 8 ++--- src/abe/core/tests.rs | 26 +++++++------- src/abe/policy.rs | 2 +- src/abe/policy/access_structure.rs | 18 +++++----- src/abe/policy/attribute.rs | 4 +-- src/abe/policy/dimension.rs | 26 +++++++------- src/abe/policy/tests.rs | 44 +++++++++++------------ src/lib.rs | 2 ++ src/test_utils/mod.rs | 6 ++-- 12 files changed, 108 insertions(+), 102 deletions(-) diff --git a/src/abe.rs b/src/abe.rs index 41b31d04..536d17b2 100644 --- a/src/abe.rs +++ b/src/abe.rs @@ -1,4 +1,4 @@ -mod api; +pub mod api; pub mod core; mod policy; @@ -7,8 +7,8 @@ pub mod encrypted_header; pub use api::Covercrypt; pub use core::{MasterPublicKey, MasterSecretKey, UserSecretKey, XEnc}; pub use policy::{ - AccessPolicy, AccessStructure, Attribute, Dimension, EncryptionStatus, QualifiedAttribute, - SecurityMode, + AccessPolicy, AccessStructure, Attribute, Dimension, EncryptionHint, EncryptionStatus, + QualifiedAttribute, }; #[cfg(any(test, feature = "test-utils"))] diff --git a/src/abe/core/mod.rs b/src/abe/core/mod.rs index 639a38f3..02d6ad5d 100644 --- a/src/abe/core/mod.rs +++ b/src/abe/core/mod.rs @@ -1,7 +1,7 @@ #![allow(non_snake_case)] use crate::{ - abe::policy::{AccessStructure, EncryptionStatus, Right, SecurityMode}, + abe::policy::{AccessStructure, EncryptionHint, EncryptionStatus, Right}, data_struct::{RevisionMap, RevisionVec}, providers::{ElGamal, MlKem}, Error, @@ -71,17 +71,17 @@ enum RightSecretKey { impl RightSecretKey { /// Generates a new random right secret key cryptographically bound to the Covercrypt binding /// point `h`. - fn random(rng: &mut impl CryptoRngCore, security_mode: SecurityMode) -> Result { + fn random(rng: &mut impl CryptoRngCore, security_mode: EncryptionHint) -> Result { match security_mode { - SecurityMode::PreQuantum => { + EncryptionHint::PreQuantum => { let sk = ::SecretKey::random(rng); Ok(Self::PreQuantum { sk }) } - SecurityMode::PostQuantum => { + EncryptionHint::PostQuantum => { let (dk, _) = MlKem::keygen(rng)?; Ok(Self::PostQuantum { dk }) } - SecurityMode::Hybridized => { + EncryptionHint::Hybridized => { let sk = ::SecretKey::random(rng); let (dk, _) = MlKem::keygen(rng)?; Ok(Self::Hybridized { sk, dk }) @@ -103,37 +103,39 @@ impl RightSecretKey { } /// Returns the security mode of this right secret key. - fn security_mode(&self) -> SecurityMode { + fn security_mode(&self) -> EncryptionHint { match self { - Self::Hybridized { .. } => SecurityMode::Hybridized, - Self::PostQuantum { .. } => SecurityMode::PostQuantum, - Self::PreQuantum { .. } => SecurityMode::PreQuantum, + Self::Hybridized { .. } => EncryptionHint::Hybridized, + Self::PostQuantum { .. } => EncryptionHint::PostQuantum, + Self::PreQuantum { .. } => EncryptionHint::PreQuantum, } } /// Sets the security mode of this right secret key. fn set_security_mode( self, - security_mode: SecurityMode, + security_mode: EncryptionHint, rng: &mut impl CryptoRngCore, ) -> Result { Ok(match (self, security_mode) { - (Self::Hybridized { sk, .. }, SecurityMode::PreQuantum) => Self::PreQuantum { sk }, - (Self::Hybridized { dk, .. }, SecurityMode::PostQuantum) => Self::PostQuantum { dk }, - (Self::Hybridized { sk, dk }, SecurityMode::Hybridized) => Self::Hybridized { sk, dk }, - (Self::PostQuantum { .. }, SecurityMode::PreQuantum) => Self::PostQuantum { + (Self::Hybridized { sk, .. }, EncryptionHint::PreQuantum) => Self::PreQuantum { sk }, + (Self::Hybridized { dk, .. }, EncryptionHint::PostQuantum) => Self::PostQuantum { dk }, + (Self::Hybridized { sk, dk }, EncryptionHint::Hybridized) => { + Self::Hybridized { sk, dk } + } + (Self::PostQuantum { .. }, EncryptionHint::PreQuantum) => Self::PostQuantum { dk: >::keygen(rng)?.0, }, - (Self::PostQuantum { dk }, SecurityMode::PostQuantum) => Self::PostQuantum { dk }, - (Self::PostQuantum { dk }, SecurityMode::Hybridized) => Self::Hybridized { + (Self::PostQuantum { dk }, EncryptionHint::PostQuantum) => Self::PostQuantum { dk }, + (Self::PostQuantum { dk }, EncryptionHint::Hybridized) => Self::Hybridized { sk: ::keygen(rng)?.0, dk, }, - (Self::PreQuantum { sk }, SecurityMode::PreQuantum) => Self::PreQuantum { sk }, - (Self::PreQuantum { .. }, SecurityMode::PostQuantum) => Self::PostQuantum { + (Self::PreQuantum { sk }, EncryptionHint::PreQuantum) => Self::PreQuantum { sk }, + (Self::PreQuantum { .. }, EncryptionHint::PostQuantum) => Self::PostQuantum { dk: >::keygen(rng)?.0, }, - (Self::PreQuantum { sk }, SecurityMode::Hybridized) => Self::Hybridized { + (Self::PreQuantum { sk }, EncryptionHint::Hybridized) => Self::Hybridized { sk, dk: >::keygen(rng)?.0, }, @@ -162,11 +164,11 @@ enum RightPublicKey { impl RightPublicKey { /// Returns the security mode of this right public key. - pub fn security_mode(&self) -> SecurityMode { + pub fn security_mode(&self) -> EncryptionHint { match self { - Self::Hybridized { .. } => SecurityMode::Hybridized, - Self::PostQuantum { .. } => SecurityMode::PostQuantum, - Self::PreQuantum { .. } => SecurityMode::PreQuantum, + Self::Hybridized { .. } => EncryptionHint::Hybridized, + Self::PostQuantum { .. } => EncryptionHint::PostQuantum, + Self::PreQuantum { .. } => EncryptionHint::PreQuantum, } } } @@ -466,7 +468,7 @@ impl MasterPublicKey { fn select_subkeys( &self, targets: &HashSet, - ) -> Result<(SecurityMode, Vec<&RightPublicKey>), Error> { + ) -> Result<(EncryptionHint, Vec<&RightPublicKey>), Error> { let subkeys = targets .iter() .map(|r| { @@ -583,11 +585,11 @@ impl XEnc { } } - pub fn security_mode(&self) -> SecurityMode { + pub fn security_mode(&self) -> EncryptionHint { match self { - Self::Hybridized { .. } => SecurityMode::Hybridized, - Self::PostQuantum { .. } => SecurityMode::PostQuantum, - Self::PreQuantum { .. } => SecurityMode::PreQuantum, + Self::Hybridized { .. } => EncryptionHint::Hybridized, + Self::PostQuantum { .. } => EncryptionHint::PostQuantum, + Self::PreQuantum { .. } => EncryptionHint::PreQuantum, } } } diff --git a/src/abe/core/primitives.rs b/src/abe/core/primitives.rs index 73c1bfa1..ef4e284a 100644 --- a/src/abe/core/primitives.rs +++ b/src/abe/core/primitives.rs @@ -5,7 +5,7 @@ use crate::{ TracingSecretKey, UserId, UserSecretKey, XEnc, MIN_TRACING_LEVEL, SHARED_SECRET_LENGTH, SIGNATURE_LENGTH, SIGNING_KEY_LENGTH, TAG_LENGTH, }, - policy::{AccessStructure, EncryptionStatus, Right, SecurityMode}, + policy::{AccessStructure, EncryptionHint, EncryptionStatus, Right}, }, data_struct::{RevisionMap, RevisionVec}, providers::{ElGamal, MlKem}, @@ -351,7 +351,7 @@ pub fn encaps( let S = Secret::random(rng); match is_hybridized { - SecurityMode::PreQuantum => { + EncryptionHint::PreQuantum => { let r = G_hash(&S)?; let c = mpk.set_traps(&r); @@ -364,7 +364,7 @@ pub fn encaps( }); pre_quantum_encaps(S, c, r, subkeys) } - SecurityMode::PostQuantum => { + EncryptionHint::PostQuantum => { let subkeys = coordinate_keys.into_iter().map(|subkey| { if let RightPublicKey::PostQuantum { ek } = subkey { ek @@ -374,7 +374,7 @@ pub fn encaps( }); post_quantum_encaps(S, subkeys, rng) } - SecurityMode::Hybridized => { + EncryptionHint::Hybridized => { let r = G_hash(&S)?; let c = mpk.set_traps(&r); @@ -837,7 +837,7 @@ pub fn master_decaps( pub fn update_msk( rng: &mut impl CryptoRngCore, msk: &mut MasterSecretKey, - rights: HashMap, + rights: HashMap, ) -> Result<(), Error> { let mut secrets = take(&mut msk.secrets); secrets.retain(|r| rights.contains_key(r)); diff --git a/src/abe/core/serialization/mod.rs b/src/abe/core/serialization/mod.rs index 995a868b..ad5465f1 100644 --- a/src/abe/core/serialization/mod.rs +++ b/src/abe/core/serialization/mod.rs @@ -297,7 +297,7 @@ mod tests { primitives::{encaps, rekey, setup, update_msk, usk_keygen}, MIN_TRACING_LEVEL, }, - policy::{AccessPolicy, EncryptionStatus, Right, SecurityMode}, + policy::{AccessPolicy, EncryptionHint, EncryptionStatus, Right}, KemAc, }, test_utils::cc_keygen, @@ -314,15 +314,15 @@ mod tests { let universe = HashMap::from([ ( coordinate_1.clone(), - (SecurityMode::Hybridized, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::Hybridized, EncryptionStatus::EncryptDecrypt), ), ( coordinate_2.clone(), - (SecurityMode::Hybridized, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::Hybridized, EncryptionStatus::EncryptDecrypt), ), ( coordinate_3.clone(), - (SecurityMode::Hybridized, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::Hybridized, EncryptionStatus::EncryptDecrypt), ), ]); diff --git a/src/abe/core/tests.rs b/src/abe/core/tests.rs index 581fd3e1..8376ce32 100644 --- a/src/abe/core/tests.rs +++ b/src/abe/core/tests.rs @@ -6,7 +6,7 @@ use crate::{ abe::{ core::{ primitives::{decaps, encaps, refresh, rekey, update_msk}, - SecurityMode, + EncryptionHint, }, policy::{AccessPolicy, EncryptionStatus, Right}, Covercrypt, KemAc, PkeAc, @@ -21,8 +21,8 @@ use super::{ #[test] fn security_mode_ordering() { - assert!(SecurityMode::PreQuantum < SecurityMode::PostQuantum); - assert!(SecurityMode::PostQuantum < SecurityMode::Hybridized); + assert!(EncryptionHint::PreQuantum < EncryptionHint::PostQuantum); + assert!(EncryptionHint::PostQuantum < EncryptionHint::Hybridized); } /// This test asserts that it is possible to encapsulate a key for a given @@ -41,11 +41,11 @@ fn test_encapsulation() { HashMap::from_iter([ ( other_coordinate.clone(), - (SecurityMode::PreQuantum, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::PreQuantum, EncryptionStatus::EncryptDecrypt), ), ( target_coordinate.clone(), - (SecurityMode::PreQuantum, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::PreQuantum, EncryptionStatus::EncryptDecrypt), ), ]), ) @@ -103,7 +103,7 @@ fn test_update() { .map(|_| { ( Right::random(&mut rng), - (SecurityMode::PreQuantum, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::PreQuantum, EncryptionStatus::EncryptDecrypt), ) }) .collect::>(); @@ -157,11 +157,11 @@ fn test_rekey() { HashMap::from_iter([ ( coordinate_1.clone(), - (SecurityMode::PreQuantum, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::PreQuantum, EncryptionStatus::EncryptDecrypt), ), ( coordinate_2.clone(), - (SecurityMode::PreQuantum, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::PreQuantum, EncryptionStatus::EncryptDecrypt), ), ]), ) @@ -241,11 +241,11 @@ fn test_integrity_check() { HashMap::from_iter([ ( coordinate_1.clone(), - (SecurityMode::PreQuantum, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::PreQuantum, EncryptionStatus::EncryptDecrypt), ), ( coordinate_2.clone(), - (SecurityMode::PreQuantum, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::PreQuantum, EncryptionStatus::EncryptDecrypt), ), ]), ) @@ -309,7 +309,7 @@ fn test_covercrypt_kem() { .generate_user_secret_key(&mut msk, &ap) .expect("cannot generate usk"); let (secret, enc) = cc.encaps(&mpk, &ap).unwrap(); - assert_eq!(enc.security_mode(), SecurityMode::PreQuantum); + assert_eq!(enc.security_mode(), EncryptionHint::PreQuantum); let res = cc.decaps(&usk, &enc).unwrap(); assert_eq!(secret, res.unwrap()); @@ -322,7 +322,7 @@ fn test_covercrypt_kem() { .generate_user_secret_key(&mut msk, &ap) .expect("cannot generate usk"); let (secret, enc) = cc.encaps(&mpk, &ap).unwrap(); - assert_eq!(enc.security_mode(), SecurityMode::PostQuantum); + assert_eq!(enc.security_mode(), EncryptionHint::PostQuantum); let res = cc.decaps(&usk, &enc).unwrap(); assert_eq!(secret, res.unwrap()); @@ -335,7 +335,7 @@ fn test_covercrypt_kem() { .generate_user_secret_key(&mut msk, &ap) .expect("cannot generate usk"); let (secret, enc) = cc.encaps(&mpk, &ap).unwrap(); - assert_eq!(enc.security_mode(), SecurityMode::Hybridized); + assert_eq!(enc.security_mode(), EncryptionHint::Hybridized); let res = cc.decaps(&usk, &enc).unwrap(); assert_eq!(secret, res.unwrap()); } diff --git a/src/abe/policy.rs b/src/abe/policy.rs index d9b8b647..9f29ad8c 100644 --- a/src/abe/policy.rs +++ b/src/abe/policy.rs @@ -9,7 +9,7 @@ mod tests; pub use access_policy::AccessPolicy; pub use access_structure::AccessStructure; -pub use attribute::{EncryptionStatus, QualifiedAttribute, SecurityMode}; +pub use attribute::{EncryptionHint, EncryptionStatus, QualifiedAttribute}; pub use dimension::{Attribute, Dimension}; pub use rights::Right; diff --git a/src/abe/policy/access_structure.rs b/src/abe/policy/access_structure.rs index 6223e0bf..3279da3c 100644 --- a/src/abe/policy/access_structure.rs +++ b/src/abe/policy/access_structure.rs @@ -2,7 +2,7 @@ use std::collections::{hash_map::Entry, HashMap, HashSet}; use crate::{ abe::policy::{ - attribute::SecurityMode, AccessPolicy, Attribute, Dimension, EncryptionStatus, + attribute::EncryptionHint, AccessPolicy, Attribute, Dimension, EncryptionStatus, QualifiedAttribute, Right, Version, }, data_struct::Dict, @@ -102,7 +102,7 @@ impl AccessStructure { pub fn add_attribute( &mut self, attribute: QualifiedAttribute, - security_mode: SecurityMode, + security_mode: EncryptionHint, after: Option<&str>, ) -> Result<(), Error> { let cnt = self @@ -170,7 +170,9 @@ impl AccessStructure { /// Generates all rights defined by this access structure and return their /// hybridization and activation status. - pub(crate) fn omega(&self) -> Result, Error> { + pub(crate) fn omega( + &self, + ) -> Result, Error> { let universe = self.dimensions.iter().collect::>(); combine(universe.as_slice()) .into_iter() @@ -318,11 +320,11 @@ impl AccessStructure { /// - D2::B2 fn combine( dimensions: &[(&String, &Dimension)], -) -> Vec<(Vec, SecurityMode, EncryptionStatus)> { +) -> Vec<(Vec, EncryptionHint, EncryptionStatus)> { if dimensions.is_empty() { vec![( vec![], - SecurityMode::PreQuantum, + EncryptionHint::PreQuantum, EncryptionStatus::EncryptDecrypt, )] } else { @@ -408,9 +410,9 @@ mod tests { structure.add_anarchy("Country".to_string()).unwrap(); [ - ("France", SecurityMode::PreQuantum), - ("Germany", SecurityMode::PreQuantum), - ("Spain", SecurityMode::PreQuantum), + ("France", EncryptionHint::PreQuantum), + ("Germany", EncryptionHint::PreQuantum), + ("Spain", EncryptionHint::PreQuantum), ] .into_iter() .try_for_each(|(attribute, mode)| { diff --git a/src/abe/policy/attribute.rs b/src/abe/policy/attribute.rs index 6f8740d2..43bffcec 100644 --- a/src/abe/policy/attribute.rs +++ b/src/abe/policy/attribute.rs @@ -6,13 +6,13 @@ use serde::{Deserialize, Serialize}; use crate::Error; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub enum SecurityMode { +pub enum EncryptionHint { PreQuantum, PostQuantum, Hybridized, } -impl Serializable for SecurityMode { +impl Serializable for EncryptionHint { type Error = Error; fn length(&self) -> usize { diff --git a/src/abe/policy/dimension.rs b/src/abe/policy/dimension.rs index a0d81e52..95f72eff 100644 --- a/src/abe/policy/dimension.rs +++ b/src/abe/policy/dimension.rs @@ -5,7 +5,7 @@ use std::{ use serde::{Deserialize, Serialize}; -use super::{EncryptionStatus, SecurityMode}; +use super::{EncryptionHint, EncryptionStatus}; use crate::{data_struct::Dict, Error}; type Name = String; @@ -13,12 +13,12 @@ type Name = String; #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] pub struct Attribute { pub(crate) id: usize, - pub(crate) security_mode: SecurityMode, + pub(crate) security_mode: EncryptionHint, pub(crate) encryption_status: EncryptionStatus, } impl Attribute { - pub fn new(security_mode: SecurityMode, id: usize) -> Self { + pub fn new(security_mode: EncryptionHint, id: usize) -> Self { Self { id, security_mode, @@ -30,7 +30,7 @@ impl Attribute { self.id } - pub fn get_security_mode(&self) -> SecurityMode { + pub fn get_security_mode(&self) -> EncryptionHint { self.security_mode } @@ -106,7 +106,7 @@ impl Dimension { pub fn add_attribute( &mut self, attribute: Name, - security_mode: SecurityMode, + security_mode: EncryptionHint, after: Option<&str>, id: usize, ) -> Result<(), Error> { @@ -267,10 +267,10 @@ mod serialization { fn test_attribute_serialization() { use cosmian_crypto_core::bytes_ser_de::test_serialization; - let attribute = Attribute::new(SecurityMode::PreQuantum, 13); + let attribute = Attribute::new(EncryptionHint::PreQuantum, 13); test_serialization(&attribute).unwrap(); - let attribute = Attribute::new(SecurityMode::Hybridized, usize::MAX); + let attribute = Attribute::new(EncryptionHint::Hybridized, usize::MAX); test_serialization(&attribute).unwrap(); } @@ -318,20 +318,20 @@ mod serialization { use cosmian_crypto_core::bytes_ser_de::test_serialization; let mut d = Dimension::Hierarchy(Dict::new()); - d.add_attribute("A".to_string(), SecurityMode::PreQuantum, None, 0) + d.add_attribute("A".to_string(), EncryptionHint::PreQuantum, None, 0) .unwrap(); - d.add_attribute("B".to_string(), SecurityMode::Hybridized, Some("A"), 1) + d.add_attribute("B".to_string(), EncryptionHint::Hybridized, Some("A"), 1) .unwrap(); - d.add_attribute("C".to_string(), SecurityMode::Hybridized, Some("B"), 2) + d.add_attribute("C".to_string(), EncryptionHint::Hybridized, Some("B"), 2) .unwrap(); test_serialization(&d).unwrap(); let mut d = Dimension::Anarchy(HashMap::new()); - d.add_attribute("A".to_string(), SecurityMode::PreQuantum, None, 0) + d.add_attribute("A".to_string(), EncryptionHint::PreQuantum, None, 0) .unwrap(); - d.add_attribute("B".to_string(), SecurityMode::Hybridized, None, 1) + d.add_attribute("B".to_string(), EncryptionHint::Hybridized, None, 1) .unwrap(); - d.add_attribute("C".to_string(), SecurityMode::Hybridized, None, 2) + d.add_attribute("C".to_string(), EncryptionHint::Hybridized, None, 2) .unwrap(); test_serialization(&d).unwrap(); } diff --git a/src/abe/policy/tests.rs b/src/abe/policy/tests.rs index 4a96166c..3c619d8f 100644 --- a/src/abe/policy/tests.rs +++ b/src/abe/policy/tests.rs @@ -1,5 +1,5 @@ use crate::{ - abe::policy::{AccessStructure, SecurityMode}, + abe::policy::{AccessStructure, EncryptionHint}, Error, }; @@ -11,7 +11,7 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), dimension: "SEC".to_string(), name: "LOW".to_string(), }, - SecurityMode::PreQuantum, + EncryptionHint::PreQuantum, None, )?; policy.add_attribute( @@ -19,7 +19,7 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), dimension: "SEC".to_string(), name: "MED".to_string(), }, - SecurityMode::PostQuantum, + EncryptionHint::PostQuantum, Some("LOW"), )?; policy.add_attribute( @@ -27,17 +27,17 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), dimension: "SEC".to_string(), name: "TOP".to_string(), }, - SecurityMode::Hybridized, + EncryptionHint::Hybridized, Some("MED"), )?; policy.add_anarchy("DPT".to_string())?; [ - ("RD", SecurityMode::PreQuantum), - ("HR", SecurityMode::PreQuantum), - ("MKG", SecurityMode::PreQuantum), - ("FIN", SecurityMode::PreQuantum), - ("DEV", SecurityMode::PreQuantum), + ("RD", EncryptionHint::PreQuantum), + ("HR", EncryptionHint::PreQuantum), + ("MKG", EncryptionHint::PreQuantum), + ("FIN", EncryptionHint::PreQuantum), + ("DEV", EncryptionHint::PreQuantum), ] .into_iter() .try_for_each(|(attribute, mode)| { @@ -54,11 +54,11 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), if complete { policy.add_anarchy("CTR".to_string())?; [ - ("EN", SecurityMode::PreQuantum), - ("DE", SecurityMode::PreQuantum), - ("IT", SecurityMode::PreQuantum), - ("FR", SecurityMode::PreQuantum), - ("SP", SecurityMode::PreQuantum), + ("EN", EncryptionHint::PreQuantum), + ("DE", EncryptionHint::PreQuantum), + ("IT", EncryptionHint::PreQuantum), + ("FR", EncryptionHint::PreQuantum), + ("SP", EncryptionHint::PreQuantum), ] .into_iter() .try_for_each(|(attribute, mode)| { @@ -109,20 +109,20 @@ fn test_edit_anarchic_attributes() { // Add new attribute Sales let new_attr = QualifiedAttribute::new("DPT", "Sales"); assert!(structure - .add_attribute(new_attr.clone(), SecurityMode::PreQuantum, None) + .add_attribute(new_attr.clone(), EncryptionHint::PreQuantum, None) .is_ok()); assert_eq!(structure.attributes().count(), 9); // Try adding already existing attribute HR let duplicate_attr = QualifiedAttribute::new("DPT", "HR"); assert!(structure - .add_attribute(duplicate_attr, SecurityMode::PreQuantum, None) + .add_attribute(duplicate_attr, EncryptionHint::PreQuantum, None) .is_err()); // Try adding attribute to non existing dimension let missing_dimension = QualifiedAttribute::new("Missing", "dimension"); assert!(structure - .add_attribute(missing_dimension.clone(), SecurityMode::PreQuantum, None) + .add_attribute(missing_dimension.clone(), EncryptionHint::PreQuantum, None) .is_err()); // Remove research attribute @@ -154,14 +154,14 @@ fn test_edit_anarchic_attributes() { structure .add_attribute( QualifiedAttribute::new("DimensionTest", "Attr1"), - SecurityMode::PreQuantum, + EncryptionHint::PreQuantum, None, ) .unwrap(); structure .add_attribute( QualifiedAttribute::new("DimensionTest", "Attr2"), - SecurityMode::PreQuantum, + EncryptionHint::PreQuantum, None, ) .unwrap(); @@ -220,7 +220,7 @@ fn test_edit_hierarchic_attributes() { structure .add_attribute( QualifiedAttribute::new("SEC", "OTHER"), - SecurityMode::PreQuantum, + EncryptionHint::PreQuantum, None, ) .unwrap(); @@ -249,7 +249,7 @@ fn test_edit_hierarchic_attributes() { structure .add_attribute( QualifiedAttribute::new("SEC", "LOW"), - SecurityMode::PreQuantum, + EncryptionHint::PreQuantum, None, ) .unwrap(); @@ -286,7 +286,7 @@ fn test_edit_hierarchic_attributes() { structure .add_attribute( QualifiedAttribute::new("SEC", "MID"), - SecurityMode::PreQuantum, + EncryptionHint::PreQuantum, Some("LOW"), ) .unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 189c5720..935a18d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,5 +19,7 @@ mod test_utils; pub mod base; +pub use abe::api; pub use abe::*; + pub use error::Error; diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs index c177fb01..70058df8 100644 --- a/src/test_utils/mod.rs +++ b/src/test_utils/mod.rs @@ -20,8 +20,8 @@ mod tests { use super::*; use crate::abe::{ - encrypted_header::EncryptedHeader, AccessPolicy, Covercrypt, KemAc, QualifiedAttribute, - SecurityMode, + encrypted_header::EncryptedHeader, AccessPolicy, Covercrypt, EncryptionHint, KemAc, + QualifiedAttribute, }; #[test] @@ -34,7 +34,7 @@ mod tests { let _ = &mut msk.access_structure.add_attribute( QualifiedAttribute::new("DPT", "Sales"), - SecurityMode::PreQuantum, + EncryptionHint::PreQuantum, None, )?; let mpk = cc.update_msk(&mut msk)?; From 35bc0efa7c0d59590b20e4a8198a97ba4705a100 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Mon, 19 Jan 2026 16:22:51 +0100 Subject: [PATCH 08/23] revert module-tree breaking change --- src/abe.rs | 63 ++----------------------------- src/abe/api.rs | 2 +- src/abe/core/serialization/mod.rs | 2 +- src/abe/core/tests.rs | 3 +- src/abe/encrypted_header.rs | 3 +- src/abe/traits.rs | 59 +++++++++++++++++++++++++++++ src/base.rs | 2 +- src/lib.rs | 1 - src/test_utils/mod.rs | 2 +- 9 files changed, 70 insertions(+), 67 deletions(-) create mode 100644 src/abe/traits.rs diff --git a/src/abe.rs b/src/abe.rs index 536d17b2..4daa330d 100644 --- a/src/abe.rs +++ b/src/abe.rs @@ -1,6 +1,8 @@ +mod policy; + pub mod api; pub mod core; -mod policy; +pub mod traits; pub mod encrypted_header; @@ -13,62 +15,3 @@ pub use policy::{ #[cfg(any(test, feature = "test-utils"))] pub use policy::gen_structure; - -use cosmian_crypto_core::{traits::AE, Secret}; - -pub trait KemAc { - type EncapsulationKey; - type DecapsulationKey; - type Encapsulation; - type Error: std::error::Error; - - /// Generates a new encapsulation for the given access policy. - /// - /// # Error - /// - /// Returns an error if the access policy is not valid. - fn encaps( - &self, - ek: &Self::EncapsulationKey, - ap: &AccessPolicy, - ) -> Result<(Secret, Self::Encapsulation), Self::Error>; - - /// Attempts opening the given encapsulation with the given key. - /// - /// Returns the encapsulated secret upon success or `None` if this key was - /// not authorized to open this encapsulation. - fn decaps( - &self, - dk: &Self::DecapsulationKey, - enc: &Self::Encapsulation, - ) -> Result>, Self::Error>; -} - -pub trait PkeAc> { - type EncryptionKey; - type DecryptionKey; - type Ciphertext; - type Error: std::error::Error; - - /// Encrypts the given plaintext under the given access policy. - /// - /// # Error - /// - /// Returns an error if the access policy is not valid. - fn encrypt( - &self, - ek: &Self::EncryptionKey, - ap: &AccessPolicy, - ptx: &[u8], - ) -> Result; - - /// Attempts decrypting the given ciphertext with the given key. - /// - /// Returns the plaintext upon success, or `None` if this key was not - /// authorized to decrypt this ciphertext. - fn decrypt( - &self, - dk: &Self::DecryptionKey, - ctx: &Self::Ciphertext, - ) -> Result, Self::Error>; -} diff --git a/src/abe/api.rs b/src/abe/api.rs index 392fb152..dd165643 100644 --- a/src/abe/api.rs +++ b/src/abe/api.rs @@ -8,7 +8,7 @@ use crate::{ SHARED_SECRET_LENGTH, }, policy::AccessPolicy, - KemAc, PkeAc, + traits::{KemAc, PkeAc}, }, Error, }; diff --git a/src/abe/core/serialization/mod.rs b/src/abe/core/serialization/mod.rs index ad5465f1..ec090b28 100644 --- a/src/abe/core/serialization/mod.rs +++ b/src/abe/core/serialization/mod.rs @@ -298,7 +298,7 @@ mod tests { MIN_TRACING_LEVEL, }, policy::{AccessPolicy, EncryptionHint, EncryptionStatus, Right}, - KemAc, + traits::KemAc, }, test_utils::cc_keygen, }; diff --git a/src/abe/core/tests.rs b/src/abe/core/tests.rs index 8376ce32..c1a61b5f 100644 --- a/src/abe/core/tests.rs +++ b/src/abe/core/tests.rs @@ -9,7 +9,8 @@ use crate::{ EncryptionHint, }, policy::{AccessPolicy, EncryptionStatus, Right}, - Covercrypt, KemAc, PkeAc, + traits::{KemAc, PkeAc}, + Covercrypt, }, test_utils::cc_keygen, }; diff --git a/src/abe/encrypted_header.rs b/src/abe/encrypted_header.rs index aa6a0a34..0e7f326c 100644 --- a/src/abe/encrypted_header.rs +++ b/src/abe/encrypted_header.rs @@ -6,7 +6,8 @@ use cosmian_crypto_core::{ use crate::{ abe::{ core::{XEnc, SHARED_SECRET_LENGTH}, - AccessPolicy, Covercrypt, KemAc, MasterPublicKey, UserSecretKey, + traits::KemAc, + AccessPolicy, Covercrypt, MasterPublicKey, UserSecretKey, }, Error, }; diff --git a/src/abe/traits.rs b/src/abe/traits.rs new file mode 100644 index 00000000..8b3dd27b --- /dev/null +++ b/src/abe/traits.rs @@ -0,0 +1,59 @@ +use crate::AccessPolicy; +use cosmian_crypto_core::{traits::AE, Secret}; + +pub trait KemAc { + type EncapsulationKey; + type DecapsulationKey; + type Encapsulation; + type Error: std::error::Error; + + /// Generates a new encapsulation for the given access policy. + /// + /// # Error + /// + /// Returns an error if the access policy is not valid. + fn encaps( + &self, + ek: &Self::EncapsulationKey, + ap: &AccessPolicy, + ) -> Result<(Secret, Self::Encapsulation), Self::Error>; + + /// Attempts opening the given encapsulation with the given key. + /// + /// Returns the encapsulated secret upon success or `None` if this key was + /// not authorized to open this encapsulation. + fn decaps( + &self, + dk: &Self::DecapsulationKey, + enc: &Self::Encapsulation, + ) -> Result>, Self::Error>; +} + +pub trait PkeAc> { + type EncryptionKey; + type DecryptionKey; + type Ciphertext; + type Error: std::error::Error; + + /// Encrypts the given plaintext under the given access policy. + /// + /// # Error + /// + /// Returns an error if the access policy is not valid. + fn encrypt( + &self, + ek: &Self::EncryptionKey, + ap: &AccessPolicy, + ptx: &[u8], + ) -> Result; + + /// Attempts decrypting the given ciphertext with the given key. + /// + /// Returns the plaintext upon success, or `None` if this key was not + /// authorized to decrypt this ciphertext. + fn decrypt( + &self, + dk: &Self::DecryptionKey, + ctx: &Self::Ciphertext, + ) -> Result, Self::Error>; +} diff --git a/src/base.rs b/src/base.rs index 5f4d0b2c..0f953eea 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1,5 +1,5 @@ use crate::{ - abe::{AccessPolicy, KemAc}, + abe::{traits::KemAc, AccessPolicy}, providers::{MlKem, PreQuantumKem}, AccessStructure, Covercrypt, Error, MasterPublicKey, MasterSecretKey, UserSecretKey, XEnc, }; diff --git a/src/lib.rs b/src/lib.rs index 935a18d9..91716930 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,6 @@ mod abe; mod data_struct; mod error; mod providers; -mod traits; #[cfg(any(test, feature = "test-utils"))] mod test_utils; diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs index 70058df8..e89d373a 100644 --- a/src/test_utils/mod.rs +++ b/src/test_utils/mod.rs @@ -20,7 +20,7 @@ mod tests { use super::*; use crate::abe::{ - encrypted_header::EncryptedHeader, AccessPolicy, Covercrypt, EncryptionHint, KemAc, + encrypted_header::EncryptedHeader, traits::KemAc, AccessPolicy, Covercrypt, EncryptionHint, QualifiedAttribute, }; From 483408958b378c42c6f1ee3ecac5358279024067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Mon, 19 Jan 2026 16:54:00 +0100 Subject: [PATCH 09/23] revert EncryptionHint-member breaking change --- src/abe/core/mod.rs | 14 +++++------ src/abe/core/primitives.rs | 2 +- src/abe/core/tests.rs | 18 +++++++------- src/abe/policy/access_structure.rs | 8 +++---- src/abe/policy/attribute.rs | 6 ++--- src/abe/policy/dimension.rs | 6 ++--- src/abe/policy/tests.rs | 38 +++++++++++++++--------------- src/test_utils/mod.rs | 2 +- 8 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/abe/core/mod.rs b/src/abe/core/mod.rs index 02d6ad5d..49435e51 100644 --- a/src/abe/core/mod.rs +++ b/src/abe/core/mod.rs @@ -73,7 +73,7 @@ impl RightSecretKey { /// point `h`. fn random(rng: &mut impl CryptoRngCore, security_mode: EncryptionHint) -> Result { match security_mode { - EncryptionHint::PreQuantum => { + EncryptionHint::Classic => { let sk = ::SecretKey::random(rng); Ok(Self::PreQuantum { sk }) } @@ -107,7 +107,7 @@ impl RightSecretKey { match self { Self::Hybridized { .. } => EncryptionHint::Hybridized, Self::PostQuantum { .. } => EncryptionHint::PostQuantum, - Self::PreQuantum { .. } => EncryptionHint::PreQuantum, + Self::PreQuantum { .. } => EncryptionHint::Classic, } } @@ -118,12 +118,12 @@ impl RightSecretKey { rng: &mut impl CryptoRngCore, ) -> Result { Ok(match (self, security_mode) { - (Self::Hybridized { sk, .. }, EncryptionHint::PreQuantum) => Self::PreQuantum { sk }, + (Self::Hybridized { sk, .. }, EncryptionHint::Classic) => Self::PreQuantum { sk }, (Self::Hybridized { dk, .. }, EncryptionHint::PostQuantum) => Self::PostQuantum { dk }, (Self::Hybridized { sk, dk }, EncryptionHint::Hybridized) => { Self::Hybridized { sk, dk } } - (Self::PostQuantum { .. }, EncryptionHint::PreQuantum) => Self::PostQuantum { + (Self::PostQuantum { .. }, EncryptionHint::Classic) => Self::PostQuantum { dk: >::keygen(rng)?.0, }, (Self::PostQuantum { dk }, EncryptionHint::PostQuantum) => Self::PostQuantum { dk }, @@ -131,7 +131,7 @@ impl RightSecretKey { sk: ::keygen(rng)?.0, dk, }, - (Self::PreQuantum { sk }, EncryptionHint::PreQuantum) => Self::PreQuantum { sk }, + (Self::PreQuantum { sk }, EncryptionHint::Classic) => Self::PreQuantum { sk }, (Self::PreQuantum { .. }, EncryptionHint::PostQuantum) => Self::PostQuantum { dk: >::keygen(rng)?.0, }, @@ -168,7 +168,7 @@ impl RightPublicKey { match self { Self::Hybridized { .. } => EncryptionHint::Hybridized, Self::PostQuantum { .. } => EncryptionHint::PostQuantum, - Self::PreQuantum { .. } => EncryptionHint::PreQuantum, + Self::PreQuantum { .. } => EncryptionHint::Classic, } } } @@ -589,7 +589,7 @@ impl XEnc { match self { Self::Hybridized { .. } => EncryptionHint::Hybridized, Self::PostQuantum { .. } => EncryptionHint::PostQuantum, - Self::PreQuantum { .. } => EncryptionHint::PreQuantum, + Self::PreQuantum { .. } => EncryptionHint::Classic, } } } diff --git a/src/abe/core/primitives.rs b/src/abe/core/primitives.rs index ef4e284a..f8dde602 100644 --- a/src/abe/core/primitives.rs +++ b/src/abe/core/primitives.rs @@ -351,7 +351,7 @@ pub fn encaps( let S = Secret::random(rng); match is_hybridized { - EncryptionHint::PreQuantum => { + EncryptionHint::Classic => { let r = G_hash(&S)?; let c = mpk.set_traps(&r); diff --git a/src/abe/core/tests.rs b/src/abe/core/tests.rs index c1a61b5f..c83ec2cb 100644 --- a/src/abe/core/tests.rs +++ b/src/abe/core/tests.rs @@ -22,7 +22,7 @@ use super::{ #[test] fn security_mode_ordering() { - assert!(EncryptionHint::PreQuantum < EncryptionHint::PostQuantum); + assert!(EncryptionHint::Classic < EncryptionHint::PostQuantum); assert!(EncryptionHint::PostQuantum < EncryptionHint::Hybridized); } @@ -42,11 +42,11 @@ fn test_encapsulation() { HashMap::from_iter([ ( other_coordinate.clone(), - (EncryptionHint::PreQuantum, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::Classic, EncryptionStatus::EncryptDecrypt), ), ( target_coordinate.clone(), - (EncryptionHint::PreQuantum, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::Classic, EncryptionStatus::EncryptDecrypt), ), ]), ) @@ -104,7 +104,7 @@ fn test_update() { .map(|_| { ( Right::random(&mut rng), - (EncryptionHint::PreQuantum, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::Classic, EncryptionStatus::EncryptDecrypt), ) }) .collect::>(); @@ -158,11 +158,11 @@ fn test_rekey() { HashMap::from_iter([ ( coordinate_1.clone(), - (EncryptionHint::PreQuantum, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::Classic, EncryptionStatus::EncryptDecrypt), ), ( coordinate_2.clone(), - (EncryptionHint::PreQuantum, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::Classic, EncryptionStatus::EncryptDecrypt), ), ]), ) @@ -242,11 +242,11 @@ fn test_integrity_check() { HashMap::from_iter([ ( coordinate_1.clone(), - (EncryptionHint::PreQuantum, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::Classic, EncryptionStatus::EncryptDecrypt), ), ( coordinate_2.clone(), - (EncryptionHint::PreQuantum, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::Classic, EncryptionStatus::EncryptDecrypt), ), ]), ) @@ -310,7 +310,7 @@ fn test_covercrypt_kem() { .generate_user_secret_key(&mut msk, &ap) .expect("cannot generate usk"); let (secret, enc) = cc.encaps(&mpk, &ap).unwrap(); - assert_eq!(enc.security_mode(), EncryptionHint::PreQuantum); + assert_eq!(enc.security_mode(), EncryptionHint::Classic); let res = cc.decaps(&usk, &enc).unwrap(); assert_eq!(secret, res.unwrap()); diff --git a/src/abe/policy/access_structure.rs b/src/abe/policy/access_structure.rs index 3279da3c..50ac8859 100644 --- a/src/abe/policy/access_structure.rs +++ b/src/abe/policy/access_structure.rs @@ -324,7 +324,7 @@ fn combine( if dimensions.is_empty() { vec![( vec![], - EncryptionHint::PreQuantum, + EncryptionHint::Classic, EncryptionStatus::EncryptDecrypt, )] } else { @@ -410,9 +410,9 @@ mod tests { structure.add_anarchy("Country".to_string()).unwrap(); [ - ("France", EncryptionHint::PreQuantum), - ("Germany", EncryptionHint::PreQuantum), - ("Spain", EncryptionHint::PreQuantum), + ("France", EncryptionHint::Classic), + ("Germany", EncryptionHint::Classic), + ("Spain", EncryptionHint::Classic), ] .into_iter() .try_for_each(|(attribute, mode)| { diff --git a/src/abe/policy/attribute.rs b/src/abe/policy/attribute.rs index 43bffcec..970ff445 100644 --- a/src/abe/policy/attribute.rs +++ b/src/abe/policy/attribute.rs @@ -7,7 +7,7 @@ use crate::Error; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub enum EncryptionHint { - PreQuantum, + Classic, PostQuantum, Hybridized, } @@ -24,7 +24,7 @@ impl Serializable for EncryptionHint { ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, ) -> Result { match self { - Self::PreQuantum => ser.write(&0usize), + Self::Classic => ser.write(&0usize), Self::Hybridized => ser.write(&1usize), Self::PostQuantum => ser.write(&2usize), } @@ -34,7 +34,7 @@ impl Serializable for EncryptionHint { fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { let status = de.read::()?; match status { - 0 => Ok(Self::PreQuantum), + 0 => Ok(Self::Classic), 1 => Ok(Self::Hybridized), 2 => Ok(Self::PostQuantum), n => Err(Error::ConversionFailed(format!( diff --git a/src/abe/policy/dimension.rs b/src/abe/policy/dimension.rs index 95f72eff..19c91b8c 100644 --- a/src/abe/policy/dimension.rs +++ b/src/abe/policy/dimension.rs @@ -267,7 +267,7 @@ mod serialization { fn test_attribute_serialization() { use cosmian_crypto_core::bytes_ser_de::test_serialization; - let attribute = Attribute::new(EncryptionHint::PreQuantum, 13); + let attribute = Attribute::new(EncryptionHint::Classic, 13); test_serialization(&attribute).unwrap(); let attribute = Attribute::new(EncryptionHint::Hybridized, usize::MAX); @@ -318,7 +318,7 @@ mod serialization { use cosmian_crypto_core::bytes_ser_de::test_serialization; let mut d = Dimension::Hierarchy(Dict::new()); - d.add_attribute("A".to_string(), EncryptionHint::PreQuantum, None, 0) + d.add_attribute("A".to_string(), EncryptionHint::Classic, None, 0) .unwrap(); d.add_attribute("B".to_string(), EncryptionHint::Hybridized, Some("A"), 1) .unwrap(); @@ -327,7 +327,7 @@ mod serialization { test_serialization(&d).unwrap(); let mut d = Dimension::Anarchy(HashMap::new()); - d.add_attribute("A".to_string(), EncryptionHint::PreQuantum, None, 0) + d.add_attribute("A".to_string(), EncryptionHint::Classic, None, 0) .unwrap(); d.add_attribute("B".to_string(), EncryptionHint::Hybridized, None, 1) .unwrap(); diff --git a/src/abe/policy/tests.rs b/src/abe/policy/tests.rs index 3c619d8f..8467e3d8 100644 --- a/src/abe/policy/tests.rs +++ b/src/abe/policy/tests.rs @@ -11,7 +11,7 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), dimension: "SEC".to_string(), name: "LOW".to_string(), }, - EncryptionHint::PreQuantum, + EncryptionHint::Classic, None, )?; policy.add_attribute( @@ -33,11 +33,11 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), policy.add_anarchy("DPT".to_string())?; [ - ("RD", EncryptionHint::PreQuantum), - ("HR", EncryptionHint::PreQuantum), - ("MKG", EncryptionHint::PreQuantum), - ("FIN", EncryptionHint::PreQuantum), - ("DEV", EncryptionHint::PreQuantum), + ("RD", EncryptionHint::Classic), + ("HR", EncryptionHint::Classic), + ("MKG", EncryptionHint::Classic), + ("FIN", EncryptionHint::Classic), + ("DEV", EncryptionHint::Classic), ] .into_iter() .try_for_each(|(attribute, mode)| { @@ -54,11 +54,11 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), if complete { policy.add_anarchy("CTR".to_string())?; [ - ("EN", EncryptionHint::PreQuantum), - ("DE", EncryptionHint::PreQuantum), - ("IT", EncryptionHint::PreQuantum), - ("FR", EncryptionHint::PreQuantum), - ("SP", EncryptionHint::PreQuantum), + ("EN", EncryptionHint::Classic), + ("DE", EncryptionHint::Classic), + ("IT", EncryptionHint::Classic), + ("FR", EncryptionHint::Classic), + ("SP", EncryptionHint::Classic), ] .into_iter() .try_for_each(|(attribute, mode)| { @@ -109,20 +109,20 @@ fn test_edit_anarchic_attributes() { // Add new attribute Sales let new_attr = QualifiedAttribute::new("DPT", "Sales"); assert!(structure - .add_attribute(new_attr.clone(), EncryptionHint::PreQuantum, None) + .add_attribute(new_attr.clone(), EncryptionHint::Classic, None) .is_ok()); assert_eq!(structure.attributes().count(), 9); // Try adding already existing attribute HR let duplicate_attr = QualifiedAttribute::new("DPT", "HR"); assert!(structure - .add_attribute(duplicate_attr, EncryptionHint::PreQuantum, None) + .add_attribute(duplicate_attr, EncryptionHint::Classic, None) .is_err()); // Try adding attribute to non existing dimension let missing_dimension = QualifiedAttribute::new("Missing", "dimension"); assert!(structure - .add_attribute(missing_dimension.clone(), EncryptionHint::PreQuantum, None) + .add_attribute(missing_dimension.clone(), EncryptionHint::Classic, None) .is_err()); // Remove research attribute @@ -154,14 +154,14 @@ fn test_edit_anarchic_attributes() { structure .add_attribute( QualifiedAttribute::new("DimensionTest", "Attr1"), - EncryptionHint::PreQuantum, + EncryptionHint::Classic, None, ) .unwrap(); structure .add_attribute( QualifiedAttribute::new("DimensionTest", "Attr2"), - EncryptionHint::PreQuantum, + EncryptionHint::Classic, None, ) .unwrap(); @@ -220,7 +220,7 @@ fn test_edit_hierarchic_attributes() { structure .add_attribute( QualifiedAttribute::new("SEC", "OTHER"), - EncryptionHint::PreQuantum, + EncryptionHint::Classic, None, ) .unwrap(); @@ -249,7 +249,7 @@ fn test_edit_hierarchic_attributes() { structure .add_attribute( QualifiedAttribute::new("SEC", "LOW"), - EncryptionHint::PreQuantum, + EncryptionHint::Classic, None, ) .unwrap(); @@ -286,7 +286,7 @@ fn test_edit_hierarchic_attributes() { structure .add_attribute( QualifiedAttribute::new("SEC", "MID"), - EncryptionHint::PreQuantum, + EncryptionHint::Classic, Some("LOW"), ) .unwrap(); diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs index e89d373a..bb16f4ae 100644 --- a/src/test_utils/mod.rs +++ b/src/test_utils/mod.rs @@ -34,7 +34,7 @@ mod tests { let _ = &mut msk.access_structure.add_attribute( QualifiedAttribute::new("DPT", "Sales"), - EncryptionHint::PreQuantum, + EncryptionHint::Classic, None, )?; let mpk = cc.update_msk(&mut msk)?; From 2e3457786c25e0e9222ddad646fee1ce8207cc5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Tue, 20 Jan 2026 15:53:23 +0100 Subject: [PATCH 10/23] fix: upgrade CryptoCore --- Cargo.toml | 6 +++--- src/providers.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e4764d00..d9ede7b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,9 +46,9 @@ curve25519 = ["cosmian_rust_curve25519_provider"] test-utils = [] [dependencies] -cosmian_rust_nist_ec_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/restructure-repo", optional = true } -cosmian_rust_curve25519_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/restructure-repo", optional = true } -cosmian_crypto_core = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/restructure-repo", default-features = false, features = [ +cosmian_rust_nist_ec_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/revert-breaking-changes", optional = true } +cosmian_rust_curve25519_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/revert-breaking-changes", optional = true } +cosmian_crypto_core = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/revert-breaking-changes", default-features = false, features = [ "aes", "ser", "sha3" diff --git a/src/providers.rs b/src/providers.rs index 5343cacb..4a756450 100644 --- a/src/providers.rs +++ b/src/providers.rs @@ -1,4 +1,4 @@ -use cosmian_crypto_core::{kdf::Kdf as ShakeKDF, traits::cyclic_group_to_kem::GenericKem}; +use cosmian_crypto_core::{kdf::Kdf256 as ShakeKDF256, traits::cyclic_group_to_kem::GenericKem}; mod kem; mod nike; @@ -8,4 +8,4 @@ pub use nike::ElGamal; pub const PRE_QUANTUM_KEM_KEY_LENGTH: usize = 32; -pub type PreQuantumKem = GenericKem; +pub type PreQuantumKem = GenericKem; From 63cde14b44afbcf1e01539dc588e0a7c83b23868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Tue, 20 Jan 2026 17:08:32 +0100 Subject: [PATCH 11/23] add hybridized implementation --- src/base.rs | 59 +++++++++++++++++++++++++++++++++++++- src/providers/kem/mlkem.rs | 9 ++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/base.rs b/src/base.rs index 0f953eea..b6900764 100644 --- a/src/base.rs +++ b/src/base.rs @@ -4,7 +4,9 @@ use crate::{ AccessStructure, Covercrypt, Error, MasterPublicKey, MasterSecretKey, UserSecretKey, XEnc, }; use cosmian_crypto_core::{ - reexport::{rand_core::CryptoRngCore, zeroize::ZeroizeOnDrop}, + bytes_ser_de::Serializable, + kdf::Hasher, + reexport::{rand_core::CryptoRngCore, tiny_keccak::Sha3, zeroize::ZeroizeOnDrop}, traits::{kem_to_pke::GenericPKE, KEM}, SymmetricKey, }; @@ -23,6 +25,10 @@ pub enum DKey { AbeScheme(AbeDKey), PreQuantum(>::DecapsulationKey), PostQuantum(>::DecapsulationKey), + Hybridized( + >::DecapsulationKey, + >::DecapsulationKey, + ), } impl DKey { @@ -124,6 +130,10 @@ pub enum EKey { AbeScheme(Covercrypt, MasterPublicKey, Option), PreQuantum(>::EncapsulationKey), PostQuantum(>::EncapsulationKey), + Hybridized( + >::EncapsulationKey, + >::EncapsulationKey, + ), } impl EKey { @@ -146,6 +156,10 @@ pub enum Enc { AbeScheme(XEnc), PreQuantum(>::Encapsulation), PostQuantum(>::Encapsulation), + Hybridized( + >::Encapsulation, + >::Encapsulation, + ), } #[derive(Debug, Clone)] @@ -153,6 +167,7 @@ pub enum Configuration { AbeScheme, PreQuantum, PostQuantum, + Hybridized, } impl Configuration { @@ -174,6 +189,14 @@ impl Configuration { let (dk, ek) = MlKem::keygen(rng)?; Ok((DKey::PostQuantum(dk), EKey::PostQuantum(ek))) } + Self::Hybridized => { + let (pre_dk, pre_ek) = PreQuantumKem::keygen(rng)?; + let (post_dk, post_ek) = MlKem::keygen(rng)?; + Ok(( + DKey::Hybridized(pre_dk, post_dk), + EKey::Hybridized(pre_ek, post_ek), + )) + } } } } @@ -219,6 +242,19 @@ impl KEM<32> for ConfigurableKEM { let (key, enc) = MlKem::enc(ek, rng).map_err(|e| Error::Kem(e.to_string()))?; Ok((key, Enc::PostQuantum(enc))) } + EKey::Hybridized(pre_ek, post_ek) => { + let (k1, enc1) = + PreQuantumKem::enc(pre_ek, rng).map_err(|e| Error::Kem(e.to_string()))?; + let (k2, enc2) = MlKem::enc(post_ek, rng).map_err(|e| Error::Kem(e.to_string()))?; + let mut key = SymmetricKey::default(); + let mut hasher = Sha3::v256(); + hasher.update(&*k1); + hasher.update(&*k2); + hasher.update(&enc1.serialize()?); + hasher.update(&enc2); + hasher.finalize(&mut *key); + Ok((key, Enc::Hybridized(enc1, enc2))) + } } } @@ -247,6 +283,18 @@ impl KEM<32> for ConfigurableKEM { (DKey::PostQuantum(dk), Enc::PostQuantum(enc)) => { MlKem::dec(dk, enc).map_err(|e| Error::Kem(e.to_string())) } + (DKey::Hybridized(dk1, dk2), Enc::Hybridized(enc1, enc2)) => { + let k1 = PreQuantumKem::dec(dk1, enc1).map_err(|e| Error::Kem(e.to_string()))?; + let k2 = MlKem::dec(dk2, enc2).map_err(|e| Error::Kem(e.to_string()))?; + let mut key = SymmetricKey::default(); + let mut hasher = Sha3::v256(); + hasher.update(&*k1); + hasher.update(&*k2); + hasher.update(&enc1.serialize()?); + hasher.update(enc2); + hasher.finalize(&mut *key); + Ok(key) + } _ => Err(Error::KeyError( "cannot proceed with decapsulation: incompatible types".to_string(), )), @@ -299,4 +347,13 @@ mod tests { _ => panic!("incorrect error returned"), } } + + #[test] + fn test_hybridized_kem() { + let mut rng = CsRng::from_entropy(); + let (sk, pk) = Configuration::Hybridized.keygen(&mut rng).unwrap(); + let (key, enc) = ConfigurableKEM::enc(&pk, &mut rng).unwrap(); + let key_ = ConfigurableKEM::dec(&sk, &enc).unwrap(); + assert_eq!(key, key_); + } } diff --git a/src/providers/kem/mlkem.rs b/src/providers/kem/mlkem.rs index 19818e0e..c04c44ae 100644 --- a/src/providers/kem/mlkem.rs +++ b/src/providers/kem/mlkem.rs @@ -1,4 +1,5 @@ use crate::Error; +use core::ops::Deref; use cosmian_crypto_core::{ bytes_ser_de::{Deserializer, Serializable, Serializer}, reexport::{ @@ -81,6 +82,14 @@ macro_rules! make_mlkem { #[derive(Debug, PartialEq, Eq, Clone, Hash)] pub struct $enc(Box::CiphertextSize>>); + impl Deref for $enc { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.0.as_slice() + } + } + impl Serializable for $enc { type Error = CryptoCoreError; From d1a8aec271c2fade51a608a7e23ce0f50f309c2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Tue, 20 Jan 2026 17:23:05 +0100 Subject: [PATCH 12/23] enforce KEM semantics across `ConfigurableKEM` variants --- src/base.rs | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/base.rs b/src/base.rs index b6900764..dd9acca9 100644 --- a/src/base.rs +++ b/src/base.rs @@ -8,7 +8,7 @@ use cosmian_crypto_core::{ kdf::Hasher, reexport::{rand_core::CryptoRngCore, tiny_keccak::Sha3, zeroize::ZeroizeOnDrop}, traits::{kem_to_pke::GenericPKE, KEM}, - SymmetricKey, + Secret, SymmetricKey, }; #[derive(Debug)] @@ -201,6 +201,18 @@ impl Configuration { } } +/// Interface of a CCA KEM that can be configured to be either: +/// - a pre-quantum KEM based on the ElGamal provider; +/// - a post-quantum KEM based on the MlKem provider; +/// - a hybridized KEM based on both the ElGamal and MlKem provider and +/// guaranteeing bast-of-both security; +/// - an ABE KEM based on Covercrypt and in which case the security against a +/// post-quantum adversary is defined on a per-attribute basis similarly to +/// Covercrypt. +/// +/// Note while the constant-time characteristic of these variant depends on the +/// specific provider, the ABE variant *is not* constant-time as Covercrypt is +/// not. #[derive(Debug, Clone)] pub struct ConfigurableKEM; @@ -268,13 +280,11 @@ impl KEM<32> for ConfigurableKEM { let (ss, _) = crate::abe::core::primitives::master_decaps(msk, xenc, false)?; Ok(SymmetricKey::from(ss)) } - AbeDKey::User(cc, usk) => cc.decaps(usk, xenc).and_then(|res| { - let ss = res.ok_or_else(|| { - Error::OperationNotPermitted( - "user key does not have the required access right".to_string(), - ) - })?; - Ok(SymmetricKey::from(ss)) + AbeDKey::User(cc, usk) => cc.decaps(usk, xenc).map(|res| { + // If the user does not have the right to decapsulate, + // return a random key to preserve the KEM semantics. + let ss = res.unwrap_or_else(|| Secret::random(&mut *cc.rng())); + SymmetricKey::from(ss) }), }, (DKey::PreQuantum(dk), Enc::PreQuantum(enc)) => { @@ -338,14 +348,8 @@ mod tests { // Check user *cannot* decrypt the KO access policy. mpk.set_access_policy(ko_ap).unwrap(); let (_key, enc) = ConfigurableKEM::enc(&mpk, &mut rng).unwrap(); - let res = ConfigurableKEM::dec(&usk, &enc); - assert!(res.is_err()); - match res { - Err(Error::OperationNotPermitted(msg)) => { - assert_eq!(&msg, "user key does not have the required access right") - } - _ => panic!("incorrect error returned"), - } + let key_ = ConfigurableKEM::dec(&usk, &enc).unwrap(); + assert!(key != key_); } #[test] @@ -355,5 +359,9 @@ mod tests { let (key, enc) = ConfigurableKEM::enc(&pk, &mut rng).unwrap(); let key_ = ConfigurableKEM::dec(&sk, &enc).unwrap(); assert_eq!(key, key_); + + let (sk, _) = Configuration::Hybridized.keygen(&mut rng).unwrap(); + let key_ = ConfigurableKEM::dec(&sk, &enc).unwrap(); + assert!(key != key_); } } From ada7c4c5b45c1c5c1fa3bdc7aaa367defe67b84b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Sun, 25 Jan 2026 21:37:54 +0100 Subject: [PATCH 13/23] upgrade CryptoCore, use OpenSSL provider for P256 and remove base --- .gitignore | 1 - Cargo.lock | 1055 +++++++++++++++++++++++++++++++++++ Cargo.toml | 8 +- src/abe/api.rs | 17 +- src/abe/core/mod.rs | 29 +- src/abe/core/tests.rs | 20 +- src/abe/policy/attribute.rs | 23 +- src/abe/traits.rs | 9 +- src/error.rs | 2 +- src/lib.rs | 2 - src/providers.rs | 6 - src/providers/kem/mlkem.rs | 15 +- src/providers/nike.rs | 2 +- 13 files changed, 1134 insertions(+), 55 deletions(-) create mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore index a91b5abe..56cedb12 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ *nix* /*.sh /.vscode -Cargo.lock **/.#* **/#*# **/*~ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..1dc743d1 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1055 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", + "zeroize", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cosmian_cover_crypt" +version = "15.0.0" +dependencies = [ + "cosmian_crypto_core", + "cosmian_openssl_provider", + "cosmian_rust_curve25519_provider", + "criterion", + "ml-kem", + "serde", + "serde_json", + "zeroize", +] + +[[package]] +name = "cosmian_crypto_core" +version = "10.4.0" +source = "git+https://github.com/Cosmian/crypto_core.git?branch=tbz%2Fimplement-openssl-provider#c5a0ee061bcb7b504a78585d463b35cb9aa26cf9" +dependencies = [ + "aead", + "aes-gcm", + "curve25519-dalek", + "ed25519-dalek", + "getrandom", + "leb128", + "rand_chacha", + "rand_core", + "sha2", + "signature", + "tiny-keccak", + "zeroize", +] + +[[package]] +name = "cosmian_openssl_provider" +version = "1.0.0" +source = "git+https://github.com/Cosmian/crypto_core.git?branch=tbz%2Fimplement-openssl-provider#c5a0ee061bcb7b504a78585d463b35cb9aa26cf9" +dependencies = [ + "cosmian_crypto_core", + "openssl", + "zeroize", +] + +[[package]] +name = "cosmian_rust_curve25519_provider" +version = "1.0.0" +source = "git+https://github.com/Cosmian/crypto_core.git?branch=tbz%2Fimplement-openssl-provider#c5a0ee061bcb7b504a78585d463b35cb9aa26cf9" +dependencies = [ + "cosmian_crypto_core", + "curve25519-dalek", + "zeroize", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "criterion" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools", + "num-traits", + "oorandom", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rand_core", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hybrid-array" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2d35805454dc9f8662a98d6d61886ffe26bd465f5960e0e55345c70d5c0d2a9" +dependencies = [ + "typenum", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "kem" +version = "0.3.0-pre.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8645470337db67b01a7f966decf7d0bafedbae74147d33e641c67a91df239f" +dependencies = [ + "rand_core", + "zeroize", +] + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "ml-kem" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcaee19a45f916d98f24a551cc9a2cdae705a040e66f3cbc4f3a282ea6a2e982" +dependencies = [ + "hybrid-array", + "kem", + "rand_core", + "sha3", + "zeroize", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zerocopy" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" diff --git a/Cargo.toml b/Cargo.toml index d9ede7b7..24a609b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,14 +41,14 @@ required-features = ["test-utils"] default = ["mlkem-512", "curve25519"] mlkem-512 = [] mlkem-768 = [] -p-256 = ["cosmian_rust_nist_ec_provider"] +p-256 = ["cosmian_openssl_provider"] curve25519 = ["cosmian_rust_curve25519_provider"] test-utils = [] [dependencies] -cosmian_rust_nist_ec_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/revert-breaking-changes", optional = true } -cosmian_rust_curve25519_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/revert-breaking-changes", optional = true } -cosmian_crypto_core = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/revert-breaking-changes", default-features = false, features = [ +cosmian_openssl_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/implement-openssl-provider", optional = true } +cosmian_rust_curve25519_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/implement-openssl-provider", optional = true } +cosmian_crypto_core = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/implement-openssl-provider", default-features = false, features = [ "aes", "ser", "sha3" diff --git a/src/abe/api.rs b/src/abe/api.rs index dd165643..3ff4732d 100644 --- a/src/abe/api.rs +++ b/src/abe/api.rs @@ -13,7 +13,9 @@ use crate::{ Error, }; use cosmian_crypto_core::{ - reexport::rand_core::SeedableRng, traits::AE, CsRng, Secret, SymmetricKey, + reexport::rand_core::{RngCore, SeedableRng}, + traits::AE, + CsRng, Secret, SymmetricKey, }; use std::sync::{Arc, Mutex, MutexGuard}; @@ -192,7 +194,12 @@ impl KemAc for Covercrypt { } } -impl> PkeAc for Covercrypt +impl< + const KEY_LENGTH: usize, + const NONCE_LENGTH: usize, + const TAG_LENGTH: usize, + E: AE, + > PkeAc for Covercrypt where Error: From, { @@ -212,7 +219,9 @@ where // this encapsulation also requires locking the RNG. let mut rng = self.rng.lock().expect("poisoned lock"); let key = SymmetricKey::::derive(&seed, b"Covercrypt AE key")?; - let ctx = E::encrypt(&key, ptx, &mut *rng)?; + let mut nonce = [0; NONCE_LENGTH]; + rng.fill_bytes(&mut nonce); + let ctx = E::encrypt(&key, ptx, &nonce)?; Ok((enc, ctx)) } @@ -224,7 +233,7 @@ where self.decaps(usk, &ctx.0)? .map(|seed| { let key = SymmetricKey::derive(&seed, b"Covercrypt AE key")?; - E::decrypt(&key, &ctx.1).map_err(Self::Error::from) + E::decrypt(&key, ctx.1.as_ref()).map_err(Self::Error::from) }) .transpose() } diff --git a/src/abe/core/mod.rs b/src/abe/core/mod.rs index 49435e51..da0482e3 100644 --- a/src/abe/core/mod.rs +++ b/src/abe/core/mod.rs @@ -11,10 +11,7 @@ use cosmian_crypto_core::{ traits::{Sampling, Zero, KEM, NIKE}, SymmetricKey, }; -use std::{ - collections::{HashMap, HashSet, LinkedList}, - hash::Hash, -}; +use std::collections::{HashMap, HashSet, LinkedList}; mod serialization; @@ -174,7 +171,7 @@ impl RightPublicKey { } /// Covercrypt user IDs are used to make user keys unique and traceable. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] +#[derive(Clone, Debug, PartialEq, Eq, Default)] struct UserId(LinkedList<::SecretKey>); impl UserId { @@ -206,7 +203,18 @@ impl UserId { struct TracingSecretKey { s: ::SecretKey, tracers: LinkedList<(::SecretKey, ::PublicKey)>, - users: HashSet, + // Since `Hash` is not a fallible operation, it cannot be implemented on FFI + // providers like OpenSSL. And since `Zeroizing>` does not implement + // `Hash` either, a `HashSet` cannot be using without extracting the raw + // bytes which then requires manually zeroizing them everywhere they may be + // leaking. Using a linked list implies a linear complexity in the number of + // comparisons, which themselves have a linear complexity in the tracing + // dimension. This is not ideal, but it is safe. + // + // Since this is an internal implementation detail, the container used may + // be change later without breaking change as long as it serializes to the + // same bytes. + users: LinkedList, } impl TracingSecretKey { @@ -215,7 +223,7 @@ impl TracingSecretKey { let tracers = (0..=level) .map(|_| ::keygen(rng)) .collect::>()?; - let users = HashSet::new(); + let users = LinkedList::new(); Ok(Self { s, tracers, users }) } @@ -272,14 +280,17 @@ impl TracingSecretKey { /// Adds the given user ID to the list of known users. fn add_user(&mut self, id: UserId) { - self.users.insert(id); + self.users.push_front(id); } /// Removes the given user ID from the list of known users. /// /// Returns true if the user was in the list. fn del_user(&mut self, id: &UserId) -> bool { - self.users.remove(id) + self.users + .extract_if(|id_| id == id_) + .collect::>() + .is_empty() } /// Generates the associated tracing public key. diff --git a/src/abe/core/tests.rs b/src/abe/core/tests.rs index c83ec2cb..ffe38412 100644 --- a/src/abe/core/tests.rs +++ b/src/abe/core/tests.rs @@ -1,6 +1,6 @@ use std::collections::{HashMap, HashSet}; -use cosmian_crypto_core::{reexport::rand_core::SeedableRng, Aes256Gcm, CsRng}; +use cosmian_crypto_core::{reexport::rand_core::SeedableRng, traits::AE_InPlace, Aes256Gcm, CsRng}; use crate::{ abe::{ @@ -349,12 +349,22 @@ fn test_covercrypt_pke() { let ptx = "testing encryption/decryption".as_bytes(); - let ctx = PkeAc::<{ Aes256Gcm::KEY_LENGTH }, Aes256Gcm>::encrypt(&cc, &mpk, &ap, ptx) - .expect("cannot encrypt!"); + let ctx = PkeAc::< + { Aes256Gcm::KEY_LENGTH }, + { Aes256Gcm::NONCE_LENGTH }, + { Aes256Gcm::TAG_LENGTH }, + Aes256Gcm, + >::encrypt(&cc, &mpk, &ap, ptx) + .expect("cannot encrypt!"); let usk = cc .generate_user_secret_key(&mut msk, &ap) .expect("cannot generate usk"); - let ptx1 = PkeAc::<{ Aes256Gcm::KEY_LENGTH }, Aes256Gcm>::decrypt(&cc, &usk, &ctx) - .expect("cannot decrypt the ciphertext"); + let ptx1 = PkeAc::< + { Aes256Gcm::KEY_LENGTH }, + { Aes256Gcm::NONCE_LENGTH }, + { Aes256Gcm::TAG_LENGTH }, + Aes256Gcm, + >::decrypt(&cc, &usk, &ctx) + .expect("cannot decrypt the ciphertext"); assert_eq!(ptx, &*ptx1.unwrap()); } diff --git a/src/abe/policy/attribute.rs b/src/abe/policy/attribute.rs index 970ff445..ec394fef 100644 --- a/src/abe/policy/attribute.rs +++ b/src/abe/policy/attribute.rs @@ -1,6 +1,6 @@ use std::{convert::TryFrom, fmt::Debug, ops::BitOr}; -use cosmian_crypto_core::bytes_ser_de::Serializable; +use cosmian_crypto_core::bytes_ser_de::{Deserializer, Serializable, Serializer}; use serde::{Deserialize, Serialize}; use crate::Error; @@ -19,10 +19,7 @@ impl Serializable for EncryptionHint { 1 } - fn write( - &self, - ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, - ) -> Result { + fn write(&self, ser: &mut Serializer) -> Result { match self { Self::Classic => ser.write(&0usize), Self::Hybridized => ser.write(&1usize), @@ -31,7 +28,7 @@ impl Serializable for EncryptionHint { .map_err(Error::from) } - fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + fn read(de: &mut Deserializer) -> Result { let status = de.read::()?; match status { 0 => Ok(Self::Classic), @@ -79,10 +76,7 @@ impl Serializable for EncryptionStatus { 1 } - fn write( - &self, - ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, - ) -> Result { + fn write(&self, ser: &mut Serializer) -> Result { match self { Self::DecryptOnly => ser.write(&0usize), Self::EncryptDecrypt => ser.write(&1usize), @@ -90,7 +84,7 @@ impl Serializable for EncryptionStatus { .map_err(Error::from) } - fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + fn read(de: &mut Deserializer) -> Result { let status = de.read::()?; match status { 0 => Ok(Self::DecryptOnly), @@ -189,14 +183,11 @@ impl Serializable for QualifiedAttribute { self.dimension.length() + self.name.length() } - fn write( - &self, - ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, - ) -> Result { + fn write(&self, ser: &mut Serializer) -> Result { Ok(ser.write(&self.dimension)? + ser.write(&self.name)?) } - fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + fn read(de: &mut Deserializer) -> Result { Ok(Self { dimension: de.read()?, name: de.read()?, diff --git a/src/abe/traits.rs b/src/abe/traits.rs index 8b3dd27b..2b7ef1c4 100644 --- a/src/abe/traits.rs +++ b/src/abe/traits.rs @@ -29,10 +29,17 @@ pub trait KemAc { ) -> Result>, Self::Error>; } -pub trait PkeAc> { +pub trait PkeAc< + const KEY_LENGTH: usize, + const NONCE_LENGTH: usize, + const TAG_LENGTH: usize, + E: AE, +> +{ type EncryptionKey; type DecryptionKey; type Ciphertext; + type Error: std::error::Error; /// Encrypts the given plaintext under the given access policy. diff --git a/src/error.rs b/src/error.rs index 191b08c9..797244dc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -23,7 +23,7 @@ impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Kem(err) => write!(f, "Kyber error: {err}"), - Self::CryptoCoreError(err) => write!(f, "CryptoCore error{err}"), + Self::CryptoCoreError(err) => write!(f, "CryptoCore error: {err}"), Self::KeyError(err) => write!(f, "{err}"), Self::AttributeNotFound(err) => write!(f, "attribute not found: {err}"), Self::ExistingDimension(dimension) => { diff --git a/src/lib.rs b/src/lib.rs index 91716930..710c53e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,8 +16,6 @@ mod providers; #[cfg(any(test, feature = "test-utils"))] mod test_utils; -pub mod base; - pub use abe::api; pub use abe::*; diff --git a/src/providers.rs b/src/providers.rs index 4a756450..c2726d0a 100644 --- a/src/providers.rs +++ b/src/providers.rs @@ -1,11 +1,5 @@ -use cosmian_crypto_core::{kdf::Kdf256 as ShakeKDF256, traits::cyclic_group_to_kem::GenericKem}; - mod kem; mod nike; pub use kem::MlKem; pub use nike::ElGamal; - -pub const PRE_QUANTUM_KEM_KEY_LENGTH: usize = 32; - -pub type PreQuantumKem = GenericKem; diff --git a/src/providers/kem/mlkem.rs b/src/providers/kem/mlkem.rs index c04c44ae..f7aca02b 100644 --- a/src/providers/kem/mlkem.rs +++ b/src/providers/kem/mlkem.rs @@ -15,13 +15,19 @@ use ml_kem::{ EncodedSizeUser, KemCore, }; -const SHARED_SECRET_LENGTH: usize = 32; +pub const KEY_LENGTH: usize = 32; macro_rules! make_mlkem { ($base: ident, $ek: ident, $ek_len: literal, $dk: ident, $dk_len: literal, $enc: ident, $enc_len:literal) => { #[derive(Debug, PartialEq, Clone)] pub struct $ek(Box<::EncapsulationKey>); + impl From<&$dk> for $ek { + fn from(dk: &$dk) -> Self { + Self(Box::new(dk.0.encapsulation_key().clone())) + } + } + impl Serializable for $ek { type Error = CryptoCoreError; @@ -111,7 +117,7 @@ macro_rules! make_mlkem { pub struct $base; - impl KEM for $base { + impl KEM for $base { type EncapsulationKey = $ek; type DecapsulationKey = $dk; type Encapsulation = $enc; @@ -127,8 +133,7 @@ macro_rules! make_mlkem { fn enc( ek: &Self::EncapsulationKey, rng: &mut impl CryptoRngCore, - ) -> Result<(SymmetricKey, Self::Encapsulation), Self::Error> - { + ) -> Result<(SymmetricKey, Self::Encapsulation), Self::Error> { let (enc, mut ss) = ek.0.encapsulate(rng) .map_err(|e| Error::Kem(format!("{:?}", e)))?; @@ -139,7 +144,7 @@ macro_rules! make_mlkem { fn dec( dk: &Self::DecapsulationKey, enc: &Self::Encapsulation, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { let mut ss = dk.0.decapsulate(&enc.0) .map_err(|e| Self::Error::Kem(format!("{e:?}")))?; diff --git a/src/providers/nike.rs b/src/providers/nike.rs index 86dfdeca..6f496341 100644 --- a/src/providers/nike.rs +++ b/src/providers/nike.rs @@ -5,4 +5,4 @@ compile_error!("only one elliptic curve can be chosen at a time"); pub use cosmian_rust_curve25519_provider::R25519 as ElGamal; #[cfg(feature = "p-256")] -pub use cosmian_rust_nist_ec_provider::P256 as ElGamal; +pub use cosmian_openssl_provider::p256::P256 as ElGamal; From da753771dd12fe62d40f0dc990a52b329cc2d4fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Thu, 5 Feb 2026 16:42:54 +0100 Subject: [PATCH 14/23] cleanup lib.rs --- src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 710c53e1..df225d3d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,5 @@ mod providers; #[cfg(any(test, feature = "test-utils"))] mod test_utils; -pub use abe::api; pub use abe::*; - pub use error::Error; From 2a9aeeae58d67382980d4357ec36c2107457acfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Sun, 8 Feb 2026 22:48:31 +0100 Subject: [PATCH 15/23] expose MLKEM --- src/lib.rs | 1 + src/providers.rs | 6 +++--- src/providers/kem.rs | 4 ++-- src/providers/kem/mlkem.rs | 4 ---- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index df225d3d..18d94551 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,3 +18,4 @@ mod test_utils; pub use abe::*; pub use error::Error; +pub use providers::*; diff --git a/src/providers.rs b/src/providers.rs index c2726d0a..a50688f8 100644 --- a/src/providers.rs +++ b/src/providers.rs @@ -1,5 +1,5 @@ -mod kem; +pub mod kem; mod nike; -pub use kem::MlKem; -pub use nike::ElGamal; +pub(crate) use kem::MlKem; +pub(crate) use nike::ElGamal; diff --git a/src/providers/kem.rs b/src/providers/kem.rs index f798da4a..47e9576e 100644 --- a/src/providers/kem.rs +++ b/src/providers/kem.rs @@ -4,7 +4,7 @@ compile_error!("only one MLKEM version can be chosen at a time"); pub mod mlkem; #[cfg(feature = "mlkem-512")] -pub use mlkem::MlKem512 as MlKem; +pub(crate) use mlkem::MlKem512 as MlKem; #[cfg(feature = "mlkem-768")] -pub use mlkem::MlKem768 as MlKem; +pub(crate) use mlkem::MlKem768 as MlKem; diff --git a/src/providers/kem/mlkem.rs b/src/providers/kem/mlkem.rs index f7aca02b..81380343 100644 --- a/src/providers/kem/mlkem.rs +++ b/src/providers/kem/mlkem.rs @@ -155,7 +155,6 @@ macro_rules! make_mlkem { }; } -#[cfg(feature = "mlkem-512")] make_mlkem!( MlKem512, EncapsulationKey512, @@ -166,7 +165,6 @@ make_mlkem!( 768 ); -#[cfg(feature = "mlkem-768")] make_mlkem!( MlKem768, EncapsulationKey768, @@ -201,8 +199,6 @@ mod tests { }; } - #[cfg(feature = "mlkem-512")] test_mlkem!(MlKem512, test_mlkem512); - #[cfg(feature = "mlkem-768")] test_mlkem!(MlKem768, test_mlkem768); } From 24da8bd245db168af6733bb61940ba16f7bbe301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Tue, 10 Feb 2026 17:24:32 +0100 Subject: [PATCH 16/23] derive Debug and Clone for MlKem implementations --- src/providers/kem/mlkem.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/providers/kem/mlkem.rs b/src/providers/kem/mlkem.rs index 81380343..da7932c4 100644 --- a/src/providers/kem/mlkem.rs +++ b/src/providers/kem/mlkem.rs @@ -115,6 +115,7 @@ macro_rules! make_mlkem { } } + #[derive(Debug, Copy, Clone)] pub struct $base; impl KEM for $base { From 467823f5f2aad7ff21e02f1562235b281ab2c253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Tue, 10 Feb 2026 18:06:15 +0100 Subject: [PATCH 17/23] implement Serializable for AccessPolicy --- src/abe/policy/access_policy.rs | 68 ++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/src/abe/policy/access_policy.rs b/src/abe/policy/access_policy.rs index 57ce4fe8..b8ca195d 100644 --- a/src/abe/policy/access_policy.rs +++ b/src/abe/policy/access_policy.rs @@ -7,9 +7,11 @@ use std::{ collections::LinkedList, fmt::Debug, - ops::{BitAnd, BitOr}, + ops::{BitAnd, BitOr, Deref}, }; +use cosmian_crypto_core::bytes_ser_de::Serializable; + use crate::{abe::policy::QualifiedAttribute, Error}; /// An access policy is a boolean expression of qualified attributes. @@ -216,8 +218,63 @@ impl BitOr for AccessPolicy { } } +impl Serializable for AccessPolicy { + type Error = Error; + + fn length(&self) -> usize { + match self { + AccessPolicy::Broadcast => 1, + AccessPolicy::Term(qualified_attribute) => 1 + qualified_attribute.length(), + AccessPolicy::Conjunction(access_policy, access_policy1) => { + 1 + access_policy.length() + access_policy1.length() + } + AccessPolicy::Disjunction(access_policy, access_policy1) => { + 1 + access_policy.length() + access_policy1.length() + } + } + } + + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + match self { + AccessPolicy::Broadcast => ser.write(&0_u64).map_err(Error::from), + AccessPolicy::Term(qualified_attribute) => { + Ok(ser.write(&1_u64)? + ser.write(qualified_attribute)?) + } + AccessPolicy::Conjunction(access_policy, access_policy1) => Ok(ser.write(&2_u64)? + + ser.write(access_policy.deref())? + + ser.write(access_policy1.deref())?), + AccessPolicy::Disjunction(access_policy, access_policy1) => Ok(ser.write(&3_u64)? + + ser.write(access_policy.deref())? + + ser.write(access_policy1.deref())?), + } + } + + fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + match de.read::()? { + 0 => Ok(Self::Broadcast), + 1 => Ok(Self::Term(de.read()?)), + 2 => Ok(Self::Conjunction( + Box::new(de.read()?), + Box::new(de.read()?), + )), + 3 => Ok(Self::Disjunction( + Box::new(de.read()?), + Box::new(de.read()?), + )), + n => Err(Error::ConversionFailed(format!( + "{n} is not a valid access policy tag" + ))), + } + } +} + #[cfg(test)] mod tests { + use cosmian_crypto_core::bytes_ser_de::test_serialization; + use super::AccessPolicy; #[test] @@ -225,13 +282,20 @@ mod tests { // These are valid access policies. let ap = AccessPolicy::parse("(D1::A && (D2::A) || D2::B)").unwrap(); println!("{ap:#?}"); + test_serialization(&ap).unwrap(); let ap = AccessPolicy::parse("D1::A && D2::A || D2::B").unwrap(); println!("{ap:#?}"); + test_serialization(&ap).unwrap(); let ap = AccessPolicy::parse("D1::A && (D2::A || D2::B)").unwrap(); println!("{ap:#?}"); + test_serialization(&ap).unwrap(); let ap = AccessPolicy::parse("D1::A (D2::A || D2::B)").unwrap(); println!("{ap:#?}"); - assert_eq!(AccessPolicy::parse("*").unwrap(), AccessPolicy::Broadcast); + test_serialization(&ap).unwrap(); + let ap = AccessPolicy::parse("*").unwrap(); + test_serialization(&ap).unwrap(); + assert_eq!(ap, AccessPolicy::Broadcast); + assert!(AccessPolicy::parse("").is_err()); // These are invalid access policies. From 7fdf7c374acde39fa101f6b75d993d3bcc87276b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 11 Feb 2026 19:25:52 +0100 Subject: [PATCH 18/23] move the configurable KEM implem back to the CoverCrypt repository --- Cargo.toml | 8 +- src/kem.rs | 522 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 +- 3 files changed, 528 insertions(+), 5 deletions(-) create mode 100644 src/kem.rs diff --git a/Cargo.toml b/Cargo.toml index 24a609b7..3d114758 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,13 +41,13 @@ required-features = ["test-utils"] default = ["mlkem-512", "curve25519"] mlkem-512 = [] mlkem-768 = [] -p-256 = ["cosmian_openssl_provider"] -curve25519 = ["cosmian_rust_curve25519_provider"] +p-256 = [] +curve25519 = [] test-utils = [] [dependencies] -cosmian_openssl_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/implement-openssl-provider", optional = true } -cosmian_rust_curve25519_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/implement-openssl-provider", optional = true } +cosmian_openssl_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/implement-openssl-provider" } +cosmian_rust_curve25519_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/implement-openssl-provider" } cosmian_crypto_core = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/implement-openssl-provider", default-features = false, features = [ "aes", "ser", diff --git a/src/kem.rs b/src/kem.rs new file mode 100644 index 00000000..eeab21ab --- /dev/null +++ b/src/kem.rs @@ -0,0 +1,522 @@ +#![allow(clippy::type_complexity)] +use crate::{ + providers::kem::mlkem::{MlKem512, MlKem768}, + traits::KemAc, + AccessPolicy, AccessStructure, Covercrypt, Error, MasterPublicKey, MasterSecretKey, + UserSecretKey, XEnc, +}; +use cosmian_crypto_core::{ + bytes_ser_de::{Deserializer, Serializable, Serializer}, + reexport::rand_core::{CryptoRngCore, SeedableRng}, + traits::{cyclic_group_to_kem::GenericKem, KEM}, + CsRng, SymmetricKey, +}; +use cosmian_openssl_provider::{hash::Sha256, kem::MonadicKEM, p256::P256}; +use cosmian_rust_curve25519_provider::R25519; +use zeroize::Zeroizing; + +// In order to avoid defining one enumeration type per KEM object with one +// variant per concrete KEM option, this module uses dynamic typing on the +// concrete key and encapsulation types by to consuming and returning byte +// strings. Serialization can be used once the concrete KEM is chosen to +// retrieve the typed objects. +// +// The following functions implement this logic: they are parametric on a KEM +// type -- and thus need to be called once the concrete KEM implementation is +// known, and perform both the KEM operation and serialization/deserialization +// of the key and encapsulation objects. + +#[allow(clippy::type_complexity)] +fn generic_keygen>( + rng: &mut impl CryptoRngCore, +) -> Result<(Zeroizing>, Zeroizing>), Error> +where + Kem::DecapsulationKey: Serializable, +{ + let (dk, ek) = Kem::keygen(rng).map_err(|e| Error::Kem(e.to_string()))?; + Ok(( + dk.serialize() + .map_err(|e| Error::ConversionFailed(format!("DK serialization error in KEM: {e}")))?, + ek.serialize() + .map_err(|e| Error::ConversionFailed(format!("EK serialization error in KEM: {e}")))?, + )) +} + +fn generic_enc>( + ek: &[u8], + rng: &mut impl CryptoRngCore, +) -> Result<(SymmetricKey, Zeroizing>), Error> { + let ek = >::EncapsulationKey::deserialize(ek) + .map_err(|e| Error::ConversionFailed(format!("EK deserialization error in KEM: {e}")))?; + let (key, enc) = Kem::enc(&ek, rng).map_err(|e| Error::Kem(e.to_string()))?; + Ok(( + key, + enc.serialize().map_err(|e| { + Error::ConversionFailed(format!("encapsulation serialization error in KEM: {e}")) + })?, + )) +} + +fn generic_dec>( + dk: &[u8], + enc: &[u8], +) -> Result, Error> +where + Kem::DecapsulationKey: Serializable, +{ + let dk = >::DecapsulationKey::deserialize(dk) + .map_err(|e| Error::ConversionFailed(format!("DK deserialization error in KEM: {e}")))?; + let enc = >::Encapsulation::deserialize(enc).map_err(|e| { + Error::ConversionFailed(format!("encapsulation deserialization error in KEM: {e}")) + })?; + Kem::dec(&dk, &enc).map_err(|e| Error::Kem(e.to_string())) +} + +// However, in order to enforce type safety, KEM objects must be tagged by the +// concrete KEM used. + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum PreQuantumKemTag { + P256, + R25519, +} + +impl Serializable for PreQuantumKemTag { + type Error = Error; + + fn length(&self) -> usize { + 1 + } + + fn write(&self, ser: &mut Serializer) -> Result { + match self { + Self::P256 => ser.write(&1_u64), + Self::R25519 => ser.write(&2_u64), + } + .map_err(Error::from) + } + + fn read(de: &mut Deserializer) -> Result { + match de.read::()? { + 1 => Ok(Self::P256), + 2 => Ok(Self::R25519), + n => Err(Error::ConversionFailed(format!( + "{n} is not a valid pre-quantum-KEM tag" + ))), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum PostQuantumKemTag { + MlKem512, + MlKem768, +} + +impl Serializable for PostQuantumKemTag { + type Error = Error; + + fn length(&self) -> usize { + 1 + } + + fn write(&self, ser: &mut Serializer) -> Result { + match self { + Self::MlKem512 => ser.write(&1_u64), + Self::MlKem768 => ser.write(&2_u64), + } + .map_err(Error::from) + } + + fn read(de: &mut Deserializer) -> Result { + match de.read::()? { + 1 => Ok(Self::MlKem512), + 2 => Ok(Self::MlKem768), + n => Err(Error::ConversionFailed(format!( + "{n} is not a valid post-quantum-KEM tag" + ))), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum KemTag { + PreQuantum(PreQuantumKemTag), + PostQuantum(PostQuantumKemTag), + Hybridized(PreQuantumKemTag, PostQuantumKemTag), + Abe, +} + +impl Serializable for KemTag { + type Error = Error; + + fn length(&self) -> usize { + match self { + Self::PreQuantum(_) | Self::PostQuantum(_) => 2, + Self::Hybridized(_, _) => 3, + Self::Abe => 1, + } + } + + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + match self { + Self::PreQuantum(tag) => Ok(ser.write(&1_u64)? + ser.write(tag)?), + Self::PostQuantum(tag) => Ok(ser.write(&2_u64)? + ser.write(tag)?), + Self::Hybridized(tag1, tag2) => { + Ok(ser.write(&3_u64)? + ser.write(tag1)? + ser.write(tag2)?) + } + Self::Abe => Ok(ser.write(&4_u64)?), + } + } + + fn read(de: &mut Deserializer) -> Result { + match de.read::()? { + 1 => de.read::().map(Self::PreQuantum), + 2 => de.read::().map(Self::PostQuantum), + 3 => de + .read::<(PreQuantumKemTag, PostQuantumKemTag)>() + .map(|(tag1, tag2)| Self::Hybridized(tag1, tag2)) + .map_err(Self::Error::from), + 4 => Ok(Self::Abe), + n => Err(Error::ConversionFailed(format!( + "{n} is not a valid KEM tag" + ))), + } + } +} + +// Finally, we can implement a KEM-like interface for our configurable KEM which +// deserializes KEM objects as couple (tag, bytes), checks tag legality and +// compatibility across objects before the KEM operation with corresponding +// implementation, and finally serializes returned objects as (tag, bytes) +// couples. + +type P256Kem = MonadicKEM<32, P256, Sha256>; +type R25519Kem = GenericKem<32, R25519, Sha256>; + +// Even though lengths of the keys encapsulated by the two combined KEM schemes +// can vary, it is much simpler to enforce their equality, which is performed +// here by binding the three key lengths required by the KEM combiner to the +// same one. +type KemCombiner = + cosmian_crypto_core::traits::kem_combiner::KemCombiner< + LENGTH, + LENGTH, + LENGTH, + Kem1, + Kem2, + Sha256, // SHA256 from the OpenSSL provider. + >; + +pub struct ConfigurableKEM; + +impl ConfigurableKEM { + #[allow(clippy::too_many_arguments)] + pub fn keygen( + tag: KemTag, + access_structure: Option, + ) -> Result<(Zeroizing>, Zeroizing>), Error> { + let rng = &mut CsRng::from_entropy(); + + let (dk_bytes, ek_bytes) = match tag { + KemTag::PreQuantum(PreQuantumKemTag::P256) => { + generic_keygen::<{ P256Kem::KEY_LENGTH }, P256Kem>(rng) + } + KemTag::PreQuantum(PreQuantumKemTag::R25519) => { + generic_keygen::<{ R25519Kem::KEY_LENGTH }, R25519Kem>(rng) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem512) => { + generic_keygen::<{ MlKem512::KEY_LENGTH }, MlKem512>(rng) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem768) => { + generic_keygen::<{ MlKem768::KEY_LENGTH }, MlKem768>(rng) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem512) => { + generic_keygen::< + { P256Kem::KEY_LENGTH }, + KemCombiner<{ P256Kem::KEY_LENGTH }, P256Kem, MlKem512>, + >(rng) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem768) => { + generic_keygen::< + { P256Kem::KEY_LENGTH }, + KemCombiner<{ P256Kem::KEY_LENGTH }, P256Kem, MlKem768>, + >(rng) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem512) => { + generic_keygen::< + { R25519Kem::KEY_LENGTH }, + KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem512>, + >(rng) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem768) => { + generic_keygen::< + { R25519Kem::KEY_LENGTH }, + KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem768>, + >(rng) + } + KemTag::Abe => { + let access_structure = access_structure.ok_or_else(|| { + Error::OperationNotPermitted( + "cannot execute a Covercrypt key generation without an access structure" + .to_owned(), + ) + })?; + let cc = Covercrypt::default(); + let (mut msk, _) = cc.setup()?; + msk.access_structure = access_structure; + let mpk = cc.update_msk(&mut msk)?; + Ok((msk.serialize()?, mpk.serialize()?)) + } + }?; + + Ok(((tag, dk_bytes).serialize()?, (tag, ek_bytes).serialize()?)) + } + + pub fn enc( + ek_bytes: &[u8], + access_policy: Option<&AccessPolicy>, + ) -> Result<(Zeroizing>, Zeroizing>), Error> { + let (tag, ek_bytes) = + <(KemTag, Zeroizing>)>::deserialize(ek_bytes).map_err(|e| { + Error::ConversionFailed(format!( + "failed deserializing the tag and encapsulation key in configurable KEM: {e}" + )) + })?; + + let rng = &mut CsRng::from_entropy(); + match tag { + KemTag::PreQuantum(PreQuantumKemTag::P256) => { + generic_enc::<{ P256Kem::KEY_LENGTH }, P256Kem>(&ek_bytes, rng) + .and_then(|(key, enc)| Ok((key.serialize()?, (tag, enc).serialize()?))) + } + KemTag::PreQuantum(PreQuantumKemTag::R25519) => { + generic_enc::<{ R25519Kem::KEY_LENGTH }, R25519Kem>(&ek_bytes, rng) + .and_then(|(key, enc)| Ok((key.serialize()?, (tag, enc).serialize()?))) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem512) => { + generic_enc::<{ MlKem512::KEY_LENGTH }, MlKem512>(&ek_bytes, rng) + .and_then(|(key, enc)| Ok((key.serialize()?, (tag, enc).serialize()?))) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem768) => { + generic_enc::<{ MlKem768::KEY_LENGTH }, MlKem768>(&ek_bytes, rng) + .and_then(|(key, enc)| Ok((key.serialize()?, (tag, enc).serialize()?))) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem512) => { + generic_enc::< + { P256Kem::KEY_LENGTH }, + KemCombiner<{ P256Kem::KEY_LENGTH }, P256Kem, MlKem512>, + >(&ek_bytes, rng) + .and_then(|(key, enc)| Ok((key.serialize()?, (tag, enc).serialize()?))) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem768) => { + generic_enc::< + { P256Kem::KEY_LENGTH }, + KemCombiner<{ P256Kem::KEY_LENGTH }, P256Kem, MlKem768>, + >(&ek_bytes, rng) + .and_then(|(key, enc)| Ok((key.serialize()?, (tag, enc).serialize()?))) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem512) => { + generic_enc::< + { R25519Kem::KEY_LENGTH }, + KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem512>, + >(&ek_bytes, rng) + .and_then(|(key, enc)| Ok((key.serialize()?, (tag, enc).serialize()?))) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem768) => { + generic_enc::< + { R25519Kem::KEY_LENGTH }, + KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem768>, + >(&ek_bytes, rng) + .and_then(|(key, enc)| Ok((key.serialize()?, (tag, enc).serialize()?))) + } + KemTag::Abe => { + let ap = access_policy.ok_or_else(|| { + Error::OperationNotPermitted( + "cannot create a Covercrypt encapsulation without an access policy" + .to_owned(), + ) + })?; + let mpk = MasterPublicKey::deserialize(&ek_bytes)?; + let (key, enc) = Covercrypt::default().encaps(&mpk, ap)?; + Ok((key.serialize()?, (tag, enc.serialize()?).serialize()?)) + } + } + } + + pub fn usk_gen( + msk: Zeroizing>, + access_policy: &AccessPolicy, + ) -> Result<(Zeroizing>, Zeroizing>), Error> { + let (tag, msk_bytes) = <(KemTag, Zeroizing>)>::deserialize(&msk).map_err(|e| { + Error::ConversionFailed(format!( + "failed deserializing CoverCrypt MSK in configurable KEM: {e}" + )) + })?; + + let mut msk = MasterSecretKey::deserialize(&msk_bytes)?; + let usk = Covercrypt::default().generate_user_secret_key(&mut msk, access_policy)?; + + Ok(( + (tag, msk.serialize()?).serialize()?, + (tag, usk.serialize()?).serialize()?, + )) + } + + pub fn dec(dk: &[u8], enc: &[u8]) -> Result>, Error> { + let (dk_tag, dk_bytes) = <(KemTag, Vec)>::deserialize(dk).map_err(|e| { + Error::ConversionFailed(format!( + "failed deserializing the tag and decapsulation key in configurable KEM: {e}" + )) + })?; + + let (enc_tag, enc_bytes) = <(KemTag, Vec)>::deserialize(enc).map_err(|e| { + Error::ConversionFailed(format!( + "failed deserializing the tag and encapsulation in configurable KEM: {e}" + )) + })?; + + if dk_tag != enc_tag { + return Err(Error::OperationNotPermitted(format!( + "heterogeneous decapsulation-key and encapsulation tags: {dk_tag:?} != {enc_tag:?}" + ))); + } + + match dk_tag { + KemTag::PreQuantum(PreQuantumKemTag::P256) => { + generic_dec::<{ P256Kem::KEY_LENGTH }, P256Kem>(&dk_bytes, &enc_bytes) + .and_then(|key| key.serialize().map_err(Error::from)) + } + KemTag::PreQuantum(PreQuantumKemTag::R25519) => { + generic_dec::<{ R25519Kem::KEY_LENGTH }, R25519Kem>(&dk_bytes, &enc_bytes) + .and_then(|key| key.serialize().map_err(Error::from)) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem512) => { + generic_dec::<{ MlKem512::KEY_LENGTH }, MlKem512>(&dk_bytes, &enc_bytes) + .and_then(|key| key.serialize().map_err(Error::from)) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem768) => { + generic_dec::<{ MlKem768::KEY_LENGTH }, MlKem768>(&dk_bytes, &enc_bytes) + .and_then(|key| key.serialize().map_err(Error::from)) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem512) => { + generic_dec::< + { P256Kem::KEY_LENGTH }, + KemCombiner<{ P256Kem::KEY_LENGTH }, P256Kem, MlKem512>, + >(&dk_bytes, &enc_bytes) + .and_then(|key| key.serialize().map_err(Error::from)) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem768) => { + generic_dec::< + { P256Kem::KEY_LENGTH }, + KemCombiner<{ P256Kem::KEY_LENGTH }, P256Kem, MlKem768>, + >(&dk_bytes, &enc_bytes) + .and_then(|key| key.serialize().map_err(Error::from)) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem512) => { + generic_dec::< + { R25519Kem::KEY_LENGTH }, + KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem512>, + >(&dk_bytes, &enc_bytes) + .and_then(|key| key.serialize().map_err(Error::from)) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem768) => { + generic_dec::< + { R25519Kem::KEY_LENGTH }, + KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem768>, + >(&dk_bytes, &enc_bytes) + .and_then(|key| key.serialize().map_err(Error::from)) + } + KemTag::Abe => { + let usk = UserSecretKey::deserialize(&dk_bytes)?; + let enc = XEnc::deserialize(&enc_bytes)?; + let key = Covercrypt::default().decaps(&usk, &enc)?.ok_or_else(|| { + Error::OperationNotPermitted( + "cannot open Covercrypt encapsulation: incompatible access rights" + .to_owned(), + ) + })?; + Ok(key.serialize()?) + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::gen_structure; + + use super::*; + use cosmian_crypto_core::bytes_ser_de::test_serialization; + + #[test] + fn test_tag_serialization() { + // Exhaustively test serializations. + test_serialization(&KemTag::PreQuantum(PreQuantumKemTag::P256)).unwrap(); + test_serialization(&KemTag::PreQuantum(PreQuantumKemTag::R25519)).unwrap(); + test_serialization(&KemTag::PostQuantum(PostQuantumKemTag::MlKem512)).unwrap(); + test_serialization(&KemTag::Hybridized( + PreQuantumKemTag::P256, + PostQuantumKemTag::MlKem512, + )) + .unwrap(); + test_serialization(&KemTag::Hybridized( + PreQuantumKemTag::R25519, + PostQuantumKemTag::MlKem512, + )) + .unwrap(); + test_serialization(&KemTag::Abe).unwrap(); + } + + #[test] + fn test_configurable_kem() { + fn run_test(tag: KemTag) { + let (dk, ek) = ConfigurableKEM::keygen(tag, None).unwrap(); + let (key, enc) = ConfigurableKEM::enc(&ek, None).unwrap(); + let key_ = ConfigurableKEM::dec(&dk, &enc).unwrap(); + assert_eq!(key, key_); + } + + run_test(KemTag::PreQuantum(PreQuantumKemTag::P256)); + + run_test(KemTag::PreQuantum(PreQuantumKemTag::R25519)); + + run_test(KemTag::PostQuantum(PostQuantumKemTag::MlKem512)); + + run_test(KemTag::PostQuantum(PostQuantumKemTag::MlKem768)); + + run_test(KemTag::Hybridized( + PreQuantumKemTag::P256, + PostQuantumKemTag::MlKem512, + )); + + run_test(KemTag::Hybridized( + PreQuantumKemTag::P256, + PostQuantumKemTag::MlKem768, + )); + + run_test(KemTag::Hybridized( + PreQuantumKemTag::R25519, + PostQuantumKemTag::MlKem512, + )); + run_test(KemTag::Hybridized( + PreQuantumKemTag::R25519, + PostQuantumKemTag::MlKem768, + )); + + println!("testing CoverCrypt ABE..."); + let mut access_structure = AccessStructure::new(); + gen_structure(&mut access_structure, true).unwrap(); + let usk_access_policy = AccessPolicy::parse("DPT::MKG").unwrap(); + let enc_access_policy = AccessPolicy::parse("*").unwrap(); + + let (msk, mpk) = ConfigurableKEM::keygen(KemTag::Abe, Some(access_structure)).unwrap(); + let (_msk, usk) = ConfigurableKEM::usk_gen(msk, &usk_access_policy).unwrap(); + let (key, enc) = ConfigurableKEM::enc(&mpk, Some(&enc_access_policy)).unwrap(); + let key_ = ConfigurableKEM::dec(&usk, &enc).unwrap(); + assert_eq!(key, key_); + } +} diff --git a/src/lib.rs b/src/lib.rs index 18d94551..f8e3cb81 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ mod abe; mod data_struct; mod error; +mod kem; mod providers; #[cfg(any(test, feature = "test-utils"))] @@ -18,4 +19,4 @@ mod test_utils; pub use abe::*; pub use error::Error; -pub use providers::*; +pub use kem::*; From 7fc73e5edb583278f38c08d15e2a3404e58b9156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 11 Feb 2026 19:51:17 +0100 Subject: [PATCH 19/23] add tag to discriminate USK vs MSK --- src/kem.rs | 153 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 115 insertions(+), 38 deletions(-) diff --git a/src/kem.rs b/src/kem.rs index eeab21ab..e94d690b 100644 --- a/src/kem.rs +++ b/src/kem.rs @@ -211,6 +211,82 @@ type KemCombiner = Sha256, // SHA256 from the OpenSSL provider. >; +pub struct ConfigurableKemDk(Zeroizing>); +pub struct ConfigurableKemEk(Zeroizing>); +pub struct ConfigurableKemEnc(Zeroizing>); + +impl TryFrom<&ConfigurableKemDk> for MasterSecretKey { + type Error = Error; + + fn try_from(dk: &ConfigurableKemDk) -> Result { + let (tag, dk_bytes) = <(KemTag, Zeroizing>)>::deserialize(&dk.0).map_err(|e| { + Error::ConversionFailed(format!( + "failed deserializing the tag and decapsulation key in configurable KEM: {e}" + )) + })?; + + if tag == KemTag::Abe { + let mut de = Deserializer::new(&dk_bytes); + match de.read::()? { + 0 => Ok(de.read()?), + n => Err(Error::ConversionFailed(format!( + "{n} is not a valid configurable-KEM MSK tag" + ))), + } + } else { + Err(Error::ConversionFailed(format!( + "cannot deserialize CoverCrypt MSK from configurable-KEM decapsulation key with tag: {tag:?}" + ))) + } + } +} + +impl TryFrom<&ConfigurableKemDk> for UserSecretKey { + type Error = Error; + + fn try_from(dk: &ConfigurableKemDk) -> Result { + let (tag, dk_bytes) = <(KemTag, Zeroizing>)>::deserialize(&dk.0).map_err(|e| { + Error::ConversionFailed(format!( + "failed deserializing the tag and decapsulation key in configurable KEM: {e}" + )) + })?; + + if tag == KemTag::Abe { + let mut de = Deserializer::new(&dk_bytes); + match de.read::()? { + 1 => Ok(de.read()?), + n => Err(Error::ConversionFailed(format!( + "{n} is not a valid configurable-KEM USK tag" + ))), + } + } else { + Err(Error::ConversionFailed(format!( + "cannot deserialize CoverCrypt USK from configurable-KEM decapsulation key with tag: {tag:?}" + ))) + } + } +} + +impl TryFrom for ConfigurableKemDk { + type Error = Error; + + fn try_from(msk: MasterSecretKey) -> Result { + Ok(ConfigurableKemDk( + (KemTag::Abe, (1u64, msk).serialize()?).serialize()?, + )) + } +} + +impl TryFrom for ConfigurableKemDk { + type Error = Error; + + fn try_from(usk: UserSecretKey) -> Result { + Ok(ConfigurableKemDk( + (KemTag::Abe, (1_u64, usk).serialize()?).serialize()?, + )) + } +} + pub struct ConfigurableKEM; impl ConfigurableKEM { @@ -218,7 +294,7 @@ impl ConfigurableKEM { pub fn keygen( tag: KemTag, access_structure: Option, - ) -> Result<(Zeroizing>, Zeroizing>), Error> { + ) -> Result<(ConfigurableKemDk, ConfigurableKemEk), Error> { let rng = &mut CsRng::from_entropy(); let (dk_bytes, ek_bytes) = match tag { @@ -269,19 +345,22 @@ impl ConfigurableKEM { let (mut msk, _) = cc.setup()?; msk.access_structure = access_structure; let mpk = cc.update_msk(&mut msk)?; - Ok((msk.serialize()?, mpk.serialize()?)) + Ok(((0_u64, msk).serialize()?, mpk.serialize()?)) } }?; - Ok(((tag, dk_bytes).serialize()?, (tag, ek_bytes).serialize()?)) + Ok(( + ConfigurableKemDk((tag, dk_bytes).serialize()?), + ConfigurableKemEk((tag, ek_bytes).serialize()?), + )) } pub fn enc( - ek_bytes: &[u8], + ek_bytes: &ConfigurableKemEk, access_policy: Option<&AccessPolicy>, - ) -> Result<(Zeroizing>, Zeroizing>), Error> { + ) -> Result<(Zeroizing>, ConfigurableKemEnc), Error> { let (tag, ek_bytes) = - <(KemTag, Zeroizing>)>::deserialize(ek_bytes).map_err(|e| { + <(KemTag, Zeroizing>)>::deserialize(&ek_bytes.0).map_err(|e| { Error::ConversionFailed(format!( "failed deserializing the tag and encapsulation key in configurable KEM: {e}" )) @@ -345,35 +424,20 @@ impl ConfigurableKEM { Ok((key.serialize()?, (tag, enc.serialize()?).serialize()?)) } } + .map(|(key, enc)| (key, ConfigurableKemEnc(enc))) } - pub fn usk_gen( - msk: Zeroizing>, - access_policy: &AccessPolicy, - ) -> Result<(Zeroizing>, Zeroizing>), Error> { - let (tag, msk_bytes) = <(KemTag, Zeroizing>)>::deserialize(&msk).map_err(|e| { - Error::ConversionFailed(format!( - "failed deserializing CoverCrypt MSK in configurable KEM: {e}" - )) - })?; - - let mut msk = MasterSecretKey::deserialize(&msk_bytes)?; - let usk = Covercrypt::default().generate_user_secret_key(&mut msk, access_policy)?; - - Ok(( - (tag, msk.serialize()?).serialize()?, - (tag, usk.serialize()?).serialize()?, - )) - } - - pub fn dec(dk: &[u8], enc: &[u8]) -> Result>, Error> { - let (dk_tag, dk_bytes) = <(KemTag, Vec)>::deserialize(dk).map_err(|e| { + pub fn dec( + dk: &ConfigurableKemDk, + enc: &ConfigurableKemEnc, + ) -> Result>, Error> { + let (dk_tag, dk_bytes) = <(KemTag, Vec)>::deserialize(&dk.0).map_err(|e| { Error::ConversionFailed(format!( "failed deserializing the tag and decapsulation key in configurable KEM: {e}" )) })?; - let (enc_tag, enc_bytes) = <(KemTag, Vec)>::deserialize(enc).map_err(|e| { + let (enc_tag, enc_bytes) = <(KemTag, Vec)>::deserialize(&enc.0).map_err(|e| { Error::ConversionFailed(format!( "failed deserializing the tag and encapsulation in configurable KEM: {e}" )) @@ -431,15 +495,24 @@ impl ConfigurableKEM { .and_then(|key| key.serialize().map_err(Error::from)) } KemTag::Abe => { - let usk = UserSecretKey::deserialize(&dk_bytes)?; - let enc = XEnc::deserialize(&enc_bytes)?; - let key = Covercrypt::default().decaps(&usk, &enc)?.ok_or_else(|| { - Error::OperationNotPermitted( - "cannot open Covercrypt encapsulation: incompatible access rights" - .to_owned(), - ) - })?; - Ok(key.serialize()?) + let mut de = Deserializer::new(&dk_bytes); + match de.read::()? { + 1 => { + let usk = de.read()?; + let enc = XEnc::deserialize(&enc_bytes)?; + let key = Covercrypt::default().decaps(&usk, &enc)?.ok_or_else(|| { + Error::OperationNotPermitted( + "cannot open Covercrypt encapsulation: incompatible access rights" + .to_owned(), + ) + })?; + Ok(key.serialize()?) + } + + n => Err(Error::ConversionFailed(format!( + "{n} is not a valid configurable-KEM USK tag" + ))), + } } } } @@ -514,7 +587,11 @@ mod tests { let enc_access_policy = AccessPolicy::parse("*").unwrap(); let (msk, mpk) = ConfigurableKEM::keygen(KemTag::Abe, Some(access_structure)).unwrap(); - let (_msk, usk) = ConfigurableKEM::usk_gen(msk, &usk_access_policy).unwrap(); + let mut msk = MasterSecretKey::try_from(&msk).unwrap(); + let usk = Covercrypt::default() + .generate_user_secret_key(&mut msk, &usk_access_policy) + .unwrap(); + let usk = ConfigurableKemDk::try_from(usk).unwrap(); let (key, enc) = ConfigurableKEM::enc(&mpk, Some(&enc_access_policy)).unwrap(); let key_ = ConfigurableKEM::dec(&usk, &enc).unwrap(); assert_eq!(key, key_); From ec84eadf7e63291667683b26e2a7ba1f35bd28f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 11 Feb 2026 20:27:57 +0100 Subject: [PATCH 20/23] improve generic implementation --- src/kem.rs | 459 ++++++++++++++++++++++++++++------------------------- 1 file changed, 240 insertions(+), 219 deletions(-) diff --git a/src/kem.rs b/src/kem.rs index e94d690b..c2425442 100644 --- a/src/kem.rs +++ b/src/kem.rs @@ -7,73 +7,16 @@ use crate::{ }; use cosmian_crypto_core::{ bytes_ser_de::{Deserializer, Serializable, Serializer}, - reexport::rand_core::{CryptoRngCore, SeedableRng}, + reexport::rand_core::SeedableRng, traits::{cyclic_group_to_kem::GenericKem, KEM}, - CsRng, SymmetricKey, + CsRng, }; use cosmian_openssl_provider::{hash::Sha256, kem::MonadicKEM, p256::P256}; use cosmian_rust_curve25519_provider::R25519; use zeroize::Zeroizing; -// In order to avoid defining one enumeration type per KEM object with one -// variant per concrete KEM option, this module uses dynamic typing on the -// concrete key and encapsulation types by to consuming and returning byte -// strings. Serialization can be used once the concrete KEM is chosen to -// retrieve the typed objects. -// -// The following functions implement this logic: they are parametric on a KEM -// type -- and thus need to be called once the concrete KEM implementation is -// known, and perform both the KEM operation and serialization/deserialization -// of the key and encapsulation objects. - -#[allow(clippy::type_complexity)] -fn generic_keygen>( - rng: &mut impl CryptoRngCore, -) -> Result<(Zeroizing>, Zeroizing>), Error> -where - Kem::DecapsulationKey: Serializable, -{ - let (dk, ek) = Kem::keygen(rng).map_err(|e| Error::Kem(e.to_string()))?; - Ok(( - dk.serialize() - .map_err(|e| Error::ConversionFailed(format!("DK serialization error in KEM: {e}")))?, - ek.serialize() - .map_err(|e| Error::ConversionFailed(format!("EK serialization error in KEM: {e}")))?, - )) -} - -fn generic_enc>( - ek: &[u8], - rng: &mut impl CryptoRngCore, -) -> Result<(SymmetricKey, Zeroizing>), Error> { - let ek = >::EncapsulationKey::deserialize(ek) - .map_err(|e| Error::ConversionFailed(format!("EK deserialization error in KEM: {e}")))?; - let (key, enc) = Kem::enc(&ek, rng).map_err(|e| Error::Kem(e.to_string()))?; - Ok(( - key, - enc.serialize().map_err(|e| { - Error::ConversionFailed(format!("encapsulation serialization error in KEM: {e}")) - })?, - )) -} - -fn generic_dec>( - dk: &[u8], - enc: &[u8], -) -> Result, Error> -where - Kem::DecapsulationKey: Serializable, -{ - let dk = >::DecapsulationKey::deserialize(dk) - .map_err(|e| Error::ConversionFailed(format!("DK deserialization error in KEM: {e}")))?; - let enc = >::Encapsulation::deserialize(enc).map_err(|e| { - Error::ConversionFailed(format!("encapsulation deserialization error in KEM: {e}")) - })?; - Kem::dec(&dk, &enc).map_err(|e| Error::Kem(e.to_string())) -} - -// However, in order to enforce type safety, KEM objects must be tagged by the -// concrete KEM used. +// In order to enforce type safety, KEM objects must be tagged by the concrete +// KEM used. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum PreQuantumKemTag { @@ -188,105 +131,158 @@ impl Serializable for KemTag { } } -// Finally, we can implement a KEM-like interface for our configurable KEM which -// deserializes KEM objects as couple (tag, bytes), checks tag legality and -// compatibility across objects before the KEM operation with corresponding -// implementation, and finally serializes returned objects as (tag, bytes) -// couples. +// In order to avoid defining one enumeration type per KEM object with one +// variant per concrete KEM option, this module uses dynamic typing on the +// concrete key and encapsulation types by to consuming and returning byte +// strings. Serialization can be used once the concrete KEM is chosen to +// retrieve the typed objects. +// +// The following functions implement this logic: they are parametric on a KEM +// type -- and thus need to be called once the concrete KEM implementation is +// known, and perform both the KEM operation and serialization/deserialization +// of the key and encapsulation objects. -type P256Kem = MonadicKEM<32, P256, Sha256>; -type R25519Kem = GenericKem<32, R25519, Sha256>; +pub struct ConfigurableKemDk(Zeroizing>); -// Even though lengths of the keys encapsulated by the two combined KEM schemes -// can vary, it is much simpler to enforce their equality, which is performed -// here by binding the three key lengths required by the KEM combiner to the -// same one. -type KemCombiner = - cosmian_crypto_core::traits::kem_combiner::KemCombiner< - LENGTH, - LENGTH, - LENGTH, - Kem1, - Kem2, - Sha256, // SHA256 from the OpenSSL provider. - >; +impl ConfigurableKemDk { + pub fn get_tag(&self) -> Result { + let mut de = Deserializer::new(&self.0); + de.read().map_err(|e| { + Error::ConversionFailed(format!( + "failed reading tag from configurable-KEM decapsulation key: {e:?}" + )) + }) + } +} -pub struct ConfigurableKemDk(Zeroizing>); pub struct ConfigurableKemEk(Zeroizing>); -pub struct ConfigurableKemEnc(Zeroizing>); -impl TryFrom<&ConfigurableKemDk> for MasterSecretKey { - type Error = Error; - - fn try_from(dk: &ConfigurableKemDk) -> Result { - let (tag, dk_bytes) = <(KemTag, Zeroizing>)>::deserialize(&dk.0).map_err(|e| { +impl ConfigurableKemEk { + pub fn get_tag(&self) -> Result { + let mut de = Deserializer::new(&self.0); + de.read().map_err(|e| { Error::ConversionFailed(format!( - "failed deserializing the tag and decapsulation key in configurable KEM: {e}" + "failed reading tag from configurable-KEM encapsulation key: {e:?}" )) - })?; + }) + } +} - if tag == KemTag::Abe { - let mut de = Deserializer::new(&dk_bytes); - match de.read::()? { - 0 => Ok(de.read()?), - n => Err(Error::ConversionFailed(format!( - "{n} is not a valid configurable-KEM MSK tag" - ))), - } - } else { - Err(Error::ConversionFailed(format!( - "cannot deserialize CoverCrypt MSK from configurable-KEM decapsulation key with tag: {tag:?}" - ))) - } +pub struct ConfigurableKemEnc(Zeroizing>); + +impl ConfigurableKemEnc { + pub fn get_tag(&self) -> Result { + let mut de = Deserializer::new(&self.0); + de.read().map_err(|e| { + Error::ConversionFailed(format!( + "failed reading tag from configurable-KEM encapsulation: {e:?}" + )) + }) } } -impl TryFrom<&ConfigurableKemDk> for UserSecretKey { - type Error = Error; +#[allow(clippy::type_complexity)] +fn generic_keygen>( + tag: KemTag, +) -> Result<(ConfigurableKemDk, ConfigurableKemEk), Error> +where + Kem::DecapsulationKey: Serializable, +{ + let mut rng = CsRng::from_entropy(); + let (dk, ek) = Kem::keygen(&mut rng).map_err(|e| Error::Kem(e.to_string()))?; + Ok(( + ConfigurableKemDk((tag, dk).serialize().map_err(|e| { + Error::ConversionFailed(format!( + "failed serializing the tag and decapsulation key in configurable KEM: {e}" + )) + })?), + ConfigurableKemEk((tag, ek).serialize().map_err(|e| { + Error::ConversionFailed(format!( + "failed serializing the tag and encapsulation key in configurable KEM: {e}" + )) + })?), + )) +} - fn try_from(dk: &ConfigurableKemDk) -> Result { - let (tag, dk_bytes) = <(KemTag, Zeroizing>)>::deserialize(&dk.0).map_err(|e| { +fn generic_enc>( + ek: &ConfigurableKemEk, +) -> Result<(Zeroizing>, ConfigurableKemEnc), Error> { + let mut rng = CsRng::from_entropy(); + let (tag, ek) = <(KemTag, >::EncapsulationKey)>::deserialize(&ek.0) + .map_err(|e| { Error::ConversionFailed(format!( - "failed deserializing the tag and decapsulation key in configurable KEM: {e}" + "failed deserializing the tag and encapsulation key in configurable KEM: {e}" )) })?; - if tag == KemTag::Abe { - let mut de = Deserializer::new(&dk_bytes); - match de.read::()? { - 1 => Ok(de.read()?), - n => Err(Error::ConversionFailed(format!( - "{n} is not a valid configurable-KEM USK tag" - ))), - } - } else { - Err(Error::ConversionFailed(format!( - "cannot deserialize CoverCrypt USK from configurable-KEM decapsulation key with tag: {tag:?}" - ))) - } - } + let (key, enc) = Kem::enc(&ek, &mut rng).map_err(|e| Error::Kem(e.to_string()))?; + + Ok(( + key.serialize()?, + ConfigurableKemEnc((tag, enc).serialize().map_err(|e| { + Error::ConversionFailed(format!( + "failed serializing the tag and encapsulation in configurable KEM: {e}" + )) + })?), + )) } -impl TryFrom for ConfigurableKemDk { - type Error = Error; +fn generic_dec>( + dk: &ConfigurableKemDk, + enc: &ConfigurableKemEnc, +) -> Result>, Error> +where + Kem::DecapsulationKey: Serializable, +{ + let (dk_tag, dk) = <(KemTag, >::DecapsulationKey)>::deserialize(&dk.0) + .map_err(|e| { + Error::ConversionFailed(format!( + "failed deserializing the tag and decapsulation key in configurable KEM: {e}" + )) + })?; - fn try_from(msk: MasterSecretKey) -> Result { - Ok(ConfigurableKemDk( - (KemTag::Abe, (1u64, msk).serialize()?).serialize()?, + let (enc_tag, enc) = <(KemTag, >::Encapsulation)>::deserialize(&enc.0) + .map_err(|e| { + Error::ConversionFailed(format!( + "failed deserializing the tag and encapsulation in configurable KEM: {e}" )) + })?; + + if dk_tag != enc_tag { + return Err(Error::OperationNotPermitted(format!( + "heterogeneous decapsulation-key and encapsulation tags: {dk_tag:?} != {enc_tag:?}" + ))); } -} -impl TryFrom for ConfigurableKemDk { - type Error = Error; + let key = Kem::dec(&dk, &enc) + .map_err(|e| Error::Kem(format!("configurable-KEM decapsulation error: {e}")))?; - fn try_from(usk: UserSecretKey) -> Result { - Ok(ConfigurableKemDk( - (KemTag::Abe, (1_u64, usk).serialize()?).serialize()?, - )) - } + Ok(key.serialize()?) } +// We can now implement a KEM-like interface for our configurable KEM which +// deserializes KEM objects as couple (tag, bytes), checks tag legality and +// compatibility across objects before the KEM operation with corresponding +// implementation, and finally serializes returned objects as (tag, bytes) +// couples. + +type P256Kem = MonadicKEM<32, P256, Sha256>; +type R25519Kem = GenericKem<32, R25519, Sha256>; + +// Even though lengths of the keys encapsulated by the two combined KEM schemes +// can vary, it is much simpler to enforce their equality, which is performed +// here by binding the three key lengths required by the KEM combiner to the +// same one. +type KemCombiner = + cosmian_crypto_core::traits::kem_combiner::KemCombiner< + LENGTH, + LENGTH, + LENGTH, + Kem1, + Kem2, + Sha256, // SHA256 from the OpenSSL provider. + >; + pub struct ConfigurableKEM; impl ConfigurableKEM { @@ -295,44 +291,42 @@ impl ConfigurableKEM { tag: KemTag, access_structure: Option, ) -> Result<(ConfigurableKemDk, ConfigurableKemEk), Error> { - let rng = &mut CsRng::from_entropy(); - - let (dk_bytes, ek_bytes) = match tag { + match tag { KemTag::PreQuantum(PreQuantumKemTag::P256) => { - generic_keygen::<{ P256Kem::KEY_LENGTH }, P256Kem>(rng) + generic_keygen::<{ P256Kem::KEY_LENGTH }, P256Kem>(tag) } KemTag::PreQuantum(PreQuantumKemTag::R25519) => { - generic_keygen::<{ R25519Kem::KEY_LENGTH }, R25519Kem>(rng) + generic_keygen::<{ R25519Kem::KEY_LENGTH }, R25519Kem>(tag) } KemTag::PostQuantum(PostQuantumKemTag::MlKem512) => { - generic_keygen::<{ MlKem512::KEY_LENGTH }, MlKem512>(rng) + generic_keygen::<{ MlKem512::KEY_LENGTH }, MlKem512>(tag) } KemTag::PostQuantum(PostQuantumKemTag::MlKem768) => { - generic_keygen::<{ MlKem768::KEY_LENGTH }, MlKem768>(rng) + generic_keygen::<{ MlKem768::KEY_LENGTH }, MlKem768>(tag) } KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem512) => { generic_keygen::< { P256Kem::KEY_LENGTH }, KemCombiner<{ P256Kem::KEY_LENGTH }, P256Kem, MlKem512>, - >(rng) + >(tag) } KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem768) => { generic_keygen::< { P256Kem::KEY_LENGTH }, KemCombiner<{ P256Kem::KEY_LENGTH }, P256Kem, MlKem768>, - >(rng) + >(tag) } KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem512) => { generic_keygen::< { R25519Kem::KEY_LENGTH }, KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem512>, - >(rng) + >(tag) } KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem768) => { generic_keygen::< { R25519Kem::KEY_LENGTH }, KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem768>, - >(rng) + >(tag) } KemTag::Abe => { let access_structure = access_structure.ok_or_else(|| { @@ -345,72 +339,55 @@ impl ConfigurableKEM { let (mut msk, _) = cc.setup()?; msk.access_structure = access_structure; let mpk = cc.update_msk(&mut msk)?; - Ok(((0_u64, msk).serialize()?, mpk.serialize()?)) + Ok(( + // Tag ABE decapsulation key with 0 when this is the MSK. + ConfigurableKemDk((tag, 0_u64, msk).serialize()?), + ConfigurableKemEk((tag, mpk).serialize()?), + )) } - }?; - - Ok(( - ConfigurableKemDk((tag, dk_bytes).serialize()?), - ConfigurableKemEk((tag, ek_bytes).serialize()?), - )) + } } pub fn enc( - ek_bytes: &ConfigurableKemEk, + ek: &ConfigurableKemEk, access_policy: Option<&AccessPolicy>, ) -> Result<(Zeroizing>, ConfigurableKemEnc), Error> { - let (tag, ek_bytes) = - <(KemTag, Zeroizing>)>::deserialize(&ek_bytes.0).map_err(|e| { - Error::ConversionFailed(format!( - "failed deserializing the tag and encapsulation key in configurable KEM: {e}" - )) - })?; - - let rng = &mut CsRng::from_entropy(); - match tag { + match ek.get_tag()? { KemTag::PreQuantum(PreQuantumKemTag::P256) => { - generic_enc::<{ P256Kem::KEY_LENGTH }, P256Kem>(&ek_bytes, rng) - .and_then(|(key, enc)| Ok((key.serialize()?, (tag, enc).serialize()?))) + generic_enc::<{ P256Kem::KEY_LENGTH }, P256Kem>(ek) } KemTag::PreQuantum(PreQuantumKemTag::R25519) => { - generic_enc::<{ R25519Kem::KEY_LENGTH }, R25519Kem>(&ek_bytes, rng) - .and_then(|(key, enc)| Ok((key.serialize()?, (tag, enc).serialize()?))) + generic_enc::<{ R25519Kem::KEY_LENGTH }, R25519Kem>(ek) } KemTag::PostQuantum(PostQuantumKemTag::MlKem512) => { - generic_enc::<{ MlKem512::KEY_LENGTH }, MlKem512>(&ek_bytes, rng) - .and_then(|(key, enc)| Ok((key.serialize()?, (tag, enc).serialize()?))) + generic_enc::<{ MlKem512::KEY_LENGTH }, MlKem512>(ek) } KemTag::PostQuantum(PostQuantumKemTag::MlKem768) => { - generic_enc::<{ MlKem768::KEY_LENGTH }, MlKem768>(&ek_bytes, rng) - .and_then(|(key, enc)| Ok((key.serialize()?, (tag, enc).serialize()?))) + generic_enc::<{ MlKem768::KEY_LENGTH }, MlKem768>(ek) } KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem512) => { generic_enc::< { P256Kem::KEY_LENGTH }, KemCombiner<{ P256Kem::KEY_LENGTH }, P256Kem, MlKem512>, - >(&ek_bytes, rng) - .and_then(|(key, enc)| Ok((key.serialize()?, (tag, enc).serialize()?))) + >(ek) } KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem768) => { generic_enc::< { P256Kem::KEY_LENGTH }, KemCombiner<{ P256Kem::KEY_LENGTH }, P256Kem, MlKem768>, - >(&ek_bytes, rng) - .and_then(|(key, enc)| Ok((key.serialize()?, (tag, enc).serialize()?))) + >(ek) } KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem512) => { generic_enc::< { R25519Kem::KEY_LENGTH }, KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem512>, - >(&ek_bytes, rng) - .and_then(|(key, enc)| Ok((key.serialize()?, (tag, enc).serialize()?))) + >(ek) } KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem768) => { generic_enc::< { R25519Kem::KEY_LENGTH }, KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem768>, - >(&ek_bytes, rng) - .and_then(|(key, enc)| Ok((key.serialize()?, (tag, enc).serialize()?))) + >(ek) } KemTag::Abe => { let ap = access_policy.ok_or_else(|| { @@ -419,87 +396,74 @@ impl ConfigurableKEM { .to_owned(), ) })?; - let mpk = MasterPublicKey::deserialize(&ek_bytes)?; + let (tag, mpk) = <(KemTag, MasterPublicKey)>::deserialize(&ek.0)?; let (key, enc) = Covercrypt::default().encaps(&mpk, ap)?; - Ok((key.serialize()?, (tag, enc.serialize()?).serialize()?)) + Ok(( + key.serialize()?, + ConfigurableKemEnc((tag, enc).serialize()?), + )) } } - .map(|(key, enc)| (key, ConfigurableKemEnc(enc))) } pub fn dec( dk: &ConfigurableKemDk, enc: &ConfigurableKemEnc, ) -> Result>, Error> { - let (dk_tag, dk_bytes) = <(KemTag, Vec)>::deserialize(&dk.0).map_err(|e| { - Error::ConversionFailed(format!( - "failed deserializing the tag and decapsulation key in configurable KEM: {e}" - )) - })?; - - let (enc_tag, enc_bytes) = <(KemTag, Vec)>::deserialize(&enc.0).map_err(|e| { - Error::ConversionFailed(format!( - "failed deserializing the tag and encapsulation in configurable KEM: {e}" - )) - })?; - - if dk_tag != enc_tag { - return Err(Error::OperationNotPermitted(format!( - "heterogeneous decapsulation-key and encapsulation tags: {dk_tag:?} != {enc_tag:?}" - ))); - } - - match dk_tag { + match dk.get_tag()? { KemTag::PreQuantum(PreQuantumKemTag::P256) => { - generic_dec::<{ P256Kem::KEY_LENGTH }, P256Kem>(&dk_bytes, &enc_bytes) - .and_then(|key| key.serialize().map_err(Error::from)) + generic_dec::<{ P256Kem::KEY_LENGTH }, P256Kem>(dk, enc) } KemTag::PreQuantum(PreQuantumKemTag::R25519) => { - generic_dec::<{ R25519Kem::KEY_LENGTH }, R25519Kem>(&dk_bytes, &enc_bytes) - .and_then(|key| key.serialize().map_err(Error::from)) + generic_dec::<{ R25519Kem::KEY_LENGTH }, R25519Kem>(dk, enc) } KemTag::PostQuantum(PostQuantumKemTag::MlKem512) => { - generic_dec::<{ MlKem512::KEY_LENGTH }, MlKem512>(&dk_bytes, &enc_bytes) - .and_then(|key| key.serialize().map_err(Error::from)) + generic_dec::<{ MlKem512::KEY_LENGTH }, MlKem512>(dk, enc) } KemTag::PostQuantum(PostQuantumKemTag::MlKem768) => { - generic_dec::<{ MlKem768::KEY_LENGTH }, MlKem768>(&dk_bytes, &enc_bytes) - .and_then(|key| key.serialize().map_err(Error::from)) + generic_dec::<{ MlKem768::KEY_LENGTH }, MlKem768>(dk, enc) } KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem512) => { generic_dec::< { P256Kem::KEY_LENGTH }, KemCombiner<{ P256Kem::KEY_LENGTH }, P256Kem, MlKem512>, - >(&dk_bytes, &enc_bytes) - .and_then(|key| key.serialize().map_err(Error::from)) + >(dk, enc) } KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem768) => { generic_dec::< { P256Kem::KEY_LENGTH }, KemCombiner<{ P256Kem::KEY_LENGTH }, P256Kem, MlKem768>, - >(&dk_bytes, &enc_bytes) - .and_then(|key| key.serialize().map_err(Error::from)) + >(dk, enc) } KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem512) => { generic_dec::< { R25519Kem::KEY_LENGTH }, KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem512>, - >(&dk_bytes, &enc_bytes) - .and_then(|key| key.serialize().map_err(Error::from)) + >(dk, enc) } KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem768) => { generic_dec::< { R25519Kem::KEY_LENGTH }, KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem768>, - >(&dk_bytes, &enc_bytes) - .and_then(|key| key.serialize().map_err(Error::from)) + >(dk, enc) } KemTag::Abe => { - let mut de = Deserializer::new(&dk_bytes); + let (tag, enc) = <(KemTag, XEnc)>::deserialize(&enc.0).map_err(|e| { + Error::ConversionFailed(format!( + "failed deserializing the tag and CoverCrypt encapsulation in configurable KEM: {e}" + )) + })?; + if tag != KemTag::Abe { + return Err(Error::OperationNotPermitted(format!( + "heterogeneous decapsulation-key and encapsulation tags: {:?} != {tag:?}", + KemTag::Abe + ))); + } + let mut de = Deserializer::new(&dk.0); + let _ = de.read::()?; match de.read::()? { 1 => { let usk = de.read()?; - let enc = XEnc::deserialize(&enc_bytes)?; let key = Covercrypt::default().decaps(&usk, &enc)?.ok_or_else(|| { Error::OperationNotPermitted( "cannot open Covercrypt encapsulation: incompatible access rights" @@ -518,6 +482,63 @@ impl ConfigurableKEM { } } +// Finally, CoverCrypt keys must be convertible to and from configurable-KEM +// keys in order to be managed. + +impl TryFrom<&ConfigurableKemDk> for MasterSecretKey { + type Error = Error; + + fn try_from(dk: &ConfigurableKemDk) -> Result { + let mut de = Deserializer::new(&dk.0); + let _ = de.read::()?; + match de.read::()? { + 0 => Ok(de.read()?), + n => Err(Error::ConversionFailed(format!( + "{n} is not a valid configurable-KEM MSK tag" + ))), + } + } +} + +impl TryFrom<&ConfigurableKemDk> for UserSecretKey { + type Error = Error; + + fn try_from(dk: &ConfigurableKemDk) -> Result { + let mut de = Deserializer::new(&dk.0); + let _ = de.read::()?; + match de.read::()? { + 1 => Ok(de.read()?), + n => Err(Error::ConversionFailed(format!( + "{n} is not a valid configurable-KEM USK tag" + ))), + } + } +} + +impl TryFrom for ConfigurableKemDk { + type Error = Error; + + fn try_from(msk: MasterSecretKey) -> Result { + Ok(ConfigurableKemDk((KemTag::Abe, 0_u64, msk).serialize()?)) + } +} + +impl TryFrom for ConfigurableKemEk { + type Error = Error; + + fn try_from(mpk: MasterPublicKey) -> Result { + Ok(ConfigurableKemEk((KemTag::Abe, mpk).serialize()?)) + } +} + +impl TryFrom for ConfigurableKemDk { + type Error = Error; + + fn try_from(usk: UserSecretKey) -> Result { + Ok(ConfigurableKemDk((KemTag::Abe, 1_u64, usk).serialize()?)) + } +} + #[cfg(test)] mod tests { use crate::gen_structure; From 036e7a0e7cc5aa9c90bc8b80c35d3d654e152941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 11 Feb 2026 20:47:41 +0100 Subject: [PATCH 21/23] implement Serializable for configurable-KEM objects --- src/kem.rs | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/src/kem.rs b/src/kem.rs index c2425442..1b78fb36 100644 --- a/src/kem.rs +++ b/src/kem.rs @@ -9,7 +9,7 @@ use cosmian_crypto_core::{ bytes_ser_de::{Deserializer, Serializable, Serializer}, reexport::rand_core::SeedableRng, traits::{cyclic_group_to_kem::GenericKem, KEM}, - CsRng, + CryptoCoreError, CsRng, }; use cosmian_openssl_provider::{hash::Sha256, kem::MonadicKEM, p256::P256}; use cosmian_rust_curve25519_provider::R25519; @@ -142,6 +142,7 @@ impl Serializable for KemTag { // known, and perform both the KEM operation and serialization/deserialization // of the key and encapsulation objects. +#[derive(Debug, Clone, PartialEq, Eq)] pub struct ConfigurableKemDk(Zeroizing>); impl ConfigurableKemDk { @@ -155,6 +156,23 @@ impl ConfigurableKemDk { } } +impl Serializable for ConfigurableKemDk { + type Error = CryptoCoreError; + + fn length(&self) -> usize { + self.0.length() + } + + fn write(&self, ser: &mut Serializer) -> Result { + ser.write(&self.0) + } + + fn read(de: &mut Deserializer) -> Result { + de.read().map(Self) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] pub struct ConfigurableKemEk(Zeroizing>); impl ConfigurableKemEk { @@ -168,6 +186,23 @@ impl ConfigurableKemEk { } } +impl Serializable for ConfigurableKemEk { + type Error = CryptoCoreError; + + fn length(&self) -> usize { + self.0.length() + } + + fn write(&self, ser: &mut Serializer) -> Result { + ser.write(&self.0) + } + + fn read(de: &mut Deserializer) -> Result { + de.read().map(Self) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] pub struct ConfigurableKemEnc(Zeroizing>); impl ConfigurableKemEnc { @@ -181,6 +216,22 @@ impl ConfigurableKemEnc { } } +impl Serializable for ConfigurableKemEnc { + type Error = CryptoCoreError; + + fn length(&self) -> usize { + self.0.length() + } + + fn write(&self, ser: &mut Serializer) -> Result { + ser.write(&self.0) + } + + fn read(de: &mut Deserializer) -> Result { + de.read().map(Self) + } +} + #[allow(clippy::type_complexity)] fn generic_keygen>( tag: KemTag, @@ -569,7 +620,10 @@ mod tests { fn test_configurable_kem() { fn run_test(tag: KemTag) { let (dk, ek) = ConfigurableKEM::keygen(tag, None).unwrap(); + test_serialization(&dk).unwrap(); + test_serialization(&ek).unwrap(); let (key, enc) = ConfigurableKEM::enc(&ek, None).unwrap(); + test_serialization(&enc).unwrap(); let key_ = ConfigurableKEM::dec(&dk, &enc).unwrap(); assert_eq!(key, key_); } From 78f2ab438ba907ed8776aca46e69a629bad86486 Mon Sep 17 00:00:00 2001 From: Manuthor Date: Thu, 12 Feb 2026 23:19:23 +0100 Subject: [PATCH 22/23] chore: update crypt_core to develop --- .github/workflows/ci.yml | 4 ++-- Cargo.lock | 10 +++++----- Cargo.toml | 6 +++--- rust-toolchain.toml | 6 ++++++ 4 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 rust-toolchain.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45481eb3..f87f442c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: stable + toolchain: 1.89.0 components: rustfmt, clippy - name: Run test script run: | @@ -21,7 +21,7 @@ jobs: uses: Cosmian/reusable_workflows/.github/workflows/cargo-publish.yml@develop if: startsWith(github.ref, 'refs/tags/') with: - toolchain: stable + toolchain: 1.89.0 secrets: inherit cleanup: needs: diff --git a/Cargo.lock b/Cargo.lock index 1dc743d1..59dbc509 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -199,7 +199,7 @@ dependencies = [ [[package]] name = "cosmian_crypto_core" version = "10.4.0" -source = "git+https://github.com/Cosmian/crypto_core.git?branch=tbz%2Fimplement-openssl-provider#c5a0ee061bcb7b504a78585d463b35cb9aa26cf9" +source = "git+https://github.com/Cosmian/crypto_core.git?branch=develop#77497f47a222f8df76538e9765b1333170dd3443" dependencies = [ "aead", "aes-gcm", @@ -218,7 +218,7 @@ dependencies = [ [[package]] name = "cosmian_openssl_provider" version = "1.0.0" -source = "git+https://github.com/Cosmian/crypto_core.git?branch=tbz%2Fimplement-openssl-provider#c5a0ee061bcb7b504a78585d463b35cb9aa26cf9" +source = "git+https://github.com/Cosmian/crypto_core.git?branch=develop#77497f47a222f8df76538e9765b1333170dd3443" dependencies = [ "cosmian_crypto_core", "openssl", @@ -228,7 +228,7 @@ dependencies = [ [[package]] name = "cosmian_rust_curve25519_provider" version = "1.0.0" -source = "git+https://github.com/Cosmian/crypto_core.git?branch=tbz%2Fimplement-openssl-provider#c5a0ee061bcb7b504a78585d463b35cb9aa26cf9" +source = "git+https://github.com/Cosmian/crypto_core.git?branch=develop#77497f47a222f8df76538e9765b1333170dd3443" dependencies = [ "cosmian_crypto_core", "curve25519-dalek", @@ -1050,6 +1050,6 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.16" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 3d114758..e1e2deb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,9 +46,9 @@ curve25519 = [] test-utils = [] [dependencies] -cosmian_openssl_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/implement-openssl-provider" } -cosmian_rust_curve25519_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/implement-openssl-provider" } -cosmian_crypto_core = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/implement-openssl-provider", default-features = false, features = [ +cosmian_openssl_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "develop" } +cosmian_rust_curve25519_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "develop" } +cosmian_crypto_core = { git = "https://github.com/Cosmian/crypto_core.git", branch = "develop", default-features = false, features = [ "aes", "ser", "sha3" diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..98d44e9b --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,6 @@ +[toolchain] +# Pin to a stable toolchain (no nightly) because dependencies rely on +# `core::hint::select_unpredictable`, which is available on stable. +channel = "1.89.0" +profile = "minimal" +components = ["rustfmt", "clippy"] From 6fd97bce47213ccac9f5441d623b155c770385d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Fri, 13 Feb 2026 13:32:20 +0100 Subject: [PATCH 23/23] expose constructor for configurable KEM objects --- Cargo.lock | 62 +++---- Cargo.toml | 6 +- src/kem.rs | 465 ++++++++++++++++++++++++++++++++++------------------- 3 files changed, 332 insertions(+), 201 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 59dbc509..f6dc25b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,9 +100,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.54" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "shlex", @@ -153,18 +153,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.54" +version = "4.5.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.54" +version = "4.5.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" dependencies = [ "anstyle", "clap_lex", @@ -172,9 +172,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "const-oid" @@ -199,7 +199,7 @@ dependencies = [ [[package]] name = "cosmian_crypto_core" version = "10.4.0" -source = "git+https://github.com/Cosmian/crypto_core.git?branch=develop#77497f47a222f8df76538e9765b1333170dd3443" +source = "git+https://github.com/Cosmian/crypto_core.git?branch=tbz%2Ffix-memory-allocation-error-upon-deserialization#24afc12b77b03ce476909a0835216a01bee32c5b" dependencies = [ "aead", "aes-gcm", @@ -218,7 +218,7 @@ dependencies = [ [[package]] name = "cosmian_openssl_provider" version = "1.0.0" -source = "git+https://github.com/Cosmian/crypto_core.git?branch=develop#77497f47a222f8df76538e9765b1333170dd3443" +source = "git+https://github.com/Cosmian/crypto_core.git?branch=tbz%2Ffix-memory-allocation-error-upon-deserialization#24afc12b77b03ce476909a0835216a01bee32c5b" dependencies = [ "cosmian_crypto_core", "openssl", @@ -228,7 +228,7 @@ dependencies = [ [[package]] name = "cosmian_rust_curve25519_provider" version = "1.0.0" -source = "git+https://github.com/Cosmian/crypto_core.git?branch=develop#77497f47a222f8df76538e9765b1333170dd3443" +source = "git+https://github.com/Cosmian/crypto_core.git?branch=tbz%2Ffix-memory-allocation-error-upon-deserialization#24afc12b77b03ce476909a0835216a01bee32c5b" dependencies = [ "cosmian_crypto_core", "curve25519-dalek", @@ -393,9 +393,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "find-msvc-tools" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "foreign-types" @@ -542,15 +542,15 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.180" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "ml-kem" @@ -706,9 +706,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -718,9 +718,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -729,9 +729,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "rustc_version" @@ -861,9 +861,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.114" +version = "2.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" dependencies = [ "proc-macro2", "quote", @@ -897,9 +897,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" [[package]] name = "universal-hash" @@ -1010,18 +1010,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.33" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.33" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index e1e2deb2..69c46cbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,9 +46,9 @@ curve25519 = [] test-utils = [] [dependencies] -cosmian_openssl_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "develop" } -cosmian_rust_curve25519_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "develop" } -cosmian_crypto_core = { git = "https://github.com/Cosmian/crypto_core.git", branch = "develop", default-features = false, features = [ +cosmian_openssl_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/fix-memory-allocation-error-upon-deserialization" } +cosmian_rust_curve25519_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/fix-memory-allocation-error-upon-deserialization" } +cosmian_crypto_core = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/fix-memory-allocation-error-upon-deserialization", default-features = false, features = [ "aes", "ser", "sha3" diff --git a/src/kem.rs b/src/kem.rs index 1b78fb36..7ce583b4 100644 --- a/src/kem.rs +++ b/src/kem.rs @@ -25,7 +25,7 @@ pub enum PreQuantumKemTag { } impl Serializable for PreQuantumKemTag { - type Error = Error; + type Error = CryptoCoreError; fn length(&self) -> usize { 1 @@ -36,14 +36,13 @@ impl Serializable for PreQuantumKemTag { Self::P256 => ser.write(&1_u64), Self::R25519 => ser.write(&2_u64), } - .map_err(Error::from) } fn read(de: &mut Deserializer) -> Result { match de.read::()? { 1 => Ok(Self::P256), 2 => Ok(Self::R25519), - n => Err(Error::ConversionFailed(format!( + n => Err(CryptoCoreError::GenericDeserializationError(format!( "{n} is not a valid pre-quantum-KEM tag" ))), } @@ -57,7 +56,7 @@ pub enum PostQuantumKemTag { } impl Serializable for PostQuantumKemTag { - type Error = Error; + type Error = CryptoCoreError; fn length(&self) -> usize { 1 @@ -68,14 +67,13 @@ impl Serializable for PostQuantumKemTag { Self::MlKem512 => ser.write(&1_u64), Self::MlKem768 => ser.write(&2_u64), } - .map_err(Error::from) } fn read(de: &mut Deserializer) -> Result { match de.read::()? { 1 => Ok(Self::MlKem512), 2 => Ok(Self::MlKem768), - n => Err(Error::ConversionFailed(format!( + n => Err(CryptoCoreError::GenericDeserializationError(format!( "{n} is not a valid post-quantum-KEM tag" ))), } @@ -91,7 +89,7 @@ pub enum KemTag { } impl Serializable for KemTag { - type Error = Error; + type Error = CryptoCoreError; fn length(&self) -> usize { match self { @@ -121,10 +119,9 @@ impl Serializable for KemTag { 2 => de.read::().map(Self::PostQuantum), 3 => de .read::<(PreQuantumKemTag, PostQuantumKemTag)>() - .map(|(tag1, tag2)| Self::Hybridized(tag1, tag2)) - .map_err(Self::Error::from), + .map(|(tag1, tag2)| Self::Hybridized(tag1, tag2)), 4 => Ok(Self::Abe), - n => Err(Error::ConversionFailed(format!( + n => Err(CryptoCoreError::GenericDeserializationError(format!( "{n} is not a valid KEM tag" ))), } @@ -143,16 +140,79 @@ impl Serializable for KemTag { // of the key and encapsulation objects. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct ConfigurableKemDk(Zeroizing>); +pub struct ConfigurableKemDk(KemTag, Zeroizing>); impl ConfigurableKemDk { - pub fn get_tag(&self) -> Result { - let mut de = Deserializer::new(&self.0); - de.read().map_err(|e| { - Error::ConversionFailed(format!( - "failed reading tag from configurable-KEM decapsulation key: {e:?}" + pub fn new(tag: KemTag, bytes: Zeroizing>) -> Result { + // Check whether the given bytes are a valid serialization of the + // decapsulation key of the tagged KEM. + match tag { + KemTag::PreQuantum(PreQuantumKemTag::P256) => { + >::DecapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::PreQuantum(PreQuantumKemTag::R25519) => { + >::DecapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem512) => { + >::DecapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem768) => { + >::DecapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem512) => { + as KEM< + { P256Kem::KEY_LENGTH }, + >>::DecapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem768) => { + as KEM< + { P256Kem::KEY_LENGTH }, + >>::DecapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem512) => { + as KEM< + { R25519Kem::KEY_LENGTH }, + >>::DecapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem768) => { + as KEM< + { R25519Kem::KEY_LENGTH }, + >>::DecapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Abe => { + // For Covercrypt, the bytes can either be a valid MSK or USK. + MasterSecretKey::deserialize(&bytes) + .map_or_else( + |_| UserSecretKey::deserialize(&bytes).map(|_| ()), + |_| Ok(()), + ) + .map_err(|e| CryptoCoreError::GenericDeserializationError(e.to_string())) + } + } + .map_err(|_| { + Error::KeyError(format!( + "failed to construct a configurable-KEM decapsulation key: \ + the given bytes are not a valid decapsulation key for the KEM with tag: {tag:?}" )) - }) + })?; + + Ok(Self(tag, bytes)) + } + + pub fn get_tag(&self) -> KemTag { + self.0 + } + + pub fn get_bytes(&self) -> &Zeroizing> { + &self.1 } } @@ -160,29 +220,86 @@ impl Serializable for ConfigurableKemDk { type Error = CryptoCoreError; fn length(&self) -> usize { - self.0.length() + self.0.length() + self.1.length() } fn write(&self, ser: &mut Serializer) -> Result { - ser.write(&self.0) + Ok(ser.write(&self.0)? + ser.write(&self.1)?) } fn read(de: &mut Deserializer) -> Result { - de.read().map(Self) + Ok(Self(de.read()?, de.read()?)) } } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct ConfigurableKemEk(Zeroizing>); +pub struct ConfigurableKemEk(KemTag, Zeroizing>); impl ConfigurableKemEk { - pub fn get_tag(&self) -> Result { - let mut de = Deserializer::new(&self.0); - de.read().map_err(|e| { - Error::ConversionFailed(format!( - "failed reading tag from configurable-KEM encapsulation key: {e:?}" + pub fn new(tag: KemTag, bytes: Zeroizing>) -> Result { + // Check whether the given bytes are a valid serialization of the + // encapsulation key of the tagged KEM. + match tag { + KemTag::PreQuantum(PreQuantumKemTag::P256) => { + >::EncapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::PreQuantum(PreQuantumKemTag::R25519) => { + >::EncapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem512) => { + >::EncapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem768) => { + >::EncapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem512) => { + as KEM< + { P256Kem::KEY_LENGTH }, + >>::EncapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem768) => { + as KEM< + { P256Kem::KEY_LENGTH }, + >>::EncapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem512) => { + as KEM< + { R25519Kem::KEY_LENGTH }, + >>::EncapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem768) => { + as KEM< + { R25519Kem::KEY_LENGTH }, + >>::EncapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Abe => MasterPublicKey::deserialize(&bytes) + .map(|_| ()) + .map_err(|e| CryptoCoreError::GenericDeserializationError(e.to_string())), + } + .map_err(|_| { + Error::KeyError(format!( + "failed to construct a configurable-KEM encapsulation key: \ + the given bytes are not a valid encapsulation key for the KEM with tag: {tag:?}" )) - }) + })?; + + Ok(Self(tag, bytes)) + } + + pub fn get_tag(&self) -> KemTag { + self.0 + } + + pub fn get_bytes(&self) -> &Zeroizing> { + &self.1 } } @@ -190,29 +307,89 @@ impl Serializable for ConfigurableKemEk { type Error = CryptoCoreError; fn length(&self) -> usize { - self.0.length() + self.0.length() + self.1.length() } fn write(&self, ser: &mut Serializer) -> Result { - ser.write(&self.0) + Ok(ser.write(&self.0)? + ser.write(&self.1)?) } fn read(de: &mut Deserializer) -> Result { - de.read().map(Self) + Ok(Self(de.read()?, de.read()?)) } } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct ConfigurableKemEnc(Zeroizing>); +pub struct ConfigurableKemEnc(KemTag, Zeroizing>); impl ConfigurableKemEnc { - pub fn get_tag(&self) -> Result { - let mut de = Deserializer::new(&self.0); - de.read().map_err(|e| { - Error::ConversionFailed(format!( - "failed reading tag from configurable-KEM encapsulation: {e:?}" + pub fn new(tag: KemTag, bytes: Zeroizing>) -> Result { + // Check whether the given bytes are a valid serialization of the + // encapsulation of the tagged KEM. + match tag { + KemTag::PreQuantum(PreQuantumKemTag::P256) => { + >::Encapsulation::deserialize(&bytes) + .map(|_| ()) + } + KemTag::PreQuantum(PreQuantumKemTag::R25519) => { + >::Encapsulation::deserialize(&bytes) + .map(|_| ()) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem512) => { + >::Encapsulation::deserialize(&bytes) + .map(|_| ()) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem768) => { + >::Encapsulation::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem512) => { + as KEM< + { P256Kem::KEY_LENGTH }, + >>::Encapsulation::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem768) => { + as KEM< + { P256Kem::KEY_LENGTH }, + >>::Encapsulation::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem512) => { + as KEM< + { R25519Kem::KEY_LENGTH }, + >>::Encapsulation::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem768) => { + as KEM< + { R25519Kem::KEY_LENGTH }, + >>::Encapsulation::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Abe => { + // For Covercrypt, the bytes can either be a valid MSK or USK. + XEnc::deserialize(&bytes) + .map(|_| ()) + .map_err(|e| CryptoCoreError::GenericDeserializationError(e.to_string())) + } + } + .map_err(|_| { + Error::KeyError(format!( + "failed to construct a configurable-KEM encapsulation: \ + the given bytes are not a valid encapsulation for the KEM with tag: {tag:?}" )) - }) + })?; + + Ok(Self(tag, bytes)) + } + + pub fn get_tag(&self) -> KemTag { + self.0 + } + + pub fn get_bytes(&self) -> &Zeroizing> { + &self.1 } } @@ -220,15 +397,15 @@ impl Serializable for ConfigurableKemEnc { type Error = CryptoCoreError; fn length(&self) -> usize { - self.0.length() + self.0.length() + self.1.length() } fn write(&self, ser: &mut Serializer) -> Result { - ser.write(&self.0) + Ok(ser.write(&self.0)? + ser.write(&self.1)?) } fn read(de: &mut Deserializer) -> Result { - de.read().map(Self) + Ok(Self(de.read()?, de.read()?)) } } @@ -242,39 +419,50 @@ where let mut rng = CsRng::from_entropy(); let (dk, ek) = Kem::keygen(&mut rng).map_err(|e| Error::Kem(e.to_string()))?; Ok(( - ConfigurableKemDk((tag, dk).serialize().map_err(|e| { - Error::ConversionFailed(format!( - "failed serializing the tag and decapsulation key in configurable KEM: {e}" - )) - })?), - ConfigurableKemEk((tag, ek).serialize().map_err(|e| { - Error::ConversionFailed(format!( - "failed serializing the tag and encapsulation key in configurable KEM: {e}" - )) - })?), + ConfigurableKemDk( + tag, + dk.serialize().map_err(|e| { + Error::ConversionFailed(format!( + "failed serializing the decapsulation key in configurable KEM: {e}" + )) + })?, + ), + ConfigurableKemEk( + tag, + ek.serialize().map_err(|e| { + Error::ConversionFailed(format!( + "failed serializing the encapsulation key in configurable KEM: {e}" + )) + })?, + ), )) } fn generic_enc>( ek: &ConfigurableKemEk, ) -> Result<(Zeroizing>, ConfigurableKemEnc), Error> { - let mut rng = CsRng::from_entropy(); - let (tag, ek) = <(KemTag, >::EncapsulationKey)>::deserialize(&ek.0) - .map_err(|e| { + let tag = ek.get_tag(); + let ek = + >::EncapsulationKey::deserialize(ek.get_bytes()).map_err(|e| { Error::ConversionFailed(format!( - "failed deserializing the tag and encapsulation key in configurable KEM: {e}" + "failed deserializing the encapsulation key in configurable KEM: {e}" )) })?; - let (key, enc) = Kem::enc(&ek, &mut rng).map_err(|e| Error::Kem(e.to_string()))?; + let mut rng = CsRng::from_entropy(); + let (key, enc) = Kem::enc(&ek, &mut rng) + .map_err(|e| Error::Kem(format!("configurable-KEM encapsulation error: {e}")))?; Ok(( key.serialize()?, - ConfigurableKemEnc((tag, enc).serialize().map_err(|e| { - Error::ConversionFailed(format!( - "failed serializing the tag and encapsulation in configurable KEM: {e}" - )) - })?), + ConfigurableKemEnc( + tag, + enc.serialize().map_err(|e| { + Error::ConversionFailed(format!( + "failed serializing the encapsulation in configurable KEM: {e}" + )) + })?, + ), )) } @@ -285,26 +473,29 @@ fn generic_dec>( where Kem::DecapsulationKey: Serializable, { - let (dk_tag, dk) = <(KemTag, >::DecapsulationKey)>::deserialize(&dk.0) - .map_err(|e| { - Error::ConversionFailed(format!( - "failed deserializing the tag and decapsulation key in configurable KEM: {e}" - )) - })?; - - let (enc_tag, enc) = <(KemTag, >::Encapsulation)>::deserialize(&enc.0) - .map_err(|e| { - Error::ConversionFailed(format!( - "failed deserializing the tag and encapsulation in configurable KEM: {e}" - )) - })?; + let tag = dk.get_tag(); - if dk_tag != enc_tag { + if tag != enc.get_tag() { return Err(Error::OperationNotPermitted(format!( - "heterogeneous decapsulation-key and encapsulation tags: {dk_tag:?} != {enc_tag:?}" + "heterogeneous decapsulation-key and encapsulation tags: {tag:?} != {:?}", + enc.get_tag() ))); } + let dk = + >::DecapsulationKey::deserialize(dk.get_bytes()).map_err(|e| { + Error::ConversionFailed(format!( + "failed deserializing the decapsulation key in configurable KEM: {e}" + )) + })?; + + let enc = + >::Encapsulation::deserialize(enc.get_bytes()).map_err(|e| { + Error::ConversionFailed(format!( + "failed deserializing the encapsulation in configurable KEM: {e}" + )) + })?; + let key = Kem::dec(&dk, &enc) .map_err(|e| Error::Kem(format!("configurable-KEM decapsulation error: {e}")))?; @@ -391,9 +582,8 @@ impl ConfigurableKEM { msk.access_structure = access_structure; let mpk = cc.update_msk(&mut msk)?; Ok(( - // Tag ABE decapsulation key with 0 when this is the MSK. - ConfigurableKemDk((tag, 0_u64, msk).serialize()?), - ConfigurableKemEk((tag, mpk).serialize()?), + ConfigurableKemDk(tag, msk.serialize()?), + ConfigurableKemEk(tag, mpk.serialize()?), )) } } @@ -403,7 +593,7 @@ impl ConfigurableKEM { ek: &ConfigurableKemEk, access_policy: Option<&AccessPolicy>, ) -> Result<(Zeroizing>, ConfigurableKemEnc), Error> { - match ek.get_tag()? { + match ek.get_tag() { KemTag::PreQuantum(PreQuantumKemTag::P256) => { generic_enc::<{ P256Kem::KEY_LENGTH }, P256Kem>(ek) } @@ -447,12 +637,10 @@ impl ConfigurableKEM { .to_owned(), ) })?; - let (tag, mpk) = <(KemTag, MasterPublicKey)>::deserialize(&ek.0)?; + let tag = ek.get_tag(); + let mpk = MasterPublicKey::deserialize(ek.get_bytes())?; let (key, enc) = Covercrypt::default().encaps(&mpk, ap)?; - Ok(( - key.serialize()?, - ConfigurableKemEnc((tag, enc).serialize()?), - )) + Ok((key.serialize()?, ConfigurableKemEnc(tag, enc.serialize()?))) } } } @@ -461,7 +649,7 @@ impl ConfigurableKEM { dk: &ConfigurableKemDk, enc: &ConfigurableKemEnc, ) -> Result>, Error> { - match dk.get_tag()? { + match dk.get_tag() { KemTag::PreQuantum(PreQuantumKemTag::P256) => { generic_dec::<{ P256Kem::KEY_LENGTH }, P256Kem>(dk, enc) } @@ -499,97 +687,39 @@ impl ConfigurableKEM { >(dk, enc) } KemTag::Abe => { - let (tag, enc) = <(KemTag, XEnc)>::deserialize(&enc.0).map_err(|e| { - Error::ConversionFailed(format!( - "failed deserializing the tag and CoverCrypt encapsulation in configurable KEM: {e}" - )) - })?; - if tag != KemTag::Abe { + if enc.get_tag() != KemTag::Abe { return Err(Error::OperationNotPermitted(format!( - "heterogeneous decapsulation-key and encapsulation tags: {:?} != {tag:?}", - KemTag::Abe + "heterogeneous decapsulation-key and encapsulation tags: {:?} != {:?}", + KemTag::Abe, + enc.get_tag(), ))); } - let mut de = Deserializer::new(&dk.0); - let _ = de.read::()?; - match de.read::()? { - 1 => { - let usk = de.read()?; - let key = Covercrypt::default().decaps(&usk, &enc)?.ok_or_else(|| { - Error::OperationNotPermitted( - "cannot open Covercrypt encapsulation: incompatible access rights" - .to_owned(), - ) - })?; - Ok(key.serialize()?) - } - - n => Err(Error::ConversionFailed(format!( - "{n} is not a valid configurable-KEM USK tag" - ))), - } - } - } - } -} -// Finally, CoverCrypt keys must be convertible to and from configurable-KEM -// keys in order to be managed. - -impl TryFrom<&ConfigurableKemDk> for MasterSecretKey { - type Error = Error; + let enc = XEnc::deserialize(enc.get_bytes()).map_err(|e| { + Error::ConversionFailed(format!( + "failed deserializing the CoverCrypt encapsulation in configurable KEM: {e}" + )) + })?; - fn try_from(dk: &ConfigurableKemDk) -> Result { - let mut de = Deserializer::new(&dk.0); - let _ = de.read::()?; - match de.read::()? { - 0 => Ok(de.read()?), - n => Err(Error::ConversionFailed(format!( - "{n} is not a valid configurable-KEM MSK tag" - ))), - } - } -} + let usk = UserSecretKey::deserialize(dk.get_bytes()).map_err(|e| { + Error::ConversionFailed(format!( + "failed deserializing the CoverCrypt encapsulation in configurable KEM: {e}" + )) + })?; -impl TryFrom<&ConfigurableKemDk> for UserSecretKey { - type Error = Error; + let key = Covercrypt::default().decaps(&usk, &enc)?.ok_or_else(|| { + Error::OperationNotPermitted( + "cannot open Covercrypt encapsulation: incompatible access rights" + .to_owned(), + ) + })?; - fn try_from(dk: &ConfigurableKemDk) -> Result { - let mut de = Deserializer::new(&dk.0); - let _ = de.read::()?; - match de.read::()? { - 1 => Ok(de.read()?), - n => Err(Error::ConversionFailed(format!( - "{n} is not a valid configurable-KEM USK tag" - ))), + Ok(key.serialize()?) + } } } } -impl TryFrom for ConfigurableKemDk { - type Error = Error; - - fn try_from(msk: MasterSecretKey) -> Result { - Ok(ConfigurableKemDk((KemTag::Abe, 0_u64, msk).serialize()?)) - } -} - -impl TryFrom for ConfigurableKemEk { - type Error = Error; - - fn try_from(mpk: MasterPublicKey) -> Result { - Ok(ConfigurableKemEk((KemTag::Abe, mpk).serialize()?)) - } -} - -impl TryFrom for ConfigurableKemDk { - type Error = Error; - - fn try_from(usk: UserSecretKey) -> Result { - Ok(ConfigurableKemDk((KemTag::Abe, 1_u64, usk).serialize()?)) - } -} - #[cfg(test)] mod tests { use crate::gen_structure; @@ -662,11 +792,12 @@ mod tests { let enc_access_policy = AccessPolicy::parse("*").unwrap(); let (msk, mpk) = ConfigurableKEM::keygen(KemTag::Abe, Some(access_structure)).unwrap(); - let mut msk = MasterSecretKey::try_from(&msk).unwrap(); + let tag = msk.get_tag(); + let mut msk = MasterSecretKey::deserialize(msk.get_bytes()).unwrap(); let usk = Covercrypt::default() .generate_user_secret_key(&mut msk, &usk_access_policy) .unwrap(); - let usk = ConfigurableKemDk::try_from(usk).unwrap(); + let usk = ConfigurableKemDk::new(tag, usk.serialize().unwrap()).unwrap(); let (key, enc) = ConfigurableKEM::enc(&mpk, Some(&enc_access_policy)).unwrap(); let key_ = ConfigurableKEM::dec(&usk, &enc).unwrap(); assert_eq!(key, key_);