From fa35aa3302dc77b06ee95dc292a8eacfd7841ac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 15 Oct 2025 12:28:30 +0200 Subject: [PATCH 01/21] cleanup some code --- Cargo.toml | 9 +++---- src/ae.rs | 5 ++-- src/api.rs | 8 +++--- src/core/kem/mlkem.rs | 16 +++++++----- src/core/nike/r25519.rs | 44 +++++++++++++++++---------------- src/core/primitives.rs | 9 ++++--- src/data_struct/revision_vec.rs | 2 +- src/traits.rs | 6 +++-- 8 files changed, 53 insertions(+), 46 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 144bfef2..54f1cd81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,10 +46,10 @@ curve25519 = ["cosmian_crypto_core/curve25519"] test-utils = [] [dependencies] -cosmian_crypto_core = { version = "10.0.1", default-features = false, features = [ - "ser", - "sha3", +cosmian_crypto_core = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/expose-more-sha3", default-features = false, features = [ "aes", + "ser", + "sha3" ] } elliptic-curve = { version = "0.13.8", optional = true } ml-kem = { version = "0.2.1", features = ["zeroize"] } @@ -57,11 +57,8 @@ p256 = { version = "0.13.2", optional = true } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["preserve_order"] } subtle = { version = "2.6.1", optional = true } -tiny-keccak = { version = "2.0.2", features = ["kmac", "sha3"] } -zeroize = "1.6.0" [dev-dependencies] -base64 = { version = "0.21.0" } criterion = { version = "0.5", features = [ "html_reports", ], default-features = false } diff --git a/src/ae.rs b/src/ae.rs index 9b657dc4..90c488c1 100644 --- a/src/ae.rs +++ b/src/ae.rs @@ -1,8 +1,7 @@ use cosmian_crypto_core::{ - reexport::rand_core::CryptoRngCore, Aes256Gcm, Dem, FixedSizeCBytes, Instantiable, Nonce, - RandomFixedSizeCBytes, SymmetricKey, + reexport::{rand_core::CryptoRngCore, zeroize::Zeroizing}, + Aes256Gcm, Dem, FixedSizeCBytes, Instantiable, Nonce, RandomFixedSizeCBytes, SymmetricKey, }; -use zeroize::Zeroizing; use crate::{traits::AE, Error}; diff --git a/src/api.rs b/src/api.rs index f833de15..2a91fba9 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,7 +1,9 @@ use std::sync::{Mutex, MutexGuard}; -use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, Secret, SymmetricKey}; -use zeroize::Zeroizing; +use cosmian_crypto_core::{ + reexport::{rand_core::SeedableRng, zeroize::Zeroizing}, + CsRng, Secret, SymmetricKey, +}; use super::{ core::primitives::{prune, update_msk, usk_keygen}, @@ -30,7 +32,7 @@ impl Default for Covercrypt { } impl Covercrypt { - pub fn rng(&self) -> MutexGuard { + pub fn rng(&self) -> MutexGuard<'_, CsRng> { self.rng.lock().expect("poisoned mutex") } diff --git a/src/core/kem/mlkem.rs b/src/core/kem/mlkem.rs index e3722163..d3817919 100644 --- a/src/core/kem/mlkem.rs +++ b/src/core/kem/mlkem.rs @@ -1,14 +1,14 @@ -use cosmian_crypto_core::bytes_ser_de::{Deserializer, Serializable, Serializer}; -use cosmian_crypto_core::{reexport::rand_core::CryptoRngCore, Secret}; +use crate::{core::SHARED_SECRET_LENGTH, traits::Kem, Error}; +use cosmian_crypto_core::{ + bytes_ser_de::{Deserializer, Serializable, Serializer}, + reexport::{rand_core::CryptoRngCore, zeroize::Zeroize}, + Secret, +}; use ml_kem::{ array::Array, kem::{Decapsulate, Encapsulate}, EncodedSizeUser, KemCore, }; -use zeroize::Zeroize; - -use crate::traits::Kem; -use crate::{core::SHARED_SECRET_LENGTH, Error}; macro_rules! make_mlkem { ($base: ident, $ek: ident, $ek_len: literal, $dk: ident, $dk_len: literal, $enc: ident, $enc_len:literal) => { @@ -134,6 +134,7 @@ macro_rules! make_mlkem { }; } +#[cfg(feature = "mlkem-512")] make_mlkem!( MlKem512, EncapsulationKey512, @@ -144,6 +145,7 @@ make_mlkem!( 768 ); +#[cfg(feature = "mlkem-768")] make_mlkem!( MlKem768, EncapsulationKey768, @@ -178,6 +180,8 @@ mod tests { }; } + #[cfg(feature = "mlkem-512")] test_mlkem!(MlKem512, test_mlkem512); + #[cfg(feature = "mlkem-768")] test_mlkem!(MlKem768, test_mlkem768); } diff --git a/src/core/nike/r25519.rs b/src/core/nike/r25519.rs index abb4208d..be87bbaa 100644 --- a/src/core/nike/r25519.rs +++ b/src/core/nike/r25519.rs @@ -9,27 +9,22 @@ 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::rand_core::CryptoRngCore; - -use cosmian_crypto_core::CryptoCoreError; -pub use cosmian_crypto_core::R25519PrivateKey as Scalar; -pub use cosmian_crypto_core::R25519PublicKey as EcPoint; -use tiny_keccak::Hasher; -use tiny_keccak::Sha3; -use zeroize::Zeroize; - -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, Zeroize)] +use cosmian_crypto_core::{ + bytes_ser_de::{Serializable, Serializer}, + reexport::{ + rand_core::CryptoRngCore, + tiny_keccak::{Hasher, Sha3}, + zeroize::Zeroize, + }, + CryptoCoreError, R25519PrivateKey as Scalar, R25519PublicKey as EcPoint, +}; + +use crate::{ + traits::{Group, KeyHomomorphicNike, Nike, One, Ring, Sampling, Zero}, + Error, +}; + +#[derive(Clone, Debug, PartialEq, Eq)] pub struct R25519Point(EcPoint); impl Zero for R25519Point { @@ -42,6 +37,13 @@ impl Zero for R25519Point { } } +// 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; diff --git a/src/core/primitives.rs b/src/core/primitives.rs index 718fc317..ad15bdd4 100644 --- a/src/core/primitives.rs +++ b/src/core/primitives.rs @@ -5,13 +5,14 @@ use std::{ use cosmian_crypto_core::{ bytes_ser_de::Serializable, - reexport::rand_core::{CryptoRngCore, RngCore}, + reexport::{ + rand_core::{CryptoRngCore, RngCore}, + tiny_keccak::{Hasher, Kmac, Sha3}, + zeroize::Zeroize, + }, RandomFixedSizeCBytes, Secret, SymmetricKey, }; -use tiny_keccak::{Hasher, Kmac, Sha3}; -use zeroize::Zeroize; - use crate::{ abe_policy::{AccessStructure, AttributeStatus, EncryptionHint, Right}, core::{ diff --git a/src/data_struct/revision_vec.rs b/src/data_struct/revision_vec.rs index 1922cf0d..b8dd0fd7 100644 --- a/src/data_struct/revision_vec.rs +++ b/src/data_struct/revision_vec.rs @@ -131,7 +131,7 @@ impl RevisionVec { /// Iterates through all versions of all entry in a breadth-first manner. #[must_use] - pub fn bfs(&self) -> BfsQueue { + pub fn bfs(&self) -> BfsQueue<'_, T> { BfsQueue::new(self) } diff --git a/src/traits.rs b/src/traits.rs index 0e23dc19..6cb9604d 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,4 +1,7 @@ -use cosmian_crypto_core::{reexport::rand_core::CryptoRngCore, Secret, SymmetricKey}; +use cosmian_crypto_core::{ + reexport::{rand_core::CryptoRngCore, zeroize::Zeroizing}, + Secret, SymmetricKey, +}; use std::ops::Add; use std::ops::AddAssign; use std::ops::Div; @@ -6,7 +9,6 @@ use std::ops::Mul; use std::ops::MulAssign; use std::ops::Sub; use std::ops::SubAssign; -use zeroize::Zeroizing; use crate::AccessPolicy; From 414aafc576e7c39181d522b1e4c9125ac9a7e606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 15 Oct 2025 17:25:12 +0200 Subject: [PATCH 02/21] serialization review following CryptoCore improvements --- Cargo.toml | 2 +- src/abe_policy/access_structure.rs | 42 +--- src/abe_policy/attribute.rs | 85 +++++++ src/abe_policy/dimension.rs | 129 +++------- src/abe_policy/mod.rs | 28 +++ src/core/mod.rs | 1 - src/core/serialization/mod.rs | 389 ++++++----------------------- src/data_struct/dictionary.rs | 26 ++ src/data_struct/error.rs | 12 + src/data_struct/revision_map.rs | 27 ++ src/data_struct/revision_vec.rs | 23 ++ 11 files changed, 309 insertions(+), 455 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 54f1cd81..b932d569 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ curve25519 = ["cosmian_crypto_core/curve25519"] test-utils = [] [dependencies] -cosmian_crypto_core = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/expose-more-sha3", default-features = false, features = [ +cosmian_crypto_core = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/impl-serializable-for-u8-array", default-features = false, features = [ "aes", "ser", "sha3" diff --git a/src/abe_policy/access_structure.rs b/src/abe_policy/access_structure.rs index 13259657..4b67e7ce 100644 --- a/src/abe_policy/access_structure.rs +++ b/src/abe_policy/access_structure.rs @@ -356,55 +356,23 @@ impl Default for AccessStructure { mod serialization { use super::*; - use cosmian_crypto_core::bytes_ser_de::{ - to_leb128_len, Deserializer, Serializable, Serializer, - }; + use cosmian_crypto_core::bytes_ser_de::{Deserializer, Serializable, Serializer}; impl Serializable for AccessStructure { type Error = Error; fn length(&self) -> usize { - 1 + to_leb128_len(self.dimensions.len()) - + self - .dimensions - .iter() - .map(|(name, dimension)| { - let l = name.len(); - to_leb128_len(l) + l + dimension.length() - }) - .sum::() + self.version.length() + self.dimensions.length() } fn write(&self, ser: &mut Serializer) -> Result { - let mut n = ser.write_leb128_u64(self.version as u64)?; - n += ser.write_leb128_u64(self.dimensions.len() as u64)?; - self.dimensions.iter().try_for_each(|(name, dimension)| { - n += ser.write_vec(name.as_bytes())?; - n += ser.write(dimension)?; - Ok::<_, Self::Error>(()) - })?; - Ok(n) + Ok(self.version.write(ser)? + self.dimensions.write(ser)?) } fn read(de: &mut Deserializer) -> Result { - let version = de.read_leb128_u64()?; - let dimensions = if version == Version::V1 as u64 { - (0..de.read_leb128_u64()?) - .map(|_| { - let name = String::from_utf8(de.read_vec()?) - .map_err(|e| Error::ConversionFailed(e.to_string()))?; - let dimension = de.read::()?; - Ok((name, dimension)) - }) - .collect::, Error>>() - } else { - Err(Error::ConversionFailed( - "unable to deserialize versions prior to V3".to_string(), - )) - }?; Ok(Self { - version: Version::V1, - dimensions, + version: de.read()?, + dimensions: de.read()?, }) } } diff --git a/src/abe_policy/attribute.rs b/src/abe_policy/attribute.rs index fcea1993..5881019a 100644 --- a/src/abe_policy/attribute.rs +++ b/src/abe_policy/attribute.rs @@ -1,5 +1,6 @@ use std::{convert::TryFrom, fmt::Debug, ops::BitOr}; +use cosmian_crypto_core::bytes_ser_de::Serializable; use serde::{Deserialize, Serialize}; use crate::Error; @@ -146,3 +147,87 @@ impl TryFrom<&str> for QualifiedAttribute { Ok(Self::new(dimension.trim(), component.trim())) } } + +impl Serializable for EncryptionHint { + type Error = Error; + + fn length(&self) -> usize { + 1 + } + + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + match self { + EncryptionHint::Classic => ser.write(&0usize), + EncryptionHint::Hybridized => ser.write(&1usize), + } + .map_err(Error::from) + } + + fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + let hint = de.read::()?; + match hint { + 0 => Ok(EncryptionHint::Classic), + 1 => Ok(EncryptionHint::Hybridized), + n => Err(Error::ConversionFailed(format!( + "invalid encryption-hint value: {}", + n + ))), + } + } +} + +impl Serializable for AttributeStatus { + type Error = Error; + + fn length(&self) -> usize { + 1 + } + + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + match self { + AttributeStatus::DecryptOnly => ser.write(&0usize), + AttributeStatus::EncryptDecrypt => ser.write(&1usize), + } + .map_err(Error::from) + } + + fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + let status = de.read::()?; + match status { + 0 => Ok(AttributeStatus::DecryptOnly), + 1 => Ok(AttributeStatus::EncryptDecrypt), + n => Err(Error::ConversionFailed(format!( + "invalid attribute-status value: {}", + n + ))), + } + } +} + +impl Serializable for QualifiedAttribute { + type Error = Error; + + fn length(&self) -> usize { + self.dimension.length() + self.name.length() + } + + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + Ok(ser.write(&self.dimension)? + ser.write(&self.name)?) + } + + fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + Ok(Self { + dimension: de.read()?, + name: de.read()?, + }) + } +} diff --git a/src/abe_policy/dimension.rs b/src/abe_policy/dimension.rs index b6a85a4f..5737df8f 100644 --- a/src/abe_policy/dimension.rs +++ b/src/abe_policy/dimension.rs @@ -60,13 +60,6 @@ impl Dimension { } } - pub fn is_ordered(&self) -> bool { - match self { - Self::Anarchy(_) => false, - Self::Hierarchy(_) => true, - } - } - /// Returns an iterator over the attributes name. /// /// If the dimension is ordered, the names are returned in this order, otherwise they are @@ -239,9 +232,7 @@ impl Dimension { } mod serialization { - use cosmian_crypto_core::bytes_ser_de::{ - to_leb128_len, Deserializer, Serializable, Serializer, - }; + use cosmian_crypto_core::bytes_ser_de::Serializable; use super::*; @@ -249,44 +240,25 @@ mod serialization { type Error = Error; fn length(&self) -> usize { - 2 + to_leb128_len(self.id) + self.id.length() + self.encryption_hint.length() + self.write_status.length() } - fn write(&self, ser: &mut Serializer) -> Result { - let mut n = ser.write_leb128_u64(self.id as u64)?; - n += ser.write_leb128_u64(::from(self.encryption_hint) as u64)?; - n += ser.write_leb128_u64(::from(self.write_status) as u64)?; - Ok(n) + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + Ok(self.id.write(ser)? + + self.encryption_hint.write(ser)? + + self.write_status.write(ser)?) } - fn read(de: &mut Deserializer) -> Result { - let id = de.read_leb128_u64()?.try_into()?; - let hint = de.read_leb128_u64()?; - let encryption_hint = if 0 == hint { - EncryptionHint::Classic - } else if 1 == hint { - EncryptionHint::Hybridized - } else { - return Err(Error::ConversionFailed(format!( - "erroneous hint value {hint}" - ))); - }; - - let status = de.read_leb128_u64()?; - let write_status = if 0 == status { - AttributeStatus::DecryptOnly - } else if 1 == status { - AttributeStatus::EncryptDecrypt - } else { - return Err(Error::ConversionFailed(format!( - "erroneous status value {hint}" - ))); - }; - + fn read( + de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer, + ) -> Result { Ok(Self { - id, - encryption_hint, - write_status, + id: de.read()?, + encryption_hint: de.read()?, + write_status: de.read()?, }) } } @@ -306,21 +278,9 @@ mod serialization { type Error = Error; fn length(&self) -> usize { - let f = |attributes: Box>| { - attributes - .map(|(name, attribute)| { - let l = name.len(); - to_leb128_len(l) + l + attribute.length() - }) - .sum::() - }; 1 + match self { - Dimension::Anarchy(attributes) => { - to_leb128_len(attributes.len()) + f(Box::new(attributes.iter())) - } - Dimension::Hierarchy(attributes) => { - to_leb128_len(attributes.len()) + f(Box::new(attributes.iter())) - } + Dimension::Anarchy(inner) => inner.length(), + Dimension::Hierarchy(inner) => inner.length(), } } @@ -328,52 +288,27 @@ mod serialization { &self, ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, ) -> Result { - let write_attributes = - |mut attributes: Box>, - ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer| - -> Result { - attributes.try_fold(0, |mut n, (name, attribute)| { - n += ser.write_vec(name.as_bytes())?; - n += ser.write(attribute)?; - Ok(n) - }) - }; - - let mut n = ser.write_leb128_u64(self.is_ordered() as u64)?; match self { - Dimension::Anarchy(attributes) => { - n += ser.write_leb128_u64(attributes.len() as u64)?; - n += write_attributes(Box::new(attributes.iter()), ser)?; - } - Dimension::Hierarchy(attributes) => { - n += ser.write_leb128_u64(attributes.len() as u64)?; - n += write_attributes(Box::new(attributes.iter()), ser)?; - } - }; - - Ok(n) + Dimension::Anarchy(inner) => Ok(ser.write(&0usize)? + ser.write(inner)?), + Dimension::Hierarchy(inner) => Ok(ser.write(&1usize)? + + ser + .write(inner) + .map_err(|e| Error::ConversionFailed(e.to_string()))?), + } } fn read( de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer, ) -> Result { - let is_ordered = de.read_leb128_u64()?; - let l = de.read_leb128_u64()?; - let attributes = (0..l).map(|_| { - let name = String::from_utf8(de.read_vec()?) - .map_err(|e| Error::ConversionFailed(e.to_string()))?; - let attribute = de.read::()?; - Ok::<_, Error>((name, attribute)) - }); - - if 0 == is_ordered { - attributes.collect::>().map(Self::Anarchy) - } else if 1 == is_ordered { - attributes.collect::>().map(Self::Hierarchy) - } else { - Err(Error::ConversionFailed(format!( - "invalid boolean value {is_ordered}" - ))) + let t = de.read::()?; + match t { + 0 => Ok(Self::Anarchy(de.read()?)), + 1 => Ok(Self::Hierarchy(de.read().map_err( + |e: crate::data_struct::error::Error| Error::ConversionFailed(e.to_string()), + )?)), + n => Err(Error::ConversionFailed(format!( + "invalid dimension type: {n}" + ))), } } } diff --git a/src/abe_policy/mod.rs b/src/abe_policy/mod.rs index 3f28693e..df3a70c4 100644 --- a/src/abe_policy/mod.rs +++ b/src/abe_policy/mod.rs @@ -10,12 +10,40 @@ mod tests; pub use access_policy::AccessPolicy; pub use access_structure::AccessStructure; pub use attribute::{AttributeStatus, EncryptionHint, QualifiedAttribute}; +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; + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Version { V1, } + +impl Serializable for Version { + type Error = Error; + + fn length(&self) -> usize { + 1 + } + + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + match self { + Version::V1 => Ok(ser.write(&1usize)?), + } + } + + fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + let version = de.read::()?; + match version { + 1 => Ok(Self::V1), + n => Err(Error::ConversionFailed(format!("invalid version: {n}"))), + } + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs index bbaf9453..150ef4fb 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -402,7 +402,6 @@ pub struct MasterPublicKey { impl MasterPublicKey { /// Returns the tracing level of this MPK. - #[inline(always)] pub fn tracing_level(&self) -> usize { self.tpk.tracing_level() } diff --git a/src/core/serialization/mod.rs b/src/core/serialization/mod.rs index e7289101..a7dfcfb0 100644 --- a/src/core/serialization/mod.rs +++ b/src/core/serialization/mod.rs @@ -1,20 +1,12 @@ //! Implements the serialization methods for the `Covercrypt` objects. -use std::collections::{HashMap, HashSet, LinkedList}; - -use cosmian_crypto_core::{ - bytes_ser_de::{to_leb128_len, Deserializer, Serializable, Serializer}, - FixedSizeCBytes, SymmetricKey, -}; +use cosmian_crypto_core::bytes_ser_de::{Deserializer, Serializable, Serializer}; use super::{ Encapsulations, RightPublicKey, RightSecretKey, TracingPublicKey, TracingSecretKey, UserId, - SIGNATURE_LENGTH, SIGNING_KEY_LENGTH, TAG_LENGTH, }; use crate::{ - abe_policy::{AccessStructure, Right}, - core::{MasterPublicKey, MasterSecretKey, UserSecretKey, XEnc, SHARED_SECRET_LENGTH}, - data_struct::{RevisionMap, RevisionVec}, + core::{MasterPublicKey, MasterSecretKey, UserSecretKey, XEnc}, Error, }; @@ -22,25 +14,15 @@ impl Serializable for TracingPublicKey { type Error = Error; fn length(&self) -> usize { - to_leb128_len(self.0.len()) + self.0.iter().map(Serializable::length).sum::() + self.0.length() } fn write(&self, ser: &mut Serializer) -> Result { - let mut n = ser.write_leb128_u64(self.0.len() as u64)?; - for pk in self.0.iter() { - n += pk.write(ser)?; - } - Ok(n) + self.0.write(ser) } fn read(de: &mut Deserializer) -> Result { - let n_pk = ::try_from(de.read_leb128_u64()?)?; - let mut tracers = LinkedList::new(); - for _ in 0..n_pk { - let tracer = de.read()?; - tracers.push_back(tracer); - } - Ok(Self(tracers)) + de.read().map(Self) } } @@ -56,32 +38,22 @@ impl Serializable for RightPublicKey { fn write(&self, ser: &mut Serializer) -> Result { match self { - Self::Hybridized { H, ek } => { - let mut n = ser.write_leb128_u64(1)?; - n += ser.write(H)?; - n += ser.write(ek)?; - Ok(n) - } - Self::Classic { H } => { - let mut n = ser.write_leb128_u64(0)?; - n += ser.write(H)?; - Ok(n) - } + Self::Classic { H } => Ok(0usize.write(ser)? + H.write(ser)?), + Self::Hybridized { H, ek } => Ok(1usize.write(ser)? + ser.write(H)? + ser.write(ek)?), } } fn read(de: &mut Deserializer) -> Result { - let is_hybridized = de.read_leb128_u64()?; - let H = de.read()?; - if 1 == is_hybridized { - let ek = de.read()?; - Ok(Self::Hybridized { H, ek }) - } else if 0 == is_hybridized { - Ok(Self::Classic { H }) - } else { - Err(Error::ConversionFailed(format!( - "invalid hybridization flag {is_hybridized}" - ))) + let is_hybridized = de.read::()?; + match is_hybridized { + 0 => Ok(Self::Classic { H: de.read()? }), + 1 => Ok(Self::Hybridized { + H: de.read()?, + ek: de.read()?, + }), + n => Err(Error::ConversionFailed(format!( + "invalid hybridization flag {n}" + ))), } } } @@ -90,42 +62,20 @@ impl Serializable for MasterPublicKey { type Error = Error; fn length(&self) -> usize { - self.tpk.length() - + to_leb128_len(self.encryption_keys.len()) - + self - .encryption_keys - .iter() - .map(|(coordinate, pk)| coordinate.length() + pk.length()) - .sum::() - + self.access_structure.length() + self.tpk.length() + self.encryption_keys.length() + self.access_structure.length() } fn write(&self, ser: &mut Serializer) -> Result { - let mut n = ser.write(&self.tpk)?; - n += ser.write_leb128_u64(self.encryption_keys.len() as u64)?; - for (coordinate, pk) in &self.encryption_keys { - n += ser.write(coordinate)?; - n += ser.write(pk)?; - } - n += ser.write(&self.access_structure)?; - - Ok(n) + Ok(self.tpk.write(ser)? + + self.encryption_keys.write(ser)? + + self.access_structure.write(ser)?) } fn read(de: &mut Deserializer) -> Result { - let tpk = de.read::()?; - let n_coordinates = ::try_from(de.read_leb128_u64()?)?; - let mut coordinate_keys = HashMap::with_capacity(n_coordinates); - for _ in 0..n_coordinates { - let coordinate = de.read::()?; - let pk = de.read::()?; - coordinate_keys.insert(coordinate, pk); - } - let access_structure = de.read::()?; Ok(Self { - tpk, - encryption_keys: coordinate_keys, - access_structure, + tpk: de.read()?, + encryption_keys: de.read()?, + access_structure: de.read()?, }) } } @@ -134,52 +84,19 @@ impl Serializable for TracingSecretKey { type Error = Error; fn length(&self) -> usize { - self.s.length() - + to_leb128_len(self.users.len()) - + self.users.iter().map(Serializable::length).sum::() - + to_leb128_len(self.tracers.len()) - + self - .tracers - .iter() - .map(|(sk, pk)| sk.length() + pk.length()) - .sum::() + self.s.length() + self.users.length() + self.tracers.length() } fn write(&self, ser: &mut Serializer) -> Result { - let mut n = self.s.write(ser)?; - - n += ser.write_leb128_u64(self.tracers.len() as u64)?; - for (sk, pk) in &self.tracers { - n += ser.write(sk)?; - n += ser.write(pk)?; - } - - n = ser.write_leb128_u64(self.users.len() as u64)?; - for id in &self.users { - n += ser.write(id)?; - } - - Ok(n) + Ok(self.s.write(ser)? + self.tracers.write(ser)? + self.users.write(ser)?) } fn read(de: &mut Deserializer) -> Result { - let s = de.read()?; - - let n_tracers = ::try_from(de.read_leb128_u64()?)?; - let mut tracers = LinkedList::new(); - for _ in 0..n_tracers { - let sk = de.read()?; - let pk = de.read()?; - tracers.push_back((sk, pk)); - } - - let n_users = ::try_from(de.read_leb128_u64()?)?; - let mut users = HashSet::with_capacity(n_users); - for _ in 0..n_users { - let id = de.read()?; - users.insert(id); - } - Ok(Self { s, tracers, users }) + Ok(Self { + s: de.read()?, + tracers: de.read()?, + users: de.read()?, + }) } } @@ -188,70 +105,24 @@ impl Serializable for MasterSecretKey { fn length(&self) -> usize { self.tsk.length() - + to_leb128_len(self.secrets.len()) - + self - .secrets - .iter() - .map(|(coordinate, chain)| { - coordinate.length() - + to_leb128_len(chain.len()) - + chain.iter().map(|(_, k)| 1 + k.length()).sum::() - }) - .sum::() - + self.signing_key.as_ref().map_or_else(|| 0, |key| key.len()) + + self.secrets.length() + + self.signing_key.length() + self.access_structure.length() } fn write(&self, ser: &mut Serializer) -> Result { - let mut n = ser.write(&self.tsk)?; - n += ser.write_leb128_u64(self.secrets.len() as u64)?; - for (coordinate, chain) in &self.secrets.map { - n += ser.write(coordinate)?; - n += ser.write_leb128_u64(chain.len() as u64)?; - for (is_activated, sk) in chain { - n += ser.write_leb128_u64((*is_activated).into())?; - n += ser.write(sk)?; - } - } - if let Some(kmac_key) = &self.signing_key { - n += ser.write_array(&**kmac_key)?; - } - n += ser.write(&self.access_structure)?; - Ok(n) + Ok(self.tsk.write(ser)? + + self.secrets.write(ser)? + + self.signing_key.write(ser)? + + self.access_structure.write(ser)?) } fn read(de: &mut Deserializer) -> Result { - let tsk = de.read::()?; - let n_coordinates = ::try_from(de.read_leb128_u64()?)?; - let mut coordinate_keypairs = RevisionMap::with_capacity(n_coordinates); - for _ in 0..n_coordinates { - let coordinate = de.read()?; - let n_keys = ::try_from(de.read_leb128_u64()?)?; - let chain = (0..n_keys) - .map(|_| -> Result<_, Error> { - let is_activated = de.read_leb128_u64()? == 1; - let sk = de.read::()?; - Ok((is_activated, sk)) - }) - .collect::, _>>()?; - coordinate_keypairs.map.insert(coordinate, chain); - } - - let signing_key = if de.value().len() < SIGNING_KEY_LENGTH { - None - } else { - Some(SymmetricKey::try_from_bytes( - de.read_array::()?, - )?) - }; - - let access_structure = de.read()?; - Ok(Self { - tsk, - secrets: coordinate_keypairs, - signing_key, - access_structure, + tsk: de.read()?, + secrets: de.read()?, + signing_key: de.read()?, + access_structure: de.read()?, }) } } @@ -260,25 +131,15 @@ impl Serializable for UserId { type Error = Error; fn length(&self) -> usize { - to_leb128_len(self.0.len()) + self.iter().map(|marker| marker.length()).sum::() + self.0.length() } fn write(&self, ser: &mut Serializer) -> Result { - let mut n = ser.write_leb128_u64(self.0.len() as u64)?; - for marker in &self.0 { - n += ser.write(marker)?; - } - Ok(n) + self.0.write(ser) } fn read(de: &mut Deserializer) -> Result { - let length = ::try_from(de.read_leb128_u64()?)?; - let mut id = LinkedList::new(); - for _ in 0..length { - let marker = de.read()?; - id.push_back(marker); - } - Ok(Self(id)) + de.read().map(Self) } } @@ -328,77 +189,22 @@ impl Serializable for UserSecretKey { type Error = Error; fn length(&self) -> usize { - self.id.length() - + to_leb128_len(self.ps.len()) - + self.ps.iter().map(|p| p.length()).sum::() - + to_leb128_len(self.secrets.len()) - + self - .secrets - .iter() - .map(|(coordinate, chain)| { - coordinate.length() - + to_leb128_len(chain.len()) - + chain.iter().map(|sk| sk.length()).sum::() - }) - .sum::() - + self.signature.as_ref().map_or_else(|| 0, |kmac| kmac.len()) + self.id.length() + self.ps.length() + self.secrets.length() + self.signature.length() } fn write(&self, ser: &mut Serializer) -> Result { - let mut n = ser.write(&self.id)?; - - n += ser.write_leb128_u64(self.ps.len() as u64)?; - for p in &self.ps { - n += ser.write(p)?; - } - - n += ser.write_leb128_u64(self.secrets.len() as u64)?; - for (coordinate, chain) in self.secrets.iter() { - n += ser.write(coordinate)?; - n += ser.write_leb128_u64(chain.len() as u64)?; - for sk in chain { - n += ser.write(sk)?; - } - } - if let Some(kmac) = &self.signature { - n += ser.write_array(kmac)?; - } - Ok(n) + Ok(self.id.write(ser)? + + self.ps.write(ser)? + + self.secrets.write(ser)? + + self.signature.write(ser)?) } fn read(de: &mut Deserializer) -> Result { - let id = de.read::()?; - - let n_ps = usize::try_from(de.read_leb128_u64()?)?; - - let mut ps = Vec::with_capacity(n_ps); - for _ in 0..n_ps { - let p = de.read()?; - ps.push(p); - } - - let n_coordinates = ::try_from(de.read_leb128_u64()?)?; - let mut coordinate_keys = RevisionVec::with_capacity(n_coordinates); - for _ in 0..n_coordinates { - let coordinate = de.read()?; - let n_keys = ::try_from(de.read_leb128_u64()?)?; - let new_chain = (0..n_keys) - .map(|_| de.read::()) - .collect::>()?; - coordinate_keys.insert_new_chain(coordinate, new_chain); - } - - let msk_signature = if de.value().len() < SIGNATURE_LENGTH { - None - } else { - Some(de.read_array::()?) - }; - Ok(Self { - id, - ps, - secrets: coordinate_keys, - signature: msk_signature, + id: de.read()?, + ps: de.read()?, + secrets: de.read()?, + signature: de.read()?, }) } } @@ -408,63 +214,26 @@ impl Serializable for Encapsulations { fn length(&self) -> usize { 1 + match self { - Encapsulations::HEncs(vec) => { - to_leb128_len(vec.len()) - + vec.iter().map(|(E, F)| E.length() + F.len()).sum::() - } - Encapsulations::CEncs(vec) => { - to_leb128_len(vec.len()) + vec.iter().map(|F| F.len()).sum::() - } + Encapsulations::HEncs(vec) => vec.length(), + Encapsulations::CEncs(vec) => vec.length(), } } fn write(&self, ser: &mut Serializer) -> Result { match self { - Encapsulations::HEncs(vec) => { - let mut n = ser.write_leb128_u64(1)?; - n += ser.write_leb128_u64(vec.len() as u64)?; - for (E, F) in vec.iter() { - n += ser.write(E)?; - n += ser.write_array(F)?; - } - Ok(n) - } - Encapsulations::CEncs(vec) => { - let mut n = ser.write_leb128_u64(0)?; - n += ser.write_leb128_u64(vec.len() as u64)?; - for F in vec.iter() { - n += ser.write_array(F)?; - } - Ok(n) - } + Encapsulations::CEncs(vec) => Ok(0usize.write(ser)? + vec.write(ser)?), + Encapsulations::HEncs(vec) => Ok(1usize.write(ser)? + vec.write(ser)?), } } fn read(de: &mut Deserializer) -> Result { let is_hybridized = de.read_leb128_u64()?; - if is_hybridized == 1 { - let len = usize::try_from(de.read_leb128_u64()?)?; - let vec = (0..len) - .map(|_| { - let E = de.read()?; - let F = de.read_array::()?; - Ok::<_, Error>((E, F)) - }) - .collect::, _>>()?; - Ok(Self::HEncs(vec)) - } else if 0 == is_hybridized { - let len = usize::try_from(de.read_leb128_u64()?)?; - let vec = (0..len) - .map(|_| { - let F = de.read_array::()?; - Ok::<_, Error>(F) - }) - .collect::, _>>()?; - Ok(Self::CEncs(vec)) - } else { - Err(Error::ConversionFailed(format!( - "invalid hybridization flag {is_hybridized}" - ))) + match is_hybridized { + 0 => Ok(Self::CEncs(de.read()?)), + 1 => Ok(Self::HEncs(de.read()?)), + n => Err(Error::ConversionFailed(format!( + "invalid encapsulation type: {n}" + ))), } } } @@ -473,50 +242,32 @@ impl Serializable for XEnc { type Error = Error; fn length(&self) -> usize { - TAG_LENGTH - + to_leb128_len(self.c.len()) - + self.c.iter().map(Serializable::length).sum::() - + self.encapsulations.length() + self.tag.length() + self.c.length() + self.encapsulations.length() } fn write(&self, ser: &mut Serializer) -> Result { - let mut n = ser.write_array(&self.tag)?; - n += ser.write_leb128_u64(self.c.len() as u64)?; - for trap in &self.c { - n += ser.write(trap)?; - } - n += ser.write(&self.encapsulations)?; - Ok(n) + Ok(self.tag.write(ser)? + self.c.write(ser)? + self.encapsulations.write(ser)?) } fn read(de: &mut Deserializer) -> Result { - let tag = de.read_array::()?; - let n_traps = ::try_from(de.read_leb128_u64()?)?; - let mut traps = Vec::with_capacity(n_traps); - for _ in 0..n_traps { - let trap = de.read()?; - traps.push(trap); - } - let encapsulations = Encapsulations::read(de)?; Ok(Self { - tag, - c: traps, - encapsulations, + tag: de.read()?, + c: de.read()?, + encapsulations: de.read()?, }) } } #[cfg(test)] mod tests { - use std::collections::HashMap; + use std::collections::{HashMap, HashSet}; use cosmian_crypto_core::{ bytes_ser_de::test_serialization, reexport::rand_core::SeedableRng, CsRng, }; - use super::*; use crate::{ - abe_policy::{AttributeStatus, EncryptionHint}, + abe_policy::{AttributeStatus, EncryptionHint, Right}, api::Covercrypt, core::{ primitives::{encaps, rekey, setup, update_msk, usk_keygen}, diff --git a/src/data_struct/dictionary.rs b/src/data_struct/dictionary.rs index d3390ba8..f9977571 100644 --- a/src/data_struct/dictionary.rs +++ b/src/data_struct/dictionary.rs @@ -7,6 +7,7 @@ use std::{ mem::swap, }; +use cosmian_crypto_core::bytes_ser_de::Serializable; use serde::{ de::{MapAccess, Visitor}, ser::SerializeMap, @@ -265,6 +266,31 @@ where } } +impl Serializable for Dict +where + K: Hash + PartialEq + Eq + Clone + Debug, +{ + type Error = Error; + + fn length(&self) -> usize { + self.indices.length() + self.entries.length() + } + + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + Ok(self.indices.write(ser)? + self.entries.write(ser)?) + } + + fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + Ok(Self { + indices: de.read()?, + entries: de.read()?, + }) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/data_struct/error.rs b/src/data_struct/error.rs index 18e011ac..47feac2f 100644 --- a/src/data_struct/error.rs +++ b/src/data_struct/error.rs @@ -1,5 +1,7 @@ use std::fmt::{Debug, Display}; +use cosmian_crypto_core::CryptoCoreError; + type Key = String; #[derive(Debug)] @@ -7,6 +9,7 @@ pub enum Error { EntryNotFound(Key), ExistingEntry(Key), AlreadyHasChild(Key), + Serialization(CryptoCoreError), } impl Display for Error { @@ -17,6 +20,7 @@ impl Display for Error { Self::AlreadyHasChild(key) => { write!(f, "Entry with key {key} already has a child.") } + Self::Serialization(e) => write!(f, "Serialization error: {e}"), } } } @@ -43,3 +47,11 @@ impl Error { Self::AlreadyHasChild(format!("{key:?}")) } } + +impl std::error::Error for Error {} + +impl From for Error { + fn from(error: CryptoCoreError) -> Self { + Self::Serialization(error) + } +} diff --git a/src/data_struct/revision_map.rs b/src/data_struct/revision_map.rs index a0bc35b7..60461b1f 100644 --- a/src/data_struct/revision_map.rs +++ b/src/data_struct/revision_map.rs @@ -8,6 +8,10 @@ use std::{ hash::Hash, }; +use cosmian_crypto_core::bytes_ser_de::Serializable; + +use crate::Error; + /// A `RevisionMap` is a `HashMap` which keys are mapped to sequences of values. /// Upon insertion for an existing key, the new value is prepended to the /// sequence of older values instead of replacing it. @@ -176,6 +180,29 @@ where } } +impl Serializable for RevisionMap +where + K: Hash + PartialEq + Eq + Debug + Serializable, + V: Debug + Serializable, +{ + type Error = Error; + + fn length(&self) -> usize { + self.map.length() + } + + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + Ok(self.map.write(ser)?) + } + + fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + Ok(Self { map: de.read()? }) + } +} + #[cfg(test)] mod tests { #![allow(clippy::unnecessary_to_owned)] diff --git a/src/data_struct/revision_vec.rs b/src/data_struct/revision_vec.rs index b8dd0fd7..ac337fab 100644 --- a/src/data_struct/revision_vec.rs +++ b/src/data_struct/revision_vec.rs @@ -3,6 +3,10 @@ use std::collections::{ LinkedList, VecDeque, }; +use cosmian_crypto_core::bytes_ser_de::Serializable; + +use crate::Error; + /// A `RevisionVec` is a vector that stores pairs containing a key /// and a sequence of values. Inserting a new value in the sequence /// associated to an existing key prepends this value to the sequence. @@ -202,6 +206,25 @@ impl FromIterator<(K, T)> for RevisionVec { } } +impl Serializable for RevisionVec { + type Error = Error; + + fn length(&self) -> usize { + self.chains.length() + } + + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + Ok(self.chains.write(ser)?) + } + + fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + Ok(Self { chains: de.read()? }) + } +} + #[cfg(test)] mod tests { From 7015fa8fae326b2dec31b079c91bc502930382a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 15 Oct 2025 17:28:55 +0200 Subject: [PATCH 03/21] review imports --- src/core/primitives.rs | 14 ++++---------- src/core/serialization/mod.rs | 8 ++++---- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/core/primitives.rs b/src/core/primitives.rs index ad15bdd4..b0d752fe 100644 --- a/src/core/primitives.rs +++ b/src/core/primitives.rs @@ -16,18 +16,15 @@ use cosmian_crypto_core::{ use crate::{ abe_policy::{AccessStructure, AttributeStatus, EncryptionHint, Right}, core::{ - kem::{self, MlKem}, - Encapsulations, KmacSignature, MasterPublicKey, MasterSecretKey, RightPublicKey, - RightSecretKey, TracingSecretKey, UserId, UserSecretKey, XEnc, MIN_TRACING_LEVEL, - SHARED_SECRET_LENGTH, SIGNATURE_LENGTH, SIGNING_KEY_LENGTH, TAG_LENGTH, + kem::MlKem, nike::ElGamal, Encapsulations, KmacSignature, MasterPublicKey, MasterSecretKey, + RightPublicKey, RightSecretKey, TracingSecretKey, UserId, UserSecretKey, XEnc, + MIN_TRACING_LEVEL, SHARED_SECRET_LENGTH, SIGNATURE_LENGTH, SIGNING_KEY_LENGTH, TAG_LENGTH, }, data_struct::{RevisionMap, RevisionVec}, traits::{Kem, Nike, Sampling}, Error, }; -use super::nike::ElGamal; - fn xor_2(lhs: &[u8; LENGTH], rhs: &[u8; LENGTH]) -> [u8; LENGTH] { let mut out = [0; LENGTH]; for pos in 0..LENGTH { @@ -347,10 +344,7 @@ fn h_decaps( A: &::PublicKey, c: &[::PublicKey], tag: &[u8; TAG_LENGTH], - encs: &[( - ::Encapsulation, - [u8; SHARED_SECRET_LENGTH], - )], + encs: &[(::Encapsulation, [u8; SHARED_SECRET_LENGTH])], ) -> Result>, Error> { let T = { let mut hasher = Sha3::v256(); diff --git a/src/core/serialization/mod.rs b/src/core/serialization/mod.rs index a7dfcfb0..1d8964c1 100644 --- a/src/core/serialization/mod.rs +++ b/src/core/serialization/mod.rs @@ -2,11 +2,11 @@ use cosmian_crypto_core::bytes_ser_de::{Deserializer, Serializable, Serializer}; -use super::{ - Encapsulations, RightPublicKey, RightSecretKey, TracingPublicKey, TracingSecretKey, UserId, -}; use crate::{ - core::{MasterPublicKey, MasterSecretKey, UserSecretKey, XEnc}, + core::{ + Encapsulations, MasterPublicKey, MasterSecretKey, RightPublicKey, RightSecretKey, + TracingPublicKey, TracingSecretKey, UserId, UserSecretKey, XEnc, + }, Error, }; From 4e4b10e1cdb48d6f847108250729dc96386a1b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 15 Oct 2025 17:34:41 +0200 Subject: [PATCH 04/21] fix tests --- src/core/nike/p256.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/core/nike/p256.rs b/src/core/nike/p256.rs index e640ade6..ac7ca4a8 100644 --- a/src/core/nike/p256.rs +++ b/src/core/nike/p256.rs @@ -11,6 +11,9 @@ 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; @@ -18,9 +21,6 @@ use elliptic_curve::Field; use elliptic_curve::PrimeField; use p256::{ProjectivePoint, Scalar}; use subtle::ConstantTimeEq; -use tiny_keccak::Hasher; -use tiny_keccak::Sha3; -use zeroize::Zeroize; use crate::traits::Group; use crate::traits::KeyHomomorphicNike; @@ -31,7 +31,7 @@ use crate::traits::Sampling; use crate::traits::Zero; use crate::Error; -#[derive(Clone, Debug, PartialEq, Eq, Zeroize)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct P256Point(ProjectivePoint); impl Zero for P256Point { @@ -44,6 +44,12 @@ impl Zero for P256Point { } } +impl Zeroize for P256Point { + fn zeroize(&mut self) { + self.0.zeroize() + } +} + impl Add for P256Point { type Output = Self; @@ -134,7 +140,7 @@ impl Sum for P256Point { } } -#[derive(Clone, Debug, PartialEq, Eq, Zeroize)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct P256Scalar(Scalar); impl Hash for P256Scalar { From 4173b71d4188a9a2bcfc5eea7ec0d4ab5dac942d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 15 Oct 2025 17:46:03 +0200 Subject: [PATCH 05/21] fix clippy --- src/core/serialization/mod.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/core/serialization/mod.rs b/src/core/serialization/mod.rs index 1d8964c1..96ec8ba8 100644 --- a/src/core/serialization/mod.rs +++ b/src/core/serialization/mod.rs @@ -18,11 +18,13 @@ impl Serializable for TracingPublicKey { } fn write(&self, ser: &mut Serializer) -> Result { - self.0.write(ser) + #[allow(clippy::needless_question_mark)] + Ok(self.0.write(ser)?) } fn read(de: &mut Deserializer) -> Result { - de.read().map(Self) + #[allow(clippy::needless_question_mark)] + Ok(Self(de.read()?)) } } @@ -135,11 +137,13 @@ impl Serializable for UserId { } fn write(&self, ser: &mut Serializer) -> Result { - self.0.write(ser) + #[allow(clippy::needless_question_mark)] + Ok(self.0.write(ser)?) } fn read(de: &mut Deserializer) -> Result { - de.read().map(Self) + #[allow(clippy::needless_question_mark)] + Ok(Self(de.read()?)) } } From 3a11ec7b41948e2c9c1297be6fb8179369c52109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 22 Oct 2025 18:13:56 +0200 Subject: [PATCH 06/21] add an enumeration member to the security-status enumeration --- src/abe_policy/access_structure.rs | 30 +++--- src/abe_policy/attribute.rs | 164 +++++++++++++---------------- src/abe_policy/dimension.rs | 52 +++++---- src/abe_policy/mod.rs | 2 +- src/abe_policy/tests.rs | 44 ++++---- src/api.rs | 1 + src/core/mod.rs | 146 +++++++++++++++++-------- src/core/primitives.rs | 133 ++++++++++++----------- src/core/serialization/mod.rs | 52 +++++---- src/core/tests.rs | 29 +++-- src/data_struct/revision_map.rs | 25 ++++- src/lib.rs | 2 +- src/test_utils/mod.rs | 6 +- 13 files changed, 378 insertions(+), 308 deletions(-) diff --git a/src/abe_policy/access_structure.rs b/src/abe_policy/access_structure.rs index 4b67e7ce..22c97ef4 100644 --- a/src/abe_policy/access_structure.rs +++ b/src/abe_policy/access_structure.rs @@ -2,8 +2,8 @@ use std::collections::{hash_map::Entry, HashMap, HashSet}; use crate::{ abe_policy::{ - AccessPolicy, Attribute, AttributeStatus, Dimension, EncryptionHint, QualifiedAttribute, - Right, + attribute::SecurityMode, AccessPolicy, Attribute, Dimension, EncryptionStatus, + QualifiedAttribute, Right, }, data_struct::Dict, Error, @@ -104,7 +104,7 @@ impl AccessStructure { pub fn add_attribute( &mut self, attribute: QualifiedAttribute, - encryption_hint: EncryptionHint, + security_mode: SecurityMode, after: Option<&str>, ) -> Result<(), Error> { let cnt = self @@ -116,7 +116,7 @@ impl AccessStructure { self.dimensions .get_mut(&attribute.dimension) .ok_or_else(|| Error::DimensionNotFound(attribute.dimension.clone()))? - .add_attribute(attribute.name, encryption_hint, after, cnt)?; + .add_attribute(attribute.name, security_mode, after, cnt)?; Ok(()) } @@ -172,7 +172,7 @@ 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() @@ -320,27 +320,27 @@ impl AccessStructure { /// - D2::B2 fn combine( dimensions: &[(&String, &Dimension)], -) -> Vec<(Vec, EncryptionHint, AttributeStatus)> { +) -> Vec<(Vec, SecurityMode, EncryptionStatus)> { if dimensions.is_empty() { vec![( vec![], - EncryptionHint::Classic, - AttributeStatus::EncryptDecrypt, + SecurityMode::Classic, + EncryptionStatus::EncryptDecrypt, )] } else { let (_, current_dimension) = &dimensions[0]; let partial_combinations = combine(&dimensions[1..]); let mut res = vec![]; for component in current_dimension.attributes() { - for (ids, is_hybridized, is_activated) in &partial_combinations { + for (ids, security_mode, is_activated) in partial_combinations.clone() { res.push(( [vec![component.get_id()], ids.clone()].concat(), - *is_hybridized | component.get_encryption_hint(), - *is_activated | component.get_status(), + security_mode.min(component.get_security_mode()), + is_activated | component.get_status(), )); } } - [partial_combinations.clone(), res].concat() + [partial_combinations, res].concat() } } @@ -410,9 +410,9 @@ mod tests { structure.add_anarchy("Country".to_string()).unwrap(); [ - ("France", EncryptionHint::Classic), - ("Germany", EncryptionHint::Classic), - ("Spain", EncryptionHint::Classic), + ("France", SecurityMode::Classic), + ("Germany", SecurityMode::Classic), + ("Spain", SecurityMode::Classic), ] .into_iter() .try_for_each(|(attribute, hint)| { diff --git a/src/abe_policy/attribute.rs b/src/abe_policy/attribute.rs index 5881019a..1cac05a4 100644 --- a/src/abe_policy/attribute.rs +++ b/src/abe_policy/attribute.rs @@ -5,53 +5,62 @@ use serde::{Deserialize, Serialize}; use crate::Error; -/// Hint the user about which kind of encryption to use. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub enum EncryptionHint { - /// Hybridized encryption should be used. - Hybridized, - /// Classic encryption should be used. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)] +pub enum SecurityMode { + // Due the derivation of `Ord`, definition order matters. Classic, + PostQuantum, + Hybridized, } -impl BitOr for EncryptionHint { - type Output = Self; +impl Serializable for SecurityMode { + type Error = Error; - fn bitor(self, rhs: Self) -> Self::Output { - if self == Self::Hybridized || rhs == Self::Hybridized { - Self::Hybridized - } else { - Self::Classic - } + fn length(&self) -> usize { + 1 } -} -impl EncryptionHint { - #[must_use] - pub fn new(is_hybridized: bool) -> Self { - if is_hybridized { - Self::Hybridized - } else { - Self::Classic + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + match self { + Self::Classic => ser.write(&0usize), + Self::PostQuantum => ser.write(&1usize), + Self::Hybridized => ser.write(&2usize), } + .map_err(Error::from) } -} -impl From for bool { - fn from(val: EncryptionHint) -> Self { - val == EncryptionHint::Hybridized + fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + let status = de.read::()?; + match status { + 0 => Ok(Self::Classic), + 1 => Ok(Self::PostQuantum), + 2 => Ok(Self::Hybridized), + n => Err(Error::ConversionFailed(format!( + "invalid security-mode value: {}", + n + ))), + } } } /// Whether to provide an encryption key in the master public key for this /// attribute. #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub enum AttributeStatus { +pub enum EncryptionStatus { EncryptDecrypt, DecryptOnly, } -impl BitOr for AttributeStatus { +impl Default for EncryptionStatus { + fn default() -> Self { + Self::EncryptDecrypt + } +} + +impl BitOr for EncryptionStatus { type Output = Self; fn bitor(self, rhs: Self) -> Self::Output { @@ -63,9 +72,40 @@ impl BitOr for AttributeStatus { } } -impl From for bool { - fn from(val: AttributeStatus) -> Self { - val == AttributeStatus::EncryptDecrypt +impl From for bool { + fn from(val: EncryptionStatus) -> Self { + val == EncryptionStatus::EncryptDecrypt + } +} + +impl Serializable for EncryptionStatus { + type Error = Error; + + fn length(&self) -> usize { + 1 + } + + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + match self { + Self::DecryptOnly => ser.write(&0usize), + Self::EncryptDecrypt => ser.write(&1usize), + } + .map_err(Error::from) + } + + fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + let status = de.read::()?; + match status { + 0 => Ok(Self::DecryptOnly), + 1 => Ok(Self::EncryptDecrypt), + n => Err(Error::ConversionFailed(format!( + "invalid attribute-status value: {}", + n + ))), + } } } @@ -148,68 +188,6 @@ impl TryFrom<&str> for QualifiedAttribute { } } -impl Serializable for EncryptionHint { - type Error = Error; - - fn length(&self) -> usize { - 1 - } - - fn write( - &self, - ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, - ) -> Result { - match self { - EncryptionHint::Classic => ser.write(&0usize), - EncryptionHint::Hybridized => ser.write(&1usize), - } - .map_err(Error::from) - } - - fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { - let hint = de.read::()?; - match hint { - 0 => Ok(EncryptionHint::Classic), - 1 => Ok(EncryptionHint::Hybridized), - n => Err(Error::ConversionFailed(format!( - "invalid encryption-hint value: {}", - n - ))), - } - } -} - -impl Serializable for AttributeStatus { - type Error = Error; - - fn length(&self) -> usize { - 1 - } - - fn write( - &self, - ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, - ) -> Result { - match self { - AttributeStatus::DecryptOnly => ser.write(&0usize), - AttributeStatus::EncryptDecrypt => ser.write(&1usize), - } - .map_err(Error::from) - } - - fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { - let status = de.read::()?; - match status { - 0 => Ok(AttributeStatus::DecryptOnly), - 1 => Ok(AttributeStatus::EncryptDecrypt), - n => Err(Error::ConversionFailed(format!( - "invalid attribute-status value: {}", - n - ))), - } - } -} - impl Serializable for QualifiedAttribute { type Error = Error; diff --git a/src/abe_policy/dimension.rs b/src/abe_policy/dimension.rs index 5737df8f..755fd089 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::{attribute::EncryptionHint, AttributeStatus}; +use super::{EncryptionStatus, SecurityMode}; use crate::{data_struct::Dict, Error}; type Name = String; @@ -13,16 +13,16 @@ type Name = String; #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] pub struct Attribute { pub(crate) id: usize, - pub(crate) encryption_hint: EncryptionHint, - pub(crate) write_status: AttributeStatus, + pub(crate) mode: SecurityMode, + pub(crate) status: EncryptionStatus, } impl Attribute { - pub fn new(encryption_hint: EncryptionHint, id: usize) -> Self { + pub fn new(encryption_hint: SecurityMode, id: usize) -> Self { Self { id, - encryption_hint, - write_status: AttributeStatus::EncryptDecrypt, + mode: encryption_hint, + status: EncryptionStatus::EncryptDecrypt, } } @@ -30,12 +30,12 @@ impl Attribute { self.id } - pub fn get_encryption_hint(&self) -> EncryptionHint { - self.encryption_hint + pub fn get_security_mode(&self) -> SecurityMode { + self.mode } - pub fn get_status(&self) -> AttributeStatus { - self.write_status + pub fn get_status(&self) -> EncryptionStatus { + self.status } } @@ -106,7 +106,7 @@ impl Dimension { pub fn add_attribute( &mut self, attribute: Name, - hint: EncryptionHint, + hint: SecurityMode, after: Option<&str>, id: usize, ) -> Result<(), Error> { @@ -185,11 +185,11 @@ impl Dimension { match self { Self::Anarchy(attributes) => attributes .get_mut(name) - .map(|attr| attr.write_status = AttributeStatus::DecryptOnly) + .map(|attr| attr.status = EncryptionStatus::DecryptOnly) .ok_or(Error::AttributeNotFound(name.to_string())), Self::Hierarchy(attributes) => attributes .get_mut(name) - .map(|attr| attr.write_status = AttributeStatus::DecryptOnly) + .map(|attr| attr.status = EncryptionStatus::DecryptOnly) .ok_or(Error::AttributeNotFound(name.to_string())), } } @@ -240,16 +240,14 @@ mod serialization { type Error = Error; fn length(&self) -> usize { - self.id.length() + self.encryption_hint.length() + self.write_status.length() + self.id.length() + self.mode.length() + self.status.length() } fn write( &self, ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, ) -> Result { - Ok(self.id.write(ser)? - + self.encryption_hint.write(ser)? - + self.write_status.write(ser)?) + Ok(self.id.write(ser)? + self.mode.write(ser)? + self.status.write(ser)?) } fn read( @@ -257,8 +255,8 @@ mod serialization { ) -> Result { Ok(Self { id: de.read()?, - encryption_hint: de.read()?, - write_status: de.read()?, + mode: de.read()?, + status: de.read()?, }) } } @@ -267,10 +265,10 @@ mod serialization { fn test_attribute_serialization() { use cosmian_crypto_core::bytes_ser_de::test_serialization; - let attribute = Attribute::new(EncryptionHint::Classic, 13); + let attribute = Attribute::new(SecurityMode::Classic, 13); test_serialization(&attribute).unwrap(); - let attribute = Attribute::new(EncryptionHint::Hybridized, usize::MAX); + let attribute = Attribute::new(SecurityMode::Hybridized, usize::MAX); test_serialization(&attribute).unwrap(); } @@ -318,20 +316,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(), EncryptionHint::Classic, None, 0) + d.add_attribute("A".to_string(), SecurityMode::Classic, None, 0) .unwrap(); - d.add_attribute("B".to_string(), EncryptionHint::Hybridized, Some("A"), 1) + d.add_attribute("B".to_string(), SecurityMode::Hybridized, Some("A"), 1) .unwrap(); - d.add_attribute("C".to_string(), EncryptionHint::Hybridized, Some("B"), 2) + d.add_attribute("C".to_string(), SecurityMode::Hybridized, Some("B"), 2) .unwrap(); test_serialization(&d).unwrap(); let mut d = Dimension::Anarchy(HashMap::new()); - d.add_attribute("A".to_string(), EncryptionHint::Classic, None, 0) + d.add_attribute("A".to_string(), SecurityMode::Classic, None, 0) .unwrap(); - d.add_attribute("B".to_string(), EncryptionHint::Hybridized, None, 1) + d.add_attribute("B".to_string(), SecurityMode::Hybridized, None, 1) .unwrap(); - d.add_attribute("C".to_string(), EncryptionHint::Hybridized, None, 2) + d.add_attribute("C".to_string(), SecurityMode::Hybridized, None, 2) .unwrap(); test_serialization(&d).unwrap(); } diff --git a/src/abe_policy/mod.rs b/src/abe_policy/mod.rs index df3a70c4..a54e23b9 100644 --- a/src/abe_policy/mod.rs +++ b/src/abe_policy/mod.rs @@ -9,7 +9,7 @@ mod tests; pub use access_policy::AccessPolicy; pub use access_structure::AccessStructure; -pub use attribute::{AttributeStatus, EncryptionHint, QualifiedAttribute}; +pub use attribute::{EncryptionStatus, QualifiedAttribute, SecurityMode}; use cosmian_crypto_core::bytes_ser_de::Serializable; pub use dimension::{Attribute, Dimension}; pub use rights::Right; diff --git a/src/abe_policy/tests.rs b/src/abe_policy/tests.rs index e085f98d..97eb9f9d 100644 --- a/src/abe_policy/tests.rs +++ b/src/abe_policy/tests.rs @@ -1,6 +1,4 @@ -use crate::{abe_policy::AccessStructure, Error}; - -use super::EncryptionHint; +use crate::{abe_policy::AccessStructure, Error, SecurityMode}; pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), Error> { policy.add_hierarchy("SEC".to_string())?; @@ -10,7 +8,7 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), dimension: "SEC".to_string(), name: "LOW".to_string(), }, - EncryptionHint::Classic, + SecurityMode::Classic, None, )?; policy.add_attribute( @@ -18,17 +16,17 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), dimension: "SEC".to_string(), name: "TOP".to_string(), }, - EncryptionHint::Hybridized, + SecurityMode::Hybridized, Some("LOW"), )?; policy.add_anarchy("DPT".to_string())?; [ - ("RD", EncryptionHint::Classic), - ("HR", EncryptionHint::Classic), - ("MKG", EncryptionHint::Classic), - ("FIN", EncryptionHint::Classic), - ("DEV", EncryptionHint::Classic), + ("RD", SecurityMode::Classic), + ("HR", SecurityMode::Classic), + ("MKG", SecurityMode::Classic), + ("FIN", SecurityMode::Classic), + ("DEV", SecurityMode::Classic), ] .into_iter() .try_for_each(|(attribute, hint)| { @@ -45,11 +43,11 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), if complete { policy.add_anarchy("CTR".to_string())?; [ - ("EN", EncryptionHint::Classic), - ("DE", EncryptionHint::Classic), - ("IT", EncryptionHint::Classic), - ("FR", EncryptionHint::Classic), - ("SP", EncryptionHint::Classic), + ("EN", SecurityMode::Classic), + ("DE", SecurityMode::Classic), + ("IT", SecurityMode::Classic), + ("FR", SecurityMode::Classic), + ("SP", SecurityMode::Classic), ] .into_iter() .try_for_each(|(attribute, hint)| { @@ -100,20 +98,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::Classic, None) + .add_attribute(new_attr.clone(), SecurityMode::Classic, None) .is_ok()); assert_eq!(structure.attributes().count(), 8); // Try adding already existing attribute HR let duplicate_attr = QualifiedAttribute::new("DPT", "HR"); assert!(structure - .add_attribute(duplicate_attr, EncryptionHint::Classic, None) + .add_attribute(duplicate_attr, SecurityMode::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::Classic, None) + .add_attribute(missing_dimension.clone(), SecurityMode::Classic, None) .is_err()); // Remove research attribute @@ -145,14 +143,14 @@ fn test_edit_anarchic_attributes() { structure .add_attribute( QualifiedAttribute::new("DimensionTest", "Attr1"), - EncryptionHint::Classic, + SecurityMode::Classic, None, ) .unwrap(); structure .add_attribute( QualifiedAttribute::new("DimensionTest", "Attr2"), - EncryptionHint::Classic, + SecurityMode::Classic, None, ) .unwrap(); @@ -207,7 +205,7 @@ fn test_edit_hierarchic_attributes() { structure .add_attribute( QualifiedAttribute::new("SEC", "MID"), - EncryptionHint::Classic, + SecurityMode::Classic, None, ) .unwrap(); @@ -232,7 +230,7 @@ fn test_edit_hierarchic_attributes() { structure .add_attribute( QualifiedAttribute::new("SEC", "LOW"), - EncryptionHint::Classic, + SecurityMode::Classic, None, ) .unwrap(); @@ -265,7 +263,7 @@ fn test_edit_hierarchic_attributes() { structure .add_attribute( QualifiedAttribute::new("SEC", "MID"), - EncryptionHint::Classic, + SecurityMode::Classic, Some("LOW"), ) .unwrap(); diff --git a/src/api.rs b/src/api.rs index 2a91fba9..c32776ba 100644 --- a/src/api.rs +++ b/src/api.rs @@ -18,6 +18,7 @@ use crate::{ traits::{KemAc, PkeAc}, AccessPolicy, Error, }; + #[derive(Debug)] pub struct Covercrypt { rng: Mutex, diff --git a/src/core/mod.rs b/src/core/mod.rs index 150ef4fb..cb703259 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -10,10 +10,10 @@ use kem::MlKem; use nike::ElGamal; use crate::{ - abe_policy::{AccessStructure, Right}, + abe_policy::{AccessStructure, EncryptionStatus, Right}, data_struct::{RevisionMap, RevisionVec}, traits::{Kem, Nike, Sampling, Zero}, - Error, + Error, SecurityMode, }; mod kem; @@ -53,28 +53,41 @@ type Tag = [u8; TAG_LENGTH]; pub const MIN_TRACING_LEVEL: usize = 1; /// The Covercrypt subkeys hold the DH secret key associated to a right. -/// Subkeys can be hybridized, in which case they also hold a PQ-KEM secret key. +/// +/// Subkeys can be hybridized in which case they also hold a PQ-KEM secret key, +/// or post-quantum in which case they only hold a PQ-KEM secret key. #[derive(Clone, Debug, PartialEq)] enum RightSecretKey { - Hybridized { + Classic { sk: ::SecretKey, + }, + PostQuantum { dk: ::DecapsulationKey, }, - Classic { + Hybridized { sk: ::SecretKey, + dk: ::DecapsulationKey, }, } impl RightSecretKey { /// Generates a new random right secret key cryptographically bound to the Covercrypt binding /// point `h`. - fn random(rng: &mut impl CryptoRngCore, hybridize: bool) -> Result { - let sk = ::SecretKey::random(rng); - if hybridize { - let (dk, _) = MlKem::keygen(rng)?; - Ok(Self::Hybridized { sk, dk }) - } else { - Ok(Self::Classic { sk }) + fn random(rng: &mut impl CryptoRngCore, security_mode: SecurityMode) -> Result { + match security_mode { + SecurityMode::Classic => { + let sk = ::SecretKey::random(rng); + Ok(Self::Classic { sk }) + } + SecurityMode::PostQuantum => { + let (dk, _) = MlKem::keygen(rng)?; + Ok(Self::PostQuantum { dk }) + } + SecurityMode::Hybridized => { + let sk = ::SecretKey::random(rng); + let (dk, _) = MlKem::keygen(rng)?; + Ok(Self::Hybridized { sk, dk }) + } } } @@ -86,51 +99,81 @@ impl RightSecretKey { H: h * sk, ek: dk.ek(), }, + Self::PostQuantum { dk } => RightPublicKey::PostQuantum { ek: dk.ek() }, Self::Classic { sk } => RightPublicKey::Classic { H: h * sk }, } } - /// Returns true if this right secret key is hybridized. - fn is_hybridized(&self) -> bool { + /// Returns the security mode of this right secret key. + fn security_mode(&self) -> SecurityMode { match self { - Self::Hybridized { .. } => true, - Self::Classic { .. } => false, + Self::Hybridized { .. } => SecurityMode::Hybridized, + Self::PostQuantum { .. } => SecurityMode::PostQuantum, + Self::Classic { .. } => SecurityMode::Classic, } } - fn drop_hybridization(&self) -> Self { - match self { - Self::Hybridized { sk: x_i, .. } => Self::Classic { sk: x_i.clone() }, - Self::Classic { .. } => self.clone(), - } + /// Sets the security mode of this right secret key. + fn set_security_mode( + self, + security_mode: SecurityMode, + rng: &mut impl CryptoRngCore, + ) -> Result { + Ok(match (self, security_mode) { + (Self::Hybridized { sk, .. }, SecurityMode::Classic) => Self::Classic { sk }, + (Self::Hybridized { dk, .. }, SecurityMode::PostQuantum) => Self::PostQuantum { dk }, + (Self::Hybridized { sk, dk }, SecurityMode::Hybridized) => Self::Hybridized { sk, dk }, + (Self::PostQuantum { .. }, SecurityMode::Classic) => Self::PostQuantum { + dk: ::keygen(rng)?.0, + }, + (Self::PostQuantum { dk }, SecurityMode::PostQuantum) => Self::PostQuantum { dk }, + (Self::PostQuantum { dk }, SecurityMode::Hybridized) => Self::Hybridized { + sk: ::keygen(rng)?.0, + dk, + }, + (Self::Classic { sk }, SecurityMode::Classic) => Self::Classic { sk }, + (Self::Classic { .. }, SecurityMode::PostQuantum) => Self::PostQuantum { + dk: ::keygen(rng)?.0, + }, + (Self::Classic { sk }, SecurityMode::Hybridized) => Self::Hybridized { + sk, + dk: ::keygen(rng)?.0, + }, + }) } } -/// The Covercrypt public keys hold the DH secret public key associated to a right. -/// Subkeys can be hybridized, in which case they also hold a PQ-KEM public key. +/// The Covercrypt public keys hold the DH secret public key associated to a +/// right. +/// +/// Subkeys can be hybridized in which case they also hold a PQ-KEM public key, +/// or post-quantum, in which case they only hold a PQ-KEM public key. #[derive(Clone, Debug, PartialEq)] enum RightPublicKey { - Hybridized { + Classic { H: ::PublicKey, + }, + PostQuantum { ek: ::EncapsulationKey, }, - Classic { + Hybridized { H: ::PublicKey, + ek: ::EncapsulationKey, }, } impl RightPublicKey { - pub fn is_hybridized(&self) -> bool { + /// Returns the security mode of this right public key. + pub fn security_mode(&self) -> SecurityMode { match self { - Self::Hybridized { .. } => true, - Self::Classic { .. } => false, + Self::Hybridized { .. } => SecurityMode::Hybridized, + Self::PostQuantum { .. } => SecurityMode::PostQuantum, + Self::Classic { .. } => SecurityMode::Classic, } } } /// Covercrypt user IDs are used to make user keys unique and traceable. -/// -/// They are composed of a sequence of `LENGTH` scalars. #[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] struct UserId(LinkedList<::SecretKey>); @@ -340,7 +383,7 @@ impl TracingPublicKey { #[derive(Debug, PartialEq)] pub struct MasterSecretKey { tsk: TracingSecretKey, - secrets: RevisionMap, + secrets: RevisionMap, signing_key: Option>, pub access_structure: AccessStructure, } @@ -373,8 +416,8 @@ impl MasterSecretKey { .secrets .iter() .filter_map(|(r, secrets)| { - secrets.front().and_then(|(is_activated, csk)| { - if *is_activated { + secrets.front().and_then(|(status, csk)| { + if &EncryptionStatus::EncryptDecrypt == status { Some((r.clone(), csk.cpk(&h))) } else { None @@ -417,26 +460,37 @@ impl MasterPublicKey { fn select_subkeys( &self, targets: &HashSet, - ) -> Result<(bool, Vec<&RightPublicKey>), Error> { - // This mutable variable is set to false if at least one sub-key is not - // hybridized. - let mut is_hybridized = true; - + ) -> Result<(SecurityMode, Vec<&RightPublicKey>), Error> { let subkeys = targets .iter() .map(|r| { - let subkey = self - .encryption_keys + self.encryption_keys .get(r) - .ok_or_else(|| Error::KeyError(format!("no public key for right '{r:#?}'")))?; - if !subkey.is_hybridized() { - is_hybridized = false; + .ok_or_else(|| Error::KeyError(format!("no public key for right '{r:#?}'"))) + }) + .collect::, Error>>()?; + + let (security_mode, is_homogeneous) = subkeys + .iter() + .map(|k| (k.security_mode(), true)) + .reduce(|(lhs_mode, lhs_bool), (rhs_mode, rhs_bool)| { + if lhs_mode == rhs_mode { + (lhs_mode, lhs_bool && rhs_bool) + } else { + (lhs_mode, false) } - Ok(subkey) }) - .collect::>()?; + .ok_or_else(|| { + Error::OperationNotPermitted("target set cannot be empty".to_string()) + })?; - Ok((is_hybridized, subkeys)) + if is_homogeneous { + Ok((security_mode, subkeys)) + } else { + Err(Error::OperationNotPermitted( + "cannot select subkeys with different security modes".to_string(), + )) + } } } diff --git a/src/core/primitives.rs b/src/core/primitives.rs index b0d752fe..3f0ab18a 100644 --- a/src/core/primitives.rs +++ b/src/core/primitives.rs @@ -14,7 +14,7 @@ use cosmian_crypto_core::{ }; use crate::{ - abe_policy::{AccessStructure, AttributeStatus, EncryptionHint, Right}, + abe_policy::{AccessStructure, EncryptionStatus, Right, SecurityMode}, core::{ kem::MlKem, nike::ElGamal, Encapsulations, KmacSignature, MasterPublicKey, MasterSecretKey, RightPublicKey, RightSecretKey, TracingSecretKey, UserId, UserSecretKey, XEnc, @@ -57,27 +57,12 @@ fn sign( keys: &RevisionVec, ) -> Result, Error> { if let Some(kmac_key) = &msk.signing_key { - let mut kmac = Kmac::v256(&**kmac_key, b"USK signature"); - for marker in id.iter() { - kmac.update(&marker.serialize()?) - } // Subkeys ordering needs to be deterministic to allow deterministic // signatures. This explains why a hash-map is not used in USK. - for (coordinate, keys) in keys.iter() { - kmac.update(coordinate); - for subkey in keys.iter() { - match subkey { - RightSecretKey::Hybridized { sk: s_i, dk: dk_i } => { - kmac.update(&s_i.serialize()?); - kmac.update(&dk_i.serialize()?); - } - RightSecretKey::Classic { sk: s_i } => { - kmac.update(&s_i.serialize()?); - } - } - } - } let mut res = [0; SIGNATURE_LENGTH]; + let mut kmac = Kmac::v256(&**kmac_key, b"USK signature"); + kmac.update(&id.serialize()?); + kmac.update(&keys.serialize()?); kmac.finalize(&mut res); Ok(Some(res)) } else { @@ -184,24 +169,24 @@ pub fn usk_keygen( /// Generates a hybridized encapsulation of the given secret S with the given /// marker c, ElGamal random r and subkeys. -fn h_encaps( +fn h_encaps<'a>( S: Secret, c: Vec<::PublicKey>, r: ::SecretKey, - subkeys: &[&RightPublicKey], + subkeys: impl IntoIterator< + Item = ( + &'a ::PublicKey, + &'a ::EncapsulationKey, + ), + >, rng: &mut impl CryptoRngCore, ) -> Result<(Secret, XEnc), Error> { let encs = subkeys - .iter() - .map(|subkey| match subkey { - RightPublicKey::Hybridized { H, ek } => { - let K1 = ElGamal::session_key(&r, H)?; - let (K2, E) = MlKem::enc(ek, rng)?; - Ok((K1, K2, E)) - } - RightPublicKey::Classic { .. } => { - Err(Error::Kem("all subkeys should be hybridized".to_string())) - } + .into_iter() + .map(|(H, ek)| { + let K1 = ElGamal::session_key(&r, H)?; + let (K2, E) = MlKem::enc(ek, rng)?; + Ok((K1, K2, E)) }) .collect::, Error>>()?; @@ -252,11 +237,11 @@ fn h_encaps( /// Generates a classic encapsulation of the given secret S with the given /// marker c, ElGamal random r and subkeys. -fn c_encaps( +fn c_encaps<'a>( S: Secret, c: Vec<::PublicKey>, r: ::SecretKey, - subkeys: Vec<&RightPublicKey>, + subkeys: impl IntoIterator::PublicKey>, ) -> Result<(Secret, XEnc), Error> { // In classic mode, T is only updated with c. let T = { @@ -272,11 +257,7 @@ fn c_encaps( let encs = subkeys .into_iter() - .map(|subkey| -> Result<_, _> { - let H = match subkey { - RightPublicKey::Hybridized { H, .. } => H, - RightPublicKey::Classic { H } => H, - }; + .map(|H| -> Result<_, _> { let K1 = ElGamal::session_key(&r, H)?; let F = xor_2(&S, &*H_hash(&K1, None, &T)?); Ok(F) @@ -329,10 +310,34 @@ pub fn encaps( let r = G_hash(&S)?; let c = mpk.set_traps(&r); - if is_hybridized { - h_encaps(S, c, r, &coordinate_keys, rng) - } else { - c_encaps(S, c, r, coordinate_keys) + match is_hybridized { + SecurityMode::Classic => { + let subkeys = coordinate_keys.into_iter().map(|subkey| { + if let RightPublicKey::Classic { H } = subkey { + Ok(H) + } else { + Err(Error::OperationNotPermitted(format!( + "expected a classic right public key: {subkey:?}" + ))) + } + .unwrap() + }); + c_encaps(S, c, r, subkeys) + } + SecurityMode::PostQuantum => todo!(), + SecurityMode::Hybridized => { + let subkeys = coordinate_keys.into_iter().map(|subkey| { + if let RightPublicKey::Hybridized { H, ek } = subkey { + Ok((H, ek)) + } else { + Err(Error::OperationNotPermitted(format!( + "expected a classic right public key: {subkey:?}" + ))) + } + .unwrap() + }); + h_encaps(S, c, r, subkeys, rng) + } } } @@ -448,6 +453,7 @@ fn c_decaps( let sk = match secret { RightSecretKey::Hybridized { sk, .. } => sk, RightSecretKey::Classic { sk } => sk, + RightSecretKey::PostQuantum { .. } => continue, }; let mut K1 = ElGamal::session_key(sk, A)?; let S = xor_in_place(H_hash(&K1, None, &T)?, F); @@ -569,8 +575,8 @@ pub fn full_decaps( Encapsulations::HEncs(encs) => { for (E, F) in encs { for (right, secret_set) in msk.secrets.iter() { - for (is_activated, secret) in secret_set { - if *is_activated { + for (status, secret) in secret_set { + if &EncryptionStatus::EncryptDecrypt == status { if let RightSecretKey::Hybridized { sk, dk } = secret { let mut K1 = ElGamal::session_key(sk, &A)?; let K2 = MlKem::dec(dk, E)?; @@ -584,11 +590,12 @@ pub fn full_decaps( Encapsulations::CEncs(encs) => { for F in encs { for (right, secret_set) in msk.secrets.iter() { - for (is_activated, secret) in secret_set { - if *is_activated { + for (status, secret) in secret_set { + if &EncryptionStatus::EncryptDecrypt == status { let sk = match secret { RightSecretKey::Hybridized { sk, .. } => sk, RightSecretKey::Classic { sk } => sk, + RightSecretKey::PostQuantum { .. } => continue, }; let mut K1 = ElGamal::session_key(sk, &A)?; try_decaps(right, &mut K1, None, F)?; @@ -609,25 +616,28 @@ pub fn full_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)); - for (r, (hint, status)) in rights { - if let Some((is_activated, coordinate_secret)) = secrets.get_latest_mut(&r) { - *is_activated = AttributeStatus::EncryptDecrypt == status; - if EncryptionHint::Classic == hint { - *coordinate_secret = coordinate_secret.drop_hybridization(); + for (r, (mode, status)) in rights { + if let Some(revisions) = secrets.get_mut(&r) { + if let Some((_, secret)) = revisions.pop_front() { + revisions.push_front((status, secret.set_security_mode(mode, rng)?)) + } else { + return Err(Error::OperationNotPermitted( + "empty revision list is illegal".to_string(), + )); } } else { - if AttributeStatus::DecryptOnly == status { + if EncryptionStatus::DecryptOnly == status { return Err(Error::OperationNotPermitted( "cannot add decrypt only secret".to_string(), )); } - let secret = RightSecretKey::random(rng, EncryptionHint::Hybridized == hint)?; - secrets.insert(r, (true, secret)); + let secret = RightSecretKey::random(rng, mode)?; + secrets.insert(r, (status, secret)); } } msk.secrets = secrets; @@ -642,16 +652,21 @@ pub fn rekey( ) -> Result<(), Error> { for r in rights { if msk.secrets.contains_key(&r) { - let is_hybridized = msk + let security_mode = msk .secrets .get_latest(&r) - .map(|(_, k)| k.is_hybridized()) + .map(|(_, k)| k.security_mode()) .ok_or_else(|| { Error::OperationNotPermitted(format!("no current key for coordinate {r:#?}")) })?; - msk.secrets - .insert(r, (true, RightSecretKey::random(rng, is_hybridized)?)); + msk.secrets.insert( + r, + ( + EncryptionStatus::default(), + RightSecretKey::random(rng, security_mode)?, + ), + ); } else { return Err(Error::OperationNotPermitted( "cannot re-key a right not belonging to the MSK".to_string(), diff --git a/src/core/serialization/mod.rs b/src/core/serialization/mod.rs index 96ec8ba8..3e6426c7 100644 --- a/src/core/serialization/mod.rs +++ b/src/core/serialization/mod.rs @@ -33,15 +33,17 @@ impl Serializable for RightPublicKey { fn length(&self) -> usize { 1 + match self { - Self::Hybridized { H, ek } => H.length() + ek.length(), Self::Classic { H } => H.length(), + Self::PostQuantum { ek } => ek.length(), + Self::Hybridized { H, ek } => H.length() + ek.length(), } } fn write(&self, ser: &mut Serializer) -> Result { match self { Self::Classic { H } => Ok(0usize.write(ser)? + H.write(ser)?), - Self::Hybridized { H, ek } => Ok(1usize.write(ser)? + ser.write(H)? + ser.write(ek)?), + Self::PostQuantum { ek } => Ok(2usize.write(ser)? + ek.write(ser)?), + Self::Hybridized { H, ek } => Ok(1usize.write(ser)? + H.write(ser)? + ek.write(ser)?), } } @@ -49,6 +51,7 @@ impl Serializable for RightPublicKey { let is_hybridized = de.read::()?; match is_hybridized { 0 => Ok(Self::Classic { H: de.read()? }), + 2 => Ok(Self::PostQuantum { ek: de.read()? }), 1 => Ok(Self::Hybridized { H: de.read()?, ek: de.read()?, @@ -154,37 +157,32 @@ impl Serializable for RightSecretKey { 1 + match self { Self::Hybridized { sk, dk } => sk.length() + dk.length(), Self::Classic { sk } => sk.length(), + Self::PostQuantum { dk } => dk.length(), } } fn write(&self, ser: &mut Serializer) -> Result { match self { + Self::Classic { sk } => Ok(0usize.write(ser)? + sk.write(ser)?), + Self::PostQuantum { dk } => Ok(2usize.write(ser)? + dk.write(ser)?), Self::Hybridized { sk, dk } => { - let mut n = ser.write_leb128_u64(1)?; - n += ser.write(sk)?; - n += ser.write(dk)?; - Ok(n) - } - Self::Classic { sk } => { - let mut n = ser.write_leb128_u64(0)?; - n += ser.write(sk)?; - Ok(n) + Ok(1usize.write(ser)? + sk.write(ser)? + dk.write(ser)?) } } } fn read(de: &mut Deserializer) -> Result { - let is_hybridized = de.read_leb128_u64()?; - let sk = de.read()?; - if 1 == is_hybridized { - let dk = de.read()?; - Ok(Self::Hybridized { sk, dk }) - } else if 0 == is_hybridized { - Ok(Self::Classic { sk }) - } else { - Err(Error::ConversionFailed(format!( - "invalid hybridization flag {is_hybridized}" - ))) + let mode = de.read_leb128_u64()?; + match mode { + 0 => Ok(Self::Classic { sk: de.read()? }), + 1 => Ok(Self::Hybridized { + sk: de.read()?, + dk: de.read()?, + }), + 2 => Ok(Self::PostQuantum { dk: de.read()? }), + _ => Err(Error::ConversionFailed(format!( + "invalid hybridization flag {mode}" + ))), } } } @@ -271,7 +269,7 @@ mod tests { }; use crate::{ - abe_policy::{AttributeStatus, EncryptionHint, Right}, + abe_policy::{EncryptionStatus, Right}, api::Covercrypt, core::{ primitives::{encaps, rekey, setup, update_msk, usk_keygen}, @@ -279,7 +277,7 @@ mod tests { }, test_utils::cc_keygen, traits::KemAc, - AccessPolicy, + AccessPolicy, SecurityMode, }; #[test] @@ -293,15 +291,15 @@ mod tests { let universe = HashMap::from([ ( coordinate_1.clone(), - (EncryptionHint::Hybridized, AttributeStatus::EncryptDecrypt), + (SecurityMode::Hybridized, EncryptionStatus::EncryptDecrypt), ), ( coordinate_2.clone(), - (EncryptionHint::Hybridized, AttributeStatus::EncryptDecrypt), + (SecurityMode::Hybridized, EncryptionStatus::EncryptDecrypt), ), ( coordinate_3.clone(), - (EncryptionHint::Hybridized, AttributeStatus::EncryptDecrypt), + (SecurityMode::Hybridized, EncryptionStatus::EncryptDecrypt), ), ]); diff --git a/src/core/tests.rs b/src/core/tests.rs index 5579e414..d79500a2 100644 --- a/src/core/tests.rs +++ b/src/core/tests.rs @@ -3,9 +3,12 @@ use std::collections::{HashMap, HashSet}; use cosmian_crypto_core::{reexport::rand_core::SeedableRng, Aes256Gcm, CsRng}; use crate::{ - abe_policy::{AccessPolicy, AttributeStatus, EncryptionHint, Right}, + abe_policy::{AccessPolicy, EncryptionStatus, Right}, api::Covercrypt, - core::primitives::{decaps, encaps, refresh, rekey, update_msk}, + core::{ + primitives::{decaps, encaps, refresh, rekey, update_msk}, + SecurityMode, + }, test_utils::cc_keygen, traits::{KemAc, PkeAc}, }; @@ -15,6 +18,12 @@ use super::{ MIN_TRACING_LEVEL, }; +#[test] +fn security_mode_ordering() { + assert!(SecurityMode::Classic < SecurityMode::PostQuantum); + assert!(SecurityMode::PostQuantum < SecurityMode::Hybridized); +} + /// This test asserts that it is possible to encapsulate a key for a given /// coordinate and that different users which key is associated with this /// coordinate can open the resulting encapsulation. @@ -31,11 +40,11 @@ fn test_encapsulation() { HashMap::from_iter([ ( other_coordinate.clone(), - (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), + (SecurityMode::Classic, EncryptionStatus::EncryptDecrypt), ), ( target_coordinate.clone(), - (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), + (SecurityMode::Classic, EncryptionStatus::EncryptDecrypt), ), ]), ) @@ -93,7 +102,7 @@ fn test_update() { .map(|_| { ( Right::random(&mut rng), - (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), + (SecurityMode::Classic, EncryptionStatus::EncryptDecrypt), ) }) .collect::>(); @@ -112,7 +121,7 @@ fn test_update() { .enumerate() .for_each(|(i, (_, (_, status)))| { if i % 2 == 0 { - *status = AttributeStatus::DecryptOnly; + *status = EncryptionStatus::DecryptOnly; } }); update_msk(&mut rng, &mut msk, coordinates.clone()).unwrap(); @@ -147,11 +156,11 @@ fn test_rekey() { HashMap::from_iter([ ( coordinate_1.clone(), - (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), + (SecurityMode::Classic, EncryptionStatus::EncryptDecrypt), ), ( coordinate_2.clone(), - (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), + (SecurityMode::Classic, EncryptionStatus::EncryptDecrypt), ), ]), ) @@ -231,11 +240,11 @@ fn test_integrity_check() { HashMap::from_iter([ ( coordinate_1.clone(), - (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), + (SecurityMode::Classic, EncryptionStatus::EncryptDecrypt), ), ( coordinate_2.clone(), - (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), + (SecurityMode::Classic, EncryptionStatus::EncryptDecrypt), ), ]), ) diff --git a/src/data_struct/revision_map.rs b/src/data_struct/revision_map.rs index 60461b1f..b9448c75 100644 --- a/src/data_struct/revision_map.rs +++ b/src/data_struct/revision_map.rs @@ -125,6 +125,16 @@ where self.map.get_mut(key).and_then(LinkedList::front_mut) } + /// Returns the last revised value for a given key, removing it from the + /// map. + pub fn take_latest(&mut self, key: &Q) -> Option + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + self.map.get_mut(key).and_then(LinkedList::pop_front) + } + /// Returns true if the given key is bound to some value. pub fn contains_key(&self, key: &K) -> bool { self.map.contains_key(key) @@ -140,14 +150,23 @@ where self.map.iter() } - /// Iterates through all revisions of a given key starting with the more - /// recent one. + /// Returns the list of revisions, starting with the more recent one. pub fn get(&self, key: &Q) -> Option<&LinkedList> where K: Borrow, Q: Hash + Eq + ?Sized, { - self.map.get(key) //.map(RevisionList::iter) + self.map.get(key) + } + + /// Returns a mutable reference on the list of revisions, starting with the + /// more recent one. + pub fn get_mut(&mut self, key: &Q) -> Option<&mut LinkedList> + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + self.map.get_mut(key) } /// Removes and returns an iterator over all revisions from a given key. diff --git a/src/lib.rs b/src/lib.rs index b900d535..406a1448 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,7 @@ mod encrypted_header; pub mod api; pub mod traits; -pub use abe_policy::{AccessStructure, EncryptionHint, QualifiedAttribute}; +pub use abe_policy::{AccessStructure, QualifiedAttribute, SecurityMode}; #[cfg(any(test, feature = "test-utils"))] pub mod test_utils; diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs index f45aa7b7..2f8ca65e 100644 --- a/src/test_utils/mod.rs +++ b/src/test_utils/mod.rs @@ -18,10 +18,10 @@ mod tests { use super::*; use crate::{ - abe_policy::{AccessPolicy, EncryptionHint, QualifiedAttribute}, + abe_policy::{AccessPolicy, QualifiedAttribute}, api::Covercrypt, traits::KemAc, - EncryptedHeader, + EncryptedHeader, SecurityMode, }; #[test] @@ -34,7 +34,7 @@ mod tests { let _ = &mut msk.access_structure.add_attribute( QualifiedAttribute::new("DPT", "Sales"), - EncryptionHint::Classic, + SecurityMode::Classic, None, )?; let mpk = cc.update_msk(&mut msk)?; From 407437b2a35dc860de5a564213f0048968689141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 22 Oct 2025 19:10:31 +0200 Subject: [PATCH 07/21] add quantum encapsulations and primitive encapsulation/decapsulation --- src/abe_policy/access_structure.rs | 44 +++++- src/abe_policy/tests.rs | 42 ++++-- src/core/mod.rs | 15 +- src/core/primitives.rs | 225 ++++++++++++++++++++++++----- src/core/serialization/mod.rs | 15 +- 5 files changed, 275 insertions(+), 66 deletions(-) diff --git a/src/abe_policy/access_structure.rs b/src/abe_policy/access_structure.rs index 22c97ef4..9241345d 100644 --- a/src/abe_policy/access_structure.rs +++ b/src/abe_policy/access_structure.rs @@ -3,14 +3,12 @@ use std::collections::{hash_map::Entry, HashMap, HashSet}; use crate::{ abe_policy::{ attribute::SecurityMode, AccessPolicy, Attribute, Dimension, EncryptionStatus, - QualifiedAttribute, Right, + QualifiedAttribute, Right, Version, }, data_struct::Dict, Error, }; -use super::Version; - #[derive(Clone, PartialEq, Eq, Debug)] pub struct AccessStructure { version: Version, @@ -309,10 +307,12 @@ impl AccessStructure { } } -/// Combines all attributes IDs from the given dimensions using at most one attribute for each -/// dimensions. Returns the disjunction of the associated hybridization and activation status. +/// Combines all attributes IDs from the given dimensions using at most one +/// attribute for each dimensions. Returns the disjunction of the associated +/// hybridization and activation status. /// -/// As an example, if dimensions D1::A1 and D2::(A2,B2) are given, the following combinations will be created: +/// As an example, if dimensions D1::A1 and D2::(A2,B2) are given, the following +/// combinations will be created: /// - D1::A1 /// - D1::A1 && D2::A2 /// - D1::A1 && D2::B2 @@ -468,6 +468,12 @@ mod tests { name: "LOW".to_string(), }, )?])?); + rights.insert(Right::from_point(vec![structure.get_attribute_id( + &QualifiedAttribute { + dimension: "SEC".to_string(), + name: "MED".to_string(), + }, + )?])?); rights.insert(Right::from_point(vec![structure.get_attribute_id( &QualifiedAttribute { dimension: "SEC".to_string(), @@ -496,6 +502,28 @@ mod tests { })?, ])?); + rights.insert(Right::from_point(vec![ + structure.get_attribute_id(&QualifiedAttribute { + dimension: "DPT".to_string(), + name: "HR".to_string(), + })?, + structure.get_attribute_id(&QualifiedAttribute { + dimension: "SEC".to_string(), + name: "MED".to_string(), + })?, + ])?); + + rights.insert(Right::from_point(vec![ + structure.get_attribute_id(&QualifiedAttribute { + dimension: "DPT".to_string(), + name: "FIN".to_string(), + })?, + structure.get_attribute_id(&QualifiedAttribute { + dimension: "SEC".to_string(), + name: "MED".to_string(), + })?, + ])?); + rights.insert(Right::from_point(vec![ structure.get_attribute_id(&QualifiedAttribute { dimension: "DPT".to_string(), @@ -528,12 +556,12 @@ mod tests { structure .generate_complementary_rights(&AccessPolicy::parse(ap)?)? .len(), - // There are 2 rights in the security dimension, plus the + // There are 3 rights in the security dimension, plus the // broadcast for this dimension. This is the restricted // space. There is only one projection of DPT::HR, which is the // universal broadcast. The complementary space is generated by // extending these two points with the restricted space. - 2 * (1 + 2) + 2 * (1 + 3) ); let ap = "SEC::LOW"; diff --git a/src/abe_policy/tests.rs b/src/abe_policy/tests.rs index 97eb9f9d..21da7485 100644 --- a/src/abe_policy/tests.rs +++ b/src/abe_policy/tests.rs @@ -11,13 +11,21 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), SecurityMode::Classic, None, )?; + policy.add_attribute( + crate::abe_policy::QualifiedAttribute { + dimension: "SEC".to_string(), + name: "MED".to_string(), + }, + SecurityMode::PostQuantum, + Some("LOW"), + )?; policy.add_attribute( crate::abe_policy::QualifiedAttribute { dimension: "SEC".to_string(), name: "TOP".to_string(), }, SecurityMode::Hybridized, - Some("LOW"), + Some("MED"), )?; policy.add_anarchy("DPT".to_string())?; @@ -72,7 +80,7 @@ fn test_edit_anarchic_attributes() { let mut structure = AccessStructure::new(); gen_structure(&mut structure, false).unwrap(); - assert_eq!(structure.attributes().count(), 7); + assert_eq!(structure.attributes().count(), 8); // Try renaming Research to already used name MKG assert!(structure @@ -93,14 +101,14 @@ fn test_edit_anarchic_attributes() { .map(|a| a.name) .collect(); - assert!(order.len() == 2); + assert!(order.len() == 3); // Add new attribute Sales let new_attr = QualifiedAttribute::new("DPT", "Sales"); assert!(structure .add_attribute(new_attr.clone(), SecurityMode::Classic, None) .is_ok()); - assert_eq!(structure.attributes().count(), 8); + assert_eq!(structure.attributes().count(), 9); // Try adding already existing attribute HR let duplicate_attr = QualifiedAttribute::new("DPT", "HR"); @@ -117,7 +125,7 @@ fn test_edit_anarchic_attributes() { // Remove research attribute let delete_attr = QualifiedAttribute::new("DPT", "Research"); structure.del_attribute(&delete_attr).unwrap(); - assert_eq!(structure.attributes().count(), 7); + assert_eq!(structure.attributes().count(), 8); // Duplicate remove assert!(structure.del_attribute(&delete_attr).is_err()); @@ -181,6 +189,10 @@ fn test_edit_hierarchic_attributes() { dimension: "SEC".to_string(), name: "LOW".to_string(), }, + QualifiedAttribute { + dimension: "SEC".to_string(), + name: "MED".to_string(), + }, QualifiedAttribute { dimension: "SEC".to_string(), name: "TOP".to_string(), @@ -204,7 +216,7 @@ fn test_edit_hierarchic_attributes() { structure .add_attribute( - QualifiedAttribute::new("SEC", "MID"), + QualifiedAttribute::new("SEC", "OTHER"), SecurityMode::Classic, None, ) @@ -218,7 +230,11 @@ fn test_edit_hierarchic_attributes() { vec![ QualifiedAttribute { dimension: "SEC".to_string(), - name: "MID".to_string(), + name: "OTHER".to_string(), + }, + QualifiedAttribute { + dimension: "SEC".to_string(), + name: "MED".to_string(), }, QualifiedAttribute { dimension: "SEC".to_string(), @@ -247,7 +263,11 @@ fn test_edit_hierarchic_attributes() { }, QualifiedAttribute { dimension: "SEC".to_string(), - name: "MID".to_string(), + name: "OTHER".to_string(), + }, + QualifiedAttribute { + dimension: "SEC".to_string(), + name: "MED".to_string(), }, QualifiedAttribute { dimension: "SEC".to_string(), @@ -257,7 +277,7 @@ fn test_edit_hierarchic_attributes() { ); structure - .del_attribute(&QualifiedAttribute::new("SEC", "MID")) + .del_attribute(&QualifiedAttribute::new("SEC", "OTHER")) .unwrap(); structure @@ -282,6 +302,10 @@ fn test_edit_hierarchic_attributes() { dimension: "SEC".to_string(), name: "MID".to_string(), }, + QualifiedAttribute { + dimension: "SEC".to_string(), + name: "MED".to_string(), + }, QualifiedAttribute { dimension: "SEC".to_string(), name: "TOP".to_string(), diff --git a/src/core/mod.rs b/src/core/mod.rs index cb703259..f45aec4e 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -457,6 +457,11 @@ impl MasterPublicKey { /// Returns the subkeys associated with the given rights in this public key, /// alongside a boolean value that is true if all of them are hybridized. + /// + /// # Error + /// + /// Returns an error in case a key is missing for one of the target rights + /// or these rights do not define an homogeneous set of keys. fn select_subkeys( &self, targets: &HashSet, @@ -526,8 +531,9 @@ impl UserSecretKey { #[derive(Debug, Clone, PartialEq)] enum Encapsulations { - HEncs(Vec<(::Encapsulation, [u8; SHARED_SECRET_LENGTH])>), - CEncs(Vec<[u8; SHARED_SECRET_LENGTH]>), + Hybridized(Vec<(::Encapsulation, [u8; SHARED_SECRET_LENGTH])>), + Quantum(Vec<(::Encapsulation, [u8; SHARED_SECRET_LENGTH])>), + Classic(Vec<[u8; SHARED_SECRET_LENGTH]>), } /// Covercrypt encapsulation. @@ -553,8 +559,9 @@ impl XEnc { pub fn count(&self) -> usize { match &self.encapsulations { - Encapsulations::HEncs(vec) => vec.len(), - Encapsulations::CEncs(vec) => vec.len(), + Encapsulations::Hybridized(vec) => vec.len(), + Encapsulations::Quantum(vec) => vec.len(), + Encapsulations::Classic(vec) => vec.len(), } } } diff --git a/src/core/primitives.rs b/src/core/primitives.rs index 3f0ab18a..7c5920d7 100644 --- a/src/core/primitives.rs +++ b/src/core/primitives.rs @@ -87,14 +87,15 @@ fn G_hash(seed: &Secret) -> Result<::Secr } fn H_hash( - K1: &::PublicKey, + K1: Option<&::PublicKey>, K2: Option<&Secret>, T: &Secret, ) -> Result, Error> { - let mut hasher = Sha3::v256(); - // SHARED_SECRET_LENGTH = 32 = 256 / 8 let mut H = Secret::::new(); - hasher.update(&K1.serialize()?); + let mut hasher = Sha3::v256(); + if let Some(K1) = K1 { + hasher.update(&K1.serialize()?); + } if let Some(K2) = K2 { hasher.update(&**K2); } @@ -208,7 +209,7 @@ fn h_encaps<'a>( let encs = encs .into_iter() .map(|(mut K1, K2, E)| -> Result<_, _> { - let F = xor_2(&S, &*H_hash(&K1, Some(&K2), &T)?); + let F = xor_2(&S, &*H_hash(Some(&K1), Some(&K2), &T)?); K1.zeroize(); Ok((E, F)) }) @@ -230,7 +231,64 @@ fn h_encaps<'a>( XEnc { tag, c, - encapsulations: Encapsulations::HEncs(encs), + encapsulations: Encapsulations::Hybridized(encs), + }, + )) +} + +/// Generates post-quantum encapsulation of the given secret S with the given +/// marker c, ElGamal random r and subkeys. +fn q_encaps<'a>( + S: Secret, + c: Vec<::PublicKey>, + subkeys: impl IntoIterator::EncapsulationKey>, + rng: &mut impl CryptoRngCore, +) -> Result<(Secret, XEnc), Error> { + let encs = subkeys + .into_iter() + .map(|ek| MlKem::enc(ek, rng)) + .collect::, Error>>()?; + + let T = { + let mut hasher = Sha3::v256(); + let mut T = Secret::new(); + c.iter().try_for_each(|ck| { + hasher.update(&ck.serialize()?); + Ok::<_, Error>(()) + })?; + encs.iter().try_for_each(|(_, E)| { + hasher.update(&E.serialize()?); + Ok::<_, Error>(()) + })?; + hasher.finalize(&mut *T); + T + }; + + let encs = encs + .into_iter() + .map(|(K2, E)| -> Result<_, _> { + let F = xor_2(&S, &*H_hash(None, Some(&K2), &T)?); + Ok((E, F)) + }) + .collect::, Error>>()?; + + let U = { + let mut U = Secret::new(); + let mut hasher = Sha3::v256(); + hasher.update(&*T); + encs.iter().for_each(|(_, F)| hasher.update(F)); + hasher.finalize(&mut *U); + U + }; + + let (tag, ss) = J_hash(&S, &U); + + Ok(( + ss, + XEnc { + tag, + c, + encapsulations: Encapsulations::Hybridized(encs), }, )) } @@ -259,7 +317,7 @@ fn c_encaps<'a>( .into_iter() .map(|H| -> Result<_, _> { let K1 = ElGamal::session_key(&r, H)?; - let F = xor_2(&S, &*H_hash(&K1, None, &T)?); + let F = xor_2(&S, &*H_hash(Some(&K1), None, &T)?); Ok(F) }) .collect::, Error>>()?; @@ -280,7 +338,7 @@ fn c_encaps<'a>( XEnc { tag, c, - encapsulations: Encapsulations::CEncs(encs), + encapsulations: Encapsulations::Classic(encs), }, )) } @@ -298,12 +356,12 @@ pub fn encaps( mpk: &MasterPublicKey, encryption_set: &HashSet, ) -> Result<(Secret, XEnc), Error> { + // A typed key container would avoid the need for casting in the match arms + // but would also involve additional overhead. let (is_hybridized, mut coordinate_keys) = mpk.select_subkeys(encryption_set)?; - // Shuffling must be performed *before* generating the encapsulations since - // rights are hashed in-order. If shuffling is performed after generating - // the encapsulations, there would be no way to know in which order to - // perform hashing upon decapsulation. + // Shuffling must be performed *before* generating the final encapsulations + // in order to have a deterministic digest. shuffle(&mut coordinate_keys, rng); let S = Secret::random(rng); @@ -314,33 +372,101 @@ pub fn encaps( SecurityMode::Classic => { let subkeys = coordinate_keys.into_iter().map(|subkey| { if let RightPublicKey::Classic { H } = subkey { - Ok(H) + H } else { - Err(Error::OperationNotPermitted(format!( - "expected a classic right public key: {subkey:?}" - ))) + panic!("select_subkeys already ensures homogeneity") } - .unwrap() }); c_encaps(S, c, r, subkeys) } - SecurityMode::PostQuantum => todo!(), + SecurityMode::PostQuantum => { + let subkeys = coordinate_keys.into_iter().map(|subkey| { + if let RightPublicKey::PostQuantum { ek } = subkey { + ek + } else { + panic!("select_subkeys already ensures homogeneity") + } + }); + q_encaps(S, c, subkeys, rng) + } SecurityMode::Hybridized => { let subkeys = coordinate_keys.into_iter().map(|subkey| { if let RightPublicKey::Hybridized { H, ek } = subkey { - Ok((H, ek)) + (H, ek) } else { - Err(Error::OperationNotPermitted(format!( - "expected a classic right public key: {subkey:?}" - ))) + panic!("select_subkeys already ensures homogeneity") } - .unwrap() }); h_encaps(S, c, r, subkeys, rng) } } } +/// Attempts to open the given hybridized encapsulations with this user secret +/// key. +fn q_decaps( + rng: &mut impl CryptoRngCore, + usk: &UserSecretKey, + c: &[::PublicKey], + tag: &[u8; TAG_LENGTH], + encs: &[(::Encapsulation, [u8; SHARED_SECRET_LENGTH])], +) -> Result>, Error> { + let T = { + let mut hasher = Sha3::v256(); + let mut T = Secret::::new(); + c.iter().try_for_each(|ck| { + hasher.update(&ck.serialize()?); + Ok::<_, Error>(()) + })?; + encs.iter().try_for_each(|(E, _)| { + hasher.update(&E.serialize()?); + Ok::<_, Error>(()) + })?; + hasher.finalize(&mut *T); + T + }; + + let U = { + let mut U = Secret::::new(); + let mut hasher = Sha3::v256(); + hasher.update(&*T); + encs.iter().for_each(|(_, F)| hasher.update(F)); + hasher.finalize(&mut *U); + U + }; + + // Shuffle encapsulation to counter timing attacks attempting to determine + // which right was used to open an encapsulation. + let mut encs = encs.iter().collect::>(); + shuffle(&mut encs, rng); + + // Loop order matters: this ordering is faster. + for mut revision in usk.secrets.revisions() { + // Shuffle secrets to counter timing attacks attempting to determine + // whether successive encapsulations target the same user right. + shuffle(&mut revision, rng); + for (E, F) in &encs { + for (_, secret) in &revision { + if let RightSecretKey::PostQuantum { dk } = secret { + let K2 = MlKem::dec(dk, E)?; + let S_ij = xor_in_place(H_hash(None, Some(&K2), &T)?, F); + let (tag_ij, ss) = J_hash(&S_ij, &U); + if tag == &tag_ij { + // Fujisaki-Okamoto + let r = G_hash(&S_ij)?; + let c_ij = usk.set_traps(&r); + if c == c_ij { + return Ok(Some(ss)); + } + } + } + } + } + } + + Ok(None) +} + /// Attempts to open the given hybridized encapsulations with this user secret /// key. fn h_decaps( @@ -390,7 +516,7 @@ fn h_decaps( if let RightSecretKey::Hybridized { sk, dk } = secret { let mut K1 = ElGamal::session_key(sk, A)?; let K2 = MlKem::dec(dk, E)?; - let S_ij = xor_in_place(H_hash(&K1, Some(&K2), &T)?, F); + let S_ij = xor_in_place(H_hash(Some(&K1), Some(&K2), &T)?, F); let (tag_ij, ss) = J_hash(&S_ij, &U); if tag == &tag_ij { // Fujisaki-Okamoto @@ -456,7 +582,7 @@ fn c_decaps( RightSecretKey::PostQuantum { .. } => continue, }; let mut K1 = ElGamal::session_key(sk, A)?; - let S = xor_in_place(H_hash(&K1, None, &T)?, F); + 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 { @@ -490,10 +616,13 @@ pub fn decaps( .sum(); match &encapsulation.encapsulations { - Encapsulations::HEncs(encs) => { + Encapsulations::Hybridized(encs) => { h_decaps(rng, usk, &A, &encapsulation.c, &encapsulation.tag, encs) } - Encapsulations::CEncs(encs) => { + Encapsulations::Quantum(encs) => { + q_decaps(rng, usk, &encapsulation.c, &encapsulation.tag, encs) + } + Encapsulations::Classic(encs) => { c_decaps(rng, usk, &A, &encapsulation.c, &encapsulation.tag, encs) } } @@ -528,7 +657,7 @@ pub fn full_decaps( Ok::<_, Error>(()) })?; - if let Encapsulations::HEncs(encs) = &encapsulation.encapsulations { + if let Encapsulations::Hybridized(encs) = &encapsulation.encapsulations { encs.iter().try_for_each(|(E, _)| { hasher.update(&E.serialize()?); Ok::<_, Error>(()) @@ -543,8 +672,9 @@ pub fn full_decaps( let mut hasher = Sha3::v256(); hasher.update(&*T); match &encapsulation.encapsulations { - Encapsulations::HEncs(encs) => encs.iter().for_each(|(_, F)| hasher.update(F)), - Encapsulations::CEncs(encs) => encs.iter().for_each(|F| hasher.update(F)), + Encapsulations::Hybridized(encs) => encs.iter().for_each(|(_, F)| hasher.update(F)), + Encapsulations::Quantum(encs) => encs.iter().for_each(|(_, F)| hasher.update(F)), + Encapsulations::Classic(encs) => encs.iter().for_each(|F| hasher.update(F)), } hasher.finalize(&mut *U); U @@ -553,17 +683,19 @@ pub fn full_decaps( let mut enc_ss = None; let mut rights = HashSet::with_capacity(encapsulation.count()); let mut try_decaps = |right: &Right, - K1: &mut ::PublicKey, + K1: Option<::PublicKey>, K2: Option>, F| { - let S_ij = xor_in_place(H_hash(K1, K2.as_ref(), &T)?, F); + let S_ij = xor_in_place(H_hash(K1.as_ref(), K2.as_ref(), &T)?, F); let (tag_ij, ss) = J_hash(&S_ij, &U); if encapsulation.tag == tag_ij { // Fujisaki-Okamoto let r = G_hash(&S_ij)?; let c_ij = msk.tsk.set_traps(&r); if encapsulation.c == c_ij { - K1.zeroize(); + if let Some(mut K1) = K1 { + K1.zeroize(); + } enc_ss = Some(ss); rights.insert(right.clone()); } @@ -572,22 +704,36 @@ pub fn full_decaps( }; match &encapsulation.encapsulations { - Encapsulations::HEncs(encs) => { + Encapsulations::Hybridized(encs) => { for (E, F) in encs { for (right, secret_set) in msk.secrets.iter() { for (status, secret) in secret_set { if &EncryptionStatus::EncryptDecrypt == status { if let RightSecretKey::Hybridized { sk, dk } = secret { - let mut K1 = ElGamal::session_key(sk, &A)?; + let K1 = ElGamal::session_key(sk, &A)?; + let K2 = MlKem::dec(dk, E)?; + try_decaps(right, Some(K1), Some(K2), F)?; + } + } + } + } + } + } + Encapsulations::Quantum(encs) => { + for (E, F) in encs { + for (right, secret_set) in msk.secrets.iter() { + for (status, secret) in secret_set { + if &EncryptionStatus::EncryptDecrypt == status { + if let RightSecretKey::PostQuantum { dk } = secret { let K2 = MlKem::dec(dk, E)?; - try_decaps(right, &mut K1, Some(K2), F)?; + try_decaps(right, None, Some(K2), F)?; } } } } } } - Encapsulations::CEncs(encs) => { + Encapsulations::Classic(encs) => { for F in encs { for (right, secret_set) in msk.secrets.iter() { for (status, secret) in secret_set { @@ -597,14 +743,15 @@ pub fn full_decaps( RightSecretKey::Classic { sk } => sk, RightSecretKey::PostQuantum { .. } => continue, }; - let mut K1 = ElGamal::session_key(sk, &A)?; - try_decaps(right, &mut K1, None, F)?; + let K1 = ElGamal::session_key(sk, &A)?; + try_decaps(right, Some(K1), None, F)?; } } } } } } + enc_ss .map(|ss| (ss, rights)) .ok_or_else(|| Error::Kem("could not open the encapsulation".to_string())) diff --git a/src/core/serialization/mod.rs b/src/core/serialization/mod.rs index 3e6426c7..94c47789 100644 --- a/src/core/serialization/mod.rs +++ b/src/core/serialization/mod.rs @@ -216,23 +216,26 @@ impl Serializable for Encapsulations { fn length(&self) -> usize { 1 + match self { - Encapsulations::HEncs(vec) => vec.length(), - Encapsulations::CEncs(vec) => vec.length(), + Encapsulations::Hybridized(vec) => vec.length(), + Encapsulations::Quantum(vec) => vec.length(), + Encapsulations::Classic(vec) => vec.length(), } } fn write(&self, ser: &mut Serializer) -> Result { match self { - Encapsulations::CEncs(vec) => Ok(0usize.write(ser)? + vec.write(ser)?), - Encapsulations::HEncs(vec) => Ok(1usize.write(ser)? + vec.write(ser)?), + Encapsulations::Classic(vec) => Ok(0usize.write(ser)? + vec.write(ser)?), + Encapsulations::Quantum(vec) => Ok(2usize.write(ser)? + vec.write(ser)?), + Encapsulations::Hybridized(vec) => Ok(1usize.write(ser)? + vec.write(ser)?), } } fn read(de: &mut Deserializer) -> Result { let is_hybridized = de.read_leb128_u64()?; match is_hybridized { - 0 => Ok(Self::CEncs(de.read()?)), - 1 => Ok(Self::HEncs(de.read()?)), + 0 => Ok(Self::Classic(de.read()?)), + 1 => Ok(Self::Hybridized(de.read()?)), + 2 => Ok(Self::Quantum(de.read()?)), n => Err(Error::ConversionFailed(format!( "invalid encapsulation type: {n}" ))), From a391be717d37fe7966d61e72c79f324059fde83a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 22 Oct 2025 19:40:59 +0200 Subject: [PATCH 08/21] adding the possibility to select quantum encapsulations --- src/abe_policy/access_structure.rs | 2 +- src/abe_policy/attribute.rs | 9 ++++---- src/abe_policy/tests.rs | 2 +- src/core/mod.rs | 34 ++++++++++++++++++------------ src/core/primitives.rs | 18 ++++++++-------- src/core/serialization/mod.rs | 12 +++++------ src/core/tests.rs | 32 ++++++++++++++++++++++++++-- 7 files changed, 72 insertions(+), 37 deletions(-) diff --git a/src/abe_policy/access_structure.rs b/src/abe_policy/access_structure.rs index 9241345d..67c66b3e 100644 --- a/src/abe_policy/access_structure.rs +++ b/src/abe_policy/access_structure.rs @@ -335,7 +335,7 @@ fn combine( for (ids, security_mode, is_activated) in partial_combinations.clone() { res.push(( [vec![component.get_id()], ids.clone()].concat(), - security_mode.min(component.get_security_mode()), + security_mode.max(component.get_security_mode()), is_activated | component.get_status(), )); } diff --git a/src/abe_policy/attribute.rs b/src/abe_policy/attribute.rs index 1cac05a4..31443486 100644 --- a/src/abe_policy/attribute.rs +++ b/src/abe_policy/attribute.rs @@ -5,11 +5,10 @@ use serde::{Deserialize, Serialize}; use crate::Error; -#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub enum SecurityMode { - // Due the derivation of `Ord`, definition order matters. Classic, - PostQuantum, + Quantic, Hybridized, } @@ -26,7 +25,7 @@ impl Serializable for SecurityMode { ) -> Result { match self { Self::Classic => ser.write(&0usize), - Self::PostQuantum => ser.write(&1usize), + Self::Quantic => ser.write(&1usize), Self::Hybridized => ser.write(&2usize), } .map_err(Error::from) @@ -36,7 +35,7 @@ impl Serializable for SecurityMode { let status = de.read::()?; match status { 0 => Ok(Self::Classic), - 1 => Ok(Self::PostQuantum), + 1 => Ok(Self::Quantic), 2 => Ok(Self::Hybridized), n => Err(Error::ConversionFailed(format!( "invalid security-mode value: {}", diff --git a/src/abe_policy/tests.rs b/src/abe_policy/tests.rs index 21da7485..55306ad2 100644 --- a/src/abe_policy/tests.rs +++ b/src/abe_policy/tests.rs @@ -16,7 +16,7 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), dimension: "SEC".to_string(), name: "MED".to_string(), }, - SecurityMode::PostQuantum, + SecurityMode::Quantic, Some("LOW"), )?; policy.add_attribute( diff --git a/src/core/mod.rs b/src/core/mod.rs index f45aec4e..356c9bd2 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -61,7 +61,7 @@ enum RightSecretKey { Classic { sk: ::SecretKey, }, - PostQuantum { + Quantic { dk: ::DecapsulationKey, }, Hybridized { @@ -79,9 +79,9 @@ impl RightSecretKey { let sk = ::SecretKey::random(rng); Ok(Self::Classic { sk }) } - SecurityMode::PostQuantum => { + SecurityMode::Quantic => { let (dk, _) = MlKem::keygen(rng)?; - Ok(Self::PostQuantum { dk }) + Ok(Self::Quantic { dk }) } SecurityMode::Hybridized => { let sk = ::SecretKey::random(rng); @@ -99,7 +99,7 @@ impl RightSecretKey { H: h * sk, ek: dk.ek(), }, - Self::PostQuantum { dk } => RightPublicKey::PostQuantum { ek: dk.ek() }, + Self::Quantic { dk } => RightPublicKey::PostQuantum { ek: dk.ek() }, Self::Classic { sk } => RightPublicKey::Classic { H: h * sk }, } } @@ -108,7 +108,7 @@ impl RightSecretKey { fn security_mode(&self) -> SecurityMode { match self { Self::Hybridized { .. } => SecurityMode::Hybridized, - Self::PostQuantum { .. } => SecurityMode::PostQuantum, + Self::Quantic { .. } => SecurityMode::Quantic, Self::Classic { .. } => SecurityMode::Classic, } } @@ -121,18 +121,18 @@ impl RightSecretKey { ) -> Result { Ok(match (self, security_mode) { (Self::Hybridized { sk, .. }, SecurityMode::Classic) => Self::Classic { sk }, - (Self::Hybridized { dk, .. }, SecurityMode::PostQuantum) => Self::PostQuantum { dk }, + (Self::Hybridized { dk, .. }, SecurityMode::Quantic) => Self::Quantic { dk }, (Self::Hybridized { sk, dk }, SecurityMode::Hybridized) => Self::Hybridized { sk, dk }, - (Self::PostQuantum { .. }, SecurityMode::Classic) => Self::PostQuantum { + (Self::Quantic { .. }, SecurityMode::Classic) => Self::Quantic { dk: ::keygen(rng)?.0, }, - (Self::PostQuantum { dk }, SecurityMode::PostQuantum) => Self::PostQuantum { dk }, - (Self::PostQuantum { dk }, SecurityMode::Hybridized) => Self::Hybridized { + (Self::Quantic { dk }, SecurityMode::Quantic) => Self::Quantic { dk }, + (Self::Quantic { dk }, SecurityMode::Hybridized) => Self::Hybridized { sk: ::keygen(rng)?.0, dk, }, (Self::Classic { sk }, SecurityMode::Classic) => Self::Classic { sk }, - (Self::Classic { .. }, SecurityMode::PostQuantum) => Self::PostQuantum { + (Self::Classic { .. }, SecurityMode::Quantic) => Self::Quantic { dk: ::keygen(rng)?.0, }, (Self::Classic { sk }, SecurityMode::Hybridized) => Self::Hybridized { @@ -167,7 +167,7 @@ impl RightPublicKey { pub fn security_mode(&self) -> SecurityMode { match self { Self::Hybridized { .. } => SecurityMode::Hybridized, - Self::PostQuantum { .. } => SecurityMode::PostQuantum, + Self::PostQuantum { .. } => SecurityMode::Quantic, Self::Classic { .. } => SecurityMode::Classic, } } @@ -532,7 +532,7 @@ impl UserSecretKey { #[derive(Debug, Clone, PartialEq)] enum Encapsulations { Hybridized(Vec<(::Encapsulation, [u8; SHARED_SECRET_LENGTH])>), - Quantum(Vec<(::Encapsulation, [u8; SHARED_SECRET_LENGTH])>), + Quantic(Vec<(::Encapsulation, [u8; SHARED_SECRET_LENGTH])>), Classic(Vec<[u8; SHARED_SECRET_LENGTH]>), } @@ -560,8 +560,16 @@ impl XEnc { pub fn count(&self) -> usize { match &self.encapsulations { Encapsulations::Hybridized(vec) => vec.len(), - Encapsulations::Quantum(vec) => vec.len(), + Encapsulations::Quantic(vec) => vec.len(), Encapsulations::Classic(vec) => vec.len(), } } + + pub fn security_mode(&self) -> SecurityMode { + match self.encapsulations { + Encapsulations::Hybridized(_) => SecurityMode::Hybridized, + Encapsulations::Quantic(_) => SecurityMode::Quantic, + Encapsulations::Classic(_) => SecurityMode::Classic, + } + } } diff --git a/src/core/primitives.rs b/src/core/primitives.rs index 7c5920d7..801729a7 100644 --- a/src/core/primitives.rs +++ b/src/core/primitives.rs @@ -288,7 +288,7 @@ fn q_encaps<'a>( XEnc { tag, c, - encapsulations: Encapsulations::Hybridized(encs), + encapsulations: Encapsulations::Quantic(encs), }, )) } @@ -379,7 +379,7 @@ pub fn encaps( }); c_encaps(S, c, r, subkeys) } - SecurityMode::PostQuantum => { + SecurityMode::Quantic => { let subkeys = coordinate_keys.into_iter().map(|subkey| { if let RightPublicKey::PostQuantum { ek } = subkey { ek @@ -447,7 +447,7 @@ fn q_decaps( shuffle(&mut revision, rng); for (E, F) in &encs { for (_, secret) in &revision { - if let RightSecretKey::PostQuantum { dk } = secret { + if let RightSecretKey::Quantic { dk } = secret { let K2 = MlKem::dec(dk, E)?; let S_ij = xor_in_place(H_hash(None, Some(&K2), &T)?, F); let (tag_ij, ss) = J_hash(&S_ij, &U); @@ -579,7 +579,7 @@ fn c_decaps( let sk = match secret { RightSecretKey::Hybridized { sk, .. } => sk, RightSecretKey::Classic { sk } => sk, - RightSecretKey::PostQuantum { .. } => continue, + RightSecretKey::Quantic { .. } => continue, }; let mut K1 = ElGamal::session_key(sk, A)?; let S = xor_in_place(H_hash(Some(&K1), None, &T)?, F); @@ -619,7 +619,7 @@ pub fn decaps( Encapsulations::Hybridized(encs) => { h_decaps(rng, usk, &A, &encapsulation.c, &encapsulation.tag, encs) } - Encapsulations::Quantum(encs) => { + Encapsulations::Quantic(encs) => { q_decaps(rng, usk, &encapsulation.c, &encapsulation.tag, encs) } Encapsulations::Classic(encs) => { @@ -673,7 +673,7 @@ pub fn full_decaps( hasher.update(&*T); match &encapsulation.encapsulations { Encapsulations::Hybridized(encs) => encs.iter().for_each(|(_, F)| hasher.update(F)), - Encapsulations::Quantum(encs) => encs.iter().for_each(|(_, F)| hasher.update(F)), + Encapsulations::Quantic(encs) => encs.iter().for_each(|(_, F)| hasher.update(F)), Encapsulations::Classic(encs) => encs.iter().for_each(|F| hasher.update(F)), } hasher.finalize(&mut *U); @@ -719,12 +719,12 @@ pub fn full_decaps( } } } - Encapsulations::Quantum(encs) => { + Encapsulations::Quantic(encs) => { for (E, F) in encs { for (right, secret_set) in msk.secrets.iter() { for (status, secret) in secret_set { if &EncryptionStatus::EncryptDecrypt == status { - if let RightSecretKey::PostQuantum { dk } = secret { + if let RightSecretKey::Quantic { dk } = secret { let K2 = MlKem::dec(dk, E)?; try_decaps(right, None, Some(K2), F)?; } @@ -741,7 +741,7 @@ pub fn full_decaps( let sk = match secret { RightSecretKey::Hybridized { sk, .. } => sk, RightSecretKey::Classic { sk } => sk, - RightSecretKey::PostQuantum { .. } => continue, + RightSecretKey::Quantic { .. } => continue, }; let K1 = ElGamal::session_key(sk, &A)?; try_decaps(right, Some(K1), None, F)?; diff --git a/src/core/serialization/mod.rs b/src/core/serialization/mod.rs index 94c47789..6837b958 100644 --- a/src/core/serialization/mod.rs +++ b/src/core/serialization/mod.rs @@ -157,14 +157,14 @@ impl Serializable for RightSecretKey { 1 + match self { Self::Hybridized { sk, dk } => sk.length() + dk.length(), Self::Classic { sk } => sk.length(), - Self::PostQuantum { dk } => dk.length(), + Self::Quantic { dk } => dk.length(), } } fn write(&self, ser: &mut Serializer) -> Result { match self { Self::Classic { sk } => Ok(0usize.write(ser)? + sk.write(ser)?), - Self::PostQuantum { dk } => Ok(2usize.write(ser)? + dk.write(ser)?), + Self::Quantic { dk } => Ok(2usize.write(ser)? + dk.write(ser)?), Self::Hybridized { sk, dk } => { Ok(1usize.write(ser)? + sk.write(ser)? + dk.write(ser)?) } @@ -179,7 +179,7 @@ impl Serializable for RightSecretKey { sk: de.read()?, dk: de.read()?, }), - 2 => Ok(Self::PostQuantum { dk: de.read()? }), + 2 => Ok(Self::Quantic { dk: de.read()? }), _ => Err(Error::ConversionFailed(format!( "invalid hybridization flag {mode}" ))), @@ -217,7 +217,7 @@ impl Serializable for Encapsulations { fn length(&self) -> usize { 1 + match self { Encapsulations::Hybridized(vec) => vec.length(), - Encapsulations::Quantum(vec) => vec.length(), + Encapsulations::Quantic(vec) => vec.length(), Encapsulations::Classic(vec) => vec.length(), } } @@ -225,7 +225,7 @@ impl Serializable for Encapsulations { fn write(&self, ser: &mut Serializer) -> Result { match self { Encapsulations::Classic(vec) => Ok(0usize.write(ser)? + vec.write(ser)?), - Encapsulations::Quantum(vec) => Ok(2usize.write(ser)? + vec.write(ser)?), + Encapsulations::Quantic(vec) => Ok(2usize.write(ser)? + vec.write(ser)?), Encapsulations::Hybridized(vec) => Ok(1usize.write(ser)? + vec.write(ser)?), } } @@ -235,7 +235,7 @@ impl Serializable for Encapsulations { match is_hybridized { 0 => Ok(Self::Classic(de.read()?)), 1 => Ok(Self::Hybridized(de.read()?)), - 2 => Ok(Self::Quantum(de.read()?)), + 2 => Ok(Self::Quantic(de.read()?)), n => Err(Error::ConversionFailed(format!( "invalid encapsulation type: {n}" ))), diff --git a/src/core/tests.rs b/src/core/tests.rs index d79500a2..5db0febb 100644 --- a/src/core/tests.rs +++ b/src/core/tests.rs @@ -20,8 +20,8 @@ use super::{ #[test] fn security_mode_ordering() { - assert!(SecurityMode::Classic < SecurityMode::PostQuantum); - assert!(SecurityMode::PostQuantum < SecurityMode::Hybridized); + assert!(SecurityMode::Classic < SecurityMode::Quantic); + assert!(SecurityMode::Quantic < SecurityMode::Hybridized); } /// This test asserts that it is possible to encapsulate a key for a given @@ -299,6 +299,33 @@ fn test_reencrypt_with_msk() { #[test] fn test_covercrypt_kem() { + // Classic encapsulations. + let ap = AccessPolicy::parse("DPT::FIN && SEC::LOW").unwrap(); + let cc = Covercrypt::default(); + let (mut msk, _mpk) = cc_keygen(&cc, false).unwrap(); + let mpk = cc.update_msk(&mut msk).expect("cannot update master keys"); + let usk = cc + .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::Classic); + let res = cc.decaps(&usk, &enc).unwrap(); + assert_eq!(secret, res.unwrap()); + + // Post-quantum encapsulations. + let ap = AccessPolicy::parse("DPT::FIN && SEC::MED").unwrap(); + let cc = Covercrypt::default(); + let (mut msk, _mpk) = cc_keygen(&cc, false).unwrap(); + let mpk = cc.update_msk(&mut msk).expect("cannot update master keys"); + let usk = cc + .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::Quantic); + let res = cc.decaps(&usk, &enc).unwrap(); + assert_eq!(secret, res.unwrap()); + + // Hybridized encapsulation. let ap = AccessPolicy::parse("DPT::FIN && SEC::TOP").unwrap(); let cc = Covercrypt::default(); let (mut msk, _mpk) = cc_keygen(&cc, false).unwrap(); @@ -307,6 +334,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); let res = cc.decaps(&usk, &enc).unwrap(); assert_eq!(secret, res.unwrap()); } From 977aa588bc0778c72865d4c703e369e269a3deaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 22 Oct 2025 19:45:11 +0200 Subject: [PATCH 09/21] correct spelling (quantic -> quantum) --- src/abe_policy/attribute.rs | 6 +++--- src/abe_policy/tests.rs | 2 +- src/core/mod.rs | 28 ++++++++++++++-------------- src/core/primitives.rs | 18 +++++++++--------- src/core/serialization/mod.rs | 12 ++++++------ src/core/tests.rs | 6 +++--- 6 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/abe_policy/attribute.rs b/src/abe_policy/attribute.rs index 31443486..654bcbc1 100644 --- a/src/abe_policy/attribute.rs +++ b/src/abe_policy/attribute.rs @@ -8,7 +8,7 @@ use crate::Error; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub enum SecurityMode { Classic, - Quantic, + Quantum, Hybridized, } @@ -25,7 +25,7 @@ impl Serializable for SecurityMode { ) -> Result { match self { Self::Classic => ser.write(&0usize), - Self::Quantic => ser.write(&1usize), + Self::Quantum => ser.write(&1usize), Self::Hybridized => ser.write(&2usize), } .map_err(Error::from) @@ -35,7 +35,7 @@ impl Serializable for SecurityMode { let status = de.read::()?; match status { 0 => Ok(Self::Classic), - 1 => Ok(Self::Quantic), + 1 => Ok(Self::Quantum), 2 => Ok(Self::Hybridized), n => Err(Error::ConversionFailed(format!( "invalid security-mode value: {}", diff --git a/src/abe_policy/tests.rs b/src/abe_policy/tests.rs index 55306ad2..f2c6929e 100644 --- a/src/abe_policy/tests.rs +++ b/src/abe_policy/tests.rs @@ -16,7 +16,7 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), dimension: "SEC".to_string(), name: "MED".to_string(), }, - SecurityMode::Quantic, + SecurityMode::Quantum, Some("LOW"), )?; policy.add_attribute( diff --git a/src/core/mod.rs b/src/core/mod.rs index 356c9bd2..6ce9bc55 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -61,7 +61,7 @@ enum RightSecretKey { Classic { sk: ::SecretKey, }, - Quantic { + Quantum { dk: ::DecapsulationKey, }, Hybridized { @@ -79,9 +79,9 @@ impl RightSecretKey { let sk = ::SecretKey::random(rng); Ok(Self::Classic { sk }) } - SecurityMode::Quantic => { + SecurityMode::Quantum => { let (dk, _) = MlKem::keygen(rng)?; - Ok(Self::Quantic { dk }) + Ok(Self::Quantum { dk }) } SecurityMode::Hybridized => { let sk = ::SecretKey::random(rng); @@ -99,7 +99,7 @@ impl RightSecretKey { H: h * sk, ek: dk.ek(), }, - Self::Quantic { dk } => RightPublicKey::PostQuantum { ek: dk.ek() }, + Self::Quantum { dk } => RightPublicKey::PostQuantum { ek: dk.ek() }, Self::Classic { sk } => RightPublicKey::Classic { H: h * sk }, } } @@ -108,7 +108,7 @@ impl RightSecretKey { fn security_mode(&self) -> SecurityMode { match self { Self::Hybridized { .. } => SecurityMode::Hybridized, - Self::Quantic { .. } => SecurityMode::Quantic, + Self::Quantum { .. } => SecurityMode::Quantum, Self::Classic { .. } => SecurityMode::Classic, } } @@ -121,18 +121,18 @@ impl RightSecretKey { ) -> Result { Ok(match (self, security_mode) { (Self::Hybridized { sk, .. }, SecurityMode::Classic) => Self::Classic { sk }, - (Self::Hybridized { dk, .. }, SecurityMode::Quantic) => Self::Quantic { dk }, + (Self::Hybridized { dk, .. }, SecurityMode::Quantum) => Self::Quantum { dk }, (Self::Hybridized { sk, dk }, SecurityMode::Hybridized) => Self::Hybridized { sk, dk }, - (Self::Quantic { .. }, SecurityMode::Classic) => Self::Quantic { + (Self::Quantum { .. }, SecurityMode::Classic) => Self::Quantum { dk: ::keygen(rng)?.0, }, - (Self::Quantic { dk }, SecurityMode::Quantic) => Self::Quantic { dk }, - (Self::Quantic { dk }, SecurityMode::Hybridized) => Self::Hybridized { + (Self::Quantum { dk }, SecurityMode::Quantum) => Self::Quantum { dk }, + (Self::Quantum { dk }, SecurityMode::Hybridized) => Self::Hybridized { sk: ::keygen(rng)?.0, dk, }, (Self::Classic { sk }, SecurityMode::Classic) => Self::Classic { sk }, - (Self::Classic { .. }, SecurityMode::Quantic) => Self::Quantic { + (Self::Classic { .. }, SecurityMode::Quantum) => Self::Quantum { dk: ::keygen(rng)?.0, }, (Self::Classic { sk }, SecurityMode::Hybridized) => Self::Hybridized { @@ -167,7 +167,7 @@ impl RightPublicKey { pub fn security_mode(&self) -> SecurityMode { match self { Self::Hybridized { .. } => SecurityMode::Hybridized, - Self::PostQuantum { .. } => SecurityMode::Quantic, + Self::PostQuantum { .. } => SecurityMode::Quantum, Self::Classic { .. } => SecurityMode::Classic, } } @@ -532,7 +532,7 @@ impl UserSecretKey { #[derive(Debug, Clone, PartialEq)] enum Encapsulations { Hybridized(Vec<(::Encapsulation, [u8; SHARED_SECRET_LENGTH])>), - Quantic(Vec<(::Encapsulation, [u8; SHARED_SECRET_LENGTH])>), + Quantum(Vec<(::Encapsulation, [u8; SHARED_SECRET_LENGTH])>), Classic(Vec<[u8; SHARED_SECRET_LENGTH]>), } @@ -560,7 +560,7 @@ impl XEnc { pub fn count(&self) -> usize { match &self.encapsulations { Encapsulations::Hybridized(vec) => vec.len(), - Encapsulations::Quantic(vec) => vec.len(), + Encapsulations::Quantum(vec) => vec.len(), Encapsulations::Classic(vec) => vec.len(), } } @@ -568,7 +568,7 @@ impl XEnc { pub fn security_mode(&self) -> SecurityMode { match self.encapsulations { Encapsulations::Hybridized(_) => SecurityMode::Hybridized, - Encapsulations::Quantic(_) => SecurityMode::Quantic, + Encapsulations::Quantum(_) => SecurityMode::Quantum, Encapsulations::Classic(_) => SecurityMode::Classic, } } diff --git a/src/core/primitives.rs b/src/core/primitives.rs index 801729a7..2a18c30b 100644 --- a/src/core/primitives.rs +++ b/src/core/primitives.rs @@ -288,7 +288,7 @@ fn q_encaps<'a>( XEnc { tag, c, - encapsulations: Encapsulations::Quantic(encs), + encapsulations: Encapsulations::Quantum(encs), }, )) } @@ -379,7 +379,7 @@ pub fn encaps( }); c_encaps(S, c, r, subkeys) } - SecurityMode::Quantic => { + SecurityMode::Quantum => { let subkeys = coordinate_keys.into_iter().map(|subkey| { if let RightPublicKey::PostQuantum { ek } = subkey { ek @@ -447,7 +447,7 @@ fn q_decaps( shuffle(&mut revision, rng); for (E, F) in &encs { for (_, secret) in &revision { - if let RightSecretKey::Quantic { dk } = secret { + if let RightSecretKey::Quantum { dk } = secret { let K2 = MlKem::dec(dk, E)?; let S_ij = xor_in_place(H_hash(None, Some(&K2), &T)?, F); let (tag_ij, ss) = J_hash(&S_ij, &U); @@ -579,7 +579,7 @@ fn c_decaps( let sk = match secret { RightSecretKey::Hybridized { sk, .. } => sk, RightSecretKey::Classic { sk } => sk, - RightSecretKey::Quantic { .. } => continue, + RightSecretKey::Quantum { .. } => continue, }; let mut K1 = ElGamal::session_key(sk, A)?; let S = xor_in_place(H_hash(Some(&K1), None, &T)?, F); @@ -619,7 +619,7 @@ pub fn decaps( Encapsulations::Hybridized(encs) => { h_decaps(rng, usk, &A, &encapsulation.c, &encapsulation.tag, encs) } - Encapsulations::Quantic(encs) => { + Encapsulations::Quantum(encs) => { q_decaps(rng, usk, &encapsulation.c, &encapsulation.tag, encs) } Encapsulations::Classic(encs) => { @@ -673,7 +673,7 @@ pub fn full_decaps( hasher.update(&*T); match &encapsulation.encapsulations { Encapsulations::Hybridized(encs) => encs.iter().for_each(|(_, F)| hasher.update(F)), - Encapsulations::Quantic(encs) => encs.iter().for_each(|(_, F)| hasher.update(F)), + Encapsulations::Quantum(encs) => encs.iter().for_each(|(_, F)| hasher.update(F)), Encapsulations::Classic(encs) => encs.iter().for_each(|F| hasher.update(F)), } hasher.finalize(&mut *U); @@ -719,12 +719,12 @@ pub fn full_decaps( } } } - Encapsulations::Quantic(encs) => { + Encapsulations::Quantum(encs) => { for (E, F) in encs { for (right, secret_set) in msk.secrets.iter() { for (status, secret) in secret_set { if &EncryptionStatus::EncryptDecrypt == status { - if let RightSecretKey::Quantic { dk } = secret { + if let RightSecretKey::Quantum { dk } = secret { let K2 = MlKem::dec(dk, E)?; try_decaps(right, None, Some(K2), F)?; } @@ -741,7 +741,7 @@ pub fn full_decaps( let sk = match secret { RightSecretKey::Hybridized { sk, .. } => sk, RightSecretKey::Classic { sk } => sk, - RightSecretKey::Quantic { .. } => continue, + RightSecretKey::Quantum { .. } => continue, }; let K1 = ElGamal::session_key(sk, &A)?; try_decaps(right, Some(K1), None, F)?; diff --git a/src/core/serialization/mod.rs b/src/core/serialization/mod.rs index 6837b958..66d1989f 100644 --- a/src/core/serialization/mod.rs +++ b/src/core/serialization/mod.rs @@ -157,14 +157,14 @@ impl Serializable for RightSecretKey { 1 + match self { Self::Hybridized { sk, dk } => sk.length() + dk.length(), Self::Classic { sk } => sk.length(), - Self::Quantic { dk } => dk.length(), + Self::Quantum { dk } => dk.length(), } } fn write(&self, ser: &mut Serializer) -> Result { match self { Self::Classic { sk } => Ok(0usize.write(ser)? + sk.write(ser)?), - Self::Quantic { dk } => Ok(2usize.write(ser)? + dk.write(ser)?), + Self::Quantum { dk } => Ok(2usize.write(ser)? + dk.write(ser)?), Self::Hybridized { sk, dk } => { Ok(1usize.write(ser)? + sk.write(ser)? + dk.write(ser)?) } @@ -179,7 +179,7 @@ impl Serializable for RightSecretKey { sk: de.read()?, dk: de.read()?, }), - 2 => Ok(Self::Quantic { dk: de.read()? }), + 2 => Ok(Self::Quantum { dk: de.read()? }), _ => Err(Error::ConversionFailed(format!( "invalid hybridization flag {mode}" ))), @@ -217,7 +217,7 @@ impl Serializable for Encapsulations { fn length(&self) -> usize { 1 + match self { Encapsulations::Hybridized(vec) => vec.length(), - Encapsulations::Quantic(vec) => vec.length(), + Encapsulations::Quantum(vec) => vec.length(), Encapsulations::Classic(vec) => vec.length(), } } @@ -225,7 +225,7 @@ impl Serializable for Encapsulations { fn write(&self, ser: &mut Serializer) -> Result { match self { Encapsulations::Classic(vec) => Ok(0usize.write(ser)? + vec.write(ser)?), - Encapsulations::Quantic(vec) => Ok(2usize.write(ser)? + vec.write(ser)?), + Encapsulations::Quantum(vec) => Ok(2usize.write(ser)? + vec.write(ser)?), Encapsulations::Hybridized(vec) => Ok(1usize.write(ser)? + vec.write(ser)?), } } @@ -235,7 +235,7 @@ impl Serializable for Encapsulations { match is_hybridized { 0 => Ok(Self::Classic(de.read()?)), 1 => Ok(Self::Hybridized(de.read()?)), - 2 => Ok(Self::Quantic(de.read()?)), + 2 => Ok(Self::Quantum(de.read()?)), n => Err(Error::ConversionFailed(format!( "invalid encapsulation type: {n}" ))), diff --git a/src/core/tests.rs b/src/core/tests.rs index 5db0febb..fbf6d0d8 100644 --- a/src/core/tests.rs +++ b/src/core/tests.rs @@ -20,8 +20,8 @@ use super::{ #[test] fn security_mode_ordering() { - assert!(SecurityMode::Classic < SecurityMode::Quantic); - assert!(SecurityMode::Quantic < SecurityMode::Hybridized); + assert!(SecurityMode::Classic < SecurityMode::Quantum); + assert!(SecurityMode::Quantum < SecurityMode::Hybridized); } /// This test asserts that it is possible to encapsulate a key for a given @@ -321,7 +321,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::Quantic); + assert_eq!(enc.security_mode(), SecurityMode::Quantum); let res = cc.decaps(&usk, &enc).unwrap(); assert_eq!(secret, res.unwrap()); From 1432f78b99c1a80973f042f10d46bb130a3be770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 22 Oct 2025 19:57:53 +0200 Subject: [PATCH 10/21] use full names --- src/abe_policy/access_structure.rs | 2 +- src/abe_policy/dimension.rs | 28 +++++++++++++++------------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/abe_policy/access_structure.rs b/src/abe_policy/access_structure.rs index 67c66b3e..a0dee19e 100644 --- a/src/abe_policy/access_structure.rs +++ b/src/abe_policy/access_structure.rs @@ -336,7 +336,7 @@ fn combine( res.push(( [vec![component.get_id()], ids.clone()].concat(), security_mode.max(component.get_security_mode()), - is_activated | component.get_status(), + is_activated | component.get_encryption_status(), )); } } diff --git a/src/abe_policy/dimension.rs b/src/abe_policy/dimension.rs index 755fd089..507c9e64 100644 --- a/src/abe_policy/dimension.rs +++ b/src/abe_policy/dimension.rs @@ -13,16 +13,16 @@ type Name = String; #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] pub struct Attribute { pub(crate) id: usize, - pub(crate) mode: SecurityMode, - pub(crate) status: EncryptionStatus, + pub(crate) security_mode: SecurityMode, + pub(crate) encryption_status: EncryptionStatus, } impl Attribute { pub fn new(encryption_hint: SecurityMode, id: usize) -> Self { Self { id, - mode: encryption_hint, - status: EncryptionStatus::EncryptDecrypt, + security_mode: encryption_hint, + encryption_status: EncryptionStatus::EncryptDecrypt, } } @@ -31,11 +31,11 @@ impl Attribute { } pub fn get_security_mode(&self) -> SecurityMode { - self.mode + self.security_mode } - pub fn get_status(&self) -> EncryptionStatus { - self.status + pub fn get_encryption_status(&self) -> EncryptionStatus { + self.encryption_status } } @@ -185,11 +185,11 @@ impl Dimension { match self { Self::Anarchy(attributes) => attributes .get_mut(name) - .map(|attr| attr.status = EncryptionStatus::DecryptOnly) + .map(|attr| attr.encryption_status = EncryptionStatus::DecryptOnly) .ok_or(Error::AttributeNotFound(name.to_string())), Self::Hierarchy(attributes) => attributes .get_mut(name) - .map(|attr| attr.status = EncryptionStatus::DecryptOnly) + .map(|attr| attr.encryption_status = EncryptionStatus::DecryptOnly) .ok_or(Error::AttributeNotFound(name.to_string())), } } @@ -240,14 +240,16 @@ mod serialization { type Error = Error; fn length(&self) -> usize { - self.id.length() + self.mode.length() + self.status.length() + self.id.length() + self.security_mode.length() + self.encryption_status.length() } fn write( &self, ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, ) -> Result { - Ok(self.id.write(ser)? + self.mode.write(ser)? + self.status.write(ser)?) + Ok(self.id.write(ser)? + + self.security_mode.write(ser)? + + self.encryption_status.write(ser)?) } fn read( @@ -255,8 +257,8 @@ mod serialization { ) -> Result { Ok(Self { id: de.read()?, - mode: de.read()?, - status: de.read()?, + security_mode: de.read()?, + encryption_status: de.read()?, }) } } From ed4c3c1dbb8cb8759dde9e8b7e7a264c7f815ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 22 Oct 2025 20:02:37 +0200 Subject: [PATCH 11/21] remove unused take_latest from revision_map --- src/data_struct/revision_map.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/data_struct/revision_map.rs b/src/data_struct/revision_map.rs index b9448c75..13fc131b 100644 --- a/src/data_struct/revision_map.rs +++ b/src/data_struct/revision_map.rs @@ -125,16 +125,6 @@ where self.map.get_mut(key).and_then(LinkedList::front_mut) } - /// Returns the last revised value for a given key, removing it from the - /// map. - pub fn take_latest(&mut self, key: &Q) -> Option - where - K: Borrow, - Q: Hash + Eq + ?Sized, - { - self.map.get_mut(key).and_then(LinkedList::pop_front) - } - /// Returns true if the given key is bound to some value. pub fn contains_key(&self, key: &K) -> bool { self.map.contains_key(key) From ba397c6b0c8a65f29e82307ccbff29a62135cbc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 22 Oct 2025 20:11:34 +0200 Subject: [PATCH 12/21] add correctness check --- src/core/primitives.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/primitives.rs b/src/core/primitives.rs index 2a18c30b..f792c5ed 100644 --- a/src/core/primitives.rs +++ b/src/core/primitives.rs @@ -91,8 +91,14 @@ fn H_hash( K2: Option<&Secret>, T: &Secret, ) -> Result, Error> { - let mut H = Secret::::new(); + // Additional check to enforce the constraint on the SHARED_SECRET_LENGTH + // constant that is defined in another file. + // + // NOTE: it would be nice to perform this check at compile-time instead. + assert_eq!(SHARED_SECRET_LENGTH, 32); + let mut hasher = Sha3::v256(); + let mut H = Secret::::new(); if let Some(K1) = K1 { hasher.update(&K1.serialize()?); } From e85aa63d808f31deae27eb586fa5161b7423cfaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 22 Oct 2025 20:13:22 +0200 Subject: [PATCH 13/21] correct doc --- src/core/primitives.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/primitives.rs b/src/core/primitives.rs index f792c5ed..f15fa2fd 100644 --- a/src/core/primitives.rs +++ b/src/core/primitives.rs @@ -408,7 +408,7 @@ pub fn encaps( } } -/// Attempts to open the given hybridized encapsulations with this user secret +/// Attempts to open the given post-quantum encapsulations with this user secret /// key. fn q_decaps( rng: &mut impl CryptoRngCore, From 3509bb33d3aa7350be481e78690b179e45ca47cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Thu, 30 Oct 2025 12:22:14 +0100 Subject: [PATCH 14/21] share more code across partial/full decapsulations --- src/core/mod.rs | 60 +-- src/core/primitives.rs | 770 +++++++++++++++++--------------- src/core/serialization/mod.rs | 87 ++-- src/data_struct/revision_map.rs | 2 +- 4 files changed, 489 insertions(+), 430 deletions(-) diff --git a/src/core/mod.rs b/src/core/mod.rs index 6ce9bc55..ef59568a 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -225,10 +225,6 @@ impl TracingSecretKey { self.tracers.len() - 1 } - fn set_traps(&self, r: &::SecretKey) -> Vec<::PublicKey> { - self.tracers.iter().map(|(_, Pi)| Pi * r).collect() - } - /// 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)?); @@ -407,6 +403,10 @@ impl MasterSecretKey { }) } + fn tracing_points(&self) -> impl IntoIterator::PublicKey> { + self.tsk.tracers.iter().map(|(_, P)| P) + } + /// Generates a new MPK holding the latest public information of each right in Omega. pub fn mpk(&self) -> Result { let h = self.tsk.binding_point(); @@ -524,18 +524,11 @@ impl UserSecretKey { self.secrets.len() } - fn set_traps(&self, r: &::SecretKey) -> Vec<::PublicKey> { - self.ps.iter().map(|Pi| Pi * r).collect() + fn tracing_points(&self) -> &[::PublicKey] { + &self.ps } } -#[derive(Debug, Clone, PartialEq)] -enum Encapsulations { - Hybridized(Vec<(::Encapsulation, [u8; SHARED_SECRET_LENGTH])>), - Quantum(Vec<(::Encapsulation, [u8; SHARED_SECRET_LENGTH])>), - Classic(Vec<[u8; SHARED_SECRET_LENGTH]>), -} - /// Covercrypt encapsulation. /// /// It is created for a subset of rights from Omega. @@ -545,31 +538,46 @@ enum Encapsulations { /// - the traps used to select users that can open this encapsulation; /// - the right encapsulations. #[derive(Debug, Clone, PartialEq)] -pub struct XEnc { - tag: Tag, - c: Vec<::PublicKey>, - encapsulations: Encapsulations, +pub enum XEnc { + Classic { + tag: Tag, + c: Vec<::PublicKey>, + encapsulations: Vec<[u8; SHARED_SECRET_LENGTH]>, + }, + Quantum { + tag: Tag, + encapsulations: Vec<(::Encapsulation, [u8; SHARED_SECRET_LENGTH])>, + }, + Hybridized { + tag: Tag, + c: Vec<::PublicKey>, + encapsulations: Vec<(::Encapsulation, [u8; SHARED_SECRET_LENGTH])>, + }, } impl XEnc { /// Returns the tracing level of this encapsulation. pub fn tracing_level(&self) -> usize { - self.c.len() - 1 + match self { + Self::Classic { c, .. } => c.len() - 1, + Self::Quantum { .. } => 0, + Self::Hybridized { c, .. } => c.len() - 1, + } } pub fn count(&self) -> usize { - match &self.encapsulations { - Encapsulations::Hybridized(vec) => vec.len(), - Encapsulations::Quantum(vec) => vec.len(), - Encapsulations::Classic(vec) => vec.len(), + match self { + Self::Hybridized { encapsulations, .. } => encapsulations.len(), + Self::Quantum { encapsulations, .. } => encapsulations.len(), + Self::Classic { encapsulations, .. } => encapsulations.len(), } } pub fn security_mode(&self) -> SecurityMode { - match self.encapsulations { - Encapsulations::Hybridized(_) => SecurityMode::Hybridized, - Encapsulations::Quantum(_) => SecurityMode::Quantum, - Encapsulations::Classic(_) => SecurityMode::Classic, + match self { + Self::Hybridized { .. } => SecurityMode::Hybridized, + Self::Quantum { .. } => SecurityMode::Quantum, + Self::Classic { .. } => SecurityMode::Classic, } } } diff --git a/src/core/primitives.rs b/src/core/primitives.rs index f15fa2fd..7dc548e7 100644 --- a/src/core/primitives.rs +++ b/src/core/primitives.rs @@ -16,9 +16,9 @@ use cosmian_crypto_core::{ use crate::{ abe_policy::{AccessStructure, EncryptionStatus, Right, SecurityMode}, core::{ - kem::MlKem, nike::ElGamal, Encapsulations, KmacSignature, MasterPublicKey, MasterSecretKey, - RightPublicKey, RightSecretKey, TracingSecretKey, UserId, UserSecretKey, XEnc, - MIN_TRACING_LEVEL, SHARED_SECRET_LENGTH, SIGNATURE_LENGTH, SIGNING_KEY_LENGTH, TAG_LENGTH, + 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, }, data_struct::{RevisionMap, RevisionVec}, traits::{Kem, Nike, Sampling}, @@ -127,6 +127,40 @@ fn J_hash( (tag, seed) } +fn generate_T<'a>( + c: Option<&[::PublicKey]>, + encapsulations: Option::Encapsulation>>, +) -> Result, Error> { + let mut hasher = Sha3::v256(); + let mut T = Secret::new(); + if let Some(c) = c { + c.iter().try_for_each(|ck| { + hasher.update(&ck.serialize()?); + Ok::<_, Error>(()) + })?; + } + if let Some(encapsulations) = encapsulations { + encapsulations.into_iter().try_for_each(|E| { + hasher.update(&E.serialize()?); + Ok::<_, Error>(()) + })?; + } + hasher.finalize(&mut *T); + Ok(T) +} + +fn generate_U<'a>( + T: &Secret, + encapsulations: impl IntoIterator, +) -> Secret { + let mut U = Secret::::new(); + let mut hasher = Sha3::v256(); + hasher.update(&**T); + encapsulations.into_iter().for_each(|F| hasher.update(F)); + hasher.finalize(&mut *U); + U +} + /// Generates new MSK with the given tracing level. pub fn setup(tracing_level: usize, rng: &mut impl CryptoRngCore) -> Result { if tracing_level < MIN_TRACING_LEVEL { @@ -197,22 +231,9 @@ fn h_encaps<'a>( }) .collect::, Error>>()?; - let T = { - let mut hasher = Sha3::v256(); - let mut T = Secret::new(); - c.iter().try_for_each(|ck| { - hasher.update(&ck.serialize()?); - Ok::<_, Error>(()) - })?; - encs.iter().try_for_each(|(_, _, E)| { - hasher.update(&E.serialize()?); - Ok::<_, Error>(()) - })?; - hasher.finalize(&mut *T); - T - }; + let T = generate_T(Some(&c), Some(encs.iter().map(|(_, _, E)| E)))?; - let encs = encs + let encapsulations = encs .into_iter() .map(|(mut K1, K2, E)| -> Result<_, _> { let F = xor_2(&S, &*H_hash(Some(&K1), Some(&K2), &T)?); @@ -221,23 +242,16 @@ fn h_encaps<'a>( }) .collect::, Error>>()?; - let U = { - let mut U = Secret::new(); - let mut hasher = Sha3::v256(); - hasher.update(&*T); - encs.iter().for_each(|(_, F)| hasher.update(F)); - hasher.finalize(&mut *U); - U - }; + let U = generate_U(&T, encapsulations.iter().map(|(_, F)| F)); let (tag, ss) = J_hash(&S, &U); Ok(( ss, - XEnc { + XEnc::Hybridized { tag, c, - encapsulations: Encapsulations::Hybridized(encs), + encapsulations, }, )) } @@ -246,7 +260,6 @@ fn h_encaps<'a>( /// marker c, ElGamal random r and subkeys. fn q_encaps<'a>( S: Secret, - c: Vec<::PublicKey>, subkeys: impl IntoIterator::EncapsulationKey>, rng: &mut impl CryptoRngCore, ) -> Result<(Secret, XEnc), Error> { @@ -255,22 +268,9 @@ fn q_encaps<'a>( .map(|ek| MlKem::enc(ek, rng)) .collect::, Error>>()?; - let T = { - let mut hasher = Sha3::v256(); - let mut T = Secret::new(); - c.iter().try_for_each(|ck| { - hasher.update(&ck.serialize()?); - Ok::<_, Error>(()) - })?; - encs.iter().try_for_each(|(_, E)| { - hasher.update(&E.serialize()?); - Ok::<_, Error>(()) - })?; - hasher.finalize(&mut *T); - T - }; + let T = generate_T(None, Some(encs.iter().map(|(_, E)| E)))?; - let encs = encs + let encapsulations = encs .into_iter() .map(|(K2, E)| -> Result<_, _> { let F = xor_2(&S, &*H_hash(None, Some(&K2), &T)?); @@ -278,23 +278,15 @@ fn q_encaps<'a>( }) .collect::, Error>>()?; - let U = { - let mut U = Secret::new(); - let mut hasher = Sha3::v256(); - hasher.update(&*T); - encs.iter().for_each(|(_, F)| hasher.update(F)); - hasher.finalize(&mut *U); - U - }; + let U = generate_U(&T, encapsulations.iter().map(|(_, F)| F)); let (tag, ss) = J_hash(&S, &U); Ok(( ss, - XEnc { + XEnc::Quantum { tag, - c, - encapsulations: Encapsulations::Quantum(encs), + encapsulations, }, )) } @@ -307,19 +299,9 @@ fn c_encaps<'a>( r: ::SecretKey, subkeys: impl IntoIterator::PublicKey>, ) -> Result<(Secret, XEnc), Error> { - // In classic mode, T is only updated with c. - let T = { - let mut hasher = Sha3::v256(); - let mut T = Secret::::new(); - c.iter().try_for_each(|ck| { - hasher.update(&ck.serialize()?); - Ok::<_, Error>(()) - })?; - hasher.finalize(&mut *T); - T - }; + let T = generate_T(Some(&c), None::>)?; - let encs = subkeys + let encapsulations = subkeys .into_iter() .map(|H| -> Result<_, _> { let K1 = ElGamal::session_key(&r, H)?; @@ -328,23 +310,16 @@ fn c_encaps<'a>( }) .collect::, Error>>()?; - let U = { - let mut U = Secret::::new(); - let mut hasher = Sha3::v256(); - hasher.update(&*T); - encs.iter().for_each(|F| hasher.update(F)); - hasher.finalize(&mut *U); - U - }; + let U = generate_U(&T, &encapsulations); let (tag, ss) = J_hash(&S, &U); Ok(( ss, - XEnc { + XEnc::Classic { tag, c, - encapsulations: Encapsulations::Classic(encs), + encapsulations, }, )) } @@ -371,11 +346,12 @@ pub fn encaps( shuffle(&mut coordinate_keys, rng); let S = Secret::random(rng); - let r = G_hash(&S)?; - let c = mpk.set_traps(&r); match is_hybridized { SecurityMode::Classic => { + let r = G_hash(&S)?; + let c = mpk.set_traps(&r); + let subkeys = coordinate_keys.into_iter().map(|subkey| { if let RightPublicKey::Classic { H } = subkey { H @@ -393,9 +369,12 @@ pub fn encaps( panic!("select_subkeys already ensures homogeneity") } }); - q_encaps(S, c, subkeys, rng) + q_encaps(S, subkeys, rng) } SecurityMode::Hybridized => { + let r = G_hash(&S)?; + let c = mpk.set_traps(&r); + let subkeys = coordinate_keys.into_iter().map(|subkey| { if let RightPublicKey::Hybridized { H, ek } = subkey { (H, ek) @@ -408,201 +387,86 @@ pub fn encaps( } } -/// Attempts to open the given post-quantum encapsulations with this user secret -/// key. -fn q_decaps( - rng: &mut impl CryptoRngCore, - usk: &UserSecretKey, +#[allow(clippy::too_many_arguments)] +fn attempt_classic_decaps<'a>( + secret: &RightSecretKey, + A: &::PublicKey, + U: &Secret, + T: &Secret, + F: &[u8; 32], c: &[::PublicKey], tag: &[u8; TAG_LENGTH], - encs: &[(::Encapsulation, [u8; SHARED_SECRET_LENGTH])], -) -> Result>, Error> { - let T = { - let mut hasher = Sha3::v256(); - let mut T = Secret::::new(); - c.iter().try_for_each(|ck| { - hasher.update(&ck.serialize()?); - Ok::<_, Error>(()) - })?; - encs.iter().try_for_each(|(E, _)| { - hasher.update(&E.serialize()?); - Ok::<_, Error>(()) - })?; - hasher.finalize(&mut *T); - T - }; - - let U = { - let mut U = Secret::::new(); - let mut hasher = Sha3::v256(); - hasher.update(&*T); - encs.iter().for_each(|(_, F)| hasher.update(F)); - hasher.finalize(&mut *U); - U - }; - - // Shuffle encapsulation to counter timing attacks attempting to determine - // which right was used to open an encapsulation. - let mut encs = encs.iter().collect::>(); - shuffle(&mut encs, rng); - - // Loop order matters: this ordering is faster. - for mut revision in usk.secrets.revisions() { - // Shuffle secrets to counter timing attacks attempting to determine - // whether successive encapsulations target the same user right. - shuffle(&mut revision, rng); - for (E, F) in &encs { - for (_, secret) in &revision { - if let RightSecretKey::Quantum { dk } = secret { - let K2 = MlKem::dec(dk, E)?; - let S_ij = xor_in_place(H_hash(None, Some(&K2), &T)?, F); - let (tag_ij, ss) = J_hash(&S_ij, &U); - if tag == &tag_ij { - // Fujisaki-Okamoto - let r = G_hash(&S_ij)?; - let c_ij = usk.set_traps(&r); - if c == c_ij { - return Ok(Some(ss)); - } - } - } + tracing_points: impl IntoIterator::PublicKey>, +) -> Result>, Error> { + if let RightSecretKey::Classic { sk } = secret { + let mut K1 = ElGamal::session_key(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 + let r = G_hash(&S)?; + let c_ij = tracing_points + .into_iter() + .map(|P| P * &r) + .collect::>(); + if c == c_ij { + return Ok(Some(ss)); } } } - Ok(None) } -/// Attempts to open the given hybridized encapsulations with this user secret -/// key. -fn h_decaps( - rng: &mut impl CryptoRngCore, - usk: &UserSecretKey, +#[allow(clippy::too_many_arguments)] +fn attempt_hybridized_decaps<'a>( + secret: &RightSecretKey, A: &::PublicKey, + U: &Secret, + T: &Secret, + E: &::Encapsulation, + F: &[u8; 32], c: &[::PublicKey], tag: &[u8; TAG_LENGTH], - encs: &[(::Encapsulation, [u8; SHARED_SECRET_LENGTH])], -) -> Result>, Error> { - let T = { - let mut hasher = Sha3::v256(); - let mut T = Secret::::new(); - c.iter().try_for_each(|ck| { - hasher.update(&ck.serialize()?); - Ok::<_, Error>(()) - })?; - encs.iter().try_for_each(|(E, _)| { - hasher.update(&E.serialize()?); - Ok::<_, Error>(()) - })?; - hasher.finalize(&mut *T); - T - }; - - let U = { - let mut U = Secret::::new(); - let mut hasher = Sha3::v256(); - hasher.update(&*T); - encs.iter().for_each(|(_, F)| hasher.update(F)); - hasher.finalize(&mut *U); - U - }; - - // Shuffle encapsulation to counter timing attacks attempting to determine - // which right was used to open an encapsulation. - let mut encs = encs.iter().collect::>(); - shuffle(&mut encs, rng); - - // Loop order matters: this ordering is faster. - for mut revision in usk.secrets.revisions() { - // Shuffle secrets to counter timing attacks attempting to determine - // whether successive encapsulations target the same user right. - shuffle(&mut revision, rng); - for (E, F) in &encs { - for (_, secret) in &revision { - if let RightSecretKey::Hybridized { sk, dk } = secret { - let mut K1 = ElGamal::session_key(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); - if tag == &tag_ij { - // Fujisaki-Okamoto - let r = G_hash(&S_ij)?; - let c_ij = usk.set_traps(&r); - if c == c_ij { - K1.zeroize(); - return Ok(Some(ss)); - } - } - } + tracing_points: impl IntoIterator::PublicKey>, +) -> Result>, Error> { + if let RightSecretKey::Hybridized { sk, dk } = secret { + let mut K1 = ElGamal::session_key(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); + if tag == &tag_ij { + // Fujisaki-Okamoto + let r = G_hash(&S_ij)?; + let c_ij = tracing_points + .into_iter() + .map(|P| P * &r) + .collect::>(); + if c == c_ij { + K1.zeroize(); + return Ok(Some(ss)); } } } - Ok(None) } -/// Attempts to open the given classic encapsulations with this user secret key. -fn c_decaps( - rng: &mut impl CryptoRngCore, - usk: &UserSecretKey, - A: &::PublicKey, - c: &[::PublicKey], +fn attempt_quantum_decaps( + secret: &RightSecretKey, + U: &Secret, + T: &Secret, + E: &::Encapsulation, + F: &[u8; 32], tag: &[u8; TAG_LENGTH], - encs: &Vec<[u8; SHARED_SECRET_LENGTH]>, -) -> Result>, Error> { - let T = { - let mut hasher = Sha3::v256(); - let mut T = Secret::::new(); - c.iter().try_for_each(|ck| { - hasher.update(&ck.serialize()?); - Ok::<_, Error>(()) - })?; - hasher.finalize(&mut *T); - T - }; - - let U = { - let mut U = Secret::::new(); - let mut hasher = Sha3::v256(); - hasher.update(&*T); - encs.iter().for_each(|F| hasher.update(F)); - hasher.finalize(&mut *U); - U - }; - - // Shuffle encapsulation to counter timing attacks attempting to determine - // which right was used to open an encapsulation. - let mut encs = encs.iter().collect::>(); - shuffle(&mut encs, rng); - - // Loop order matters: this ordering is faster. - for mut revision in usk.secrets.revisions() { - // Shuffle secrets to counter timing attacks attempting to determine - // whether successive encapsulations target the same user right. - shuffle(&mut revision, rng); - for F in &encs { - for (_, secret) in &revision { - let sk = match secret { - RightSecretKey::Hybridized { sk, .. } => sk, - RightSecretKey::Classic { sk } => sk, - RightSecretKey::Quantum { .. } => continue, - }; - let mut K1 = ElGamal::session_key(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 - let r = G_hash(&S)?; - let c_ij = usk.set_traps(&r); - if c == c_ij { - return Ok(Some(ss)); - } - } - } +) -> Result>, Error> { + if let RightSecretKey::Quantum { dk } = secret { + let K2 = MlKem::dec(dk, E)?; + let S_ij = xor_in_place(H_hash(None, Some(&K2), T)?, F); + let (tag_ij, ss) = J_hash(&S_ij, U); + if tag == &tag_ij { + return Ok(Some(ss)); } } - Ok(None) } @@ -613,24 +477,141 @@ pub fn decaps( usk: &UserSecretKey, encapsulation: &XEnc, ) -> Result>, Error> { - // A = ⊙ _i (α_i. c_i) - let A = usk - .id - .iter() - .zip(encapsulation.c.iter()) - .map(|(marker, trap)| trap * marker) - .sum(); - - match &encapsulation.encapsulations { - Encapsulations::Hybridized(encs) => { - h_decaps(rng, usk, &A, &encapsulation.c, &encapsulation.tag, encs) + fn generate_tracing_closure( + usk: &UserSecretKey, + c: &[::PublicKey], + ) -> ::PublicKey { + usk.id + .iter() + .zip(c.iter()) + .map(|(marker, trap)| trap * marker) + .sum::<::PublicKey>() + } + + fn partial_quantum_decaps( + rng: &mut impl CryptoRngCore, + usk: &UserSecretKey, + tag: &[u8; TAG_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)); + + // Shuffle encapsulation to counter timing attacks attempting to determine + // which right was used to open an encapsulation. + let mut encs = encs.iter().collect::>(); + shuffle(&mut encs, rng); + + // Loop order matters: this ordering is faster. + for mut revision in usk.secrets.revisions() { + // Shuffle secrets to counter timing attacks attempting to determine + // whether successive encapsulations target the same user right. + shuffle(&mut revision, rng); + for (E, F) in &encs { + for (_, secret) in &revision { + if let Some(ss) = attempt_quantum_decaps(secret, &U, &T, E, F, tag)? { + return Ok(Some(ss)); + } + } + } } - Encapsulations::Quantum(encs) => { - q_decaps(rng, usk, &encapsulation.c, &encapsulation.tag, encs) + + Ok(None) + } + + fn partial_hybridized_decaps( + rng: &mut impl CryptoRngCore, + usk: &UserSecretKey, + c: &[::PublicKey], + tag: &[u8; TAG_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)))?; + let U = generate_U(&T, encs.iter().map(|(_, F)| F)); + + // Shuffle encapsulation to counter timing attacks attempting to determine + // which right was used to open an encapsulation. + let mut encs = encs.iter().collect::>(); + shuffle(&mut encs, rng); + + // Loop order matters: this ordering is faster. + for mut revision in usk.secrets.revisions() { + // Shuffle secrets to counter timing attacks attempting to determine + // whether successive encapsulations target the same user right. + shuffle(&mut revision, rng); + for (E, F) in &encs { + for (_, secret) in &revision { + if let Some(ss) = attempt_hybridized_decaps( + secret, + &A, + &U, + &T, + E, + F, + c, + tag, + usk.tracing_points(), + )? { + return Ok(Some(ss)); + } + } + } } - Encapsulations::Classic(encs) => { - c_decaps(rng, usk, &A, &encapsulation.c, &encapsulation.tag, encs) + + Ok(None) + } + + fn partial_classic_decaps( + rng: &mut impl CryptoRngCore, + usk: &UserSecretKey, + c: &[::PublicKey], + tag: &[u8; TAG_LENGTH], + encs: &Vec<[u8; SHARED_SECRET_LENGTH]>, + ) -> Result>, Error> { + let A = generate_tracing_closure(usk, c); + let T = generate_T(Some(c), None::>)?; + let U = generate_U(&T, encs); + + // Shuffle encapsulations to counter timing attacks attempting to determine + // which right was used to open an encapsulation. + let mut encs = encs.iter().collect::>(); + shuffle(&mut encs, rng); + + // Loop order matters: this ordering is faster. + for mut revision in usk.secrets.revisions() { + // Shuffle secrets to counter timing attacks attempting to determine + // whether successive encapsulations target the same user right. + shuffle(&mut revision, rng); + for F in &encs { + for (_, secret) in &revision { + if let Some(ss) = + attempt_classic_decaps(secret, &A, &U, &T, F, c, tag, usk.tracing_points())? + { + return Ok(Some(ss)); + } + } + } } + + Ok(None) + } + + match encapsulation { + XEnc::Hybridized { + tag, + c, + encapsulations, + } => partial_hybridized_decaps(rng, usk, c, tag, encapsulations), + XEnc::Quantum { + tag, + encapsulations, + } => partial_quantum_decaps(rng, usk, tag, encapsulations), + XEnc::Classic { + tag, + c, + encapsulations, + } => partial_classic_decaps(rng, usk, c, tag, encapsulations), } } @@ -640,9 +621,29 @@ pub fn full_decaps( msk: &MasterSecretKey, encapsulation: &XEnc, ) -> Result<(Secret, HashSet), Error> { - let A = { - let c_0 = encapsulation - .c + /// Opens the given encapsulation with the provided secrets. Returns both + /// the encapsulated secret and the right associated to the first secret + /// allowing opening this encapsulation, or returns an error if no secret + /// allow opening this encapsulation. + fn open( + secrets: &RevisionMap, + attempt_opening: impl Fn(&RightSecretKey) -> Result>, Error>, + ) -> Result<(Secret, Right), Error> { + for (right, secret_set) in secrets.iter() { + for (_, secret) in secret_set { + if let Some(ss) = attempt_opening(secret)? { + return Ok::<_, Error>((ss, right.clone())); + } + } + } + Err(Error::Kem("could not open the encapsulation".to_string())) + } + + fn generate_tracing_closure( + msk: &MasterSecretKey, + c: &[::PublicKey], + ) -> Result<::PublicKey, Error> { + let c_0 = c .first() .ok_or_else(|| Error::Kem("invalid encapsulation: C is empty".to_string()))?; let t_0 = msk @@ -652,115 +653,146 @@ pub fn full_decaps( .map(|(si, _)| si) .ok_or_else(|| Error::KeyError("MSK has no tracer".to_string()))?; - c_0 * &(&msk.tsk.s / t_0)? - }; - - let T = { - let mut hasher = Sha3::v256(); - let mut T = Secret::::new(); - encapsulation.c.iter().try_for_each(|ck| { - hasher.update(&ck.serialize()?); - Ok::<_, Error>(()) - })?; - - if let Encapsulations::Hybridized(encs) = &encapsulation.encapsulations { - encs.iter().try_for_each(|(E, _)| { - hasher.update(&E.serialize()?); - Ok::<_, Error>(()) - })?; - } - hasher.finalize(&mut *T); - T - }; - - let U = { - let mut U = Secret::::new(); - let mut hasher = Sha3::v256(); - hasher.update(&*T); - match &encapsulation.encapsulations { - Encapsulations::Hybridized(encs) => encs.iter().for_each(|(_, F)| hasher.update(F)), - Encapsulations::Quantum(encs) => encs.iter().for_each(|(_, F)| hasher.update(F)), - Encapsulations::Classic(encs) => encs.iter().for_each(|F| hasher.update(F)), - } - hasher.finalize(&mut *U); - U - }; + Ok(c_0 * &(&msk.tsk.s / t_0)?) + } - let mut enc_ss = None; - let mut rights = HashSet::with_capacity(encapsulation.count()); - let mut try_decaps = |right: &Right, - K1: Option<::PublicKey>, - K2: Option>, - F| { - let S_ij = xor_in_place(H_hash(K1.as_ref(), K2.as_ref(), &T)?, F); - let (tag_ij, ss) = J_hash(&S_ij, &U); - if encapsulation.tag == tag_ij { - // Fujisaki-Okamoto - let r = G_hash(&S_ij)?; - let c_ij = msk.tsk.set_traps(&r); - if encapsulation.c == c_ij { - if let Some(mut K1) = K1 { - K1.zeroize(); + fn full_classic_decapsulation( + msk: &MasterSecretKey, + tag: &[u8; TAG_LENGTH], + c: &[::PublicKey], + encapsulations: &[[u8; 32]], + ) -> Result<(Secret, HashSet), Error> { + let A = generate_tracing_closure(msk, c)?; + let T = generate_T(Some(c), None::>)?; + let U = generate_U(&T, encapsulations); + + // Attempts opening the encapsulation F with this right secret key. + let attempt_opening = |F, secret: &RightSecretKey| { + attempt_classic_decaps(secret, &A, &U, &T, F, c, tag, msk.tracing_points()) + }; + + let mut enc_ss = None; + let mut rights = HashSet::with_capacity(encapsulations.len()); + let mut secrets = msk.secrets.clone(); + + for F in encapsulations { + let (ss, right) = open(&secrets, |secret| attempt_opening(F, secret))?; + if let Some(enc_ss) = &enc_ss { + if &ss != enc_ss { + return Err(Error::Kem( + "malformed encapsulation: different encapsulated secrets found".to_string(), + )); } - enc_ss = Some(ss); - rights.insert(right.clone()); } + // Removes this right since well-formed encapsulations use rights + // only once. This should allow a ~2x speed-up. + secrets.remove(&right); + enc_ss = Some(ss); + rights.insert(right); } - Ok::<_, Error>(()) - }; - match &encapsulation.encapsulations { - Encapsulations::Hybridized(encs) => { - for (E, F) in encs { - for (right, secret_set) in msk.secrets.iter() { - for (status, secret) in secret_set { - if &EncryptionStatus::EncryptDecrypt == status { - if let RightSecretKey::Hybridized { sk, dk } = secret { - let K1 = ElGamal::session_key(sk, &A)?; - let K2 = MlKem::dec(dk, E)?; - try_decaps(right, Some(K1), Some(K2), F)?; - } - } - } - } - } - } - Encapsulations::Quantum(encs) => { - for (E, F) in encs { - for (right, secret_set) in msk.secrets.iter() { - for (status, secret) in secret_set { - if &EncryptionStatus::EncryptDecrypt == status { - if let RightSecretKey::Quantum { dk } = secret { - let K2 = MlKem::dec(dk, E)?; - try_decaps(right, None, Some(K2), F)?; - } - } - } + enc_ss + .map(|ss| (ss, rights)) + // An empty encapsulation should be the only way to raise this error + // since the function `open` either errors upon failure to open. + .ok_or_else(|| Error::Kem("empty encapsulation".to_string())) + } + + fn full_quantum_decapsulation( + msk: &MasterSecretKey, + tag: &[u8; TAG_LENGTH], + encapsulations: &[(::Encapsulation, [u8; SHARED_SECRET_LENGTH])], + ) -> 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)); + + let attempt_opening = + |E, F, secret: &RightSecretKey| attempt_quantum_decaps(secret, &U, &T, E, F, tag); + + let mut enc_ss = None; + let mut rights = HashSet::with_capacity(encapsulations.len()); + let mut secrets = msk.secrets.clone(); + + for (E, F) in encapsulations { + let (ss, right) = open(&secrets, |secret| attempt_opening(E, F, secret))?; + if let Some(enc_ss) = &enc_ss { + if &ss != enc_ss { + return Err(Error::Kem( + "malformed encapsulation: different encapsulated secrets found".to_string(), + )); } } + // Removes this right since well-formed encapsulations use rights + // only once. This should allow a ~2x speed-up. + secrets.remove(&right); + enc_ss = Some(ss); + rights.insert(right); } - Encapsulations::Classic(encs) => { - for F in encs { - for (right, secret_set) in msk.secrets.iter() { - for (status, secret) in secret_set { - if &EncryptionStatus::EncryptDecrypt == status { - let sk = match secret { - RightSecretKey::Hybridized { sk, .. } => sk, - RightSecretKey::Classic { sk } => sk, - RightSecretKey::Quantum { .. } => continue, - }; - let K1 = ElGamal::session_key(sk, &A)?; - try_decaps(right, Some(K1), None, F)?; - } - } + + enc_ss + .map(|ss| (ss, rights)) + // An empty encapsulation should be the only way to raise this error + // since the function `open` either errors upon failure to open. + .ok_or_else(|| Error::Kem("empty encapsulation".to_string())) + } + + fn full_hybrid_decapsulation( + msk: &MasterSecretKey, + tag: &[u8; TAG_LENGTH], + c: &[::PublicKey], + encapsulations: &[(::Encapsulation, [u8; SHARED_SECRET_LENGTH])], + ) -> Result<(Secret, HashSet), Error> { + let A = generate_tracing_closure(msk, c)?; + let T = generate_T(Some(c), Some(encapsulations.iter().map(|(E, _)| E)))?; + let U = generate_U(&T, encapsulations.iter().map(|(_, F)| F)); + + let attempt_opening = |E, F, secret: &RightSecretKey| { + attempt_hybridized_decaps(secret, &A, &U, &T, E, F, c, tag, msk.tracing_points()) + }; + + let mut enc_ss = None; + let mut rights = HashSet::with_capacity(encapsulations.len()); + let mut secrets = msk.secrets.clone(); + + for (E, F) in encapsulations { + let (ss, right) = open(&secrets, |secret| attempt_opening(E, F, secret))?; + if let Some(enc_ss) = &enc_ss { + if &ss != enc_ss { + return Err(Error::Kem( + "malformed encapsulation: different encapsulated secrets found".to_string(), + )); } } + // Removes this right since well-formed encapsulations use rights + // only once. This should allow a ~2x speed-up. + secrets.remove(&right); + enc_ss = Some(ss); + rights.insert(right); } + + enc_ss + .map(|ss| (ss, rights)) + // An empty encapsulation should be the only way to raise this error + // since the function `open` either errors upon failure to open. + .ok_or_else(|| Error::Kem("empty encapsulation".to_string())) } - enc_ss - .map(|ss| (ss, rights)) - .ok_or_else(|| Error::Kem("could not open the encapsulation".to_string())) + match encapsulation { + XEnc::Classic { + tag, + c, + encapsulations, + } => full_classic_decapsulation(msk, tag, c, encapsulations), + XEnc::Quantum { + tag, + encapsulations, + } => full_quantum_decapsulation(msk, tag, encapsulations), + XEnc::Hybridized { + tag, + c, + encapsulations, + } => full_hybrid_decapsulation(msk, tag, c, encapsulations), + } } /// Updates the MSK such that it has at least one secret per right given, and no diff --git a/src/core/serialization/mod.rs b/src/core/serialization/mod.rs index 66d1989f..2484383c 100644 --- a/src/core/serialization/mod.rs +++ b/src/core/serialization/mod.rs @@ -4,8 +4,8 @@ use cosmian_crypto_core::bytes_ser_de::{Deserializer, Serializable, Serializer}; use crate::{ core::{ - Encapsulations, MasterPublicKey, MasterSecretKey, RightPublicKey, RightSecretKey, - TracingPublicKey, TracingSecretKey, UserId, UserSecretKey, XEnc, + MasterPublicKey, MasterSecretKey, RightPublicKey, RightSecretKey, TracingPublicKey, + TracingSecretKey, UserId, UserSecretKey, XEnc, }, Error, }; @@ -211,31 +211,70 @@ impl Serializable for UserSecretKey { } } -impl Serializable for Encapsulations { +impl Serializable for XEnc { type Error = Error; fn length(&self) -> usize { 1 + match self { - Encapsulations::Hybridized(vec) => vec.length(), - Encapsulations::Quantum(vec) => vec.length(), - Encapsulations::Classic(vec) => vec.length(), + Self::Classic { + tag, + c, + encapsulations, + } => tag.length() + c.length() + encapsulations.length(), + Self::Quantum { + tag, + encapsulations, + } => tag.length() + encapsulations.length(), + Self::Hybridized { + tag, + c, + encapsulations, + } => tag.length() + c.length() + encapsulations.length(), } } fn write(&self, ser: &mut Serializer) -> Result { match self { - Encapsulations::Classic(vec) => Ok(0usize.write(ser)? + vec.write(ser)?), - Encapsulations::Quantum(vec) => Ok(2usize.write(ser)? + vec.write(ser)?), - Encapsulations::Hybridized(vec) => Ok(1usize.write(ser)? + vec.write(ser)?), + XEnc::Classic { + tag, + c, + encapsulations, + } => Ok(0usize.write(ser)? + + tag.write(ser)? + + c.write(ser)? + + encapsulations.write(ser)?), + XEnc::Quantum { + tag, + encapsulations, + } => Ok(1usize.write(ser)? + tag.write(ser)? + encapsulations.write(ser)?), + XEnc::Hybridized { + tag, + c, + encapsulations, + } => Ok(2usize.write(ser)? + + tag.write(ser)? + + c.write(ser)? + + encapsulations.write(ser)?), } } fn read(de: &mut Deserializer) -> Result { - let is_hybridized = de.read_leb128_u64()?; - match is_hybridized { - 0 => Ok(Self::Classic(de.read()?)), - 1 => Ok(Self::Hybridized(de.read()?)), - 2 => Ok(Self::Quantum(de.read()?)), + let mode = usize::read(de)?; + match mode { + 0 => Ok(Self::Classic { + tag: de.read()?, + c: de.read()?, + encapsulations: de.read()?, + }), + 1 => Ok(Self::Quantum { + tag: de.read()?, + encapsulations: de.read()?, + }), + 2 => Ok(Self::Hybridized { + tag: de.read()?, + c: de.read()?, + encapsulations: de.read()?, + }), n => Err(Error::ConversionFailed(format!( "invalid encapsulation type: {n}" ))), @@ -243,26 +282,6 @@ impl Serializable for Encapsulations { } } -impl Serializable for XEnc { - type Error = Error; - - fn length(&self) -> usize { - self.tag.length() + self.c.length() + self.encapsulations.length() - } - - fn write(&self, ser: &mut Serializer) -> Result { - Ok(self.tag.write(ser)? + self.c.write(ser)? + self.encapsulations.write(ser)?) - } - - fn read(de: &mut Deserializer) -> Result { - Ok(Self { - tag: de.read()?, - c: de.read()?, - encapsulations: de.read()?, - }) - } -} - #[cfg(test)] mod tests { use std::collections::{HashMap, HashSet}; diff --git a/src/data_struct/revision_map.rs b/src/data_struct/revision_map.rs index 13fc131b..72d71c97 100644 --- a/src/data_struct/revision_map.rs +++ b/src/data_struct/revision_map.rs @@ -26,7 +26,7 @@ use crate::Error; /// Deletions can only happen at the end of the linked list. /// /// This guarantees that the entry versions are always ordered. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct RevisionMap where K: Debug + PartialEq + Eq + Hash, From 323af14cf3187a112f6bf1452a59c8f5cad8745b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Thu, 30 Oct 2025 13:57:18 +0100 Subject: [PATCH 15/21] use CryptoCore version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b932d569..13b6e71f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ curve25519 = ["cosmian_crypto_core/curve25519"] test-utils = [] [dependencies] -cosmian_crypto_core = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/impl-serializable-for-u8-array", default-features = false, features = [ +cosmian_crypto_core = { version = "10.3", default-features = false, features = [ "aes", "ser", "sha3" From 59a38415b2a934a124b7fe2ca97ab14e37e40fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Mon, 3 Nov 2025 09:51:30 +0100 Subject: [PATCH 16/21] fix Cargo clippy --- Cargo.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 13b6e71f..ce0c398f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,14 +51,14 @@ cosmian_crypto_core = { version = "10.3", default-features = false, features = [ "ser", "sha3" ] } -elliptic-curve = { version = "0.13.8", optional = true } -ml-kem = { version = "0.2.1", features = ["zeroize"] } -p256 = { version = "0.13.2", optional = true } +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.1", optional = true } +subtle = { version = "2.6", optional = true } [dev-dependencies] -criterion = { version = "0.5", features = [ +criterion = { version = "0.7", features = [ "html_reports", ], default-features = false } From 098eea80073593d3e8c764620a353074c67b7250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Mon, 3 Nov 2025 09:58:36 +0100 Subject: [PATCH 17/21] remove a clone and fix a clippy lint --- src/abe_policy/access_structure.rs | 2 +- src/abe_policy/attribute.rs | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/abe_policy/access_structure.rs b/src/abe_policy/access_structure.rs index a0dee19e..72a3dbae 100644 --- a/src/abe_policy/access_structure.rs +++ b/src/abe_policy/access_structure.rs @@ -334,7 +334,7 @@ fn combine( for component in current_dimension.attributes() { for (ids, security_mode, is_activated) in partial_combinations.clone() { res.push(( - [vec![component.get_id()], ids.clone()].concat(), + [vec![component.get_id()], ids].concat(), security_mode.max(component.get_security_mode()), is_activated | component.get_encryption_status(), )); diff --git a/src/abe_policy/attribute.rs b/src/abe_policy/attribute.rs index 654bcbc1..208c00be 100644 --- a/src/abe_policy/attribute.rs +++ b/src/abe_policy/attribute.rs @@ -47,18 +47,12 @@ impl Serializable for SecurityMode { /// Whether to provide an encryption key in the master public key for this /// attribute. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] pub enum EncryptionStatus { - EncryptDecrypt, + #[default] EncryptDecrypt, DecryptOnly, } -impl Default for EncryptionStatus { - fn default() -> Self { - Self::EncryptDecrypt - } -} - impl BitOr for EncryptionStatus { type Output = Self; From 23dd351c86b5319e7f245c212c49413de69bcf09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Mon, 3 Nov 2025 10:07:43 +0100 Subject: [PATCH 18/21] fix formatting --- src/abe_policy/attribute.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/abe_policy/attribute.rs b/src/abe_policy/attribute.rs index 208c00be..4cebe2ed 100644 --- a/src/abe_policy/attribute.rs +++ b/src/abe_policy/attribute.rs @@ -49,7 +49,8 @@ impl Serializable for SecurityMode { /// attribute. #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] pub enum EncryptionStatus { - #[default] EncryptDecrypt, + #[default] + EncryptDecrypt, DecryptOnly, } From 09c4ac798fa93bfefebbe2a1d329a49d3453fdf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Fri, 7 Nov 2025 15:06:31 +0100 Subject: [PATCH 19/21] renamings --- benches/benches.rs | 10 ++-- src/abe_policy/access_structure.rs | 20 ++++---- src/abe_policy/attribute.rs | 18 ++++---- src/abe_policy/dimension.rs | 16 +++---- src/abe_policy/tests.rs | 48 ++++++++++---------- src/core/mod.rs | 58 ++++++++++++------------ src/core/primitives.rs | 73 ++++++++++++++++-------------- src/core/serialization/mod.rs | 30 ++++++------ src/core/tests.rs | 22 ++++----- src/test_utils/mod.rs | 2 +- 10 files changed, 151 insertions(+), 146 deletions(-) diff --git a/benches/benches.rs b/benches/benches.rs index f0eaf5a7..1b949bb0 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -83,12 +83,12 @@ macro_rules! gen_usk { }}; } -fn bench_classical_encapsulation(c: &mut Criterion) { +fn bench_pre_quantum_encapsulation(c: &mut Criterion) { let cc = Covercrypt::default(); let (_, mpk) = cc_keygen(&cc, true).unwrap(); { - let mut group = c.benchmark_group("Classic encapsulation"); + let mut group = c.benchmark_group("Pre-quantum encapsulation"); for (enc_ap, cnt_enc) in C_ENC_APS { let _ = gen_enc!(cc, mpk, enc_ap, cnt_enc); let eap = AccessPolicy::parse(enc_ap).unwrap(); @@ -99,7 +99,7 @@ fn bench_classical_encapsulation(c: &mut Criterion) { } } -fn bench_classical_decapsulation(c: &mut Criterion) { +fn bench_pre_quantum_decapsulation(c: &mut Criterion) { let cc = Covercrypt::default(); let (mut msk, mpk) = cc_keygen(&cc, true).unwrap(); @@ -188,8 +188,8 @@ criterion_group!( name = benches; config = Criterion::default().sample_size(5000); targets = - bench_classical_encapsulation, - bench_classical_decapsulation, + bench_pre_quantum_encapsulation, + bench_pre_quantum_decapsulation, bench_hybridized_encapsulation, bench_hybridized_decapsulation ); diff --git a/src/abe_policy/access_structure.rs b/src/abe_policy/access_structure.rs index 72a3dbae..b206eafe 100644 --- a/src/abe_policy/access_structure.rs +++ b/src/abe_policy/access_structure.rs @@ -174,9 +174,7 @@ impl AccessStructure { let universe = self.dimensions.iter().collect::>(); combine(universe.as_slice()) .into_iter() - .map(|(ids, is_hybridized, is_readonly)| { - Right::from_point(ids).map(|r| (r, (is_hybridized, is_readonly))) - }) + .map(|(ids, mode, status)| Right::from_point(ids).map(|r| (r, (mode, status)))) .collect() } } @@ -324,7 +322,7 @@ fn combine( if dimensions.is_empty() { vec![( vec![], - SecurityMode::Classic, + SecurityMode::PreQuantum, EncryptionStatus::EncryptDecrypt, )] } else { @@ -332,11 +330,11 @@ fn combine( let partial_combinations = combine(&dimensions[1..]); let mut res = vec![]; for component in current_dimension.attributes() { - for (ids, security_mode, is_activated) in partial_combinations.clone() { + for (ids, security_mode, encryption_status) in partial_combinations.clone() { res.push(( [vec![component.get_id()], ids].concat(), security_mode.max(component.get_security_mode()), - is_activated | component.get_encryption_status(), + encryption_status | component.get_encryption_status(), )); } } @@ -410,13 +408,13 @@ mod tests { structure.add_anarchy("Country".to_string()).unwrap(); [ - ("France", SecurityMode::Classic), - ("Germany", SecurityMode::Classic), - ("Spain", SecurityMode::Classic), + ("France", SecurityMode::PreQuantum), + ("Germany", SecurityMode::PreQuantum), + ("Spain", SecurityMode::PreQuantum), ] .into_iter() - .try_for_each(|(attribute, hint)| { - structure.add_attribute(QualifiedAttribute::new("Country", attribute), hint, None) + .try_for_each(|(attribute, mode)| { + structure.add_attribute(QualifiedAttribute::new("Country", attribute), mode, None) }) .unwrap(); diff --git a/src/abe_policy/attribute.rs b/src/abe_policy/attribute.rs index 4cebe2ed..6f8740d2 100644 --- a/src/abe_policy/attribute.rs +++ b/src/abe_policy/attribute.rs @@ -7,8 +7,8 @@ use crate::Error; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub enum SecurityMode { - Classic, - Quantum, + PreQuantum, + PostQuantum, Hybridized, } @@ -24,9 +24,9 @@ impl Serializable for SecurityMode { ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, ) -> Result { match self { - Self::Classic => ser.write(&0usize), - Self::Quantum => ser.write(&1usize), - Self::Hybridized => ser.write(&2usize), + Self::PreQuantum => ser.write(&0usize), + Self::Hybridized => ser.write(&1usize), + Self::PostQuantum => ser.write(&2usize), } .map_err(Error::from) } @@ -34,11 +34,11 @@ impl Serializable for SecurityMode { fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { let status = de.read::()?; match status { - 0 => Ok(Self::Classic), - 1 => Ok(Self::Quantum), - 2 => Ok(Self::Hybridized), + 0 => Ok(Self::PreQuantum), + 1 => Ok(Self::Hybridized), + 2 => Ok(Self::PostQuantum), n => Err(Error::ConversionFailed(format!( - "invalid security-mode value: {}", + "invalid security mode: {}", n ))), } diff --git a/src/abe_policy/dimension.rs b/src/abe_policy/dimension.rs index 507c9e64..a0d81e52 100644 --- a/src/abe_policy/dimension.rs +++ b/src/abe_policy/dimension.rs @@ -18,10 +18,10 @@ pub struct Attribute { } impl Attribute { - pub fn new(encryption_hint: SecurityMode, id: usize) -> Self { + pub fn new(security_mode: SecurityMode, id: usize) -> Self { Self { id, - security_mode: encryption_hint, + security_mode, encryption_status: EncryptionStatus::EncryptDecrypt, } } @@ -106,14 +106,14 @@ impl Dimension { pub fn add_attribute( &mut self, attribute: Name, - hint: SecurityMode, + security_mode: SecurityMode, after: Option<&str>, id: usize, ) -> Result<(), Error> { match self { Self::Anarchy(attributes) => { if let Entry::Vacant(entry) = attributes.entry(attribute) { - entry.insert(Attribute::new(hint, id)); + entry.insert(Attribute::new(security_mode, id)); Ok(()) } else { Err(Error::OperationNotPermitted( @@ -150,7 +150,7 @@ impl Dimension { .take_while(|a| Some(a) != higher_attributes.last()) .collect::>(); - new_attributes.insert(attribute, Attribute::new(hint, id)); + new_attributes.insert(attribute, Attribute::new(security_mode, id)); higher_attributes.into_iter().rev().for_each(|(name, dim)| { new_attributes.insert(name, dim); }); @@ -267,7 +267,7 @@ mod serialization { fn test_attribute_serialization() { use cosmian_crypto_core::bytes_ser_de::test_serialization; - let attribute = Attribute::new(SecurityMode::Classic, 13); + let attribute = Attribute::new(SecurityMode::PreQuantum, 13); test_serialization(&attribute).unwrap(); let attribute = Attribute::new(SecurityMode::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(), SecurityMode::Classic, None, 0) + d.add_attribute("A".to_string(), SecurityMode::PreQuantum, None, 0) .unwrap(); d.add_attribute("B".to_string(), SecurityMode::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(), SecurityMode::Classic, None, 0) + d.add_attribute("A".to_string(), SecurityMode::PreQuantum, None, 0) .unwrap(); d.add_attribute("B".to_string(), SecurityMode::Hybridized, None, 1) .unwrap(); diff --git a/src/abe_policy/tests.rs b/src/abe_policy/tests.rs index f2c6929e..02289a57 100644 --- a/src/abe_policy/tests.rs +++ b/src/abe_policy/tests.rs @@ -8,7 +8,7 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), dimension: "SEC".to_string(), name: "LOW".to_string(), }, - SecurityMode::Classic, + SecurityMode::PreQuantum, None, )?; policy.add_attribute( @@ -16,7 +16,7 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), dimension: "SEC".to_string(), name: "MED".to_string(), }, - SecurityMode::Quantum, + SecurityMode::PostQuantum, Some("LOW"), )?; policy.add_attribute( @@ -30,20 +30,20 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), policy.add_anarchy("DPT".to_string())?; [ - ("RD", SecurityMode::Classic), - ("HR", SecurityMode::Classic), - ("MKG", SecurityMode::Classic), - ("FIN", SecurityMode::Classic), - ("DEV", SecurityMode::Classic), + ("RD", SecurityMode::PreQuantum), + ("HR", SecurityMode::PreQuantum), + ("MKG", SecurityMode::PreQuantum), + ("FIN", SecurityMode::PreQuantum), + ("DEV", SecurityMode::PreQuantum), ] .into_iter() - .try_for_each(|(attribute, hint)| { + .try_for_each(|(attribute, mode)| { policy.add_attribute( crate::abe_policy::QualifiedAttribute { dimension: "DPT".to_string(), name: attribute.to_string(), }, - hint, + mode, None, ) })?; @@ -51,20 +51,20 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), if complete { policy.add_anarchy("CTR".to_string())?; [ - ("EN", SecurityMode::Classic), - ("DE", SecurityMode::Classic), - ("IT", SecurityMode::Classic), - ("FR", SecurityMode::Classic), - ("SP", SecurityMode::Classic), + ("EN", SecurityMode::PreQuantum), + ("DE", SecurityMode::PreQuantum), + ("IT", SecurityMode::PreQuantum), + ("FR", SecurityMode::PreQuantum), + ("SP", SecurityMode::PreQuantum), ] .into_iter() - .try_for_each(|(attribute, hint)| { + .try_for_each(|(attribute, mode)| { policy.add_attribute( crate::abe_policy::QualifiedAttribute { dimension: "CTR".to_string(), name: attribute.to_string(), }, - hint, + mode, None, ) })?; @@ -106,20 +106,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::Classic, None) + .add_attribute(new_attr.clone(), SecurityMode::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::Classic, None) + .add_attribute(duplicate_attr, SecurityMode::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::Classic, None) + .add_attribute(missing_dimension.clone(), SecurityMode::PreQuantum, None) .is_err()); // Remove research attribute @@ -151,14 +151,14 @@ fn test_edit_anarchic_attributes() { structure .add_attribute( QualifiedAttribute::new("DimensionTest", "Attr1"), - SecurityMode::Classic, + SecurityMode::PreQuantum, None, ) .unwrap(); structure .add_attribute( QualifiedAttribute::new("DimensionTest", "Attr2"), - SecurityMode::Classic, + SecurityMode::PreQuantum, None, ) .unwrap(); @@ -217,7 +217,7 @@ fn test_edit_hierarchic_attributes() { structure .add_attribute( QualifiedAttribute::new("SEC", "OTHER"), - SecurityMode::Classic, + SecurityMode::PreQuantum, None, ) .unwrap(); @@ -246,7 +246,7 @@ fn test_edit_hierarchic_attributes() { structure .add_attribute( QualifiedAttribute::new("SEC", "LOW"), - SecurityMode::Classic, + SecurityMode::PreQuantum, None, ) .unwrap(); @@ -283,7 +283,7 @@ fn test_edit_hierarchic_attributes() { structure .add_attribute( QualifiedAttribute::new("SEC", "MID"), - SecurityMode::Classic, + SecurityMode::PreQuantum, Some("LOW"), ) .unwrap(); diff --git a/src/core/mod.rs b/src/core/mod.rs index ef59568a..95c13866 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -58,10 +58,10 @@ pub const MIN_TRACING_LEVEL: usize = 1; /// or post-quantum in which case they only hold a PQ-KEM secret key. #[derive(Clone, Debug, PartialEq)] enum RightSecretKey { - Classic { + PreQuantum { sk: ::SecretKey, }, - Quantum { + PostQuantum { dk: ::DecapsulationKey, }, Hybridized { @@ -75,13 +75,13 @@ impl RightSecretKey { /// point `h`. fn random(rng: &mut impl CryptoRngCore, security_mode: SecurityMode) -> Result { match security_mode { - SecurityMode::Classic => { + SecurityMode::PreQuantum => { let sk = ::SecretKey::random(rng); - Ok(Self::Classic { sk }) + Ok(Self::PreQuantum { sk }) } - SecurityMode::Quantum => { + SecurityMode::PostQuantum => { let (dk, _) = MlKem::keygen(rng)?; - Ok(Self::Quantum { dk }) + Ok(Self::PostQuantum { dk }) } SecurityMode::Hybridized => { let sk = ::SecretKey::random(rng); @@ -99,8 +99,8 @@ impl RightSecretKey { H: h * sk, ek: dk.ek(), }, - Self::Quantum { dk } => RightPublicKey::PostQuantum { ek: dk.ek() }, - Self::Classic { sk } => RightPublicKey::Classic { H: h * sk }, + Self::PostQuantum { dk } => RightPublicKey::PostQuantum { ek: dk.ek() }, + Self::PreQuantum { sk } => RightPublicKey::PreQuantum { H: h * sk }, } } @@ -108,8 +108,8 @@ impl RightSecretKey { fn security_mode(&self) -> SecurityMode { match self { Self::Hybridized { .. } => SecurityMode::Hybridized, - Self::Quantum { .. } => SecurityMode::Quantum, - Self::Classic { .. } => SecurityMode::Classic, + Self::PostQuantum { .. } => SecurityMode::PostQuantum, + Self::PreQuantum { .. } => SecurityMode::PreQuantum, } } @@ -120,22 +120,22 @@ impl RightSecretKey { rng: &mut impl CryptoRngCore, ) -> Result { Ok(match (self, security_mode) { - (Self::Hybridized { sk, .. }, SecurityMode::Classic) => Self::Classic { sk }, - (Self::Hybridized { dk, .. }, SecurityMode::Quantum) => Self::Quantum { dk }, + (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::Quantum { .. }, SecurityMode::Classic) => Self::Quantum { + (Self::PostQuantum { .. }, SecurityMode::PreQuantum) => Self::PostQuantum { dk: ::keygen(rng)?.0, }, - (Self::Quantum { dk }, SecurityMode::Quantum) => Self::Quantum { dk }, - (Self::Quantum { dk }, SecurityMode::Hybridized) => Self::Hybridized { + (Self::PostQuantum { dk }, SecurityMode::PostQuantum) => Self::PostQuantum { dk }, + (Self::PostQuantum { dk }, SecurityMode::Hybridized) => Self::Hybridized { sk: ::keygen(rng)?.0, dk, }, - (Self::Classic { sk }, SecurityMode::Classic) => Self::Classic { sk }, - (Self::Classic { .. }, SecurityMode::Quantum) => Self::Quantum { + (Self::PreQuantum { sk }, SecurityMode::PreQuantum) => Self::PreQuantum { sk }, + (Self::PreQuantum { .. }, SecurityMode::PostQuantum) => Self::PostQuantum { dk: ::keygen(rng)?.0, }, - (Self::Classic { sk }, SecurityMode::Hybridized) => Self::Hybridized { + (Self::PreQuantum { sk }, SecurityMode::Hybridized) => Self::Hybridized { sk, dk: ::keygen(rng)?.0, }, @@ -150,7 +150,7 @@ impl RightSecretKey { /// or post-quantum, in which case they only hold a PQ-KEM public key. #[derive(Clone, Debug, PartialEq)] enum RightPublicKey { - Classic { + PreQuantum { H: ::PublicKey, }, PostQuantum { @@ -167,8 +167,8 @@ impl RightPublicKey { pub fn security_mode(&self) -> SecurityMode { match self { Self::Hybridized { .. } => SecurityMode::Hybridized, - Self::PostQuantum { .. } => SecurityMode::Quantum, - Self::Classic { .. } => SecurityMode::Classic, + Self::PostQuantum { .. } => SecurityMode::PostQuantum, + Self::PreQuantum { .. } => SecurityMode::PreQuantum, } } } @@ -539,12 +539,12 @@ impl UserSecretKey { /// - the right encapsulations. #[derive(Debug, Clone, PartialEq)] pub enum XEnc { - Classic { + PreQuantum { tag: Tag, c: Vec<::PublicKey>, encapsulations: Vec<[u8; SHARED_SECRET_LENGTH]>, }, - Quantum { + PostQuantum { tag: Tag, encapsulations: Vec<(::Encapsulation, [u8; SHARED_SECRET_LENGTH])>, }, @@ -559,8 +559,8 @@ impl XEnc { /// Returns the tracing level of this encapsulation. pub fn tracing_level(&self) -> usize { match self { - Self::Classic { c, .. } => c.len() - 1, - Self::Quantum { .. } => 0, + Self::PreQuantum { c, .. } => c.len() - 1, + Self::PostQuantum { .. } => 0, Self::Hybridized { c, .. } => c.len() - 1, } } @@ -568,16 +568,16 @@ impl XEnc { pub fn count(&self) -> usize { match self { Self::Hybridized { encapsulations, .. } => encapsulations.len(), - Self::Quantum { encapsulations, .. } => encapsulations.len(), - Self::Classic { encapsulations, .. } => encapsulations.len(), + Self::PostQuantum { encapsulations, .. } => encapsulations.len(), + Self::PreQuantum { encapsulations, .. } => encapsulations.len(), } } pub fn security_mode(&self) -> SecurityMode { match self { Self::Hybridized { .. } => SecurityMode::Hybridized, - Self::Quantum { .. } => SecurityMode::Quantum, - Self::Classic { .. } => SecurityMode::Classic, + Self::PostQuantum { .. } => SecurityMode::PostQuantum, + Self::PreQuantum { .. } => SecurityMode::PreQuantum, } } } diff --git a/src/core/primitives.rs b/src/core/primitives.rs index 7dc548e7..0a41bd85 100644 --- a/src/core/primitives.rs +++ b/src/core/primitives.rs @@ -257,8 +257,8 @@ fn h_encaps<'a>( } /// Generates post-quantum encapsulation of the given secret S with the given -/// marker c, ElGamal random r and subkeys. -fn q_encaps<'a>( +/// marker c and subkeys. +fn post_quantum_encaps<'a>( S: Secret, subkeys: impl IntoIterator::EncapsulationKey>, rng: &mut impl CryptoRngCore, @@ -284,16 +284,16 @@ fn q_encaps<'a>( Ok(( ss, - XEnc::Quantum { + XEnc::PostQuantum { tag, encapsulations, }, )) } -/// Generates a classic encapsulation of the given secret S with the given +/// Generates a pre-quantum encapsulation of the given secret S with the given /// marker c, ElGamal random r and subkeys. -fn c_encaps<'a>( +fn pre_quantum_encaps<'a>( S: Secret, c: Vec<::PublicKey>, r: ::SecretKey, @@ -316,7 +316,7 @@ fn c_encaps<'a>( Ok(( ss, - XEnc::Classic { + XEnc::PreQuantum { tag, c, encapsulations, @@ -348,20 +348,20 @@ pub fn encaps( let S = Secret::random(rng); match is_hybridized { - SecurityMode::Classic => { + SecurityMode::PreQuantum => { let r = G_hash(&S)?; let c = mpk.set_traps(&r); let subkeys = coordinate_keys.into_iter().map(|subkey| { - if let RightPublicKey::Classic { H } = subkey { + if let RightPublicKey::PreQuantum { H } = subkey { H } else { panic!("select_subkeys already ensures homogeneity") } }); - c_encaps(S, c, r, subkeys) + pre_quantum_encaps(S, c, r, subkeys) } - SecurityMode::Quantum => { + SecurityMode::PostQuantum => { let subkeys = coordinate_keys.into_iter().map(|subkey| { if let RightPublicKey::PostQuantum { ek } = subkey { ek @@ -369,7 +369,7 @@ pub fn encaps( panic!("select_subkeys already ensures homogeneity") } }); - q_encaps(S, subkeys, rng) + post_quantum_encaps(S, subkeys, rng) } SecurityMode::Hybridized => { let r = G_hash(&S)?; @@ -388,7 +388,7 @@ pub fn encaps( } #[allow(clippy::too_many_arguments)] -fn attempt_classic_decaps<'a>( +fn attempt_pre_quantum_decaps<'a>( secret: &RightSecretKey, A: &::PublicKey, U: &Secret, @@ -398,7 +398,7 @@ fn attempt_classic_decaps<'a>( tag: &[u8; TAG_LENGTH], tracing_points: impl IntoIterator::PublicKey>, ) -> Result>, Error> { - if let RightSecretKey::Classic { sk } = secret { + if let RightSecretKey::PreQuantum { sk } = secret { let mut K1 = ElGamal::session_key(sk, A)?; let S = xor_in_place(H_hash(Some(&K1), None, T)?, F); K1.zeroize(); @@ -451,7 +451,7 @@ fn attempt_hybridized_decaps<'a>( Ok(None) } -fn attempt_quantum_decaps( +fn attempt_post_quantum_decaps( secret: &RightSecretKey, U: &Secret, T: &Secret, @@ -459,7 +459,7 @@ fn attempt_quantum_decaps( F: &[u8; 32], tag: &[u8; TAG_LENGTH], ) -> Result>, Error> { - if let RightSecretKey::Quantum { dk } = secret { + if let RightSecretKey::PostQuantum { dk } = secret { let K2 = MlKem::dec(dk, E)?; let S_ij = xor_in_place(H_hash(None, Some(&K2), T)?, F); let (tag_ij, ss) = J_hash(&S_ij, U); @@ -488,7 +488,7 @@ pub fn decaps( .sum::<::PublicKey>() } - fn partial_quantum_decaps( + fn partial_post_quantum_decaps( rng: &mut impl CryptoRngCore, usk: &UserSecretKey, tag: &[u8; TAG_LENGTH], @@ -509,7 +509,7 @@ pub fn decaps( shuffle(&mut revision, rng); for (E, F) in &encs { for (_, secret) in &revision { - if let Some(ss) = attempt_quantum_decaps(secret, &U, &T, E, F, tag)? { + if let Some(ss) = attempt_post_quantum_decaps(secret, &U, &T, E, F, tag)? { return Ok(Some(ss)); } } @@ -562,7 +562,7 @@ pub fn decaps( Ok(None) } - fn partial_classic_decaps( + fn partial_pre_quantum_decaps( rng: &mut impl CryptoRngCore, usk: &UserSecretKey, c: &[::PublicKey], @@ -585,9 +585,16 @@ pub fn decaps( shuffle(&mut revision, rng); for F in &encs { for (_, secret) in &revision { - if let Some(ss) = - attempt_classic_decaps(secret, &A, &U, &T, F, c, tag, usk.tracing_points())? - { + if let Some(ss) = attempt_pre_quantum_decaps( + secret, + &A, + &U, + &T, + F, + c, + tag, + usk.tracing_points(), + )? { return Ok(Some(ss)); } } @@ -603,15 +610,15 @@ pub fn decaps( c, encapsulations, } => partial_hybridized_decaps(rng, usk, c, tag, encapsulations), - XEnc::Quantum { + XEnc::PostQuantum { tag, encapsulations, - } => partial_quantum_decaps(rng, usk, tag, encapsulations), - XEnc::Classic { + } => partial_post_quantum_decaps(rng, usk, tag, encapsulations), + XEnc::PreQuantum { tag, c, encapsulations, - } => partial_classic_decaps(rng, usk, c, tag, encapsulations), + } => partial_pre_quantum_decaps(rng, usk, c, tag, encapsulations), } } @@ -656,7 +663,7 @@ pub fn full_decaps( Ok(c_0 * &(&msk.tsk.s / t_0)?) } - fn full_classic_decapsulation( + fn full_pre_quantum_decapsulation( msk: &MasterSecretKey, tag: &[u8; TAG_LENGTH], c: &[::PublicKey], @@ -668,7 +675,7 @@ pub fn full_decaps( // Attempts opening the encapsulation F with this right secret key. let attempt_opening = |F, secret: &RightSecretKey| { - attempt_classic_decaps(secret, &A, &U, &T, F, c, tag, msk.tracing_points()) + attempt_pre_quantum_decaps(secret, &A, &U, &T, F, c, tag, msk.tracing_points()) }; let mut enc_ss = None; @@ -698,7 +705,7 @@ pub fn full_decaps( .ok_or_else(|| Error::Kem("empty encapsulation".to_string())) } - fn full_quantum_decapsulation( + fn full_post_quantum_decapsulation( msk: &MasterSecretKey, tag: &[u8; TAG_LENGTH], encapsulations: &[(::Encapsulation, [u8; SHARED_SECRET_LENGTH])], @@ -707,7 +714,7 @@ pub fn full_decaps( let U = generate_U(&T, encapsulations.iter().map(|(_, F)| F)); let attempt_opening = - |E, F, secret: &RightSecretKey| attempt_quantum_decaps(secret, &U, &T, E, F, tag); + |E, F, secret: &RightSecretKey| attempt_post_quantum_decaps(secret, &U, &T, E, F, tag); let mut enc_ss = None; let mut rights = HashSet::with_capacity(encapsulations.len()); @@ -778,15 +785,15 @@ pub fn full_decaps( } match encapsulation { - XEnc::Classic { + XEnc::PreQuantum { tag, c, encapsulations, - } => full_classic_decapsulation(msk, tag, c, encapsulations), - XEnc::Quantum { + } => full_pre_quantum_decapsulation(msk, tag, c, encapsulations), + XEnc::PostQuantum { tag, encapsulations, - } => full_quantum_decapsulation(msk, tag, encapsulations), + } => full_post_quantum_decapsulation(msk, tag, encapsulations), XEnc::Hybridized { tag, c, diff --git a/src/core/serialization/mod.rs b/src/core/serialization/mod.rs index 2484383c..aaa16fdc 100644 --- a/src/core/serialization/mod.rs +++ b/src/core/serialization/mod.rs @@ -33,7 +33,7 @@ impl Serializable for RightPublicKey { fn length(&self) -> usize { 1 + match self { - Self::Classic { H } => H.length(), + Self::PreQuantum { H } => H.length(), Self::PostQuantum { ek } => ek.length(), Self::Hybridized { H, ek } => H.length() + ek.length(), } @@ -41,7 +41,7 @@ impl Serializable for RightPublicKey { fn write(&self, ser: &mut Serializer) -> Result { match self { - Self::Classic { H } => Ok(0usize.write(ser)? + H.write(ser)?), + Self::PreQuantum { H } => Ok(0usize.write(ser)? + H.write(ser)?), Self::PostQuantum { ek } => Ok(2usize.write(ser)? + ek.write(ser)?), Self::Hybridized { H, ek } => Ok(1usize.write(ser)? + H.write(ser)? + ek.write(ser)?), } @@ -50,7 +50,7 @@ impl Serializable for RightPublicKey { fn read(de: &mut Deserializer) -> Result { let is_hybridized = de.read::()?; match is_hybridized { - 0 => Ok(Self::Classic { H: de.read()? }), + 0 => Ok(Self::PreQuantum { H: de.read()? }), 2 => Ok(Self::PostQuantum { ek: de.read()? }), 1 => Ok(Self::Hybridized { H: de.read()?, @@ -156,15 +156,15 @@ impl Serializable for RightSecretKey { fn length(&self) -> usize { 1 + match self { Self::Hybridized { sk, dk } => sk.length() + dk.length(), - Self::Classic { sk } => sk.length(), - Self::Quantum { dk } => dk.length(), + Self::PreQuantum { sk } => sk.length(), + Self::PostQuantum { dk } => dk.length(), } } fn write(&self, ser: &mut Serializer) -> Result { match self { - Self::Classic { sk } => Ok(0usize.write(ser)? + sk.write(ser)?), - Self::Quantum { dk } => Ok(2usize.write(ser)? + dk.write(ser)?), + Self::PreQuantum { sk } => Ok(0usize.write(ser)? + sk.write(ser)?), + Self::PostQuantum { dk } => Ok(2usize.write(ser)? + dk.write(ser)?), Self::Hybridized { sk, dk } => { Ok(1usize.write(ser)? + sk.write(ser)? + dk.write(ser)?) } @@ -174,12 +174,12 @@ impl Serializable for RightSecretKey { fn read(de: &mut Deserializer) -> Result { let mode = de.read_leb128_u64()?; match mode { - 0 => Ok(Self::Classic { sk: de.read()? }), + 0 => Ok(Self::PreQuantum { sk: de.read()? }), 1 => Ok(Self::Hybridized { sk: de.read()?, dk: de.read()?, }), - 2 => Ok(Self::Quantum { dk: de.read()? }), + 2 => Ok(Self::PostQuantum { dk: de.read()? }), _ => Err(Error::ConversionFailed(format!( "invalid hybridization flag {mode}" ))), @@ -216,12 +216,12 @@ impl Serializable for XEnc { fn length(&self) -> usize { 1 + match self { - Self::Classic { + Self::PreQuantum { tag, c, encapsulations, } => tag.length() + c.length() + encapsulations.length(), - Self::Quantum { + Self::PostQuantum { tag, encapsulations, } => tag.length() + encapsulations.length(), @@ -235,7 +235,7 @@ impl Serializable for XEnc { fn write(&self, ser: &mut Serializer) -> Result { match self { - XEnc::Classic { + XEnc::PreQuantum { tag, c, encapsulations, @@ -243,7 +243,7 @@ impl Serializable for XEnc { + tag.write(ser)? + c.write(ser)? + encapsulations.write(ser)?), - XEnc::Quantum { + XEnc::PostQuantum { tag, encapsulations, } => Ok(1usize.write(ser)? + tag.write(ser)? + encapsulations.write(ser)?), @@ -261,12 +261,12 @@ impl Serializable for XEnc { fn read(de: &mut Deserializer) -> Result { let mode = usize::read(de)?; match mode { - 0 => Ok(Self::Classic { + 0 => Ok(Self::PreQuantum { tag: de.read()?, c: de.read()?, encapsulations: de.read()?, }), - 1 => Ok(Self::Quantum { + 1 => Ok(Self::PostQuantum { tag: de.read()?, encapsulations: de.read()?, }), diff --git a/src/core/tests.rs b/src/core/tests.rs index fbf6d0d8..e038938b 100644 --- a/src/core/tests.rs +++ b/src/core/tests.rs @@ -20,8 +20,8 @@ use super::{ #[test] fn security_mode_ordering() { - assert!(SecurityMode::Classic < SecurityMode::Quantum); - assert!(SecurityMode::Quantum < SecurityMode::Hybridized); + assert!(SecurityMode::PreQuantum < SecurityMode::PostQuantum); + assert!(SecurityMode::PostQuantum < SecurityMode::Hybridized); } /// This test asserts that it is possible to encapsulate a key for a given @@ -40,11 +40,11 @@ fn test_encapsulation() { HashMap::from_iter([ ( other_coordinate.clone(), - (SecurityMode::Classic, EncryptionStatus::EncryptDecrypt), + (SecurityMode::PreQuantum, EncryptionStatus::EncryptDecrypt), ), ( target_coordinate.clone(), - (SecurityMode::Classic, EncryptionStatus::EncryptDecrypt), + (SecurityMode::PreQuantum, EncryptionStatus::EncryptDecrypt), ), ]), ) @@ -102,7 +102,7 @@ fn test_update() { .map(|_| { ( Right::random(&mut rng), - (SecurityMode::Classic, EncryptionStatus::EncryptDecrypt), + (SecurityMode::PreQuantum, EncryptionStatus::EncryptDecrypt), ) }) .collect::>(); @@ -156,11 +156,11 @@ fn test_rekey() { HashMap::from_iter([ ( coordinate_1.clone(), - (SecurityMode::Classic, EncryptionStatus::EncryptDecrypt), + (SecurityMode::PreQuantum, EncryptionStatus::EncryptDecrypt), ), ( coordinate_2.clone(), - (SecurityMode::Classic, EncryptionStatus::EncryptDecrypt), + (SecurityMode::PreQuantum, EncryptionStatus::EncryptDecrypt), ), ]), ) @@ -240,11 +240,11 @@ fn test_integrity_check() { HashMap::from_iter([ ( coordinate_1.clone(), - (SecurityMode::Classic, EncryptionStatus::EncryptDecrypt), + (SecurityMode::PreQuantum, EncryptionStatus::EncryptDecrypt), ), ( coordinate_2.clone(), - (SecurityMode::Classic, EncryptionStatus::EncryptDecrypt), + (SecurityMode::PreQuantum, EncryptionStatus::EncryptDecrypt), ), ]), ) @@ -308,7 +308,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::Classic); + assert_eq!(enc.security_mode(), SecurityMode::PreQuantum); let res = cc.decaps(&usk, &enc).unwrap(); assert_eq!(secret, res.unwrap()); @@ -321,7 +321,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::Quantum); + assert_eq!(enc.security_mode(), SecurityMode::PostQuantum); let res = cc.decaps(&usk, &enc).unwrap(); assert_eq!(secret, res.unwrap()); diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs index 2f8ca65e..8ad782d2 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"), - SecurityMode::Classic, + SecurityMode::PreQuantum, None, )?; let mpk = cc.update_msk(&mut msk)?; From 51c220360a97382bcd81206912b6e9ab3d2c8911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Fri, 7 Nov 2025 17:15:24 +0100 Subject: [PATCH 20/21] fix comment --- src/core/primitives.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/primitives.rs b/src/core/primitives.rs index 0a41bd85..dae4acc3 100644 --- a/src/core/primitives.rs +++ b/src/core/primitives.rs @@ -257,7 +257,7 @@ fn h_encaps<'a>( } /// Generates post-quantum encapsulation of the given secret S with the given -/// marker c and subkeys. +/// subkeys. fn post_quantum_encaps<'a>( S: Secret, subkeys: impl IntoIterator::EncapsulationKey>, From dce7531a1a9fa4ed9694b0b13ed5a1038c6a80c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Fri, 13 Feb 2026 16:11:22 +0100 Subject: [PATCH 21/21] feat: Covercrypt wrapper (#167) * restructure modules * WIP: implement wrapper around Covercrypt * use crypto_core providers * fix lack of ZeroizeOnDrop on MlKem implementation * fix: add ZeroizeOnDrop for abe::DKey * add test for the ABE variant of the ConfigurableKEM * revert naming breaking change * revert module-tree breaking change * revert EncryptionHint-member breaking change * fix: upgrade CryptoCore * add hybridized implementation * enforce KEM semantics across `ConfigurableKEM` variants * upgrade CryptoCore, use OpenSSL provider for P256 and remove base * cleanup lib.rs * expose MLKEM * derive Debug and Clone for MlKem implementations * implement Serializable for AccessPolicy * move the configurable KEM implem back to the CoverCrypt repository * add tag to discriminate USK vs MSK * improve generic implementation * implement Serializable for configurable-KEM objects * chore: update crypt_core to develop * expose constructor for configurable KEM objects --------- Co-authored-by: Manuthor --- .github/workflows/ci.yml | 4 +- .gitignore | 1 - Cargo.lock | 1055 +++++++++++++++++ Cargo.toml | 12 +- rust-toolchain.toml | 6 + src/abe.rs | 17 + src/{ => abe}/api.rs | 65 +- src/{ => abe}/core/mod.rs | 189 +-- src/{ => abe}/core/primitives.rs | 169 +-- src/{ => abe}/core/serialization/mod.rs | 23 +- src/{ => abe}/core/tests.rs | 58 +- src/{ => abe}/encrypted_header.rs | 8 +- src/abe/error.rs | 56 + src/{abe_policy/mod.rs => abe/policy.rs} | 5 +- .../policy}/access_policy.rs | 70 +- .../policy}/access_structure.rs | 24 +- src/{abe_policy => abe/policy}/attribute.rs | 33 +- src/{abe_policy => abe/policy}/dimension.rs | 26 +- src/{abe_policy => abe/policy}/rights.rs | 0 src/{abe_policy => abe/policy}/tests.rs | 57 +- src/abe/traits.rs | 66 ++ src/ae.rs | 36 - src/base.rs | 367 ++++++ src/core/nike/p256.rs | 434 ------- src/core/nike/r25519.rs | 403 ------- src/error.rs | 2 +- src/kem.rs | 805 +++++++++++++ src/lib.rs | 28 +- src/providers.rs | 5 + src/{core => providers}/kem.rs | 4 +- src/{core => providers}/kem/mlkem.rs | 56 +- src/{core => providers}/nike.rs | 10 +- src/test_utils/mod.rs | 18 +- src/traits.rs | 197 --- 34 files changed, 2876 insertions(+), 1433 deletions(-) create mode 100644 Cargo.lock create mode 100644 rust-toolchain.toml create mode 100644 src/abe.rs rename src/{ => abe}/api.rs (83%) rename src/{ => abe}/core/mod.rs (72%) rename src/{ => abe}/core/primitives.rs (89%) rename src/{ => abe}/core/serialization/mod.rs (94%) rename src/{ => abe}/core/tests.rs (87%) rename src/{ => abe}/encrypted_header.rs (98%) create mode 100644 src/abe/error.rs rename src/{abe_policy/mod.rs => abe/policy.rs} (93%) rename src/{abe_policy => abe/policy}/access_policy.rs (78%) rename src/{abe_policy => abe/policy}/access_structure.rs (97%) rename src/{abe_policy => abe/policy}/attribute.rs (83%) rename src/{abe_policy => abe/policy}/dimension.rs (92%) rename src/{abe_policy => abe/policy}/rights.rs (100%) rename src/{abe_policy => abe/policy}/tests.rs (86%) create mode 100644 src/abe/traits.rs delete mode 100644 src/ae.rs create mode 100644 src/base.rs delete mode 100644 src/core/nike/p256.rs delete mode 100644 src/core/nike/r25519.rs create mode 100644 src/kem.rs create mode 100644 src/providers.rs rename src/{core => providers}/kem.rs (71%) rename src/{core => providers}/kem/mlkem.rs (81%) rename src/{core => providers}/nike.rs (55%) 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/.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..f6dc25b8 --- /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.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +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.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" + +[[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%2Ffix-memory-allocation-error-upon-deserialization#24afc12b77b03ce476909a0835216a01bee32c5b" +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%2Ffix-memory-allocation-error-upon-deserialization#24afc12b77b03ce476909a0835216a01bee32c5b" +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%2Ffix-memory-allocation-error-upon-deserialization#24afc12b77b03ce476909a0835216a01bee32c5b" +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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[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.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + +[[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.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" +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.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" + +[[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.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +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.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index ce0c398f..69c46cbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,22 +41,22 @@ 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 = [] +curve25519 = [] test-utils = [] [dependencies] -cosmian_crypto_core = { version = "10.3", 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" ] } -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 } +zeroize = "1.8" [dev-dependencies] criterion = { version = "0.7", features = [ 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"] diff --git a/src/abe.rs b/src/abe.rs new file mode 100644 index 00000000..4daa330d --- /dev/null +++ b/src/abe.rs @@ -0,0 +1,17 @@ +mod policy; + +pub mod api; +pub mod core; +pub mod traits; + +pub mod encrypted_header; + +pub use api::Covercrypt; +pub use core::{MasterPublicKey, MasterSecretKey, UserSecretKey, XEnc}; +pub use policy::{ + AccessPolicy, AccessStructure, Attribute, Dimension, EncryptionHint, EncryptionStatus, + QualifiedAttribute, +}; + +#[cfg(any(test, feature = "test-utils"))] +pub use policy::gen_structure; diff --git a/src/api.rs b/src/abe/api.rs similarity index 83% rename from src/api.rs rename to src/abe/api.rs index c32776ba..3ff4732d 100644 --- a/src/api.rs +++ b/src/abe/api.rs @@ -1,33 +1,33 @@ -use std::sync::{Mutex, MutexGuard}; - -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, + abe::{ + core::{ + primitives::{ + self, master_decaps, prune, refresh, rekey, setup, update_msk, usk_keygen, + }, + MasterPublicKey, MasterSecretKey, UserSecretKey, XEnc, MIN_TRACING_LEVEL, + SHARED_SECRET_LENGTH, + }, + policy::AccessPolicy, + traits::{KemAc, PkeAc}, }, - traits::{KemAc, PkeAc}, - AccessPolicy, Error, + Error, +}; +use cosmian_crypto_core::{ + reexport::rand_core::{RngCore, SeedableRng}, + traits::AE, + CsRng, Secret, SymmetricKey, }; +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())), } } } @@ -158,7 +158,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, @@ -194,12 +194,18 @@ 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, { type EncryptionKey = MasterPublicKey; type DecryptionKey = UserSecretKey; - type Ciphertext = (XEnc, Vec); + type Ciphertext = (XEnc, E::Ciphertext); type Error = Error; fn encrypt( @@ -212,19 +218,22 @@ impl> PkeAc::derive(&seed, b"Covercrypt AE key")?; + let mut nonce = [0; NONCE_LENGTH]; + rng.fill_bytes(&mut nonce); + let ctx = E::encrypt(&key, ptx, &nonce)?; + 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.as_ref()).map_err(Self::Error::from) }) .transpose() } diff --git a/src/core/mod.rs b/src/abe/core/mod.rs similarity index 72% rename from src/core/mod.rs rename to src/abe/core/mod.rs index 95c13866..da0482e3 100644 --- a/src/core/mod.rs +++ b/src/abe/core/mod.rs @@ -1,23 +1,18 @@ #![allow(non_snake_case)] -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}, + abe::policy::{AccessStructure, EncryptionHint, EncryptionStatus, Right}, data_struct::{RevisionMap, RevisionVec}, - traits::{Kem, Nike, Sampling, Zero}, - Error, SecurityMode, + providers::{ElGamal, MlKem}, + Error, +}; +use cosmian_crypto_core::{ + reexport::{rand_core::CryptoRngCore, zeroize::ZeroizeOnDrop}, + traits::{Sampling, Zero, KEM, NIKE}, + SymmetricKey, }; +use std::collections::{HashMap, HashSet, LinkedList}; -mod kem; -mod nike; mod serialization; #[cfg(test)] @@ -59,32 +54,32 @@ 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, }, } 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 => { - let sk = ::SecretKey::random(rng); + EncryptionHint::Classic => { + let sk = ::SecretKey::random(rng); Ok(Self::PreQuantum { sk }) } - SecurityMode::PostQuantum => { + EncryptionHint::PostQuantum => { let (dk, _) = MlKem::keygen(rng)?; Ok(Self::PostQuantum { dk }) } - SecurityMode::Hybridized => { - let sk = ::SecretKey::random(rng); + EncryptionHint::Hybridized => { + let sk = ::SecretKey::random(rng); let (dk, _) = MlKem::keygen(rng)?; Ok(Self::Hybridized { sk, dk }) } @@ -93,7 +88,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, @@ -105,39 +100,41 @@ 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::Classic, } } /// 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 { - dk: ::keygen(rng)?.0, + (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::Classic) => Self::PostQuantum { + dk: >::keygen(rng)?.0, }, - (Self::PostQuantum { dk }, SecurityMode::PostQuantum) => Self::PostQuantum { dk }, - (Self::PostQuantum { dk }, SecurityMode::Hybridized) => Self::Hybridized { - sk: ::keygen(rng)?.0, + (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 { - dk: ::keygen(rng)?.0, + (Self::PreQuantum { sk }, EncryptionHint::Classic) => 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, + dk: >::keygen(rng)?.0, }, }) } @@ -151,31 +148,31 @@ 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, }, } 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::Classic, } } } /// Covercrypt user IDs are used to make user keys unique and traceable. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] -struct UserId(LinkedList<::SecretKey>); +#[derive(Clone, Debug, PartialEq, Eq, Default)] +struct UserId(LinkedList<::SecretKey>); impl UserId { /// Returns the tracing level of the USK. @@ -183,7 +180,7 @@ impl UserId { self.0.len() - 1 } - fn iter(&self) -> impl Iterator::SecretKey> { + fn iter(&self) -> impl Iterator::SecretKey> { self.0.iter() } } @@ -204,18 +201,29 @@ impl UserId { /// - the set of known user IDs. #[derive(Debug, PartialEq, Eq)] struct TracingSecretKey { - s: ::SecretKey, - tracers: LinkedList<(::SecretKey, ::PublicKey)>, - users: HashSet, + s: ::SecretKey, + tracers: LinkedList<(::SecretKey, ::PublicKey)>, + // 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 { 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(); + let users = LinkedList::new(); Ok(Self { s, tracers, users }) } @@ -227,14 +235,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}" @@ -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. @@ -289,7 +300,7 @@ impl TracingSecretKey { } /// Returns the binding points. - fn binding_point(&self) -> ::PublicKey { + fn binding_point(&self) -> ::PublicKey { (&self.s).into() } @@ -301,7 +312,7 @@ impl TracingSecretKey { .tracers .iter() .take(self.tracers.len() - 1) - .map(|_| ::SecretKey::random(rng)) + .map(|_| ::SecretKey::random(rng)) .collect::>(); let last_marker = ((&self.s @@ -310,7 +321,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); @@ -358,8 +369,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. @@ -384,6 +395,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. /// @@ -403,7 +417,7 @@ impl MasterSecretKey { }) } - fn tracing_points(&self) -> impl IntoIterator::PublicKey> { + fn tracing_points(&self) -> impl IntoIterator::PublicKey> { self.tsk.tracers.iter().map(|(_, P)| P) } @@ -436,7 +450,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, @@ -451,7 +465,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() } @@ -465,7 +479,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| { @@ -508,11 +522,14 @@ impl MasterPublicKey { #[derive(Clone, Debug, PartialEq)] pub struct UserSecretKey { id: UserId, - ps: Vec<::PublicKey>, + ps: Vec<::PublicKey>, secrets: RevisionVec, 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 { @@ -524,7 +541,7 @@ impl UserSecretKey { self.secrets.len() } - fn tracing_points(&self) -> &[::PublicKey] { + fn tracing_points(&self) -> &[::PublicKey] { &self.ps } } @@ -541,17 +558,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], + )>, }, } @@ -573,11 +596,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::Classic, } } } diff --git a/src/core/primitives.rs b/src/abe/core/primitives.rs similarity index 89% rename from src/core/primitives.rs rename to src/abe/core/primitives.rs index dae4acc3..f8dde602 100644 --- a/src/core/primitives.rs +++ b/src/abe/core/primitives.rs @@ -1,28 +1,28 @@ -use std::{ - collections::{HashMap, HashSet, LinkedList}, - mem::take, +use crate::{ + 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, EncryptionHint, EncryptionStatus, Right}, + }, + data_struct::{RevisionMap, RevisionVec}, + 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 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, - }, - data_struct::{RevisionMap, RevisionVec}, - traits::{Kem, Nike, Sampling}, - Error, +use std::{ + collections::{HashMap, HashSet, LinkedList}, + mem::take, }; fn xor_2(lhs: &[u8; LENGTH], rhs: &[u8; LENGTH]) -> [u8; LENGTH] { @@ -82,13 +82,15 @@ fn verify(msk: &MasterSecretKey, usk: &UserSecretKey) -> Result<(), Error> { } } -fn G_hash(seed: &Secret) -> Result<::SecretKey, Error> { - Ok(<::SecretKey as Sampling>::hash(&**seed)) +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 @@ -128,8 +130,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(); @@ -212,12 +216,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, @@ -225,7 +229,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)) }) @@ -235,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>>()?; @@ -260,7 +263,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 @@ -295,16 +298,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) }) @@ -348,7 +351,7 @@ pub fn encaps( let S = Secret::random(rng); match is_hybridized { - SecurityMode::PreQuantum => { + EncryptionHint::Classic => { let r = G_hash(&S)?; let c = mpk.set_traps(&r); @@ -361,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 @@ -371,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); @@ -390,18 +393,17 @@ 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 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 @@ -421,17 +423,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 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); @@ -443,7 +445,6 @@ fn attempt_hybridized_decaps<'a>( .map(|P| P * &r) .collect::>(); if c == c_ij { - K1.zeroize(); return Ok(Some(ss)); } } @@ -455,7 +456,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> { @@ -479,20 +480,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)); @@ -522,9 +526,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)))?; @@ -565,7 +572,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> { @@ -624,9 +631,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 @@ -648,8 +656,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()))?; @@ -663,11 +671,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::>)?; @@ -696,6 +705,10 @@ pub fn full_decaps( secrets.remove(&right); enc_ss = Some(ss); rights.insert(right); + + if !full { + break; + } } enc_ss @@ -705,10 +718,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)); @@ -734,6 +751,10 @@ pub fn full_decaps( secrets.remove(&right); enc_ss = Some(ss); rights.insert(right); + + if !full { + break; + } } enc_ss @@ -743,11 +764,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)))?; @@ -775,6 +800,10 @@ pub fn full_decaps( secrets.remove(&right); enc_ss = Some(ss); rights.insert(right); + + if !full { + break; + } } enc_ss @@ -789,16 +818,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), } } @@ -808,7 +837,7 @@ pub fn full_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/core/serialization/mod.rs b/src/abe/core/serialization/mod.rs similarity index 94% rename from src/core/serialization/mod.rs rename to src/abe/core/serialization/mod.rs index aaa16fdc..ec090b28 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, EncryptionHint, EncryptionStatus, Right}, + traits::KemAc, }, test_utils::cc_keygen, - traits::KemAc, - AccessPolicy, SecurityMode, }; #[test] @@ -313,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/core/tests.rs b/src/abe/core/tests.rs similarity index 87% rename from src/core/tests.rs rename to src/abe/core/tests.rs index e038938b..ffe38412 100644 --- a/src/core/tests.rs +++ b/src/abe/core/tests.rs @@ -1,16 +1,18 @@ 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_policy::{AccessPolicy, EncryptionStatus, Right}, - api::Covercrypt, - core::{ - primitives::{decaps, encaps, refresh, rekey, update_msk}, - SecurityMode, + abe::{ + core::{ + primitives::{decaps, encaps, refresh, rekey, update_msk}, + EncryptionHint, + }, + policy::{AccessPolicy, EncryptionStatus, Right}, + traits::{KemAc, PkeAc}, + Covercrypt, }, test_utils::cc_keygen, - traits::{KemAc, PkeAc}, }; use super::{ @@ -20,8 +22,8 @@ use super::{ #[test] fn security_mode_ordering() { - assert!(SecurityMode::PreQuantum < SecurityMode::PostQuantum); - assert!(SecurityMode::PostQuantum < SecurityMode::Hybridized); + assert!(EncryptionHint::Classic < EncryptionHint::PostQuantum); + assert!(EncryptionHint::PostQuantum < EncryptionHint::Hybridized); } /// This test asserts that it is possible to encapsulate a key for a given @@ -40,11 +42,11 @@ fn test_encapsulation() { HashMap::from_iter([ ( other_coordinate.clone(), - (SecurityMode::PreQuantum, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::Classic, EncryptionStatus::EncryptDecrypt), ), ( target_coordinate.clone(), - (SecurityMode::PreQuantum, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::Classic, EncryptionStatus::EncryptDecrypt), ), ]), ) @@ -102,7 +104,7 @@ fn test_update() { .map(|_| { ( Right::random(&mut rng), - (SecurityMode::PreQuantum, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::Classic, EncryptionStatus::EncryptDecrypt), ) }) .collect::>(); @@ -156,11 +158,11 @@ fn test_rekey() { HashMap::from_iter([ ( coordinate_1.clone(), - (SecurityMode::PreQuantum, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::Classic, EncryptionStatus::EncryptDecrypt), ), ( coordinate_2.clone(), - (SecurityMode::PreQuantum, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::Classic, EncryptionStatus::EncryptDecrypt), ), ]), ) @@ -240,11 +242,11 @@ fn test_integrity_check() { HashMap::from_iter([ ( coordinate_1.clone(), - (SecurityMode::PreQuantum, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::Classic, EncryptionStatus::EncryptDecrypt), ), ( coordinate_2.clone(), - (SecurityMode::PreQuantum, EncryptionStatus::EncryptDecrypt), + (EncryptionHint::Classic, EncryptionStatus::EncryptDecrypt), ), ]), ) @@ -308,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(), SecurityMode::PreQuantum); + assert_eq!(enc.security_mode(), EncryptionHint::Classic); let res = cc.decaps(&usk, &enc).unwrap(); assert_eq!(secret, res.unwrap()); @@ -321,7 +323,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()); @@ -334,7 +336,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()); } @@ -347,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/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..0e7f326c 100644 --- a/src/encrypted_header.rs +++ b/src/abe/encrypted_header.rs @@ -4,8 +4,12 @@ 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}, + traits::KemAc, + AccessPolicy, Covercrypt, 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 93% rename from src/abe_policy/mod.rs rename to src/abe/policy.rs index a54e23b9..9f29ad8c 100644 --- a/src/abe_policy/mod.rs +++ b/src/abe/policy.rs @@ -9,14 +9,15 @@ 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 attribute::{EncryptionHint, EncryptionStatus, QualifiedAttribute}; 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 78% rename from src/abe_policy/access_policy.rs rename to src/abe/policy/access_policy.rs index 4d85acad..b8ca195d 100644 --- a/src/abe_policy/access_policy.rs +++ b/src/abe/policy/access_policy.rs @@ -7,10 +7,12 @@ use std::{ collections::LinkedList, fmt::Debug, - ops::{BitAnd, BitOr}, + ops::{BitAnd, BitOr, Deref}, }; -use crate::{abe_policy::QualifiedAttribute, Error}; +use cosmian_crypto_core::bytes_ser_de::Serializable; + +use crate::{abe::policy::QualifiedAttribute, Error}; /// An access policy is a boolean expression of qualified attributes. #[derive(Debug, Clone, PartialEq, Eq)] @@ -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. diff --git a/src/abe_policy/access_structure.rs b/src/abe/policy/access_structure.rs similarity index 97% rename from src/abe_policy/access_structure.rs rename to src/abe/policy/access_structure.rs index b206eafe..50ac8859 100644 --- a/src/abe_policy/access_structure.rs +++ b/src/abe/policy/access_structure.rs @@ -1,8 +1,8 @@ use std::collections::{hash_map::Entry, HashMap, HashSet}; use crate::{ - abe_policy::{ - attribute::SecurityMode, AccessPolicy, Attribute, Dimension, EncryptionStatus, + abe::policy::{ + 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::Classic, EncryptionStatus::EncryptDecrypt, )] } else { @@ -377,7 +379,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 +391,7 @@ mod serialization { #[cfg(test)] mod tests { use super::*; - use crate::abe_policy::gen_structure; + use crate::abe::gen_structure; #[test] fn test_combine() { @@ -408,9 +410,9 @@ mod tests { structure.add_anarchy("Country".to_string()).unwrap(); [ - ("France", SecurityMode::PreQuantum), - ("Germany", SecurityMode::PreQuantum), - ("Spain", SecurityMode::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 similarity index 83% rename from src/abe_policy/attribute.rs rename to src/abe/policy/attribute.rs index 6f8740d2..ec394fef 100644 --- a/src/abe_policy/attribute.rs +++ b/src/abe/policy/attribute.rs @@ -1,40 +1,37 @@ 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; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub enum SecurityMode { - PreQuantum, +pub enum EncryptionHint { + Classic, PostQuantum, Hybridized, } -impl Serializable for SecurityMode { +impl Serializable for EncryptionHint { type Error = Error; fn length(&self) -> usize { 1 } - fn write( - &self, - ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, - ) -> Result { + fn write(&self, ser: &mut Serializer) -> Result { match self { - Self::PreQuantum => ser.write(&0usize), + Self::Classic => ser.write(&0usize), Self::Hybridized => ser.write(&1usize), Self::PostQuantum => ser.write(&2usize), } .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::PreQuantum), + 0 => Ok(Self::Classic), 1 => Ok(Self::Hybridized), 2 => Ok(Self::PostQuantum), n => Err(Error::ConversionFailed(format!( @@ -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_policy/dimension.rs b/src/abe/policy/dimension.rs similarity index 92% rename from src/abe_policy/dimension.rs rename to src/abe/policy/dimension.rs index a0d81e52..19c91b8c 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::Classic, 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::Classic, 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::Classic, 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/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 86% rename from src/abe_policy/tests.rs rename to src/abe/policy/tests.rs index 02289a57..8467e3d8 100644 --- a/src/abe_policy/tests.rs +++ b/src/abe/policy/tests.rs @@ -1,45 +1,48 @@ -use crate::{abe_policy::AccessStructure, Error, SecurityMode}; +use crate::{ + abe::policy::{AccessStructure, EncryptionHint}, + 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(), }, - SecurityMode::PreQuantum, + EncryptionHint::Classic, None, )?; policy.add_attribute( - crate::abe_policy::QualifiedAttribute { + crate::abe::policy::QualifiedAttribute { dimension: "SEC".to_string(), name: "MED".to_string(), }, - SecurityMode::PostQuantum, + EncryptionHint::PostQuantum, Some("LOW"), )?; policy.add_attribute( - crate::abe_policy::QualifiedAttribute { + crate::abe::policy::QualifiedAttribute { 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::Classic), + ("HR", EncryptionHint::Classic), + ("MKG", EncryptionHint::Classic), + ("FIN", EncryptionHint::Classic), + ("DEV", EncryptionHint::Classic), ] .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(), }, @@ -51,16 +54,16 @@ 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::Classic), + ("DE", EncryptionHint::Classic), + ("IT", EncryptionHint::Classic), + ("FR", EncryptionHint::Classic), + ("SP", EncryptionHint::Classic), ] .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(), }, @@ -106,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::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, SecurityMode::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(), SecurityMode::PreQuantum, None) + .add_attribute(missing_dimension.clone(), EncryptionHint::Classic, None) .is_err()); // Remove research attribute @@ -151,14 +154,14 @@ fn test_edit_anarchic_attributes() { structure .add_attribute( QualifiedAttribute::new("DimensionTest", "Attr1"), - SecurityMode::PreQuantum, + EncryptionHint::Classic, None, ) .unwrap(); structure .add_attribute( QualifiedAttribute::new("DimensionTest", "Attr2"), - SecurityMode::PreQuantum, + EncryptionHint::Classic, None, ) .unwrap(); @@ -217,7 +220,7 @@ fn test_edit_hierarchic_attributes() { structure .add_attribute( QualifiedAttribute::new("SEC", "OTHER"), - SecurityMode::PreQuantum, + EncryptionHint::Classic, None, ) .unwrap(); @@ -246,7 +249,7 @@ fn test_edit_hierarchic_attributes() { structure .add_attribute( QualifiedAttribute::new("SEC", "LOW"), - SecurityMode::PreQuantum, + EncryptionHint::Classic, None, ) .unwrap(); @@ -283,7 +286,7 @@ fn test_edit_hierarchic_attributes() { structure .add_attribute( QualifiedAttribute::new("SEC", "MID"), - SecurityMode::PreQuantum, + EncryptionHint::Classic, Some("LOW"), ) .unwrap(); diff --git a/src/abe/traits.rs b/src/abe/traits.rs new file mode 100644 index 00000000..2b7ef1c4 --- /dev/null +++ b/src/abe/traits.rs @@ -0,0 +1,66 @@ +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< + 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. + /// + /// # 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/ae.rs b/src/ae.rs deleted file mode 100644 index 90c488c1..00000000 --- a/src/ae.rs +++ /dev/null @@ -1,36 +0,0 @@ -use cosmian_crypto_core::{ - reexport::{rand_core::CryptoRngCore, zeroize::Zeroizing}, - Aes256Gcm, 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( - cosmian_crypto_core::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) - } -} diff --git a/src/base.rs b/src/base.rs new file mode 100644 index 00000000..dd9acca9 --- /dev/null +++ b/src/base.rs @@ -0,0 +1,367 @@ +use crate::{ + abe::{traits::KemAc, AccessPolicy}, + providers::{MlKem, PreQuantumKem}, + AccessStructure, Covercrypt, Error, MasterPublicKey, MasterSecretKey, UserSecretKey, XEnc, +}; +use cosmian_crypto_core::{ + bytes_ser_de::Serializable, + kdf::Hasher, + reexport::{rand_core::CryptoRngCore, tiny_keccak::Sha3, zeroize::ZeroizeOnDrop}, + traits::{kem_to_pke::GenericPKE, KEM}, + Secret, SymmetricKey, +}; + +#[derive(Debug)] +pub enum AbeDKey { + Master(Covercrypt, MasterSecretKey), + User(Covercrypt, UserSecretKey), +} + +// All secret keys are zeroized on drop. +impl ZeroizeOnDrop for AbeDKey {} + +#[derive(Debug, ZeroizeOnDrop)] +pub enum DKey { + AbeScheme(AbeDKey), + PreQuantum(>::DecapsulationKey), + PostQuantum(>::DecapsulationKey), + Hybridized( + >::DecapsulationKey, + >::DecapsulationKey, + ), +} + +impl DKey { + 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( + "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 key".to_string(), + )), + } + } + + 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 re-key non ABE master key".to_string(), + )), + } + } + + 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 key".to_string(), + )), + } + } + + 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 generate user secret key using a non ABE master key".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 re-encapsulate: invalid object types".to_string(), + )), + } + } +} + +#[derive(Debug, Clone)] +pub enum EKey { + AbeScheme(Covercrypt, MasterPublicKey, Option), + PreQuantum(>::EncapsulationKey), + PostQuantum(>::EncapsulationKey), + Hybridized( + >::EncapsulationKey, + >::EncapsulationKey, + ), +} + +impl EKey { + /// Sets the encapsulation key to use the provided access polity. + pub fn set_access_policy(&mut self, access_policy: 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(XEnc), + PreQuantum(>::Encapsulation), + PostQuantum(>::Encapsulation), + Hybridized( + >::Encapsulation, + >::Encapsulation, + ), +} + +#[derive(Debug, Clone)] +pub enum Configuration { + AbeScheme, + PreQuantum, + PostQuantum, + Hybridized, +} + +impl Configuration { + pub fn keygen(&self, rng: &mut impl CryptoRngCore) -> Result<(DKey, EKey), Error> { + match self { + Self::AbeScheme => { + let cc = 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))) + } + 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), + )) + } + } + } +} + +/// 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; + +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))) + } + 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))) + } + } + } + + 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).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)) => { + 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())) + } + (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(), + )), + } + } +} + +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 key_ = ConfigurableKEM::dec(&usk, &enc).unwrap(); + assert!(key != key_); + } + + #[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_); + + let (sk, _) = Configuration::Hybridized.keygen(&mut rng).unwrap(); + let key_ = ConfigurableKEM::dec(&sk, &enc).unwrap(); + assert!(key != key_); + } +} diff --git a/src/core/nike/p256.rs b/src/core/nike/p256.rs deleted file mode 100644 index ac7ca4a8..00000000 --- a/src/core/nike/p256.rs +++ /dev/null @@ -1,434 +0,0 @@ -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 p256::{ProjectivePoint, Scalar}; -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); - -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)) - } - - fn hash(seed: &[u8]) -> 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); - } -} diff --git a/src/core/nike/r25519.rs b/src/core/nike/r25519.rs deleted file mode 100644 index be87bbaa..00000000 --- a/src/core/nike/r25519.rs +++ /dev/null @@ -1,403 +0,0 @@ -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 cosmian_crypto_core::{ - bytes_ser_de::{Serializable, Serializer}, - reexport::{ - rand_core::CryptoRngCore, - tiny_keccak::{Hasher, Sha3}, - zeroize::Zeroize, - }, - CryptoCoreError, R25519PrivateKey as Scalar, R25519PublicKey as EcPoint, -}; - -use crate::{ - traits::{Group, KeyHomomorphicNike, Nike, One, Ring, Sampling, Zero}, - Error, -}; - -#[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)) - } - - fn hash(seed: &[u8]) -> 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/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/kem.rs b/src/kem.rs new file mode 100644 index 00000000..7ce583b4 --- /dev/null +++ b/src/kem.rs @@ -0,0 +1,805 @@ +#![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::SeedableRng, + traits::{cyclic_group_to_kem::GenericKem, KEM}, + CryptoCoreError, CsRng, +}; +use cosmian_openssl_provider::{hash::Sha256, kem::MonadicKEM, p256::P256}; +use cosmian_rust_curve25519_provider::R25519; +use zeroize::Zeroizing; + +// 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 = CryptoCoreError; + + 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), + } + } + + fn read(de: &mut Deserializer) -> Result { + match de.read::()? { + 1 => Ok(Self::P256), + 2 => Ok(Self::R25519), + n => Err(CryptoCoreError::GenericDeserializationError(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 = CryptoCoreError; + + 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), + } + } + + fn read(de: &mut Deserializer) -> Result { + match de.read::()? { + 1 => Ok(Self::MlKem512), + 2 => Ok(Self::MlKem768), + n => Err(CryptoCoreError::GenericDeserializationError(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 = CryptoCoreError; + + 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)), + 4 => Ok(Self::Abe), + n => Err(CryptoCoreError::GenericDeserializationError(format!( + "{n} is not a valid KEM tag" + ))), + } + } +} + +// 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. + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ConfigurableKemDk(KemTag, Zeroizing>); + +impl ConfigurableKemDk { + 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 + } +} + +impl Serializable for ConfigurableKemDk { + type Error = CryptoCoreError; + + fn length(&self) -> usize { + self.0.length() + self.1.length() + } + + fn write(&self, ser: &mut Serializer) -> Result { + Ok(ser.write(&self.0)? + ser.write(&self.1)?) + } + + fn read(de: &mut Deserializer) -> Result { + Ok(Self(de.read()?, de.read()?)) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ConfigurableKemEk(KemTag, Zeroizing>); + +impl ConfigurableKemEk { + 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 + } +} + +impl Serializable for ConfigurableKemEk { + type Error = CryptoCoreError; + + fn length(&self) -> usize { + self.0.length() + self.1.length() + } + + fn write(&self, ser: &mut Serializer) -> Result { + Ok(ser.write(&self.0)? + ser.write(&self.1)?) + } + + fn read(de: &mut Deserializer) -> Result { + Ok(Self(de.read()?, de.read()?)) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ConfigurableKemEnc(KemTag, Zeroizing>); + +impl ConfigurableKemEnc { + 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 + } +} + +impl Serializable for ConfigurableKemEnc { + type Error = CryptoCoreError; + + fn length(&self) -> usize { + self.0.length() + self.1.length() + } + + fn write(&self, ser: &mut Serializer) -> Result { + Ok(ser.write(&self.0)? + ser.write(&self.1)?) + } + + fn read(de: &mut Deserializer) -> Result { + Ok(Self(de.read()?, de.read()?)) + } +} + +#[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 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 tag = ek.get_tag(); + let ek = + >::EncapsulationKey::deserialize(ek.get_bytes()).map_err(|e| { + Error::ConversionFailed(format!( + "failed deserializing the encapsulation key in configurable KEM: {e}" + )) + })?; + + 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 encapsulation in configurable KEM: {e}" + )) + })?, + ), + )) +} + +fn generic_dec>( + dk: &ConfigurableKemDk, + enc: &ConfigurableKemEnc, +) -> Result>, Error> +where + Kem::DecapsulationKey: Serializable, +{ + let tag = dk.get_tag(); + + if tag != enc.get_tag() { + return Err(Error::OperationNotPermitted(format!( + "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}")))?; + + 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 { + #[allow(clippy::too_many_arguments)] + pub fn keygen( + tag: KemTag, + access_structure: Option, + ) -> Result<(ConfigurableKemDk, ConfigurableKemEk), Error> { + match tag { + KemTag::PreQuantum(PreQuantumKemTag::P256) => { + generic_keygen::<{ P256Kem::KEY_LENGTH }, P256Kem>(tag) + } + KemTag::PreQuantum(PreQuantumKemTag::R25519) => { + generic_keygen::<{ R25519Kem::KEY_LENGTH }, R25519Kem>(tag) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem512) => { + generic_keygen::<{ MlKem512::KEY_LENGTH }, MlKem512>(tag) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem768) => { + generic_keygen::<{ MlKem768::KEY_LENGTH }, MlKem768>(tag) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem512) => { + generic_keygen::< + { P256Kem::KEY_LENGTH }, + KemCombiner<{ P256Kem::KEY_LENGTH }, P256Kem, MlKem512>, + >(tag) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem768) => { + generic_keygen::< + { P256Kem::KEY_LENGTH }, + KemCombiner<{ P256Kem::KEY_LENGTH }, P256Kem, MlKem768>, + >(tag) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem512) => { + generic_keygen::< + { R25519Kem::KEY_LENGTH }, + KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem512>, + >(tag) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem768) => { + generic_keygen::< + { R25519Kem::KEY_LENGTH }, + KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem768>, + >(tag) + } + 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(( + ConfigurableKemDk(tag, msk.serialize()?), + ConfigurableKemEk(tag, mpk.serialize()?), + )) + } + } + } + + pub fn enc( + ek: &ConfigurableKemEk, + access_policy: Option<&AccessPolicy>, + ) -> Result<(Zeroizing>, ConfigurableKemEnc), Error> { + match ek.get_tag() { + KemTag::PreQuantum(PreQuantumKemTag::P256) => { + generic_enc::<{ P256Kem::KEY_LENGTH }, P256Kem>(ek) + } + KemTag::PreQuantum(PreQuantumKemTag::R25519) => { + generic_enc::<{ R25519Kem::KEY_LENGTH }, R25519Kem>(ek) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem512) => { + generic_enc::<{ MlKem512::KEY_LENGTH }, MlKem512>(ek) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem768) => { + 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) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem768) => { + generic_enc::< + { P256Kem::KEY_LENGTH }, + KemCombiner<{ P256Kem::KEY_LENGTH }, P256Kem, MlKem768>, + >(ek) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem512) => { + generic_enc::< + { R25519Kem::KEY_LENGTH }, + KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem512>, + >(ek) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem768) => { + generic_enc::< + { R25519Kem::KEY_LENGTH }, + KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem768>, + >(ek) + } + KemTag::Abe => { + let ap = access_policy.ok_or_else(|| { + Error::OperationNotPermitted( + "cannot create a Covercrypt encapsulation without an access policy" + .to_owned(), + ) + })?; + 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()?))) + } + } + } + + pub fn dec( + dk: &ConfigurableKemDk, + enc: &ConfigurableKemEnc, + ) -> Result>, Error> { + match dk.get_tag() { + KemTag::PreQuantum(PreQuantumKemTag::P256) => { + generic_dec::<{ P256Kem::KEY_LENGTH }, P256Kem>(dk, enc) + } + KemTag::PreQuantum(PreQuantumKemTag::R25519) => { + generic_dec::<{ R25519Kem::KEY_LENGTH }, R25519Kem>(dk, enc) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem512) => { + generic_dec::<{ MlKem512::KEY_LENGTH }, MlKem512>(dk, enc) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem768) => { + 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, enc) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem768) => { + generic_dec::< + { P256Kem::KEY_LENGTH }, + KemCombiner<{ P256Kem::KEY_LENGTH }, P256Kem, MlKem768>, + >(dk, enc) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem512) => { + generic_dec::< + { R25519Kem::KEY_LENGTH }, + KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem512>, + >(dk, enc) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem768) => { + generic_dec::< + { R25519Kem::KEY_LENGTH }, + KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem768>, + >(dk, enc) + } + KemTag::Abe => { + if enc.get_tag() != KemTag::Abe { + return Err(Error::OperationNotPermitted(format!( + "heterogeneous decapsulation-key and encapsulation tags: {:?} != {:?}", + KemTag::Abe, + enc.get_tag(), + ))); + } + + let enc = XEnc::deserialize(enc.get_bytes()).map_err(|e| { + Error::ConversionFailed(format!( + "failed deserializing the CoverCrypt encapsulation in configurable KEM: {e}" + )) + })?; + + let usk = UserSecretKey::deserialize(dk.get_bytes()).map_err(|e| { + Error::ConversionFailed(format!( + "failed deserializing the CoverCrypt encapsulation in configurable KEM: {e}" + )) + })?; + + 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(); + 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_); + } + + 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 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::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_); + } +} diff --git a/src/lib.rs b/src/lib.rs index 406a1448..f8e3cb81 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; - -mod abe_policy; -mod ae; -mod core; +mod abe; mod data_struct; -mod encrypted_header; - -pub mod api; -pub mod traits; - -pub use abe_policy::{AccessStructure, QualifiedAttribute, SecurityMode}; +mod error; +mod kem; +mod providers; #[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 use abe::*; pub use error::Error; +pub use kem::*; diff --git a/src/providers.rs b/src/providers.rs new file mode 100644 index 00000000..a50688f8 --- /dev/null +++ b/src/providers.rs @@ -0,0 +1,5 @@ +pub mod kem; +mod nike; + +pub(crate) use kem::MlKem; +pub(crate) use nike::ElGamal; diff --git a/src/core/kem.rs b/src/providers/kem.rs similarity index 71% rename from src/core/kem.rs rename to src/providers/kem.rs index f798da4a..47e9576e 100644 --- a/src/core/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/core/kem/mlkem.rs b/src/providers/kem/mlkem.rs similarity index 81% rename from src/core/kem/mlkem.rs rename to src/providers/kem/mlkem.rs index d3817919..da7932c4 100644 --- a/src/core/kem/mlkem.rs +++ b/src/providers/kem/mlkem.rs @@ -1,8 +1,13 @@ -use crate::{core::SHARED_SECRET_LENGTH, traits::Kem, Error}; +use crate::Error; +use core::ops::Deref; use cosmian_crypto_core::{ bytes_ser_de::{Deserializer, Serializable, Serializer}, - reexport::{rand_core::CryptoRngCore, zeroize::Zeroize}, - Secret, + reexport::{ + rand_core::CryptoRngCore, + zeroize::{Zeroize, ZeroizeOnDrop}, + }, + traits::KEM, + CryptoCoreError, Secret, SymmetricKey, }; use ml_kem::{ array::Array, @@ -10,13 +15,21 @@ use ml_kem::{ EncodedSizeUser, KemCore, }; +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 = Error; + type Error = CryptoCoreError; fn length(&self) -> usize { $ek_len @@ -40,6 +53,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 { @@ -48,7 +64,7 @@ macro_rules! make_mlkem { } impl Serializable for $dk { - type Error = Error; + type Error = CryptoCoreError; fn length(&self) -> usize { $dk_len @@ -72,15 +88,23 @@ 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 = 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 { @@ -91,15 +115,13 @@ macro_rules! make_mlkem { } } + #[derive(Debug, Copy, Clone)] 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; fn keygen( @@ -112,29 +134,28 @@ 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()) } } }; } -#[cfg(feature = "mlkem-512")] make_mlkem!( MlKem512, EncapsulationKey512, @@ -145,7 +166,6 @@ make_mlkem!( 768 ); -#[cfg(feature = "mlkem-768")] make_mlkem!( MlKem768, EncapsulationKey768, @@ -180,8 +200,6 @@ mod tests { }; } - #[cfg(feature = "mlkem-512")] test_mlkem!(MlKem512, test_mlkem512); - #[cfg(feature = "mlkem-768")] test_mlkem!(MlKem768, test_mlkem768); } diff --git a/src/core/nike.rs b/src/providers/nike.rs similarity index 55% rename from src/core/nike.rs rename to src/providers/nike.rs index 6ad18b96..6f496341 100644 --- a/src/core/nike.rs +++ b/src/providers/nike.rs @@ -2,13 +2,7 @@ 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; +pub use cosmian_rust_curve25519_provider::R25519 as ElGamal; #[cfg(feature = "p-256")] -pub use p256::P256 as ElGamal; +pub use cosmian_openssl_provider::p256::P256 as ElGamal; diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs index 8ad782d2..bb16f4ae 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, traits::KemAc, AccessPolicy, Covercrypt, EncryptionHint, + QualifiedAttribute, }; #[test] @@ -34,7 +34,7 @@ mod tests { let _ = &mut msk.access_structure.add_attribute( QualifiedAttribute::new("DPT", "Sales"), - SecurityMode::PreQuantum, + EncryptionHint::Classic, None, )?; let mpk = cc.update_msk(&mut msk)?; diff --git a/src/traits.rs b/src/traits.rs index 6cb9604d..8b137891 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,198 +1 @@ -use cosmian_crypto_core::{ - reexport::{rand_core::CryptoRngCore, zeroize::Zeroizing}, - Secret, 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; -} - -pub trait Zero { - fn zero() -> Self; - fn is_zero(&self) -> bool; -} - -pub trait One { - fn one() -> Self; - fn is_one(&self) -> bool; -} - -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 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>, - >, -{ -}