From edf4b9c1146d97bab43bac7700bc360c359be2da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Fri, 19 Jan 2024 19:59:33 +0100 Subject: [PATCH] wip: feat: refactor key structure --- src/core/mod.rs | 148 +++++++++++++++++++++++++------------- src/core/pq_kem/kyber.rs | 29 ++++++++ src/core/pq_kem/kyber.rs~ | 25 +++++++ src/core/pq_kem/mod.rs | 1 + src/core/pq_kem/mod.rs~ | 1 + src/core/primitives.rs | 27 ++++--- src/core/serialization.rs | 22 +++--- 7 files changed, 177 insertions(+), 76 deletions(-) create mode 100644 src/core/pq_kem/kyber.rs create mode 100644 src/core/pq_kem/kyber.rs~ create mode 100644 src/core/pq_kem/mod.rs create mode 100644 src/core/pq_kem/mod.rs~ diff --git a/src/core/mod.rs b/src/core/mod.rs index 8648b952..b9a8a449 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,104 +1,152 @@ -//! Implements the core functionalities of `Covercrypt`. - use std::{ collections::{HashMap, HashSet}, hash::Hash, - ops::Deref, }; -use cosmian_crypto_core::{R25519PrivateKey, R25519PublicKey, SymmetricKey}; -use pqc_kyber::{KYBER_INDCPA_BYTES, KYBER_INDCPA_PUBLICKEYBYTES, KYBER_INDCPA_SECRETKEYBYTES}; -use zeroize::ZeroizeOnDrop; +use cosmian_crypto_core::{R25519PrivateKey as Scalar, R25519PublicKey as EcPoint, SymmetricKey}; use crate::{ abe_policy::Partition, data_struct::{RevisionMap, RevisionVec}, }; +use self::pq_kem::kyber::{KyberPublicKey, KyberSecretKey, KYBER_INDCPA_BYTES}; + #[macro_use] pub mod macros; pub mod api; +pub mod pq_kem; pub mod primitives; pub mod serialization; -/// The symmetric key is 32 bytes long to provide 128 bits of post-quantum -/// security. +/// The length of the keys encapsulated by Covercrypt. +/// +/// They are 32 bytes long to enable reaching 128 bits of post-quantum security +/// when using it with a sensible DEM. pub const SYM_KEY_LENGTH: usize = 32; -/// The length of the KMAC key. +/// The length of the KMAC key used to sign user secret keys. +/// +/// It is only 16-byte long because no post-quantum security is needed for +/// now. An upgraded signature scheme can still be added later when quantum +/// computers become available. pub const KMAC_KEY_LENGTH: usize = 16; -/// The length of the KMAC output. +/// The length of the KMAC signature. const KMAC_LENGTH: usize = 32; + +/// KMAC signature is used to guarantee the integrity of the user secret keys. type KmacSignature = [u8; KMAC_LENGTH]; -/// Length of the `Covercrypt` tag +/// Length of the Covercrypt early abort tag const TAG_LENGTH: usize = 16; -type Tag = [u8; TAG_LENGTH]; -/// Kyber public key length -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct KyberPublicKey([u8; KYBER_INDCPA_PUBLICKEYBYTES]); +/// Covercrypt early abort tag is used during the decapsulation to verify the +/// integrity of the result. +type Tag = [u8; TAG_LENGTH]; -impl Deref for KyberPublicKey { - type Target = [u8]; +/// Number of colluding users needed to escape tracing. +const TRACING_LEVEL: usize = 2; - fn deref(&self) -> &Self::Target { - &self.0 - } +/// The Covercrypt subkeys hold the DH secret key associated to a coordinate. +/// Subkeys can be hybridized, in which case they also hold a PQ-KEM secret key. +#[derive(Debug, PartialEq, Eq)] +enum PublicSubkey { + Hybridized { pki: KyberPublicKey, hi: EcPoint }, + Classic { hi: EcPoint }, } -/// Kyber secret key length -#[derive(Debug, Clone, PartialEq, Eq, Hash, ZeroizeOnDrop)] -pub struct KyberSecretKey([u8; KYBER_INDCPA_SECRETKEYBYTES]); - -impl Deref for KyberSecretKey { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - &self.0 - } +/// The Covercrypt subkeys hold the DH secret key associated to a coordinate. +/// Subkeys can be hybridized, in which case they also hold a PQ-KEM secret key. +#[derive(Debug, PartialEq, Eq)] +enum SecretSubkey { + Hybridized { ski: KyberSecretKey, xi: Scalar }, + Classic { xi: Scalar }, } -pub(super) type PublicSubkey = (Option, R25519PublicKey); - +/// Covercrypt user IDs are used to make user keys unique and traceable. +/// +/// They are composed of a sequence of `LENGTH` scalars. +#[derive(Debug, PartialEq, Eq, Hash)] +struct Uid([Scalar; LENGTH]); + +/// Covercrypt tracing secret key. +/// +/// It is composed of: +/// - `TRACING_LEVEL + 1` scalars; +/// - the user IDs. #[derive(Debug, PartialEq, Eq)] -pub struct MasterPublicKey { - g1: R25519PublicKey, - g2: R25519PublicKey, - pub(crate) subkeys: HashMap, +struct TracingSecretKey { + s: Scalar, + scalars: [Scalar; TRACING_LEVEL], + uids: HashSet>, } -pub(super) type SecretSubkey = (Option, R25519PrivateKey); +/// Covercrypt tracing public key. +#[derive(Debug, PartialEq, Eq)] +struct TracingPublicKey([EcPoint; TRACING_LEVEL]); +/// The Covercrypt Master Secret Key (MSK). +/// +/// It is composed of: +/// - the tracing secret key used to produce challenges to trace user keys; +/// - the secret subkeys associated to each universal coordinates. #[derive(Debug, PartialEq, Eq)] pub struct MasterSecretKey { - s: R25519PrivateKey, - s1: R25519PrivateKey, - s2: R25519PrivateKey, - pub(crate) subkeys: RevisionMap, + tsk: TracingSecretKey, + subkeys: RevisionMap, kmac_key: Option>, } +/// Covercrypt Public Key (PK). +/// +/// It is composed of: +/// - the tracing public key; +/// - the public subkeys associated to each universal coordinate. +#[derive(Debug, PartialEq, Eq)] +pub struct MasterPublicKey { + tpk: TracingPublicKey, + subkeys: HashMap, +} + +/// Covercrypt User Secret Key (USK). +/// +/// It is composed of: +/// - a user ID (pair of scalars); +/// - the subkeys associated to the coordinates derived from the user +/// decryption policy; +/// - a signature from the MSK that guarantees its integrity. #[derive(Debug, PartialEq, Eq)] pub struct UserSecretKey { - a: R25519PrivateKey, - b: R25519PrivateKey, - pub(crate) subkeys: RevisionVec, - kmac: Option, + id: Uid, + subkeys: RefCell>, + msk_signature: Option, } +/// Encapsulation of a `SYM_KEY_LENGTH`-byte key for a given coordinate. +/// +/// In case the security level of the associated coordinate was set to +/// post-quantum secure, the key encapsulation is hybridized. This implies a +/// significant size overhead. #[derive(Debug, Clone, Hash, PartialEq, Eq)] enum KeyEncapsulation { - ClassicEncapsulation(Box<[u8; SYM_KEY_LENGTH]>), - HybridEncapsulation(Box<[u8; KYBER_INDCPA_BYTES]>), + Classic([u8; SYM_KEY_LENGTH]), + Hybridized([u8; KYBER_INDCPA_BYTES]), } +/// Covercrypt encapsulation. +/// +/// It is created for a subset of universal coordinates. One key encapsulation +/// is created per associated coordinate. +/// +/// It is composed of: +/// - the early abort tag; +/// - the tracing EC points; +/// - the coordinate encapsulations. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Encapsulation { - c1: R25519PublicKey, - c2: R25519PublicKey, tag: Tag, - encs: HashSet, + tracing_points: [EcPoint; TRACING_LEVEL], + coordinate_encapsulations: HashSet, } diff --git a/src/core/pq_kem/kyber.rs b/src/core/pq_kem/kyber.rs new file mode 100644 index 00000000..577cb7ec --- /dev/null +++ b/src/core/pq_kem/kyber.rs @@ -0,0 +1,29 @@ +use pqc_kyber::{KYBER_INDCPA_PUBLICKEYBYTES, KYBER_INDCPA_SECRETKEYBYTES}; +use std::ops::Deref; +use zeroize::Zeroizing; + +pub use pqc_kyber::KYBER_INDCPA_BYTES; + +/// Kyber public key length +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct KyberPublicKey([u8; KYBER_INDCPA_PUBLICKEYBYTES]); + +impl Deref for KyberPublicKey { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// Kyber secret key. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct KyberSecretKey(Zeroizing<[u8; KYBER_INDCPA_SECRETKEYBYTES]>); + +impl Deref for KyberSecretKey { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.0.as_slice() + } +} diff --git a/src/core/pq_kem/kyber.rs~ b/src/core/pq_kem/kyber.rs~ new file mode 100644 index 00000000..22cfccdb --- /dev/null +++ b/src/core/pq_kem/kyber.rs~ @@ -0,0 +1,25 @@ +use pqc_kyber::{KYBER_INDCPA_BYTES, KYBER_INDCPA_PUBLICKEYBYTES, KYBER_INDCPA_SECRETKEYBYTES}; + +/// Kyber public key length +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct KyberPublicKey([u8; KYBER_INDCPA_PUBLICKEYBYTES]); + +impl Deref for KyberPublicKey { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// Kyber secret key. +#[derive(Debug, Clone, PartialEq, Eq, Hash, ZeroizeOnDrop)] +pub struct KyberSecretKey([u8; KYBER_INDCPA_SECRETKEYBYTES]); + +impl Deref for KyberSecretKey { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/src/core/pq_kem/mod.rs b/src/core/pq_kem/mod.rs new file mode 100644 index 00000000..19c6bc24 --- /dev/null +++ b/src/core/pq_kem/mod.rs @@ -0,0 +1 @@ +pub mod kyber; diff --git a/src/core/pq_kem/mod.rs~ b/src/core/pq_kem/mod.rs~ new file mode 100644 index 00000000..8fbc3122 --- /dev/null +++ b/src/core/pq_kem/mod.rs~ @@ -0,0 +1 @@ +mod kyber; diff --git a/src/core/primitives.rs b/src/core/primitives.rs index e8b45ba5..dc38a241 100644 --- a/src/core/primitives.rs +++ b/src/core/primitives.rs @@ -1,6 +1,3 @@ -//! Implements the cryptographic primitives of `Covercrypt`, based on -//! `bib/Covercrypt.pdf`. - use std::{ collections::{HashMap, HashSet, LinkedList}, mem::take, @@ -68,7 +65,7 @@ fn compute_user_key_kmac(msk: &MasterSecretKey, usk: &UserSecretKey) -> Option Result<(), Error> { let kmac = compute_user_key_kmac(msk, usk); - if usk.kmac != kmac { + if usk.msk_signature != kmac { return Err(Error::KeyError( "The provided user key is corrupted.".to_string(), )); @@ -231,10 +228,10 @@ pub fn keygen( let mut usk = UserSecretKey { a, b, - subkeys, - kmac: None, + subkeys: RefCell::new(subkeys), + msk_signature: None, }; - usk.kmac = compute_user_key_kmac(msk, &usk); + usk.msk_signature = compute_user_key_kmac(msk, &usk); Ok(usk) } @@ -268,9 +265,9 @@ pub fn encaps( let mut coin = Zeroizing::new([0; KYBER_SYMBYTES]); rng.fill_bytes(&mut *coin); indcpa_enc(&mut epq_i, &e_i, pk_i, &*coin); - encs.insert(KeyEncapsulation::HybridEncapsulation(Box::new(epq_i))); + encs.insert(KeyEncapsulation::Hybridized(Box::new(epq_i))); } else { - encs.insert(KeyEncapsulation::ClassicEncapsulation(Box::new(e_i))); + encs.insert(KeyEncapsulation::Classic(Box::new(e_i))); } } // else unknown target partition @@ -309,7 +306,7 @@ pub fn decaps( // partitions. for (sk_j, x_j) in usk.subkeys.bfs() { let e_j = match encapsulation_i { - KeyEncapsulation::HybridEncapsulation(epq_i) => { + KeyEncapsulation::Hybridized(epq_i) => { if let Some(sk_j) = sk_j { let mut e_j = [0; SYM_KEY_LENGTH]; indcpa_dec(&mut e_j, &**epq_i, sk_j); @@ -319,7 +316,7 @@ pub fn decaps( continue; } } - KeyEncapsulation::ClassicEncapsulation(e_i) => **e_i, + KeyEncapsulation::Classic(e_i) => **e_i, }; let mut seed = Zeroizing::new([0; SYM_KEY_LENGTH]); kdf256!(&mut *seed, &(&precomp * x_j).to_bytes()); @@ -503,7 +500,7 @@ pub fn refresh( .collect::>(); // Update user key KMAC - usk.kmac = compute_user_key_kmac(msk, usk); + usk.msk_signature = compute_user_key_kmac(msk, usk); Ok(()) } @@ -572,7 +569,7 @@ mod tests { // The encapsulation holds a unique, hybridized key encapsulation. assert_eq!(encapsulation.encs.len(), 1); for key_encapsulation in &encapsulation.encs { - if let KeyEncapsulation::ClassicEncapsulation(_) = key_encapsulation { + if let KeyEncapsulation::Classic(_) = key_encapsulation { panic!("Wrong hybridization type"); } } @@ -615,7 +612,7 @@ mod tests { // The developer now has a hybridized key. assert_eq!(dev_usk.subkeys.count_elements(), 1); for key_encapsulation in &encapsulation.encs { - if let KeyEncapsulation::ClassicEncapsulation(_) = key_encapsulation { + if let KeyEncapsulation::Classic(_) = key_encapsulation { panic!("Wrong hybridization type"); } } @@ -625,7 +622,7 @@ mod tests { // Client encapsulation holds a unique, classic key encapsulation. assert_eq!(new_encapsulation.encs.len(), 1); for key_encapsulation in &new_encapsulation.encs { - if let KeyEncapsulation::HybridEncapsulation(_) = key_encapsulation { + if let KeyEncapsulation::Hybridized(_) = key_encapsulation { panic!("Wrong hybridization type"); } } diff --git a/src/core/serialization.rs b/src/core/serialization.rs index 0839fd06..540289ef 100644 --- a/src/core/serialization.rs +++ b/src/core/serialization.rs @@ -181,7 +181,7 @@ impl Serializable for UserSecretKey { fn length(&self) -> usize { let mut length = 2 * R25519PrivateKey::LENGTH - + self.kmac.as_ref().map_or_else(|| 0, |kmac| kmac.len()) + + self.msk_signature.as_ref().map_or_else(|| 0, |kmac| kmac.len()) // subkeys serialization + to_leb128_len(self.subkeys.len()) + self.subkeys.count_elements() * R25519PrivateKey::LENGTH; @@ -209,7 +209,7 @@ impl Serializable for UserSecretKey { n += ser.write_array(&x_i.to_bytes())?; } } - if let Some(kmac) = &self.kmac { + if let Some(kmac) = &self.msk_signature { n += ser.write_array(kmac)?; } Ok(n) @@ -238,8 +238,8 @@ impl Serializable for UserSecretKey { Ok(Self { a, b, - subkeys, - kmac, + subkeys: RefCell::new(subkeys), + msk_signature: kmac, }) } } @@ -249,19 +249,19 @@ impl Serializable for KeyEncapsulation { fn length(&self) -> usize { match self { - Self::ClassicEncapsulation(e_i) => 1 + e_i.len(), - Self::HybridEncapsulation(epq_i) => 1 + epq_i.len(), + Self::Classic(e_i) => 1 + e_i.len(), + Self::Hybridized(epq_i) => 1 + epq_i.len(), } } fn write(&self, ser: &mut Serializer) -> Result { let mut n = 0; match self { - Self::ClassicEncapsulation(e_i) => { + Self::Classic(e_i) => { n += ser.write_leb128_u64(0)?; n += ser.write_array(&**e_i)?; } - Self::HybridEncapsulation(epq_i) => { + Self::Hybridized(epq_i) => { n += ser.write_leb128_u64(1)?; n += ser.write_array(&**epq_i)?; } @@ -272,9 +272,9 @@ impl Serializable for KeyEncapsulation { fn read(de: &mut Deserializer) -> Result { let is_hybridized = de.read_leb128_u64()?; if is_hybridized == 1 { - Ok(Self::HybridEncapsulation(Box::new(de.read_array()?))) + Ok(Self::Hybridized(Box::new(de.read_array()?))) } else { - Ok(Self::ClassicEncapsulation(Box::new(de.read_array()?))) + Ok(Self::Classic(Box::new(de.read_array()?))) } } } @@ -464,7 +464,7 @@ mod tests { assert_eq!(usk.a, usk_.a, "Wrong `UserSecretKey` deserialization."); assert_eq!(usk.b, usk_.b, "Wrong `UserSecretKey` deserialization."); assert_eq!( - usk.kmac, usk_.kmac, + usk.msk_signature, usk_.msk_signature, "Wrong `UserSecretKey` deserialization." ); assert_eq!(usk, usk_, "Wrong `UserSecretKey` deserialization.");