From 372531936d2c71a76e3bc6fcd6ef1bdb08f2ee3a Mon Sep 17 00:00:00 2001 From: Hugo Rosenkranz-Costa Date: Fri, 12 Jan 2024 16:38:40 +0100 Subject: [PATCH] Refacto: update `Policy` & `Keys` data structure (#119) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change `Axis` to `Dimension` with a clear distinction between `Ordered` and `Unordered`. - Create a `Dictionary` data structure to store and update `Ordered Dimension` efficiently - Change the data structure of `MasterSecretKey` and `UserSecretKey` to keep track of subkeys version. - `Policy` does not count the attribute rotations anymore as they are stored in the subkeys. - a `UserSecretKey` can now be refreshed without any external `Policy` information. Co-authored-by: Théophile BRÉZOT --- CHANGELOG.md | 10 + Cargo.toml | 2 +- benches/benches.rs | 75 ++-- examples/runme.rs | 12 +- src/abe_policy/access_policy.rs | 16 +- src/abe_policy/attribute.rs | 7 + src/abe_policy/dimension.rs | 267 +++++------ src/abe_policy/partitions.rs | 16 +- src/abe_policy/policy.rs | 286 ++++-------- src/abe_policy/policy_versions.rs | 102 ++--- src/abe_policy/tests.rs | 49 +- src/core/api.rs | 77 +++- src/core/mod.rs | 16 +- src/core/primitives.rs | 422 ++++++++++++------ src/core/serialization.rs | 191 ++++---- src/data_struct/README.md | 75 ++++ src/data_struct/dictionary.rs | 355 +++++++++++++++ src/data_struct/error.rs | 44 ++ src/data_struct/mod.rs | 8 + src/data_struct/revision_map.rs | 225 ++++++++++ src/data_struct/revision_vec.rs | 233 ++++++++++ src/lib.rs | 1 + src/test_utils/mod.rs | 239 +++++----- src/test_utils/non_regression.rs | 6 +- .../tests_data/non_regression_vector.json | 2 +- src/test_utils/tests_data/policy_v2.json | 2 +- 26 files changed, 1868 insertions(+), 870 deletions(-) create mode 100644 src/data_struct/README.md create mode 100644 src/data_struct/dictionary.rs create mode 100644 src/data_struct/error.rs create mode 100644 src/data_struct/mod.rs create mode 100644 src/data_struct/revision_map.rs create mode 100644 src/data_struct/revision_vec.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bcf8d81..436885b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to this project will be documented in this file. +## [Unreleased] + +### Features + +- Change `Axis` to `Dimension` with a clear distinction between `Ordered` and `Unordered`. +- Create a `Dictionary` data structure to store and update `Ordered Dimension` efficiently +- Change the data structure of `MasterSecretKey` and `UserSecretKey` to keep track of subkeys version. +- Policy does not count the attribute rotations anymore as they are stored in the subkeys. +- a `UserSecretKey` can now be refreshed without any external `Policy` information. + ## [13.0.0] - 2023-11-06 ### Bug Fixes diff --git a/Cargo.toml b/Cargo.toml index 46100cb2..2dd3139b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmian_cover_crypt" -version = "13.0.0" +version = "13.1.0" authors = [ "Théophile Brezot ", "Bruno Grieder ", diff --git a/benches/benches.rs b/benches/benches.rs index 66bc9f30..394e1a39 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -65,10 +65,10 @@ fn policy() -> Result { fn bench_policy_editing(c: &mut Criterion) { let cover_crypt = Covercrypt::default(); let new_dep_attr = Attribute::new("Department", "Tech"); - let new_dep_name = "IT"; + let new_dep_name = "IT".to_string(); let remove_dep_attr = Attribute::new("Department", "FIN"); let old_sl_attr = Attribute::new("Security Level", "Protected"); - let new_sl_name = "Open"; + let new_sl_name = "Open".to_string(); let disable_sl_attr = Attribute::new("Security Level", "Confidential"); let mut group = c.benchmark_group("Edit Policy"); @@ -88,11 +88,13 @@ fn bench_policy_editing(c: &mut Criterion) { .add_attribute(new_dep_attr.clone(), EncryptionHint::Classic) .unwrap(); policy - .rename_attribute(&new_dep_attr, new_dep_name) + .rename_attribute(&new_dep_attr, new_dep_name.clone()) .unwrap(); policy.remove_attribute(&remove_dep_attr).unwrap(); - policy.rename_attribute(&old_sl_attr, new_sl_name).unwrap(); + policy + .rename_attribute(&old_sl_attr, new_sl_name.clone()) + .unwrap(); policy.disable_attribute(&disable_sl_attr).unwrap(); cover_crypt @@ -308,57 +310,58 @@ fn bench_header_encryption(c: &mut Criterion) { fn bench_header_decryption(c: &mut Criterion) { let policy = policy().expect("cannot generate policy"); let authenticated_data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9]; - let (user_access_policy, access_policies) = get_access_policies(); + let (user_access_policies, access_policies) = get_access_policies(); let cover_crypt = Covercrypt::default(); let (msk, mpk) = cover_crypt .generate_master_keys(&policy) .expect("cannot generate master keys"); - let user_decryption_keys: Vec<_> = user_access_policy - .iter() - .map(|ap| { - cover_crypt - .generate_user_secret_key(&msk, ap, &policy) - .expect("cannot generate user private key") - }) - .collect(); let mut group = c.benchmark_group("Header encryption and decryption"); - for (n_partitions_usk, usk) in user_decryption_keys.iter().enumerate() { + for (n_user, user_access_policy) in user_access_policies.iter().enumerate() { for (n_partition_ct, access_policy) in access_policies.iter().enumerate() { group.bench_function( &format!( "ciphertexts with {} partition(s), usk with {} partitions", n_partition_ct + 1, - n_partitions_usk + 1 + n_user + 1 ), |b| { - b.iter(|| { - let (_, encrypted_header) = EncryptedHeader::generate( - &cover_crypt, - &policy, - &mpk, - access_policy, - None, - Some(&authenticated_data), - ) - .unwrap_or_else(|_| { - panic!( - "cannot encrypt header for {} ciphertext partition(s), {} usk \ - partition(s)", - n_partition_ct + 1, - n_partitions_usk + b.iter_batched( + || { + let usk = cover_crypt + .generate_user_secret_key(&msk, user_access_policy, &policy) + .expect("cannot generate user private key"); + let (_, encrypted_header) = EncryptedHeader::generate( + &cover_crypt, + &policy, + &mpk, + access_policy, + None, + Some(&authenticated_data), ) - }); - encrypted_header - .decrypt(&cover_crypt, usk, Some(&authenticated_data)) .unwrap_or_else(|_| { panic!( - "cannot decrypt header for {} ciphertext partition(s), {} usk \ + "cannot encrypt header for {} ciphertext partition(s), {} usk \ partition(s)", n_partition_ct + 1, - n_partitions_usk + n_user ) }); - }); + (usk, encrypted_header) + }, + |(usk, encrypted_header)| { + encrypted_header + .decrypt(&cover_crypt, &usk, Some(&authenticated_data)) + .unwrap_or_else(|_| { + panic!( + "cannot decrypt header for {} ciphertext partition(s), {} \ + usk partition(s)", + n_partition_ct + 1, + n_user + ) + }); + }, + BatchSize::SmallInput, + ); }, ); } diff --git a/examples/runme.rs b/examples/runme.rs index a1229bab..70f0f1df 100644 --- a/examples/runme.rs +++ b/examples/runme.rs @@ -61,14 +61,10 @@ fn main() { assert!(encrypted_header.decrypt(&cover_crypt, &usk, None).is_ok()); // - // Rotate the `Security Level::Top Secret` attribute - policy - .rotate(&Attribute::from(("Security Level", "Top Secret"))) - .unwrap(); - - // Master keys need to be updated to take into account the policy rotation + // Rekey all keys using the `Security Level::Top Secret` attribute + let rekey_access_policy = AccessPolicy::Attr(Attribute::from(("Security Level", "Top Secret"))); cover_crypt - .update_master_keys(&policy, &mut msk, &mut mpk) + .rekey_master_keys(&rekey_access_policy, &policy, &mut msk, &mut mpk) .unwrap(); // Encrypt with rotated attribute @@ -82,7 +78,7 @@ fn main() { // refresh user secret key, do not grant old encryption access cover_crypt - .refresh_user_secret_key(&mut usk, &access_policy, &msk, &policy, false) + .refresh_user_secret_key(&mut usk, &msk, false) .unwrap(); // The user with refreshed key is able to decrypt the newly encrypted header. diff --git a/src/abe_policy/access_policy.rs b/src/abe_policy/access_policy.rs index 61188052..ab6c8ce6 100644 --- a/src/abe_policy/access_policy.rs +++ b/src/abe_policy/access_policy.rs @@ -346,7 +346,8 @@ impl AccessPolicy { /// given access policy. It is an OR expression of AND expressions. /// /// - `policy` : global policy - /// - `include_lower_attributes_from_dim` : set to `true` to combine lower attributes + /// - `include_lower_attributes_from_dim` : set to `true` to combine lower + /// attributes /// from dimension with hierarchical order pub fn to_attribute_combinations( &self, @@ -360,12 +361,13 @@ impl AccessPolicy { .get(&attr.dimension) .ok_or_else(|| Error::DimensionNotFound(attr.dimension.to_string()))?; let mut res = vec![vec![attr.clone()]]; - if let Some(order) = dim_parameters.order.as_deref() { - if include_lower_attributes_from_dim { - // add attribute values for all attributes below the given one - for name in order.iter().take_while(|&name| name != &attr.name) { - res.push(vec![Attribute::new(&attr.dimension, name)]); - } + if include_lower_attributes_from_dim && dim_parameters.is_ordered() { + // add attribute values for all attributes below the given one + for name in dim_parameters + .get_attributes_name() + .take_while(|&name| name != &attr.name) + { + res.push(vec![Attribute::new(&attr.dimension, name)]); } } Ok(res) diff --git a/src/abe_policy/attribute.rs b/src/abe_policy/attribute.rs index d33ded1f..348310fe 100644 --- a/src/abe_policy/attribute.rs +++ b/src/abe_policy/attribute.rs @@ -30,6 +30,7 @@ impl BitOr for EncryptionHint { } impl EncryptionHint { + #[must_use] pub fn new(is_hybridized: bool) -> Self { if is_hybridized { Self::Hybridized @@ -39,6 +40,12 @@ impl EncryptionHint { } } +impl From for bool { + fn from(val: EncryptionHint) -> Self { + val == EncryptionHint::Hybridized + } +} + /// Whether to provide an encryption key in the master public key for this /// attribute. #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] diff --git a/src/abe_policy/dimension.rs b/src/abe_policy/dimension.rs index 2b9ff1b1..ebf6b7b1 100644 --- a/src/abe_policy/dimension.rs +++ b/src/abe_policy/dimension.rs @@ -1,4 +1,7 @@ -use std::{collections::HashMap, fmt::Debug, vec}; +use std::{ + collections::{hash_map::Entry, HashMap}, + fmt::Debug, +}; use serde::{Deserialize, Serialize}; @@ -6,7 +9,7 @@ use super::{ attribute::{AttributeBuilder, EncryptionHint}, AttributeStatus, }; -use crate::Error; +use crate::{data_struct::Dict, Error}; /// /// Creates a dimension by its name and its underlying attribute properties. @@ -69,9 +72,9 @@ impl DimensionBuilder { #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] /// Represents an `Attribute` inside a `Dimension`. pub struct AttributeParameters { - pub rotation_values: Vec, - pub encryption_hint: EncryptionHint, - pub write_status: AttributeStatus, + pub(super) id: u32, + pub(super) encryption_hint: EncryptionHint, + pub(super) write_status: AttributeStatus, } impl AttributeParameters { @@ -80,28 +83,25 @@ impl AttributeParameters { pub fn new(encryption_hint: EncryptionHint, seed_id: &mut u32) -> Self { *seed_id += 1; Self { - rotation_values: vec![*seed_id], + id: *seed_id, encryption_hint, write_status: AttributeStatus::EncryptDecrypt, } } - /// Gets the current rotation of the Attribute. - pub fn get_current_rotation(&self) -> u32 { - self.rotation_values - .last() - .copied() - .expect("Attribute should always have at least one value") + #[must_use] + pub fn get_id(&self) -> u32 { + self.id } - /// Flattens the properties of the `AttributeParameters` into a vector of - /// tuples where each tuple contains a rotation value, the associated - /// encryption hint, and the `read_only` flag. - pub fn flatten_properties(&self) -> Vec<(u32, EncryptionHint, AttributeStatus)> { - self.rotation_values - .iter() - .map(|&value| (value, self.encryption_hint, self.write_status)) - .collect() + #[must_use] + pub fn get_encryption_hint(&self) -> EncryptionHint { + self.encryption_hint + } + + #[must_use] + pub fn get_status(&self) -> AttributeStatus { + self.write_status } } @@ -110,9 +110,9 @@ type AttributeName = String; #[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)] /// A dimension is a space that holds attributes. It can be ordered (an /// dimension) or unordered (a set). -pub struct Dimension { - pub order: Option>, - pub attributes: HashMap, +pub enum Dimension { + Unordered(HashMap), + Ordered(Dict), } impl Dimension { @@ -123,59 +123,53 @@ impl Dimension { /// /// * `dim` - The `DimensionBuilder` to base the dimension on. /// * `seed_id` - A mutable reference to a seed ID used for generating - /// unique IDs for attributes. - pub fn new(dim: &DimensionBuilder, seed_id: &mut u32) -> Self { - let attributes_mapping = dim - .attributes_properties - .iter() - .map(|attr| { - ( - attr.name.clone(), - AttributeParameters::new(attr.encryption_hint, seed_id), - ) - }) - .collect(); + /// unique values for attributes. + pub fn new(dim: DimensionBuilder, seed_id: &mut u32) -> Self { + let attributes_mapping = dim.attributes_properties.into_iter().map(|attr| { + ( + attr.name, + AttributeParameters::new(attr.encryption_hint, seed_id), + ) + }); match dim.hierarchical { - true => Self { - order: Some( - dim.attributes_properties - .iter() - .map(|attr| attr.name.clone()) - .collect(), - ), - attributes: attributes_mapping, - }, - false => Self { - order: None, - attributes: attributes_mapping, - }, + true => Self::Ordered(attributes_mapping.collect()), + false => Self::Unordered(attributes_mapping.collect()), } } - /// Rotates the attribute with the given name by incrementing its rotation - /// value. - /// - /// # Arguments - /// - /// * `attr_name` - The name of the attribute to rotate. - /// * `seed_id` - A seed used for generating the new rotation value. - /// - /// # Errors - /// - /// Returns an error if the attribute with the specified name is not found. - pub fn rotate_attribute( - &mut self, - attr_name: &AttributeName, - seed_id: &mut u32, - ) -> Result<(), Error> { - match self.attributes.get_mut(attr_name) { - Some(attr) => { - *seed_id += 1; - attr.rotation_values.push(*seed_id); - Ok(()) - } - None => Err(Error::AttributeNotFound(attr_name.to_string())), + #[must_use] + pub fn nb_attributes(&self) -> usize { + match self { + Self::Unordered(attributes) => attributes.len(), + Self::Ordered(attributes) => attributes.len(), + } + } + + #[must_use] + pub fn is_ordered(&self) -> bool { + match self { + Self::Unordered(_) => false, + Self::Ordered(_) => true, + } + } + + /// Returns an iterator over the attributes name. + /// If the dimension is ordered, the names are returned in this order, + /// otherwise they are returned in arbitrary order. + #[must_use] + pub fn get_attributes_name(&self) -> Box> { + match self { + Self::Unordered(attributes) => Box::new(attributes.keys()), + Self::Ordered(attributes) => Box::new(attributes.keys()), + } + } + + #[must_use] + pub fn get_attribute(&self, attr_name: &AttributeName) -> Option<&AttributeParameters> { + match self { + Self::Unordered(attributes) => attributes.get(attr_name), + Self::Ordered(attributes) => attributes.get(attr_name), } } @@ -191,24 +185,24 @@ impl Dimension { /// Returns an error if the operation is not permitted. pub fn add_attribute( &mut self, - attr_name: &AttributeName, + attr_name: AttributeName, encryption_hint: EncryptionHint, seed_id: &mut u32, ) -> Result<(), Error> { - if self.order.is_some() { - Err(Error::OperationNotPermitted( + match self { + Self::Unordered(attributes) => { + if let Entry::Vacant(entry) = attributes.entry(attr_name) { + entry.insert(AttributeParameters::new(encryption_hint, seed_id)); + Ok(()) + } else { + Err(Error::OperationNotPermitted( + "Attribute already in dimension".to_string(), + )) + } + } + Self::Ordered(_) => Err(Error::OperationNotPermitted( "Hierarchical dimension are immutable".to_string(), - )) - } else if self.attributes.contains_key(attr_name) { - Err(Error::OperationNotPermitted( - "Attribute already in dimension".to_string(), - )) - } else { - self.attributes.insert( - attr_name.clone(), - AttributeParameters::new(encryption_hint, seed_id), - ); - Ok(()) + )), } } @@ -223,15 +217,14 @@ impl Dimension { /// Returns an error if the operation is not permitted or if the attribute /// is not found. pub fn remove_attribute(&mut self, attr_name: &AttributeName) -> Result<(), Error> { - if self.order.is_some() { - Err(Error::OperationNotPermitted( - "Hierarchical dimension are immutable".to_string(), - )) - } else { - self.attributes + match self { + Self::Unordered(attributes) => attributes .remove(attr_name) .map(|_| ()) - .ok_or(Error::AttributeNotFound(attr_name.to_string())) + .ok_or(Error::AttributeNotFound(attr_name.to_string())), + Self::Ordered(_) => Err(Error::OperationNotPermitted( + "Hierarchical dimension are immutable".to_string(), + )), } } @@ -245,10 +238,16 @@ impl Dimension { /// /// Returns an error if the attribute is not found. pub fn disable_attribute(&mut self, attr_name: &AttributeName) -> Result<(), Error> { - self.attributes - .get_mut(attr_name) - .map(|attr| attr.write_status = AttributeStatus::DecryptOnly) - .ok_or(Error::AttributeNotFound(attr_name.to_string())) + match self { + Self::Unordered(attributes) => attributes + .get_mut(attr_name) + .map(|attr| attr.write_status = AttributeStatus::DecryptOnly) + .ok_or(Error::AttributeNotFound(attr_name.to_string())), + Self::Ordered(attributes) => attributes + .get_mut(attr_name) + .map(|attr| attr.write_status = AttributeStatus::DecryptOnly) + .ok_or(Error::AttributeNotFound(attr_name.to_string())), + } } /// Renames an attribute with a new name. @@ -265,66 +264,36 @@ impl Dimension { pub fn rename_attribute( &mut self, attr_name: &AttributeName, - new_name: &str, + new_name: String, ) -> Result<(), Error> { - if self.attributes.contains_key(new_name) { - return Err(Error::OperationNotPermitted( - "New attribute name is already used in the same dimension".to_string(), - )); - } - match self.attributes.remove(attr_name) { - Some(attr_params) => { - self.attributes.insert(new_name.to_string(), attr_params); - if let Some(order) = self.order.as_mut() { - order.iter_mut().for_each(|name| { - if name == attr_name { - *name = new_name.to_string() - } - }) + match self { + Self::Unordered(attributes) => { + if attributes.contains_key(&new_name) { + return Err(Error::OperationNotPermitted( + "New attribute name is already used in the same dimension".to_string(), + )); + } + match attributes.remove(attr_name) { + Some(attr_params) => { + attributes.insert(new_name, attr_params); + Ok(()) + } + None => Err(Error::AttributeNotFound(attr_name.to_string())), } - Ok(()) } - None => Err(Error::AttributeNotFound(attr_name.to_string())), + Self::Ordered(attributes) => attributes + .update_key(attr_name, new_name) + .map_err(|e| Error::OperationNotPermitted(e.to_string())), } } - /// Clears the old rotations of an attribute, keeping only the current ID. - /// - /// # Arguments - /// - /// * `attr_name` - The name of the attribute to clear old rotations for. - /// - /// # Errors - /// - /// Returns an error if the attribute is not found. - pub fn clear_old_attribute_values(&mut self, attr_name: &AttributeName) -> Result<(), Error> { - self.attributes - .get_mut(attr_name) - .map(|attr| { - let current_val = attr.get_current_rotation(); - attr.rotation_values.retain(|val| val == ¤t_val); - }) - .ok_or(Error::AttributeNotFound(attr_name.to_string())) - } - - /// Returns the list of Attributes of this Policy. + /// Returns an iterator over the `AttributesParameters` and parameters. /// If the dimension is ordered, the attributes are returned in order. - pub fn attributes_properties(&self) -> Vec<(String, EncryptionHint)> { - if let Some(ordered_attrs) = &self.order { - ordered_attrs - .iter() - .map(|name| { - ( - name.to_string(), - self.attributes.get(name).unwrap().encryption_hint, - ) - }) - .collect() - } else { - self.attributes - .iter() - .map(|(name, attr_params)| (name.to_string(), attr_params.encryption_hint)) - .collect() + #[must_use] + pub fn attributes(&self) -> Box> { + match self { + Self::Unordered(attributes) => Box::new(attributes.values()), + Self::Ordered(attributes) => Box::new(attributes.values()), } } } diff --git a/src/abe_policy/partitions.rs b/src/abe_policy/partitions.rs index 8ae3949a..dd94d09a 100644 --- a/src/abe_policy/partitions.rs +++ b/src/abe_policy/partitions.rs @@ -5,15 +5,15 @@ use cosmian_crypto_core::bytes_ser_de::Serializer; use crate::Error; /// Partition associated to a subset. It corresponds to a combination -/// of attributes across all axes. -#[derive(Debug, Eq, PartialEq, Clone, Hash)] +/// of attributes across all dimensions. +#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Clone, Hash)] pub struct Partition(pub(crate) Vec); impl Partition { /// Creates a `Partition` from the given list of values. - pub fn from_attribute_values(mut attribute_values: Vec) -> Result { + pub fn from_attribute_ids(mut attribute_ids: Vec) -> Result { // guard against overflow of the 1024 bytes buffer below - if attribute_values.len() > 200 { + if attribute_ids.len() > 200 { return Err(Error::InvalidAttribute( "The current implementation does not currently support more than 200 attributes \ for a partition" @@ -24,10 +24,10 @@ impl Partition { // `Department::HR && Level::Secret` // and // `Level::Secret && Department::HR` - attribute_values.sort_unstable(); + attribute_ids.sort_unstable(); // the actual size in bytes will be at least equal to the length - let mut ser = Serializer::with_capacity(attribute_values.len()); - for value in attribute_values { + let mut ser = Serializer::with_capacity(attribute_ids.len()); + for value in attribute_ids { ser.write_leb128_u64(u64::from(value))?; } Ok(Self(ser.finalize().to_vec())) @@ -63,7 +63,7 @@ mod tests { #[test] fn test_partitions() -> Result<(), Error> { let mut values: Vec = vec![12, 0, u32::MAX, 1]; - let partition = Partition::from_attribute_values(values.clone())?; + let partition = Partition::from_attribute_ids(values.clone())?; // values are sorted n Partition values.sort_unstable(); let mut de = Deserializer::new(&partition); diff --git a/src/abe_policy/policy.rs b/src/abe_policy/policy.rs index 8dc75827..189ee1c2 100644 --- a/src/abe_policy/policy.rs +++ b/src/abe_policy/policy.rs @@ -55,6 +55,7 @@ impl Policy { } /// Adds the given dimension to the policy. + /// /!\ Invalidates all previous keys and ciphers. pub fn add_dimension(&mut self, dim: DimensionBuilder) -> Result<(), Error> { if self.dimensions.get(&dim.name).is_some() { return Err(Error::ExistingPolicy(dim.name)); @@ -62,14 +63,14 @@ impl Policy { self.dimensions.insert( dim.name.clone(), - Dimension::new(&dim, &mut self.last_attribute_value), + Dimension::new(dim, &mut self.last_attribute_value), ); Ok(()) } /// Removes the given dim from the policy. - /// Fails if there is no such dim in the policy. + /// /!\ Invalidates all previous keys and ciphers. pub fn remove_dimension(&mut self, dim_name: &str) -> Result<(), Error> { self.dimensions .remove(dim_name) @@ -78,6 +79,11 @@ impl Policy { } /// Adds the given attribute to the policy. + /// /!\ No old key will be able to use this attribute. In particular, keys + /// which associated access policy was implicitly deriving rights for this + /// dimension (e.g. "`Security::High`" implicitly derives rights for all + /// attributes from any other dimensions) need to be regenerated. A refresh + /// will *not* implicitly derive rights for this attribute. /// Fails if the dim of the attribute does not exist in the policy. /// /// * `attr` - The name and dimension of the new attribute. @@ -89,11 +95,9 @@ impl Policy { encryption_hint: EncryptionHint, ) -> Result<(), Error> { match self.dimensions.get_mut(&attr.dimension) { - Some(policy_dim) => policy_dim.add_attribute( - &attr.name, - encryption_hint, - &mut self.last_attribute_value, - ), + Some(policy_dim) => { + policy_dim.add_attribute(attr.name, encryption_hint, &mut self.last_attribute_value) + } None => Err(Error::DimensionNotFound(attr.dimension)), } } @@ -103,7 +107,7 @@ impl Policy { /// once the keys are updated. pub fn remove_attribute(&mut self, attr: &Attribute) -> Result<(), Error> { if let Some(dim) = self.dimensions.get_mut(&attr.dimension) { - if dim.attributes.len() == 1 { + if dim.nb_attributes() == 1 { self.remove_dimension(&attr.dimension) } else { dim.remove_attribute(&attr.name) @@ -124,40 +128,20 @@ impl Policy { } /// Changes the name of an attribute. - pub fn rename_attribute(&mut self, attr: &Attribute, new_name: &str) -> Result<(), Error> { + pub fn rename_attribute(&mut self, attr: &Attribute, new_name: String) -> Result<(), Error> { match self.dimensions.get_mut(&attr.dimension) { Some(policy_dim) => policy_dim.rename_attribute(&attr.name, new_name), None => Err(Error::DimensionNotFound(attr.dimension.to_string())), } } - /// Rotates an attribute, changing its underlying value with an unused - /// value. - pub fn rotate(&mut self, attr: &Attribute) -> Result<(), Error> { - if let Some(dim) = self.dimensions.get_mut(&attr.dimension) { - dim.rotate_attribute(&attr.name, &mut self.last_attribute_value) - } else { - Err(Error::DimensionNotFound(attr.dimension.to_string())) - } - } - - /// Removes all rotation values but the current of an attribute. - pub fn clear_old_attribute_values(&mut self, attr: &Attribute) -> Result<(), Error> { - if let Some(dim) = self.dimensions.get_mut(&attr.dimension) { - dim.clear_old_attribute_values(&attr.name) - } else { - Err(Error::DimensionNotFound(attr.dimension.to_string())) - } - } - /// Returns the list of Attributes of this Policy. #[must_use] pub fn attributes(&self) -> Vec { self.dimensions .iter() .flat_map(|(dim_name, dim)| { - dim.attributes - .keys() + dim.get_attributes_name() .map(|attr_name| Attribute::new(dim_name, attr_name)) }) .collect::>() @@ -167,49 +151,41 @@ impl Policy { /// Fails if there is no such attribute. fn get_attribute(&self, attr: &Attribute) -> Result<&AttributeParameters, Error> { if let Some(dim) = self.dimensions.get(&attr.dimension) { - dim.attributes - .get(&attr.name) + dim.get_attribute(&attr.name) .ok_or(Error::AttributeNotFound(attr.to_string())) } else { Err(Error::DimensionNotFound(attr.dimension.to_string())) } } - /// Returns the list of all values given to this attribute over rotations. - /// The current value is returned first. - pub fn attribute_values(&self, attribute: &Attribute) -> Result, Error> { - self.get_attribute(attribute) - .map(|attr| attr.rotation_values.iter().rev().copied().collect()) - } - /// Returns the hybridization hint of the given attribute. - pub fn attribute_hybridization_hint( + pub fn get_attribute_hybridization_hint( &self, attribute: &Attribute, ) -> Result { self.get_attribute(attribute) - .map(|attr| attr.encryption_hint) + .map(AttributeParameters::get_encryption_hint) } - /// Retrieves the current value of an attribute. - pub fn attribute_current_value(&self, attribute: &Attribute) -> Result { + /// Retrieves the ID of an attribute. + pub fn get_attribute_id(&self, attribute: &Attribute) -> Result { self.get_attribute(attribute) - .map(AttributeParameters::get_current_rotation) + .map(AttributeParameters::get_id) } - /// Generates all cross-axes combinations of attribute values. + /// Generates all cross-dimension combinations of attributes. /// - /// - `current_dim` : dim for which to combine values with other - /// axes - /// - `axes` : list of axes - /// - `attr_values_per_dim` : map axes with their associated attribute - /// values - fn combine_attribute_values( + /// - `current_dim` : dim for which to combine other dim + /// attributes + /// - `dimensions` : list of dimensions + /// - `attr_values_per_dim` : map dimensions with their associated + /// attribute parameters + fn combine_attributes( current_dim: usize, - axes: &[String], - attr_values_per_dim: &HashMap>, + dimensions: &[String], + attr_params_per_dim: &HashMap>, ) -> Result, EncryptionHint, AttributeStatus)>, Error> { - let current_dim_name = match axes.get(current_dim) { + let current_dim_name = match dimensions.get(current_dim) { None => { return Ok(vec![( vec![], @@ -220,24 +196,24 @@ impl Policy { Some(dim) => dim, }; - let current_dim_values = attr_values_per_dim + let current_dim_values = attr_params_per_dim .get(current_dim_name) .ok_or_else(|| Error::DimensionNotFound(current_dim_name.to_string()))?; // Recursive call. Above checks ensure no empty list can be returned. let other_values = - Self::combine_attribute_values(current_dim + 1, axes, attr_values_per_dim)?; + Self::combine_attributes(current_dim + 1, dimensions, attr_params_per_dim)?; let mut combinations = Vec::with_capacity(current_dim_values.len() * other_values.len()); - for (current_values, is_hybridized, is_readonly) in current_dim_values { + for attr in current_dim_values { for (other_values, is_other_hybridized, is_other_readonly) in &other_values { let mut combined = Vec::with_capacity(1 + other_values.len()); - combined.push(*current_values); + combined.push(attr.get_id()); combined.extend_from_slice(other_values); combinations.push(( combined, - *is_hybridized | *is_other_hybridized, - *is_readonly | *is_other_readonly, + attr.get_encryption_hint() | *is_other_hybridized, + attr.get_status() | *is_other_readonly, )); } } @@ -250,53 +226,38 @@ impl Policy { pub fn generate_all_partitions( &self, ) -> Result, Error> { - let mut attr_values_per_dim = HashMap::with_capacity(self.dimensions.len()); + let mut attr_params_per_dim = HashMap::with_capacity(self.dimensions.len()); for (dim_name, dim) in &self.dimensions { - attr_values_per_dim.insert( - dim_name.clone(), - dim.attributes - .values() - .flat_map(|attr| attr.flatten_properties()) - .collect(), - ); + attr_params_per_dim.insert(dim_name.clone(), dim.attributes().collect()); } // Combine axes values into partitions. - let axes = attr_values_per_dim.keys().cloned().collect::>(); - let combinations = Self::combine_attribute_values(0, &axes, &attr_values_per_dim)?; + let dimensions = attr_params_per_dim.keys().cloned().collect::>(); + let combinations = Self::combine_attributes(0, &dimensions, &attr_params_per_dim)?; let mut res = HashMap::with_capacity(combinations.len()); for (combination, is_hybridized, is_readonly) in combinations { res.insert( - Partition::from_attribute_values(combination)?, + Partition::from_attribute_ids(combination)?, (is_hybridized, is_readonly), ); } Ok(res) } - /// Generates an `AccessPolicy` into the list of corresponding current - /// partitions. + /// Converts an `AccessPolicy` into a list of corresponding coordinates. /// - /// - `access_policy` : access policy to convert - /// - `follow_hierarchical_axes` : set to `true` to combine lower dim - /// attributes + /// - `access_policy` : access policy to convert + /// - `cascade_rights` : include lower rights from hierarchical dimensions pub fn access_policy_to_partitions( &self, access_policy: &AccessPolicy, - follow_hierarchical_axes: bool, - include_old_rotations: bool, + cascade_rights: bool, ) -> Result, Error> { - let attr_combinations = - access_policy.to_attribute_combinations(self, follow_hierarchical_axes)?; + let attr_combinations = access_policy.to_attribute_combinations(self, cascade_rights)?; let mut res = HashSet::with_capacity(attr_combinations.len()); for attr_combination in &attr_combinations { - for partition in generate_current_attribute_partitions( - attr_combination, - self, - include_old_rotations, - )? { - let is_unique = res.insert(partition); - if !is_unique { + for partition in generate_attribute_partitions(attr_combination, self)? { + if !res.insert(partition) { return Err(Error::ExistingCombination(format!("{attr_combination:?}"))); } } @@ -321,74 +282,42 @@ impl TryFrom<&Policy> for Vec { } } -/// Converts a list of attributes into the list of current `Partitions`, with -/// their associated hybridization hints. +/// Converts a list of attributes into a list of `Partitions`, with +/// their associated hybridization hints and attribute status. /// /// - `attributes` : list of attributes /// - `policy` : global policy data -fn generate_current_attribute_partitions( +fn generate_attribute_partitions( attributes: &[Attribute], policy: &Policy, - include_old_partitions: bool, ) -> Result, Error> { - let mut current_attr_value_per_dim = HashMap::< - String, - Vec<(u32, EncryptionHint, AttributeStatus)>, - >::with_capacity(policy.dimensions.len()); // maximum bound - for attribute in attributes.iter() { - let entry = current_attr_value_per_dim + let mut attr_params_per_dim = + HashMap::>::with_capacity(policy.dimensions.len()); + for attribute in attributes { + let entry = attr_params_per_dim .entry(attribute.dimension.clone()) .or_default(); - let attr_properties = policy.get_attribute(attribute)?; - if include_old_partitions { - for attr_value in &attr_properties.rotation_values { - entry.push(( - *attr_value, - attr_properties.encryption_hint, - attr_properties.write_status, - )); - } - } else { - entry.push(( - attr_properties.get_current_rotation(), - attr_properties.encryption_hint, - attr_properties.write_status, - )); - } + entry.push(policy.get_attribute(attribute)?); } // When a dimension is not mentioned in the attribute list, all the attribute // from this dimension are used. for (dim, dim_properties) in &policy.dimensions { - if !current_attr_value_per_dim.contains_key(dim) { + if !attr_params_per_dim.contains_key(dim) { // gather all the latest value for that dim - let values = dim_properties - .attributes - .values() - .map(|attr| { - ( - attr.get_current_rotation(), - attr.encryption_hint, - attr.write_status, - ) - }) - .collect(); - current_attr_value_per_dim.insert(dim.clone(), values); + let values = dim_properties.attributes().collect(); + attr_params_per_dim.insert(dim.clone(), values); } } - // Combine axes values into partitions. - let axes = current_attr_value_per_dim - .keys() - .cloned() - .collect::>(); - let combinations = - Policy::combine_attribute_values(0, axes.as_slice(), ¤t_attr_value_per_dim)?; - let mut res = HashSet::with_capacity(combinations.len()); - for (combination, _, _) in combinations { - res.insert(Partition::from_attribute_values(combination)?); - } - Ok(res) + // Combine dimensions attributes into partitions. + let dimensions = attr_params_per_dim.keys().cloned().collect::>(); + let combinations = Policy::combine_attributes(0, dimensions.as_slice(), &attr_params_per_dim)?; + + combinations + .into_iter() + .map(|(coordinate, _, _)| Partition::from_attribute_ids(coordinate)) + .collect::, _>>() } #[cfg(test)] @@ -403,9 +332,9 @@ mod tests { let mut axes_attributes: Vec> = vec![]; for dim in axes { let mut dim_attributes: Vec<(Attribute, u32)> = vec![]; - for name in policy.dimensions[dim].attributes.keys() { + for name in policy.dimensions[dim].get_attributes_name() { let attribute = Attribute::new(dim, name); - let value = policy.attribute_current_value(&attribute)?; + let value = policy.get_attribute_id(&attribute)?; dim_attributes.push((attribute, value)); } axes_attributes.push(dim_attributes); @@ -415,80 +344,50 @@ mod tests { #[test] fn test_combine_attribute_values() -> Result<(), Error> { - let mut policy = policy()?; + let policy = policy()?; let axes: Vec = policy.dimensions.keys().cloned().collect(); let axes_attributes = axes_attributes_from_policy(&axes, &policy)?; // this should create the combination of the first attribute // with all those of the second dim - let partitions_0 = generate_current_attribute_partitions( - &[axes_attributes[0][0].0.clone()], - &policy, - false, - )?; + let partitions_0 = + generate_attribute_partitions(&[axes_attributes[0][0].0.clone()], &policy)?; assert_eq!(axes_attributes[1].len(), partitions_0.len()); let att_0_0 = axes_attributes[0][0].1; for (_attribute, value) in &axes_attributes[1] { - let partition = Partition::from_attribute_values(vec![att_0_0, *value])?; + let partition = Partition::from_attribute_ids(vec![att_0_0, *value])?; assert!(partitions_0.contains(&partition)); } // this should create the single combination of the first attribute // of the first dim with that of the second dim - let partitions_1 = generate_current_attribute_partitions( + let partitions_1 = generate_attribute_partitions( &[ axes_attributes[0][0].0.clone(), axes_attributes[1][0].0.clone(), ], &policy, - false, )?; assert_eq!(partitions_1.len(), 1); let att_1_0 = axes_attributes[1][0].1; - assert!(partitions_1.contains(&Partition::from_attribute_values(vec![att_0_0, att_1_0])?)); + assert!(partitions_1.contains(&Partition::from_attribute_ids(vec![att_0_0, att_1_0])?)); // this should create the 2 combinations of the first attribute // of the first dim with that the wo of the second dim - let partitions_2 = generate_current_attribute_partitions( + let partitions_2 = generate_attribute_partitions( &[ axes_attributes[0][0].0.clone(), axes_attributes[1][0].0.clone(), axes_attributes[1][1].0.clone(), ], &policy, - false, )?; assert_eq!(partitions_2.len(), 2); let att_1_0 = axes_attributes[1][0].1; let att_1_1 = axes_attributes[1][1].1; - assert!(partitions_2.contains(&Partition::from_attribute_values(vec![att_0_0, att_1_0])?,)); - assert!(partitions_2.contains(&Partition::from_attribute_values(vec![att_0_0, att_1_1])?,)); - - // rotation - policy.rotate(&axes_attributes[0][0].0)?; - let axes_attributes = axes_attributes_from_policy(&axes, &policy)?; - - // this should create the single combination of the first attribute - // of the first dim with that of the second dim - let partitions_3 = generate_current_attribute_partitions( - &[ - axes_attributes[0][0].0.clone(), - axes_attributes[1][0].0.clone(), - ], - &policy, - false, - )?; - assert_eq!(partitions_3.len(), 1); - let att_1_0 = axes_attributes[1][0].1; - let att_0_0_new = axes_attributes[0][0].1; - assert!( - partitions_3.contains(&Partition::from_attribute_values(vec![ - att_0_0_new, - att_1_0 - ])?) - ); - assert!(!partitions_3.contains(&Partition::from_attribute_values(vec![att_0_0, att_1_0])?)); + assert!(partitions_2.contains(&Partition::from_attribute_ids(vec![att_0_0, att_1_0])?,)); + assert!(partitions_2.contains(&Partition::from_attribute_ids(vec![att_0_0, att_1_1])?,)); Ok(()) } @@ -497,8 +396,8 @@ mod tests { fn test_access_policy_to_partition() -> Result<(), Error> { // // create policy - let mut policy = policy()?; - policy.rotate(&Attribute::new("Department", "FIN"))?; + let policy = policy()?; + //policy.rotate(&Attribute::new("Department", "FIN"))?; // // create access policy @@ -508,7 +407,7 @@ mod tests { // // create partitions from access policy - let partitions = policy.access_policy_to_partitions(&access_policy, true, false)?; + let partitions = policy.access_policy_to_partitions(&access_policy, true)?; // // manually create the partitions @@ -516,29 +415,28 @@ mod tests { // add the partitions associated with the HR department: combine with // all attributes of the Security Level dim - let hr_value = policy.attribute_current_value(&Attribute::new("Department", "HR"))?; + let hr_value = policy.get_attribute_id(&Attribute::new("Department", "HR"))?; let dim_properties = policy.dimensions.get("Security Level").unwrap(); - for attr_name in dim_properties.attributes.keys() { + for attr_name in dim_properties.get_attributes_name() { let attr_value = - policy.attribute_current_value(&Attribute::new("Security Level", attr_name))?; + policy.get_attribute_id(&Attribute::new("Security Level", attr_name))?; let mut partition = vec![hr_value, attr_value]; partition.sort_unstable(); - partitions_.insert(Partition::from_attribute_values(partition)?); + partitions_.insert(Partition::from_attribute_ids(partition)?); } // add the other attribute combination: FIN && Low Secret - let fin_value = policy.attribute_current_value(&Attribute::new("Department", "FIN"))?; + let fin_value = policy.get_attribute_id(&Attribute::new("Department", "FIN"))?; let conf_value = - policy.attribute_current_value(&Attribute::new("Security Level", "Low Secret"))?; + policy.get_attribute_id(&Attribute::new("Security Level", "Low Secret"))?; let mut partition = vec![fin_value, conf_value]; partition.sort_unstable(); - partitions_.insert(Partition::from_attribute_values(partition)?); + partitions_.insert(Partition::from_attribute_ids(partition)?); // since this is a hierarchical dim, add the lower values: here only low secret - let prot_value = - policy.attribute_current_value(&Attribute::new("Security Level", "Protected"))?; + let prot_value = policy.get_attribute_id(&Attribute::new("Security Level", "Protected"))?; let mut partition = vec![fin_value, prot_value]; partition.sort_unstable(); - partitions_.insert(Partition::from_attribute_values(partition)?); + partitions_.insert(Partition::from_attribute_ids(partition)?); assert_eq!(partitions, partitions_); @@ -551,7 +449,7 @@ mod tests { ) .unwrap(); let partition_4 = policy - .access_policy_to_partitions(&policy_attributes_4, true, false) + .access_policy_to_partitions(&policy_attributes_4, true) .unwrap(); let policy_attributes_5 = AccessPolicy::from_boolean_expression( @@ -560,7 +458,7 @@ mod tests { ) .unwrap(); let partition_5 = policy - .access_policy_to_partitions(&policy_attributes_5, true, false) + .access_policy_to_partitions(&policy_attributes_5, true) .unwrap(); assert_eq!(partition_4.len(), 4); assert_eq!(partition_5.len(), 5); diff --git a/src/abe_policy/policy_versions.rs b/src/abe_policy/policy_versions.rs index 32e824eb..3c901cd1 100644 --- a/src/abe_policy/policy_versions.rs +++ b/src/abe_policy/policy_versions.rs @@ -13,13 +13,13 @@ use super::{ #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] pub struct PolicyV2 { /// Version number - pub version: PolicyVersion, + pub(crate) version: PolicyVersion, /// Last value taken by the attribute. pub(crate) last_attribute_value: u32, /// Policy axes: maps axes name to the list of associated attribute names /// and a boolean defining whether or not this dim is hierarchical. - pub dimensions: HashMap, + pub(crate) dimensions: HashMap, } #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] @@ -37,47 +37,43 @@ pub struct OldPolicyAxisParameters { #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] pub struct PolicyV1 { /// Version number - pub version: PolicyVersion, + version: PolicyVersion, /// Last value taken by the attribute. - pub(crate) last_attribute_value: u32, + last_attribute_value: u32, /// Maximum attribute value. Defines a maximum number of attribute /// creations (revocations + addition). - pub max_attribute_creations: u32, + max_attribute_creations: u32, /// Policy axes: maps axes name to the list of associated attribute names /// and a boolean defining whether or not this axis is hierarchical. - pub axes: HashMap, + axes: HashMap, /// Maps an attribute to its values and its hybridization hint. - pub attributes: HashMap, + attributes: HashMap, } impl From for PolicyV2 { fn from(val: PolicyV1) -> Self { let mut dimensions = HashMap::with_capacity(val.axes.len()); for (axis_name, axis_params) in val.axes { + let attributes = val + .attributes + .clone() + .into_iter() + .filter(|(attr, _)| attr.dimension == axis_name) + .map(|(attr, attr_params)| { + ( + attr.name, + AttributeParameters { + id: *attr_params.values.first().unwrap(), + encryption_hint: attr_params.encryption_hint, + write_status: AttributeStatus::EncryptDecrypt, + }, + ) + }); dimensions.insert( axis_name.clone(), - Dimension { - order: if axis_params.is_hierarchical { - Some(axis_params.attribute_names) - } else { - None - }, - attributes: val - .attributes - .clone() - .iter() - .filter(|(attr, _)| attr.dimension == axis_name) - .map(|(attr, attr_params)| { - ( - attr.name.clone(), - AttributeParameters { - rotation_values: attr_params.values.clone(), - encryption_hint: attr_params.encryption_hint, - write_status: AttributeStatus::EncryptDecrypt, - }, - ) - }) - .collect(), + match axis_params.is_hierarchical { + true => Dimension::Ordered(attributes.collect()), + false => Dimension::Unordered(attributes.collect()), }, ); } @@ -92,45 +88,41 @@ impl From for PolicyV2 { #[derive(Clone, Serialize, Deserialize, Debug)] pub struct LegacyPolicy { /// Last value taken by the attribute. - pub(crate) last_attribute_value: u32, + last_attribute_value: u32, /// Maximum attribute value. Defines a maximum number of attribute /// creations (revocations + addition). - pub max_attribute_creations: u32, + max_attribute_creations: u32, /// Policy axes: maps axes name to the list of associated attribute names /// and a boolean defining whether or not this axis is hierarchical. - pub axes: HashMap, + axes: HashMap, /// Maps an attribute to its values and its hybridization hint. - pub attributes: HashMap>, + attributes: HashMap>, } impl From for PolicyV2 { fn from(val: LegacyPolicy) -> Self { let mut dimensions = HashMap::with_capacity(val.axes.len()); for (axis_name, axis_params) in val.axes { + let attributes = val + .attributes + .clone() + .into_iter() + .filter(|(attr, _)| attr.dimension == axis_name) + .map(|(attr, values)| { + ( + attr.name, + AttributeParameters { + id: *values.first().unwrap(), + encryption_hint: EncryptionHint::Classic, + write_status: AttributeStatus::EncryptDecrypt, + }, + ) + }); dimensions.insert( axis_name.clone(), - Dimension { - order: if axis_params.is_hierarchical { - Some(axis_params.attribute_names) - } else { - None - }, - attributes: val - .attributes - .clone() - .iter() - .filter(|(attr, _)| attr.dimension == axis_name) - .map(|(attr, values)| { - ( - attr.name.clone(), - AttributeParameters { - rotation_values: values.clone(), - encryption_hint: EncryptionHint::Classic, - write_status: AttributeStatus::EncryptDecrypt, - }, - ) - }) - .collect(), + match axis_params.is_hierarchical { + true => Dimension::Ordered(attributes.collect()), + false => Dimension::Unordered(attributes.collect()), }, ); } diff --git a/src/abe_policy/tests.rs b/src/abe_policy/tests.rs index f4658a36..bf7bad30 100644 --- a/src/abe_policy/tests.rs +++ b/src/abe_policy/tests.rs @@ -64,38 +64,6 @@ fn check_policy() { for properties in &department.attributes_properties { assert!(attributes.contains(&Attribute::new("Department", &properties.name))); } - for attribute in &attributes { - assert_eq!( - policy.attribute_values(attribute).unwrap()[0], - policy.attribute_current_value(attribute).unwrap() - ); - } -} - -#[test] -fn test_rotate_policy_attributes() -> Result<(), Error> { - let mut policy = policy()?; - let attributes = policy.attributes(); - // rotate few attributes - policy.rotate(&attributes[0])?; - assert_eq!(2, policy.attribute_values(&attributes[0])?.len()); - policy.rotate(&attributes[2])?; - assert_eq!(2, policy.attribute_values(&attributes[2])?.len()); - for attribute in &attributes { - assert_eq!( - policy.attribute_values(attribute)?[0], - policy.attribute_current_value(attribute)? - ); - } - - policy.clear_old_attribute_values(&attributes[0])?; - assert_eq!(1, policy.attribute_values(&attributes[0])?.len()); - - assert!(policy - .clear_old_attribute_values(&Attribute::new("Department", "Missing")) - .is_err()); - - Ok(()) } #[test] @@ -105,25 +73,28 @@ fn test_edit_policy_attributes() -> Result<(), Error> { // Try renaming Research to already used name MKG assert!(policy - .rename_attribute(&Attribute::new("Department", "R&D"), "MKG",) + .rename_attribute(&Attribute::new("Department", "R&D"), "MKG".to_string(),) .is_err()); // Rename R&D to Research assert!(policy - .rename_attribute(&Attribute::new("Department", "R&D"), "Research",) + .rename_attribute(&Attribute::new("Department", "R&D"), "Research".to_string(),) .is_ok()); // Rename ordered dimension assert!(policy - .rename_attribute(&Attribute::new("Security Level", "Protected"), "Open",) + .rename_attribute( + &Attribute::new("Security Level", "Protected"), + "Open".to_string(), + ) .is_ok()); - let order = policy + let order: Vec<_> = policy .dimensions .get("Security Level") .unwrap() - .order - .clone() - .unwrap(); + .get_attributes_name() + .cloned() + .collect(); assert!(order.len() == 3); assert!(order.contains(&"Open".to_string())); assert!(!order.contains(&"Protected".to_string())); diff --git a/src/core/api.rs b/src/core/api.rs index de2fb11f..6ebb1c3a 100644 --- a/src/core/api.rs +++ b/src/core/api.rs @@ -7,10 +7,11 @@ use cosmian_crypto_core::{ RandomFixedSizeCBytes, SymmetricKey, }; +use super::primitives::prune; use crate::{ abe_policy::{AccessPolicy, Policy}, core::{ - primitives::{decaps, encaps, keygen, refresh, setup, update}, + primitives::{decaps, encaps, keygen, refresh, rekey, setup, update}, Encapsulation, MasterPublicKey, MasterSecretKey, UserSecretKey, SYM_KEY_LENGTH, }, Error, @@ -46,7 +47,7 @@ impl Covercrypt { ) -> Result<(MasterSecretKey, MasterPublicKey), Error> { Ok(setup( &mut *self.rng.lock().expect("Mutex lock failed!"), - &policy.generate_all_partitions()?, + policy.generate_all_partitions()?, )) } @@ -70,28 +71,67 @@ impl Covercrypt { &mut *self.rng.lock().expect("Mutex lock failed!"), msk, mpk, - &policy.generate_all_partitions()?, + policy.generate_all_partitions()?, + ) + } + + /// Generate new keys associated to the given access policy in the master + /// keys. User keys will need to be refreshed after this step. + /// - `access_policy` : describe the keys to renew + /// - `policy` : global policy + /// - `msk` : master secret key + /// - `mpk` : master public key + pub fn rekey_master_keys( + &self, + access_policy: &AccessPolicy, + policy: &Policy, + msk: &mut MasterSecretKey, + mpk: &mut MasterPublicKey, + ) -> Result<(), Error> { + rekey( + &mut *self.rng.lock().expect("Mutex lock failed!"), + msk, + mpk, + policy.access_policy_to_partitions(access_policy, false)?, + ) + } + + /// Removes old keys associated to the given master keys from the master + /// keys. This will permanently remove access to old ciphers. + /// - `access_policy` : describe the keys to prune + /// - `policy` : global policy + /// - `msk` : master secret key + pub fn prune_master_secret_key( + &self, + access_policy: &AccessPolicy, + policy: &Policy, + msk: &mut MasterSecretKey, + ) -> Result<(), Error> { + prune( + msk, + &policy.access_policy_to_partitions(access_policy, false)?, ) } /// Generates a user secret key. /// - /// A new user secret key does NOT include to old (i.e. rotated) partitions. + /// A new user secret key only has the latest keys corresponding to its + /// access policy. /// - /// - `msk` : master secret key - /// - `user_policy` : user access policy - /// - `policy` : global policy + /// - `msk` : master secret key + /// - `access_policy` : user access policy + /// - `policy` : global policy pub fn generate_user_secret_key( &self, msk: &MasterSecretKey, access_policy: &AccessPolicy, policy: &Policy, ) -> Result { - Ok(keygen( + keygen( &mut *self.rng.lock().expect("Mutex lock failed!"), msk, - &policy.access_policy_to_partitions(access_policy, true, false)?, - )) + &policy.access_policy_to_partitions(access_policy, true)?, + ) } /// Refreshes the user key according to the given master key and user @@ -102,24 +142,15 @@ impl Covercrypt { /// is set, the old user access will be preserved. /// /// - `usk` : the user key to refresh - /// - `user_policy` : the access policy of the user key /// - `msk` : master secret key - /// - `policy` : global policy of the master secret key - /// - `keep_old_accesses` : whether access to old partitions (i.e. before - /// rotation) should be kept + /// - `keep_old_rights` : whether or not to keep old decryption rights pub fn refresh_user_secret_key( &self, usk: &mut UserSecretKey, - access_policy: &AccessPolicy, msk: &MasterSecretKey, - policy: &Policy, - keep_old_rotations: bool, + keep_old_rights: bool, ) -> Result<(), Error> { - refresh( - msk, - usk, - &policy.access_policy_to_partitions(access_policy, true, keep_old_rotations)?, - ) + refresh(msk, usk, keep_old_rights) } /// Generates a random symmetric key to be used with a DEM scheme and @@ -138,7 +169,7 @@ impl Covercrypt { encaps( &mut *self.rng.lock().expect("Mutex lock failed!"), pk, - &policy.access_policy_to_partitions(access_policy, false, false)?, + &policy.access_policy_to_partitions(access_policy, false)?, ) } diff --git a/src/core/mod.rs b/src/core/mod.rs index ca78942f..5db43f16 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,6 +1,7 @@ //! Implements the core functionalities of `Covercrypt`. use std::{ + cell::RefCell, collections::{HashMap, HashSet}, hash::Hash, ops::Deref, @@ -10,7 +11,10 @@ use cosmian_crypto_core::{R25519PrivateKey, R25519PublicKey, SymmetricKey}; use pqc_kyber::{KYBER_INDCPA_BYTES, KYBER_INDCPA_PUBLICKEYBYTES, KYBER_INDCPA_SECRETKEYBYTES}; use zeroize::ZeroizeOnDrop; -use crate::abe_policy::Partition; +use crate::{ + abe_policy::Partition, + data_struct::{RevisionMap, RevisionVec}, +}; #[macro_use] pub mod macros; @@ -60,21 +64,23 @@ impl Deref for KyberSecretKey { } } -type Subkey = (Option, R25519PrivateKey); +pub(super) type PublicSubkey = (Option, R25519PublicKey); #[derive(Debug, PartialEq, Eq)] pub struct MasterPublicKey { g1: R25519PublicKey, g2: R25519PublicKey, - pub subkeys: HashMap, R25519PublicKey)>, + pub(crate) subkeys: HashMap, } +pub(super) type SecretSubkey = (Option, R25519PrivateKey); + #[derive(Debug, PartialEq, Eq)] pub struct MasterSecretKey { s: R25519PrivateKey, s1: R25519PrivateKey, s2: R25519PrivateKey, - pub subkeys: HashMap, + pub(crate) subkeys: RevisionMap, kmac_key: Option>, } @@ -82,7 +88,7 @@ pub struct MasterSecretKey { pub struct UserSecretKey { a: R25519PrivateKey, b: R25519PrivateKey, - pub subkeys: Vec, + pub(crate) subkeys: RefCell>, kmac: Option, } diff --git a/src/core/primitives.rs b/src/core/primitives.rs index bb7c0fdf..a0f3ff80 100644 --- a/src/core/primitives.rs +++ b/src/core/primitives.rs @@ -1,11 +1,14 @@ //! Implements the cryptographic primitives of `Covercrypt`, based on //! `bib/Covercrypt.pdf`. -use std::collections::{HashMap, HashSet}; +use std::{ + cell::RefCell, + collections::{HashMap, HashSet, LinkedList}, +}; use cosmian_crypto_core::{ - kdf256, reexport::rand_core::CryptoRngCore, FixedSizeCBytes, R25519PrivateKey, R25519PublicKey, - RandomFixedSizeCBytes, SymmetricKey, + kdf256, reexport::rand_core::CryptoRngCore, FixedSizeCBytes, R25519CurvePoint, + R25519PrivateKey, R25519PublicKey, RandomFixedSizeCBytes, SymmetricKey, }; use pqc_kyber::{ indcpa::{indcpa_dec, indcpa_enc, indcpa_keypair}, @@ -15,12 +18,17 @@ use tiny_keccak::{Hasher, IntoXof, Kmac, Xof}; use zeroize::Zeroizing; use super::{ - KmacSignature, KyberPublicKey, KyberSecretKey, KMAC_KEY_LENGTH, KMAC_LENGTH, SYM_KEY_LENGTH, - TAG_LENGTH, + KmacSignature, KyberPublicKey, KyberSecretKey, PublicSubkey, SecretSubkey, KMAC_KEY_LENGTH, + KMAC_LENGTH, SYM_KEY_LENGTH, TAG_LENGTH, }; use crate::{ - abe_policy::{AttributeStatus, EncryptionHint, Partition}, + abe_policy::{ + AttributeStatus, + AttributeStatus::{DecryptOnly, EncryptDecrypt}, + EncryptionHint, Partition, + }, core::{Encapsulation, KeyEncapsulation, MasterPublicKey, MasterSecretKey, UserSecretKey}, + data_struct::{RevisionMap, RevisionVec}, Error, }; @@ -41,7 +49,8 @@ fn compute_user_key_kmac(msk: &MasterSecretKey, usk: &UserSecretKey) -> Option Result<() Ok(()) } +/// Returns newly generated public and private Kyber key pair. +fn create_kyber_key_pair(rng: &mut impl CryptoRngCore) -> (KyberPublicKey, KyberSecretKey) { + let (mut sk, mut pk) = ( + KyberSecretKey([0; KYBER_INDCPA_SECRETKEYBYTES]), + KyberPublicKey([0; KYBER_INDCPA_PUBLICKEYBYTES]), + ); + indcpa_keypair(&mut pk.0, &mut sk.0, None, rng); + (pk, sk) +} + +/// Returns a newly generated pair of public and private subkeys with optional +/// Kyber keys if hybridized. +fn create_subkey_pair( + rng: &mut impl CryptoRngCore, + h: &R25519CurvePoint, + is_hybridized: EncryptionHint, +) -> (PublicSubkey, SecretSubkey) { + let sk_i = R25519PrivateKey::new(rng); + let pk_i = h * &sk_i; + + let (pk_pq, sk_pq) = if is_hybridized.into() { + let (pk, sk) = create_kyber_key_pair(rng); + (Some(pk), Some(sk)) + } else { + (None, None) + }; + ((pk_pq, pk_i), (sk_pq, sk_i)) +} + +/// Update a pair of public and private subkeys of a `ReadWrite` partition. +fn update_subkey_pair( + rng: &mut impl CryptoRngCore, + h: &R25519CurvePoint, + mpk: &mut PublicSubkey, + msk: &mut SecretSubkey, + is_hybridized: EncryptionHint, +) -> Result<(), Error> { + let (pk_pq, pk_i) = mpk; + let (sk_pq, sk_i) = msk; + + // update public subkey + *pk_i = h * &sk_i; + + // create or reuse Kyber keys + if is_hybridized.into() { + match (&pk_pq, &sk_pq) { + (None, _) => { + // generate a new Kyber key pair + let (pk, sk) = create_kyber_key_pair(rng); + pk_pq.replace(pk); + sk_pq.replace(sk); + } + (Some(_), Some(_)) => {} // keep existing key + (Some(_), None) => { + return Err(Error::KeyError( + "Kyber public key cannot be computed from the secret key.".to_string(), + )); + } + }; + } + Ok(()) +} + +/// Update the private subkey of a `ReadOnly` partition +fn update_master_subkey( + rng: &mut impl CryptoRngCore, + _h: &R25519CurvePoint, + msk: &mut SecretSubkey, + is_hybridized: EncryptionHint, +) { + let (sk_pq, _) = msk; + // Add Kyber key if needed + if is_hybridized.into() && sk_pq.is_none() { + let (_, sk) = create_kyber_key_pair(rng); + sk_pq.replace(sk); + } +} + /// Generates the master secret key and master public key of the `Covercrypt` /// scheme. /// @@ -76,7 +163,7 @@ fn verify_user_key_kmac(msk: &MasterSecretKey, usk: &UserSecretKey) -> Result<() /// - `partitions` : set of partition to be used pub fn setup( rng: &mut impl CryptoRngCore, - partitions: &HashMap, + partitions: HashMap, ) -> (MasterSecretKey, MasterPublicKey) { let s = R25519PrivateKey::new(rng); let s1 = R25519PrivateKey::new(rng); @@ -85,26 +172,15 @@ pub fn setup( let g1 = R25519PublicKey::from(&s1); let g2 = R25519PublicKey::from(&s2); - let mut sub_sk = HashMap::with_capacity(partitions.len()); + let mut sub_sk = RevisionMap::with_capacity(partitions.len()); let mut sub_pk = HashMap::with_capacity(partitions.len()); - for (partition, &(is_hybridized, _)) in partitions { - let sk_i = R25519PrivateKey::new(rng); - let pk_i = &h * &sk_i; - - let (sk_pq, pk_pq) = if is_hybridized == EncryptionHint::Hybridized { - let (mut sk, mut pk) = ( - KyberSecretKey([0; KYBER_INDCPA_SECRETKEYBYTES]), - KyberPublicKey([0; KYBER_INDCPA_PUBLICKEYBYTES]), - ); - indcpa_keypair(&mut pk.0, &mut sk.0, None, rng); - (Some(sk), Some(pk)) - } else { - (None, None) - }; - - sub_sk.insert(partition.clone(), (sk_pq, sk_i)); - sub_pk.insert(partition.clone(), (pk_pq, pk_i)); + for (partition, (is_hybridized, write_status)) in partitions { + let (public_subkey, secret_subkey) = create_subkey_pair(rng, &h, is_hybridized); + sub_sk.insert(partition.clone(), secret_subkey); + if write_status == EncryptDecrypt { + sub_pk.insert(partition, public_subkey); + } } let kmac_key = Some(SymmetricKey::::new(rng)); @@ -139,23 +215,27 @@ pub fn keygen( rng: &mut impl CryptoRngCore, msk: &MasterSecretKey, decryption_set: &HashSet, -) -> UserSecretKey { +) -> Result { let a = R25519PrivateKey::new(rng); let b = &(&msk.s - &(&a * &msk.s1)) / &msk.s2; - let subkeys = decryption_set - .iter() - .filter_map(|partition| msk.subkeys.get(partition)) - .cloned() - .collect(); + // Use the last key for each partitions in the decryption set + let mut subkeys = RevisionVec::with_capacity(decryption_set.len()); + decryption_set.iter().try_for_each(|partition| { + let subkey = msk.subkeys.get_latest(partition).ok_or(Error::KeyError( + "Master secret key and Policy are not in sync.".to_string(), + ))?; + subkeys.create_chain_with_single_value(partition.clone(), subkey.clone()); + Ok::<_, Error>(()) + })?; let mut usk = UserSecretKey { a, b, - subkeys, + subkeys: RefCell::new(subkeys), kmac: None, }; usk.kmac = compute_user_key_kmac(msk, &usk); - usk + Ok(usk) } /// Generates a `Covercrypt` encapsulation of a random symmetric key. @@ -225,7 +305,9 @@ pub fn decaps( ) -> Result, Error> { let precomp = &(&encapsulation.c1 * &usk.a) + &(&encapsulation.c2 * &usk.b); for encapsulation_i in &encapsulation.encs { - for (sk_j, x_j) in &usk.subkeys { + // BFS search user subkeys to first try the most recent rotations of each + // partitions. + for (sk_j, x_j) in usk.subkeys.borrow().bfs() { let e_j = match encapsulation_i { KeyEncapsulation::HybridEncapsulation(epq_i) => { if let Some(sk_j) = sk_j { @@ -283,72 +365,85 @@ pub fn update( rng: &mut impl CryptoRngCore, msk: &mut MasterSecretKey, mpk: &mut MasterPublicKey, - partitions_set: &HashMap, + partitions_set: HashMap, ) -> Result<(), Error> { - let mut new_sub_sk = HashMap::with_capacity(partitions_set.len()); - let mut new_sub_pk = HashMap::with_capacity(partitions_set.len()); - let h = R25519PublicKey::from(&msk.s); + // Remove keys from partitions deleted from Policy + msk.subkeys.retain(|part| partitions_set.contains_key(part)); + mpk.subkeys + .retain(|part, _| partitions_set.contains_key(part)); - for (partition, &(is_hybridized, write_status)) in partitions_set { - if let Some((sk_i, x_i)) = msk.subkeys.get(partition) { - // regenerate the public sub-key. - let h_i = &h * x_i; - // Set the correct hybridization property. - let (sk_i, pk_i) = if is_hybridized == EncryptionHint::Hybridized { - if sk_i.is_some() { - let pk_i = mpk - .subkeys - .get(partition) - .map(|(pk_i, _)| pk_i) - .unwrap_or(&None); - (sk_i.clone(), pk_i.clone()) - } else { - let (mut sk_i, mut pk_i) = ( - KyberSecretKey([0; KYBER_INDCPA_SECRETKEYBYTES]), - KyberPublicKey([0; KYBER_INDCPA_PUBLICKEYBYTES]), - ); - indcpa_keypair(&mut pk_i.0, &mut sk_i.0, None, rng); - (Some(sk_i), Some(pk_i)) + let h = R25519PublicKey::from(&msk.s); + for (partition, (is_hybridized, write_status)) in partitions_set { + // check if secret key exist for this partition + if let Some(secret_subkey) = msk.subkeys.get_latest_mut(&partition) { + // update the master secret and public subkey if needed + match (write_status, mpk.subkeys.get_mut(&partition)) { + (EncryptDecrypt, None) => unreachable!(), + (EncryptDecrypt, Some(public_subkey)) => { + update_subkey_pair(rng, &h, public_subkey, secret_subkey, is_hybridized)?; } - } else { - (None, None) - }; - - if write_status == AttributeStatus::EncryptDecrypt { - // Only add non read only partition to the public key - if sk_i.is_some() && pk_i.is_none() { - return Err(Error::KeyError( - "Kyber public key cannot be computed from the secret key.".to_string(), - )); + (DecryptOnly, None) => update_master_subkey(rng, &h, secret_subkey, is_hybridized), + (DecryptOnly, Some(_)) => { + mpk.subkeys.remove(&partition); + update_master_subkey(rng, &h, secret_subkey, is_hybridized); } - new_sub_pk.insert(partition.clone(), (pk_i, h_i)); } - new_sub_sk.insert(partition.clone(), (sk_i, x_i.clone())); } else { - // Create new entry. - let x_i = R25519PrivateKey::new(rng); - let h_i = &h * &x_i; - let (sk_pq, pk_pq) = if is_hybridized == EncryptionHint::Hybridized { - let (mut sk_pq, mut pk_pq) = ( - KyberSecretKey([0; KYBER_INDCPA_SECRETKEYBYTES]), - KyberPublicKey([0; KYBER_INDCPA_PUBLICKEYBYTES]), - ); - indcpa_keypair(&mut pk_pq.0, &mut sk_pq.0, None, rng); - (Some(sk_pq), Some(pk_pq)) - } else { - (None, None) - }; - new_sub_sk.insert(partition.clone(), (sk_pq, x_i)); - if write_status == AttributeStatus::EncryptDecrypt { - // Only add non read only partition to the public key - new_sub_pk.insert(partition.clone(), (pk_pq, h_i)); + // generate new keys + let (public_subkey, secret_subkey) = create_subkey_pair(rng, &h, is_hybridized); + msk.subkeys.insert(partition.clone(), secret_subkey); + if write_status == EncryptDecrypt { + mpk.subkeys.insert(partition, public_subkey); } } } - msk.subkeys = new_sub_sk; - mpk.subkeys = new_sub_pk; + Ok(()) +} +/// Rekeys the master keys by creating new subkeys for the given coordinates. +/// +/// # Parameters +/// +/// - `rng` : random number generator +/// - `msk` : master secret key +/// - `mpk` : master public key +/// - `coordinate` : set of keys coordinate to renew +pub fn rekey( + rng: &mut impl CryptoRngCore, + msk: &mut MasterSecretKey, + mpk: &mut MasterPublicKey, + coordinates: HashSet, +) -> Result<(), Error> { + let h = R25519PublicKey::from(&msk.s); + for coordinate in coordinates { + let is_hybridized = EncryptionHint::new( + msk.subkeys + .get_latest(&coordinate) + .and_then(|(sk_i, _)| sk_i.as_ref()) + .is_some(), + ); + let (public_subkey, secret_subkey) = create_subkey_pair(rng, &h, is_hybridized); + msk.subkeys.insert(coordinate.clone(), secret_subkey); + + // update public subkey if partition is not read only + if mpk.subkeys.contains_key(&coordinate) { + mpk.subkeys.insert(coordinate, public_subkey); + } + } + Ok(()) +} + +/// Prunes old subkeys from the master secret key for specified coordinates. +/// +/// # Parameters +/// +/// - `msk` : master secret key +/// - `coordinates` : set of subkeys coordinate to prune +pub fn prune(msk: &mut MasterSecretKey, coordinates: &HashSet) -> Result<(), Error> { + for coordinate in coordinates { + msk.subkeys.keep(coordinate, 1); + } Ok(()) } @@ -363,22 +458,50 @@ pub fn update( /// /// - `msk` : master secret key /// - `usk` : user secret key -/// - `decryption_set` : set of partitions the user is granted the decryption -/// right for /// - `keep_old_rights` : whether or not to keep old decryption rights pub fn refresh( msk: &MasterSecretKey, usk: &mut UserSecretKey, - decryption_set: &HashSet, + keep_old_rights: bool, ) -> Result<(), Error> { verify_user_key_kmac(msk, usk)?; - usk.subkeys.clear(); - for partition in decryption_set { - if let Some(x_i) = msk.subkeys.get(partition) { - usk.subkeys.push(x_i.clone()); - } - } + let new_subkeys = usk + .subkeys + .take() + .into_iter() + .filter_map(|(coordinate, user_chain)| { + msk.subkeys.get(&coordinate).and_then(|msk_chain| { + let mut msk_subkeys = if keep_old_rights { + msk_chain.iter().take(msk_chain.len()) + } else { + msk_chain.iter().take(1) + }; + let mut usk_subkeys = user_chain.into_iter(); + let first_usk_subkey = usk_subkeys.next()?; + + let mut new_usk_subkeys = LinkedList::new(); + // Add new master secret subkeys + for msk_subkey in msk_subkeys.by_ref() { + if msk_subkey == &first_usk_subkey { + new_usk_subkeys.push_back(first_usk_subkey); + break; + } + new_usk_subkeys.push_back(msk_subkey.clone()); + } + // Keep old matching subkeys between the master and user subkeys + for subkey in usk_subkeys { + if Some(&subkey) != msk_subkeys.next() { + break; + } + new_usk_subkeys.push_back(subkey); + } + Some((coordinate, new_usk_subkeys)) + }) + }) + .collect::>(); + + usk.subkeys.replace(new_subkeys); // Update user key KMAC usk.kmac = compute_user_key_kmac(msk, usk); @@ -428,21 +551,21 @@ mod tests { // secure random number generator let mut rng = CsRng::from_entropy(); // setup scheme - let (mut msk, mut mpk) = setup(&mut rng, &partitions_set); + let (mut msk, mut mpk) = setup(&mut rng, partitions_set); // The admin partition matches a hybridized sub-key. - let admin_secret_subkeys = msk.subkeys.get(&admin_partition); + let admin_secret_subkeys = msk.subkeys.get_latest(&admin_partition); assert!(admin_secret_subkeys.is_some()); assert!(admin_secret_subkeys.unwrap().0.is_some()); // The developer partition matches a classic sub-key. - let dev_secret_subkeys = msk.subkeys.get(&dev_partition); + let dev_secret_subkeys = msk.subkeys.get_latest(&dev_partition); assert!(dev_secret_subkeys.is_some()); assert!(dev_secret_subkeys.unwrap().0.is_none()); // Generate user secret keys. - let mut dev_usk = keygen(&mut rng, &msk, &users_set[0]); - let admin_usk = keygen(&mut rng, &msk, &users_set[1]); + let mut dev_usk = keygen(&mut rng, &msk, &users_set[0])?; + let admin_usk = keygen(&mut rng, &msk, &users_set[1])?; // Encapsulate key for the admin target set. let (sym_key, encapsulation) = encaps(&mut rng, &mpk, &admin_target_set).unwrap(); @@ -477,21 +600,21 @@ mod tests { ]); let client_target_set = HashSet::from([client_partition.clone()]); - update(&mut rng, &mut msk, &mut mpk, &new_partitions_set)?; - refresh(&msk, &mut dev_usk, &HashSet::from([dev_partition.clone()]))?; + update(&mut rng, &mut msk, &mut mpk, new_partitions_set)?; + refresh(&msk, &mut dev_usk, true)?; // The dev partition matches a hybridized sub-key. - let dev_secret_subkeys = msk.subkeys.get(&dev_partition); + let dev_secret_subkeys = msk.subkeys.get_latest(&dev_partition); assert!(dev_secret_subkeys.is_some()); assert!(dev_secret_subkeys.unwrap().0.is_some()); // The client partition matches a classic sub-key. - let client_secret_subkeys = msk.subkeys.get(&client_partition); + let client_secret_subkeys = msk.subkeys.get_latest(&client_partition); assert!(client_secret_subkeys.is_some()); assert!(client_secret_subkeys.unwrap().0.is_none()); // The developer now has a hybridized key. - assert_eq!(dev_usk.subkeys.len(), 1); + assert_eq!(dev_usk.subkeys.borrow().count_elements(), 1); for key_encapsulation in &encapsulation.encs { if let KeyEncapsulation::ClassicEncapsulation(_) = key_encapsulation { panic!("Wrong hybridization type"); @@ -523,7 +646,7 @@ mod tests { ); // Client is able to decapsulate. - let client_usk = keygen(&mut rng, &msk, &HashSet::from([client_partition])); + let client_usk = keygen(&mut rng, &msk, &HashSet::from([client_partition]))?; let res0 = decaps(&client_usk, &new_encapsulation); match res0 { Err(err) => panic!("Client should be able to decapsulate: {err:?}"), @@ -551,7 +674,7 @@ mod tests { // secure random number generator let mut rng = CsRng::from_entropy(); // setup scheme - let (mut msk, mut mpk) = setup(&mut rng, &partitions_set); + let (mut msk, mut mpk) = setup(&mut rng, partitions_set); // now remove partition 1 and add partition 3 let partition_3 = Partition(b"3".to_vec()); @@ -565,7 +688,7 @@ mod tests { (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), ), ]); - update(&mut rng, &mut msk, &mut mpk, &new_partitions_set)?; + update(&mut rng, &mut msk, &mut mpk, new_partitions_set)?; assert!(!msk.subkeys.contains_key(&partition_1)); assert!(msk.subkeys.contains_key(&partition_2)); assert!(msk.subkeys.contains_key(&partition_3)); @@ -598,16 +721,15 @@ mod tests { // secure random number generator let mut rng = CsRng::from_entropy(); // setup scheme - let (mut msk, mut mpk) = setup(&mut rng, &partitions_set); + let (mut msk, mut mpk) = setup(&mut rng, partitions_set); // create a user key with access to partition 1 and 2 let mut usk = keygen( &mut rng, &msk, &HashSet::from([partition_1.clone(), partition_2.clone()]), - ); + )?; - // now remove partition 1 and add partition 4 - let partition_4 = Partition(b"4".to_vec()); + // now remove partition 1 and remove hybrid key from partition 3 let new_partition_set = HashMap::from([ ( partition_2.clone(), @@ -617,29 +739,52 @@ mod tests { partition_3.clone(), (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), ), - ( - partition_4.clone(), - (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), - ), ]); //Covercrypt the master keys let old_msk = MasterSecretKey::deserialize(msk.serialize()?.as_slice())?; - update(&mut rng, &mut msk, &mut mpk, &new_partition_set)?; - // refresh the user key with partitions 2 and 4 - refresh( - &msk, - &mut usk, - &HashSet::from([partition_2.clone(), partition_4.clone()]), - )?; - assert!(!usk - .subkeys - .contains(old_msk.subkeys.get(&partition_1).unwrap())); - assert!(usk.subkeys.contains(msk.subkeys.get(&partition_2).unwrap())); - assert!(!usk + update(&mut rng, &mut msk, &mut mpk, new_partition_set)?; + // refresh the user key + refresh(&msk, &mut usk, true)?; + // user key kept old access to partition 1 + assert!(!usk.subkeys.borrow().flat_iter().any(|x| { + x == ( + &partition_1, + old_msk.subkeys.get_latest(&partition_1).unwrap(), + ) + })); + assert!(usk .subkeys - .contains(old_msk.subkeys.get(&partition_3).unwrap())); - assert!(usk.subkeys.contains(msk.subkeys.get(&partition_4).unwrap())); + .borrow() + .flat_iter() + .any(|x| { x == (&partition_2, msk.subkeys.get_latest(&partition_2).unwrap(),) })); + // user key kept the old hybrid key for partition 3 + assert!(!usk.subkeys.borrow().flat_iter().any(|x| { + x == ( + &partition_3, + old_msk.subkeys.get_latest(&partition_3).unwrap(), + ) + })); + + // add new key for partition 2 + rekey( + &mut rng, + &mut msk, + &mut mpk, + HashSet::from([partition_2.clone()]), + )?; + // refresh the user key + refresh(&msk, &mut usk, true)?; + let usk_subkeys = usk.subkeys.borrow(); + let usk_subkeys: Vec<_> = usk_subkeys + .flat_iter() + .filter(|(part, _)| *part == &partition_2) + .map(|(_, subkey)| subkey) + .collect(); + let msk_subkeys: Vec<_> = msk.subkeys.get(&partition_2).unwrap().iter().collect(); + assert_eq!(usk_subkeys.len(), 2); + assert_eq!(usk_subkeys, msk_subkeys); + Ok(()) } @@ -661,16 +806,19 @@ mod tests { // secure random number generator let mut rng = CsRng::from_entropy(); // setup scheme - let (msk, _) = setup(&mut rng, &partitions_set); + let (msk, _) = setup(&mut rng, partitions_set); // create a user key with access to partition 1 and 2 - let mut usk = keygen(&mut rng, &msk, &HashSet::from([partition_1, partition_2])); + let usk = keygen(&mut rng, &msk, &HashSet::from([partition_1, partition_2]))?; assert!(verify_user_key_kmac(&msk, &usk).is_ok()); let bytes = usk.serialize()?; let usk_ = UserSecretKey::deserialize(&bytes)?; assert!(verify_user_key_kmac(&msk, &usk_).is_ok()); - usk.subkeys.push((None, R25519PrivateKey::new(&mut rng))); + usk.subkeys.borrow_mut().create_chain_with_single_value( + Partition(b"3".to_vec()), + (None, R25519PrivateKey::new(&mut rng)), + ); // KMAC verify will fail after modifying the user key assert!(verify_user_key_kmac(&msk, &usk).is_err()); diff --git a/src/core/serialization.rs b/src/core/serialization.rs index d8fa9665..2958f093 100644 --- a/src/core/serialization.rs +++ b/src/core/serialization.rs @@ -1,6 +1,9 @@ //! Implements the serialization methods for the `Covercrypt` objects. -use std::collections::{HashMap, HashSet}; +use std::{ + cell::RefCell, + collections::{HashMap, HashSet, LinkedList}, +}; use cosmian_crypto_core::{ bytes_ser_de::{to_leb128_len, Deserializer, Serializable, Serializer}, @@ -15,22 +18,58 @@ use crate::{ Encapsulation, KeyEncapsulation, MasterPublicKey, MasterSecretKey, UserSecretKey, SYM_KEY_LENGTH, }, + data_struct::{RevisionMap, RevisionVec}, CleartextHeader, EncryptedHeader, Error, }; +/// Returns the byte length of a serialized option +macro_rules! serialize_len_option { + ($option:expr, $value:ident, $method:expr) => {{ + let mut length = 1; + if let Some($value) = &$option { + length += $method; + } + length + }}; +} + +/// Serialize an optional value as a LEB128-encoded unsigned integer followed by +/// the serialization of the contained value if any. +macro_rules! serialize_option { + ($serializer:expr, $n:expr, $option:expr, $value:ident, $method:expr) => {{ + if let Some($value) = &$option { + $n += $serializer.write_leb128_u64(1)?; + $n += $method?; + } else { + $n += $serializer.write_leb128_u64(0)?; + } + }}; +} + +/// Deserialize an optional value from a LEB128-encoded unsigned integer +/// followed by the deserialization of the contained value if any. +macro_rules! deserialize_option { + ($deserializer:expr, $method:expr) => {{ + let is_some = $deserializer.read_leb128_u64()?; + if is_some == 1 { + Some($method) + } else { + None + } + }}; +} + impl Serializable for MasterPublicKey { type Error = Error; fn length(&self) -> usize { let mut length = 2 * R25519PublicKey::LENGTH + // subkeys serialization + to_leb128_len(self.subkeys.len()) + self.subkeys.len() * R25519PublicKey::LENGTH; for (partition, (pk_i, _)) in &self.subkeys { - length += (to_leb128_len(partition.len()) + partition.len()) - + (1 + pk_i - .as_ref() - .map(|_| KYBER_INDCPA_PUBLICKEYBYTES) - .unwrap_or_default()); + length += to_leb128_len(partition.len()) + partition.len(); + length += serialize_len_option!(pk_i, _value, KYBER_INDCPA_PUBLICKEYBYTES); } length } @@ -41,12 +80,7 @@ impl Serializable for MasterPublicKey { n += ser.write_leb128_u64(self.subkeys.len() as u64)?; for (partition, (pk_i, h_i)) in &self.subkeys { n += ser.write_vec(partition)?; - if let Some(pk_i) = pk_i { - n += ser.write_leb128_u64(1)?; - n += ser.write_array(pk_i)?; - } else { - n += ser.write_leb128_u64(0)?; - } + serialize_option!(ser, n, pk_i, value, ser.write_array(value)); n += ser.write_array(&h_i.to_bytes())?; } Ok(n) @@ -58,18 +92,11 @@ impl Serializable for MasterPublicKey { let n_partitions = ::try_from(de.read_leb128_u64()?)?; let mut subkeys = HashMap::with_capacity(n_partitions); for _ in 0..n_partitions { - let partition = de.read_vec()?; - let is_hybridized = de.read_leb128_u64()?; - let pk_i = if is_hybridized == 1 { - Some(KyberPublicKey(de.read_array()?)) - } else { - None - }; - let h_i = de.read_array::<{ R25519PublicKey::LENGTH }>()?; - subkeys.insert( - Partition::from(partition), - (pk_i, R25519PublicKey::try_from_bytes(h_i)?), - ); + let partition = Partition::from(de.read_vec()?); + let pk_i = deserialize_option!(de, KyberPublicKey(de.read_array()?)); + let h_i = + R25519PublicKey::try_from_bytes(de.read_array::<{ R25519PublicKey::LENGTH }>()?)?; + subkeys.insert(partition, (pk_i, h_i)); } Ok(Self { g1, g2, subkeys }) } @@ -81,14 +108,16 @@ impl Serializable for MasterSecretKey { fn length(&self) -> usize { let mut length = 3 * R25519PrivateKey::LENGTH + self.kmac_key.as_ref().map_or_else(|| 0, |key| key.len()) + // subkeys serialization + to_leb128_len(self.subkeys.len()) - + self.subkeys.len() * R25519PrivateKey::LENGTH; - for (partition, (sk_i, _)) in &self.subkeys { - length += (to_leb128_len(partition.len()) + partition.len()) - + (1 + sk_i - .as_ref() - .map(|_| KYBER_INDCPA_SECRETKEYBYTES) - .unwrap_or_default()); + + self.subkeys.count_elements() * R25519PrivateKey::LENGTH; + for (partition, chain) in &self.subkeys.map { + length += to_leb128_len(partition.len()) + partition.len(); + length += to_leb128_len(chain.len()); + for (sk_i, _) in chain { + let x = serialize_len_option!(sk_i, _value, KYBER_INDCPA_SECRETKEYBYTES); + length += x; + } } length } @@ -98,15 +127,13 @@ impl Serializable for MasterSecretKey { n += ser.write_array(&self.s2.to_bytes())?; n += ser.write_array(&self.s.to_bytes())?; n += ser.write_leb128_u64(self.subkeys.len() as u64)?; - for (partition, (sk_i, x_i)) in &self.subkeys { + for (partition, chain) in &self.subkeys.map { n += ser.write_vec(partition)?; - if let Some(sk_i) = sk_i { - n += ser.write_leb128_u64(1)?; - n += ser.write_array(sk_i)?; - } else { - n += ser.write_leb128_u64(0)?; + n += ser.write_leb128_u64(chain.len() as u64)?; + for (sk_i, x_i) in chain { + serialize_option!(ser, n, sk_i, value, ser.write_array(value)); + n += ser.write_array(&x_i.to_bytes())?; } - n += ser.write_array(&x_i.to_bytes())?; } if let Some(kmac_key) = &self.kmac_key { n += ser.write_array(kmac_key)?; @@ -123,20 +150,18 @@ impl Serializable for MasterSecretKey { let s = R25519PrivateKey::try_from_bytes(de.read_array::<{ R25519PrivateKey::LENGTH }>()?)?; let n_partitions = ::try_from(de.read_leb128_u64()?)?; - let mut subkeys = HashMap::with_capacity(n_partitions); + let mut subkeys = RevisionMap::with_capacity(n_partitions); for _ in 0..n_partitions { - let partition = de.read_vec()?; - let is_hybridized = de.read_leb128_u64()?; - let sk_i = if is_hybridized == 1 { - Some(KyberSecretKey(de.read_array()?)) - } else { - None - }; - let x_i = de.read_array::<{ R25519PrivateKey::LENGTH }>()?; - subkeys.insert( - Partition::from(partition), - (sk_i, R25519PrivateKey::try_from_bytes(x_i)?), - ); + let partition = Partition::from(de.read_vec()?); + let n_keys = ::try_from(de.read_leb128_u64()?)?; + let chain: Result, Self::Error> = (0..n_keys) + .map(|_| { + let sk_i = deserialize_option!(de, KyberSecretKey(de.read_array()?)); + let x_i = de.read_array::<{ R25519PrivateKey::LENGTH }>()?; + Ok((sk_i, R25519PrivateKey::try_from_bytes(x_i)?)) + }) + .collect(); + subkeys.map.insert(partition, chain?); } let kmac_key = match de.read_array::<{ KMAC_KEY_LENGTH }>() { @@ -160,13 +185,15 @@ 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()) - + to_leb128_len(self.subkeys.len()) - + self.subkeys.len() * R25519PrivateKey::LENGTH; - for (sk_i, _) in &self.subkeys { - length += 1 + sk_i - .as_ref() - .map(|_| KYBER_INDCPA_SECRETKEYBYTES) - .unwrap_or_default(); + // subkeys serialization + + to_leb128_len(self.subkeys.borrow().len()) + + self.subkeys.borrow().count_elements() * R25519PrivateKey::LENGTH; + for (partition, chain) in self.subkeys.borrow().iter() { + length += to_leb128_len(partition.len()) + partition.len(); + length += to_leb128_len(chain.len()); + for (sk_i, _) in chain { + length += serialize_len_option!(sk_i, _value, KYBER_INDCPA_SECRETKEYBYTES); + } } length } @@ -174,15 +201,16 @@ impl Serializable for UserSecretKey { fn write(&self, ser: &mut Serializer) -> Result { let mut n = ser.write_array(&self.a.to_bytes())?; n += ser.write_array(&self.b.to_bytes())?; - n += ser.write_leb128_u64(self.subkeys.len() as u64)?; - for (sk_i, x_i) in &self.subkeys { - if let Some(sk_i) = sk_i { - n += ser.write_leb128_u64(1)?; - n += ser.write_array(sk_i)?; - } else { - n += ser.write_leb128_u64(0)?; + n += ser.write_leb128_u64(self.subkeys.borrow().len() as u64)?; + for (partition, chain) in self.subkeys.borrow().iter() { + // write chain partition + n += ser.write_vec(partition)?; + // iterate through all subkeys in the chain + n += ser.write_leb128_u64(chain.len() as u64)?; + for (sk_i, x_i) in chain { + serialize_option!(ser, n, sk_i, value, ser.write_array(value)); + n += ser.write_array(&x_i.to_bytes())?; } - n += ser.write_array(&x_i.to_bytes())?; } if let Some(kmac) = &self.kmac { n += ser.write_array(kmac)?; @@ -194,23 +222,26 @@ impl Serializable for UserSecretKey { let a = R25519PrivateKey::try_from_bytes(de.read_array::<{ R25519PrivateKey::LENGTH }>()?)?; let b = R25519PrivateKey::try_from_bytes(de.read_array::<{ R25519PrivateKey::LENGTH }>()?)?; let n_partitions = ::try_from(de.read_leb128_u64()?)?; - let mut subkeys = Vec::with_capacity(n_partitions); + let mut subkeys = RevisionVec::with_capacity(n_partitions); for _ in 0..n_partitions { - let is_hybridized = de.read_leb128_u64()?; - let sk_i = if is_hybridized == 1 { - Some(KyberSecretKey(de.read_array()?)) - } else { - None - }; - let x_i = de.read_array::<{ R25519PrivateKey::LENGTH }>()?; - subkeys.push((sk_i, R25519PrivateKey::try_from_bytes(x_i)?)); + let partition = Partition::from(de.read_vec()?); + // read all keys forming a chain and inserting them all at once. + let n_keys = ::try_from(de.read_leb128_u64()?)?; + let new_chain: Result, _> = (0..n_keys) + .map(|_| { + let sk_i = deserialize_option!(de, KyberSecretKey(de.read_array()?)); + let x_i = de.read_array::<{ R25519PrivateKey::LENGTH }>()?; + Ok::<_, Self::Error>((sk_i, R25519PrivateKey::try_from_bytes(x_i)?)) + }) + .collect(); + subkeys.insert_new_chain(partition, new_chain?); } let kmac = de.read_array::<{ KMAC_LENGTH }>().ok(); Ok(Self { a, b, - subkeys, + subkeys: RefCell::new(subkeys), kmac, }) } @@ -377,6 +408,8 @@ impl Serializable for CleartextHeader { #[cfg(test)] mod tests { + use std::collections::HashMap; + use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng}; use super::*; @@ -404,13 +437,13 @@ mod tests { let target_set = HashSet::from([admin_partition, dev_partition]); let mut rng = CsRng::from_entropy(); - let (msk, mpk) = setup(&mut rng, &partitions_set); + let (msk, mpk) = setup(&mut rng, partitions_set); // Check Covercrypt `MasterSecretKey` serialization. let bytes = msk.serialize()?; assert_eq!(bytes.len(), msk.length(), "Wrong master secret key length"); let msk_ = MasterSecretKey::deserialize(&bytes)?; - assert_eq!(msk, msk_, "Wrong `MasterSecretKey` derserialization."); + assert_eq!(msk, msk_, "Wrong `MasterSecretKey` deserialization."); assert!( msk_.kmac_key.is_some(), "Wrong `MasterSecretKey` deserialization." @@ -427,7 +460,7 @@ mod tests { assert_eq!(mpk, mpk_, "Wrong `PublicKey` derserialization."); // Check Covercrypt `UserSecretKey` serialization. - let usk = keygen(&mut rng, &msk, &user_set); + let usk = keygen(&mut rng, &msk, &user_set)?; let bytes = usk.serialize()?; assert_eq!(bytes.len(), usk.length(), "Wrong user secret key size"); let usk_ = UserSecretKey::deserialize(&bytes)?; diff --git a/src/data_struct/README.md b/src/data_struct/README.md new file mode 100644 index 00000000..2c68c2c6 --- /dev/null +++ b/src/data_struct/README.md @@ -0,0 +1,75 @@ +# Data structures to store Covercrypt objects + +## Overview + +### Dictionary + +A `Dictionary` is a `HashMap` keeping insertion order inspired by Python dictionary. +It is used to store ordered `Dimension` (also named axis) inside the `Policy` object. + +Pros: + +- the hierarchical order of the attributes is kept by design + +- same serialized size + +- accessing elements is almost as fast as an `HashMap` with one additional memory access + +- updating the key of an element (e.g. renaming an attribute) can be performed in constant time without modifying the order + +Cons: + +- more space in memory + +- removing an element is `O(n)` but ordered dimensions do not allow this modification + +### RevisionMap + +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. + +It is used to store master secret key where each coordinate is mapped to a list of keys. +When a coordinate is rekeyed, a new key is generated and added to the front of the associated list inside the `RevisionMap`. + +Note: the master public key is a regular `HashMap` only storing the most recent key for any coordinate as one only wants to encrypt new data with the newest key. + +Pros: + +- constant time access to the most recent key for each coordinate + +- adding a new key to the front (rekeying) is performed in constant time + +- key history for any coordinate is preserved by design (useful to refresh user keys) + +Cons: + +- following linked list pointers can be slower than iterating a regular vector + +- serialization requires following each linked list + +### RevisionVec + +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. + +It is used to store user secret key where each coordinate is stored with a list of keys. +When refreshing the user key with a given master secret key, each coordinate is updated by comparing the list of user subkeys with the master ones. + +Pros: + +- accessing the most recent keys is faster than older ones + +- updating the user key with a given master secret key is performed by only iterating each linked list once + +- key history for any coordinate is preserved by design (useful to refresh user keys) + +Cons: + +- no direct access to a given coordinate's keys (would be a nice to have but not really needed in practice) + +- multiple insertions with the same coordinate will result in multiple entries in the vector thus corrupting the structure + +- following linked list pointers can be slower than iterating a regular vector + +- serialization requires following each linked list diff --git a/src/data_struct/dictionary.rs b/src/data_struct/dictionary.rs new file mode 100644 index 00000000..a3577bae --- /dev/null +++ b/src/data_struct/dictionary.rs @@ -0,0 +1,355 @@ +use std::{ + borrow::Borrow, + collections::{hash_map::Entry, HashMap}, + fmt::{self, Debug}, + hash::Hash, + marker::PhantomData, + mem::swap, + usize, +}; + +use serde::{ + de::{MapAccess, Visitor}, + ser::SerializeMap, + Deserialize, Deserializer, Serialize, +}; + +use super::error::Error; + +type Index = usize; +/// `HashMap` keeping insertion order inspired by Python dictionary. +#[derive(Default, Clone, Eq, PartialEq, Debug)] +pub struct Dict +where + K: Hash + PartialEq + Eq + Clone + Debug, +{ + indices: HashMap, + entries: Vec<(K, V)>, +} + +impl Dict +where + K: Hash + PartialEq + Eq + Clone + Debug, +{ + #[must_use] + pub fn new() -> Self { + Self { + indices: HashMap::new(), + entries: Vec::new(), + } + } + + #[must_use] + pub fn with_capacity(capacity: usize) -> Self { + Self { + indices: HashMap::with_capacity(capacity), + entries: Vec::with_capacity(capacity), + } + } + + #[must_use] + pub fn len(&self) -> usize { + self.indices.len() + } + + #[must_use] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Inserts a new entry with a given key. + /// If a given key already exists, the entry will be overwritten without + /// changing the order. + /// Otherwise, new entries are simply pushed at the end. + pub fn insert(&mut self, key: K, value: V) -> Option { + match self.indices.entry(key.clone()) { + Entry::Occupied(e) => { + // replace existing entry value in vector + Some(std::mem::replace(&mut self.entries[*e.get()].1, value)) + } + Entry::Vacant(e) => { + let new_index = self.entries.len(); + self.entries.push((key, value)); + e.insert(new_index); + None + } + } + } + + /// Removes the entry corresponding to the given key. + /// To maintain order, all inserted entries after the removed one will be + /// shifted by one and the indices map will be updated accordingly. + /// Compared to a regular `HashMap`, this operation is O(n). + pub fn remove(&mut self, key: &K) -> Option { + let entry_index = self.indices.remove(key)?; + + // shift indices over entry_index by one + self.indices + .iter_mut() + .filter(|(_, index)| **index > entry_index) + .for_each(|(_, index)| *index -= 1); + + Some(self.entries.remove(entry_index).1) + } + + /// Updates the key for a given entry while retaining the current order. + pub fn update_key(&mut self, old_key: &K, mut new_key: K) -> Result<(), Error> { + // Get index from old_key + let index_entry = *self + .indices + .get(old_key) + .ok_or(Error::missing_entry(old_key))?; + + match self.indices.entry(new_key.clone()) { + Entry::Occupied(e) => Err(Error::existing_entry(e.key())), + Entry::Vacant(e) => { + // Insert new key inside indices + e.insert(index_entry); + // Remove old key from indices + let _ = self.indices.remove(old_key); + // Replace old_key with new_key inside entries + swap(&mut self.entries[index_entry].0, &mut new_key); + Ok(()) + } + } + } + + pub fn contains_key(&self, key: &Q) -> bool + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + self.indices.contains_key(key) + } + + pub fn get(&self, key: &Q) -> Option<&V> + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + let entry_index = self.indices.get(key)?; + self.entries.get(*entry_index).map(|(_, v)| v) + } + + pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + let entry_index = self.indices.get(key)?; + self.entries.get_mut(*entry_index).map(|(_, v)| v) + } + + pub fn get_key_value(&self, key: &Q) -> Option<(&K, &V)> + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + let entry_index = self.indices.get(key)?; + let (key, value) = self.entries.get(*entry_index)?; + Some((key, value)) + } + + /// Returns an iterator over keys and values in insertion order. + #[allow(clippy::map_identity)] // unpack &(x, y) to (&x, &y) + pub fn iter(&self) -> impl Iterator { + self.entries.iter().map(|(k, v)| (k, v)) + } + + /// Returns an iterator over values in insertion order + pub fn values(&self) -> impl Iterator { + self.entries.iter().map(|(_, v)| v) + } + + /// Returns an iterator over keys in insertion order. + pub fn keys(&self) -> impl Iterator { + self.entries.iter().map(|(k, _)| k) + } +} + +impl FromIterator<(K, V)> for Dict +where + K: Hash + PartialEq + Eq + Clone + Debug, +{ + fn from_iter>(iter: T) -> Self { + let iterator = iter.into_iter(); + let mut dict = Self::with_capacity(iterator.size_hint().0); + for (key, value) in iterator { + dict.insert(key, value); + } + dict + } +} + +impl Serialize for Dict +where + K: Hash + PartialEq + Eq + Clone + Debug + Serialize, + V: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut map = serializer.serialize_map(Some(self.len()))?; + for (k, v) in self.iter() { + map.serialize_entry(k, v)?; + } + map.end() + } +} + +struct DictVisitor +where + K: Hash + PartialEq + Eq + Clone + Debug, +{ + marker: PhantomData Dict>, +} + +impl DictVisitor +where + K: Hash + PartialEq + Eq + Clone + Debug, +{ + fn new() -> Self { + Self { + marker: PhantomData, + } + } +} + +impl<'de, K, V> Visitor<'de> for DictVisitor +where + K: Hash + PartialEq + Eq + Clone + Debug + Deserialize<'de>, + V: Deserialize<'de>, +{ + // The type that our Visitor is going to produce. + type Value = Dict; + + // Format a message stating what data this Visitor expects to receive. + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a Dict") + } + + // Deserialize MyMap from an abstract "map" provided by the + // Deserializer. The MapAccess input is a callback provided by + // the Deserializer to let us see each entry in the map. + fn visit_map(self, mut access: M) -> Result + where + M: MapAccess<'de>, + { + let mut map = Dict::with_capacity(access.size_hint().unwrap_or(0)); + + // While there are entries remaining in the input, add them + // into our map. + while let Some((key, value)) = access.next_entry()? { + map.insert(key, value); + } + + Ok(map) + } +} + +impl<'de, K, V> Deserialize<'de> for Dict +where + K: Hash + PartialEq + Eq + Clone + Debug + Deserialize<'de>, + V: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_map(DictVisitor::new()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_dict() -> Result<(), Error> { + let mut d: Dict = Dict::new(); + assert!(d.is_empty()); + + // Insertions + d.insert("ID1".to_string(), "Foo".to_string()); + d.insert("ID2".to_string(), "Bar".to_string()); + d.insert("ID3".to_string(), "Baz".to_string()); + assert_eq!(d.len(), 3); + + // Get + assert_eq!( + d.get_key_value("ID2").unwrap(), + (&"ID2".to_string(), &"Bar".to_string()) + ); + + // Edit + // Overwrite value without changing order + d.insert("ID1".to_string(), "Foox".to_string()); + + // Update key without changing order + d.update_key(&"ID2".to_string(), "ID2_bis".to_string())?; + assert!(d.get_key_value(&String::from("ID2")).is_none()); + assert_eq!( + d.get_key_value(&"ID2_bis".to_string()).unwrap(), + (&"ID2_bis".to_string(), &"Bar".to_string()) + ); + + // Update key error cases + // missing old key + assert!(d.update_key(&"Bad".to_string(), "New".to_string()).is_err()); + // existing new key + assert!(d.update_key(&"ID1".to_string(), "ID3".to_string()).is_err()); + + // Iterators + assert_eq!(d.values().collect::>(), vec!["Foox", "Bar", "Baz"]); + + assert_eq!( + d.iter().collect::>(), + vec![ + (&String::from("ID1"), &String::from("Foox")), + (&String::from("ID2_bis"), &String::from("Bar")), + (&String::from("ID3"), &String::from("Baz")), + ] + ); + + // Remove + assert!(d.remove(&String::from("Missing")).is_none()); + assert_eq!(d.remove(&String::from("ID2_bis")), Some("Bar".to_string())); + assert_eq!(d.len(), 2); + + // Check order is maintained + assert_eq!(d.values().collect::>(), vec!["Foox", "Baz"]); + + // Insertion after remove + d.insert(String::from("ID4"), String::from("Test")); + assert_eq!(d.values().collect::>(), vec!["Foox", "Baz", "Test"]); + + Ok(()) + } + + #[test] + fn test_dict_serialization() { + // Init dict + let mut d: Dict = Dict::new(); + d.insert("ID1".to_string(), "Foo".to_string()); + d.insert("ID2".to_string(), "Bar".to_string()); + d.insert("ID3".to_string(), "Baz".to_string()); + d.remove(&"ID2".to_string()); + d.insert("ID4".to_string(), "Bar2".to_string()); + + // serialize + let data = serde_json::to_vec(&d).unwrap(); + + // can be read as a hashmap but this the order will be lost + let map: HashMap = serde_json::from_slice(&data).unwrap(); + assert_eq!(map.len(), d.len()); + assert!(map.contains_key("ID1")); + assert!(map.contains_key("ID3")); + assert!(map.contains_key("ID4")); + + // deserialization as dict will keep the order + let d2: Dict = serde_json::from_slice(&data).unwrap(); + assert_eq!(d2.len(), d.len()); + assert_eq!(d2.iter().collect::>(), d.iter().collect::>()); + } +} diff --git a/src/data_struct/error.rs b/src/data_struct/error.rs new file mode 100644 index 00000000..180621c2 --- /dev/null +++ b/src/data_struct/error.rs @@ -0,0 +1,44 @@ +use std::fmt::{Debug, Display}; + +type Key = String; +#[derive(Debug)] +pub enum Error { + EntryNotFound(Key), + ExistingEntry(Key), + AlreadyHasChild(Key), +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self { + Self::EntryNotFound(key) => write!(f, "Entry not found with key: {key}."), + Self::ExistingEntry(key) => write!(f, "Already existing entry with key: {key}."), + Self::AlreadyHasChild(key) => { + write!(f, "Entry with key {key} already has a child.") + } + } + } +} + +impl Error { + pub fn missing_entry(key: &T) -> Self + where + T: Debug, + { + Self::EntryNotFound(format!("{key:?}")) + } + + pub fn existing_entry(key: &T) -> Self + where + T: Debug, + { + Self::ExistingEntry(format!("{key:?}")) + } + + pub fn already_has_child(key: &T) -> Self + where + T: Debug, + { + Self::AlreadyHasChild(format!("{key:?}")) + } +} diff --git a/src/data_struct/mod.rs b/src/data_struct/mod.rs new file mode 100644 index 00000000..0e094e66 --- /dev/null +++ b/src/data_struct/mod.rs @@ -0,0 +1,8 @@ +mod dictionary; +pub mod error; +mod revision_map; +mod revision_vec; + +pub use dictionary::Dict; +pub use revision_map::RevisionMap; +pub use revision_vec::RevisionVec; diff --git a/src/data_struct/revision_map.rs b/src/data_struct/revision_map.rs new file mode 100644 index 00000000..06db2010 --- /dev/null +++ b/src/data_struct/revision_map.rs @@ -0,0 +1,225 @@ +use std::{ + borrow::Borrow, + collections::{ + hash_map::{Entry, OccupiedEntry, VacantEntry}, + HashMap, LinkedList, + }, + fmt::Debug, + hash::Hash, +}; + +/// 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. +/// +/// Map { +/// key2: b +/// key1: a" -> a' > a +/// key3: c' -> c +/// } +/// +/// Insertions are only allowed at the front of the linked list. +/// Deletions can only happen at the end of the linked list. +/// +/// This guarantees that the entry versions are always ordered. +#[derive(Default, Debug, PartialEq, Eq)] +pub struct RevisionMap +where + K: Debug + PartialEq + Eq + Hash, + V: Debug, +{ + pub(crate) map: HashMap>, +} + +impl RevisionMap +where + K: Hash + PartialEq + Eq + Clone + Debug, + V: Clone + Debug, +{ + #[must_use] + pub fn new() -> Self { + Self { + map: HashMap::new(), + } + } + + #[must_use] + pub fn with_capacity(capacity: usize) -> Self { + Self { + map: HashMap::with_capacity(capacity), + } + } + + /// Returns the number of chains stored. + #[must_use] + pub fn len(&self) -> usize { + self.map.len() + } + + /// Returns the total number of elements stored. + pub fn count_elements(&self) -> usize { + self.map.values().map(LinkedList::len).sum() + } + + pub fn chain_length(&self, key: &K) -> usize { + self.map + .get(key) + .map_or(0, std::collections::LinkedList::len) + } + + #[must_use] + pub fn is_empty(&self) -> bool { + self.map.is_empty() + } + + fn insert_new_chain(entry: VacantEntry>, value: V) { + let mut new_chain = LinkedList::new(); + new_chain.push_front(value); + entry.insert(new_chain); + } + + fn insert_in_chain(mut entry: OccupiedEntry>, value: V) { + let chain = entry.get_mut(); + chain.push_front(value); + } + + /// Inserts value at the front of the chain for a given key + pub fn insert(&mut self, key: K, value: V) { + match self.map.entry(key) { + Entry::Occupied(entry) => Self::insert_in_chain(entry, value), + Entry::Vacant(entry) => Self::insert_new_chain(entry, value), + } + } + + /// Returns the last revised value for a given key. + pub fn get_latest(&self, key: &Q) -> Option<&V> + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + self.map.get(key).and_then(LinkedList::front) + } + + /// Returns a mutable reference to the last revised value for a given key. + pub fn get_latest_mut(&mut self, key: &Q) -> Option<&mut V> + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + self.map.get_mut(key).and_then(LinkedList::front_mut) + } + + pub fn contains_key(&self, key: &K) -> bool { + self.map.contains_key(key) + } + + /// Iterates through all keys in arbitrary order. + pub fn keys(&self) -> impl Iterator { + self.map.keys() + } + + /// Iterates through all revisions of a given key starting with the more + /// recent one. + pub fn get(&self, key: &Q) -> Option<&LinkedList> + //impl Iterator> + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + self.map.get(key) //.map(RevisionList::iter) + } + + /// Removes and returns an iterator over all revisions from a given key. + pub fn remove(&mut self, key: &Q) -> Option> + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + self.map.remove(key).map(LinkedList::into_iter) + } + + /// Keeps the n more recent values for a given key and returns the removed + /// older values. + pub fn keep(&mut self, key: &Q, n: usize) -> Option> + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + let chain = self.map.get_mut(key)?; + if n <= chain.len() { + Some(chain.split_off(n).into_iter()) + } else { + None + } + } + + /// Retains only the elements with a key validating the given predicate. + pub fn retain(&mut self, f: impl Fn(&K) -> bool) { + self.map.retain(|key, _| f(key)); + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashSet; + + use super::*; + + #[test] + fn test_revision_map() { + let mut map: RevisionMap = RevisionMap::new(); + assert!(map.is_empty()); + + // Insertions + map.insert("Part1".to_string(), "Part1V1".to_string()); + assert_eq!(map.count_elements(), 1); + assert_eq!(map.len(), 1); + map.insert("Part1".to_string(), "Part1V2".to_string()); + assert_eq!(map.count_elements(), 2); + // two elements in the same chain + assert_eq!(map.len(), 1); + + map.insert("Part2".to_string(), "Part2V1".to_string()); + map.insert("Part2".to_string(), "Part2V2".to_string()); + map.insert("Part2".to_string(), "Part2V3".to_string()); + assert_eq!(map.len(), 2); + assert_eq!(map.count_elements(), 5); + + map.insert("Part3".to_string(), "Part3V1".to_string()); + assert_eq!(map.count_elements(), 6); + + // Get + assert_eq!(map.get_latest("Part1").unwrap(), "Part1V2"); + assert_eq!(map.get_latest("Part2").unwrap(), "Part2V3"); + assert!(map.get_latest("Missing").is_none()); + + // Iterators + let vec: Vec<_> = map.get("Part1").unwrap().iter().collect(); + assert_eq!(vec, vec!["Part1V2", "Part1V1"]); + + let keys_set = map.keys().collect::>(); + assert!(keys_set.contains(&"Part1".to_string())); + assert!(keys_set.contains(&"Part2".to_string())); + + // Remove values + let vec: Vec<_> = map.remove("Part1").unwrap().collect(); + assert_eq!(vec, vec!["Part1V2".to_string(), "Part1V1".to_string()]); + assert_eq!(map.count_elements(), 4); + assert_eq!(map.len(), 2); + + // Remove older values in a chain + let vec: Vec<_> = map.keep("Part2", 1).unwrap().collect(); + assert_eq!(vec, vec!["Part2V2".to_string(), "Part2V1".to_string()]); + assert_eq!(map.count_elements(), 2); + let vec: Vec<_> = map.remove("Part2").unwrap().collect(); + assert_eq!(vec, vec!["Part2V3".to_string()]); + // Empty pop tail + assert!(map.keep("Part3", 1).unwrap().next().is_none()); + + // Retain + map.retain(|_| true); + assert_eq!(map.count_elements(), 1); + map.retain(|_| false); + assert!(map.is_empty()); + } +} diff --git a/src/data_struct/revision_vec.rs b/src/data_struct/revision_vec.rs new file mode 100644 index 00000000..0cd243a5 --- /dev/null +++ b/src/data_struct/revision_vec.rs @@ -0,0 +1,233 @@ +use std::collections::{linked_list, LinkedList, VecDeque}; + +/// 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. +/// +/// Vec [ +/// 0: key -> a" -> a' -> a +/// 1: key -> b +/// 2: key -> c' -> c +/// ] +/// +/// Insertions are only allowed at the front of the linked list. +/// Deletions can only happen at the end of the linked list. +/// +/// This guarantees that the entry versions are always ordered. +#[derive(Debug, PartialEq, Eq)] +pub struct RevisionVec { + chains: Vec<(K, LinkedList)>, +} + +impl Default for RevisionVec { + fn default() -> Self { + Self { + chains: Default::default(), + } + } +} + +impl RevisionVec { + #[must_use] + pub fn new() -> Self { + Self { chains: Vec::new() } + } + + #[must_use] + pub fn with_capacity(capacity: usize) -> Self { + Self { + chains: Vec::with_capacity(capacity), + } + } + + /// Returns the number of chains stored. + #[must_use] + pub fn len(&self) -> usize { + self.chains.len() + } + + /// Returns the total number of elements stored. + #[must_use] + pub fn count_elements(&self) -> usize { + self.chains.iter().map(|(_, chain)| chain.len()).sum() + } + + #[must_use] + pub fn is_empty(&self) -> bool { + self.chains.is_empty() + } + + /// Creates and insert a new chain with a single value. + pub fn create_chain_with_single_value(&mut self, key: K, val: T) { + // Be aware that inserting a value for a key that is already associated to a + // chain breaks the CoverCrypt scheme as two chains will exist for the same key. + + let mut new_chain = LinkedList::new(); + new_chain.push_front(val); + self.chains.push((key, new_chain)); + } + + /// Inserts a new chain with a corresponding key. + pub fn insert_new_chain(&mut self, key: K, new_chain: LinkedList) { + // Be aware that inserting a new chain for a key that is already associated to a + // chain breaks the CoverCrypt scheme as two chains will exist for the same key. + + if !new_chain.is_empty() { + self.chains.push((key, new_chain)); + } + } + + pub fn clear(&mut self) { + self.chains.clear(); + } + + /// Retains only the elements with a key validating the given predicate. + pub fn retain(&mut self, f: impl Fn(&K) -> bool) { + self.chains.retain(|(key, _)| f(key)); + } + + /// Returns an iterator over each key-chains pair + #[allow(clippy::map_identity)] // unpack &(x, y) to (&x, &y) + pub fn iter(&self) -> impl Iterator)> { + self.chains.iter().map(|(key, chain)| (key, chain)) + } + + /// Returns an iterator over each key-chains pair that allow modifying chain + #[allow(clippy::map_identity)] // unpack &mut (x, y) to (&x, &mut y) + pub fn iter_mut(&mut self) -> impl Iterator)> { + self.chains.iter_mut().map(|(ref key, chain)| (key, chain)) + } + + /// Iterates through all versions of all entries in a depth-first manner. + /// Returns the key and value for each entry. + pub fn flat_iter(&self) -> impl Iterator { + self.chains + .iter() + .flat_map(|(key, chain)| chain.iter().map(move |val| (key, val))) + } + + /// Iterates through all versions of all entry in a breadth-first manner. + #[must_use] + pub fn bfs(&self) -> BfsQueue { + BfsQueue::new(self) + } +} + +impl IntoIterator for RevisionVec { + type IntoIter = std::vec::IntoIter<(K, LinkedList)>; + type Item = (K, LinkedList); + + fn into_iter(self) -> Self::IntoIter { + self.chains.into_iter() + } +} + +/// Breadth-first search iterator for `RevisionVec`. +pub struct BfsQueue<'a, T> { + queue: VecDeque>, +} + +impl<'a, T> BfsQueue<'a, T> { + pub fn new(revision_vec: &'a RevisionVec) -> Self { + // add all chain heads to the iterator queue + Self { + queue: revision_vec + .chains + .iter() + .map(|(_, chain)| chain.iter()) + .collect(), + } + } +} + +impl<'a, T> Iterator for BfsQueue<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + // get first non-empty iterator in the queue + while let Some(mut iterator) = self.queue.pop_front() { + if let Some(element) = iterator.next() { + // put back the iterator at the end of the queue + self.queue.push_back(iterator); + return Some(element); + } + } + None + } +} + +impl FromIterator<(K, LinkedList)> for RevisionVec { + /// Creates a `RevisionVec` from an iterator + fn from_iter)>>(iter: I) -> Self { + Self { + chains: iter.into_iter().collect(), + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_revision_vec() { + let mut revision_vec: RevisionVec = RevisionVec::new(); + assert!(revision_vec.is_empty()); + assert_eq!(revision_vec.len(), 0); + + // Insert + revision_vec.insert_new_chain( + 1, + vec!["a\"".to_string(), "a'".to_string(), "a".to_string()] + .into_iter() + .collect(), + ); + revision_vec.create_chain_with_single_value(2, "b".to_string()); + revision_vec.insert_new_chain( + 3, + vec!["c'".to_string(), "c".to_string()] + .into_iter() + .collect(), + ); + + assert_eq!(revision_vec.count_elements(), 6); + assert_eq!(revision_vec.len(), 3); + + // Iterators + let depth_iter: Vec<_> = revision_vec.flat_iter().collect(); + assert_eq!( + depth_iter, + vec![ + (&1, &"a\"".to_string()), + (&1, &"a'".to_string()), + (&1, &"a".to_string()), + (&2, &"b".to_string()), + (&3, &"c'".to_string()), + (&3, &"c".to_string()), + ] + ); + + let breadth_iter: Vec<_> = revision_vec.bfs().collect(); + assert_eq!( + breadth_iter, + vec![ + &"a\"".to_string(), + &"b".to_string(), + &"c'".to_string(), + &"a'".to_string(), + &"c".to_string(), + &"a".to_string(), + ] + ); + + // Retain + revision_vec.retain(|key| key == &1); + assert_eq!(revision_vec.count_elements(), 3); + assert_eq!(revision_vec.len(), 1); + + // Clear + revision_vec.clear(); + assert!(revision_vec.is_empty()); + } +} diff --git a/src/lib.rs b/src/lib.rs index 3fcc8a72..1ddabb35 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,7 @@ mod error; pub mod abe_policy; pub mod core; +pub mod data_struct; #[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 3852b174..58e22fad 100644 --- a/src/test_utils/mod.rs +++ b/src/test_utils/mod.rs @@ -72,47 +72,75 @@ mod tests { #[test] fn test_update_master_keys() -> Result<(), Error> { - let mut policy = policy()?; + let policy = policy()?; let cover_crypt = Covercrypt::default(); let (mut msk, mut mpk) = cover_crypt.generate_master_keys(&policy)?; - let partitions_msk: Vec = msk.subkeys.clone().into_keys().collect(); - let partitions_mpk: Vec = mpk.subkeys.clone().into_keys().collect(); - assert_eq!(partitions_msk.len(), partitions_mpk.len()); - for p in &partitions_msk { - assert!(partitions_mpk.contains(p)); - } - // rotate he FIN department - policy.rotate(&Attribute::new("Department", "FIN"))?; - // update the master keys - cover_crypt.update_master_keys(&policy, &mut msk, &mut mpk)?; - let new_partitions_msk: Vec = msk.subkeys.clone().into_keys().collect(); - let new_partitions_mpk: Vec = mpk.subkeys.clone().into_keys().collect(); - assert_eq!(new_partitions_msk.len(), new_partitions_mpk.len()); - for p in &new_partitions_msk { - assert!(new_partitions_mpk.contains(p)); - } + // same number of subkeys in public and secret key + assert_eq!(mpk.subkeys.len(), 20); + assert_eq!(msk.subkeys.count_elements(), 20); + + // rekey all partitions which include `Department::FIN` + let rekey_access_policy = AccessPolicy::Attr(Attribute::new("Department", "FIN")); + cover_crypt.rekey_master_keys(&rekey_access_policy, &policy, &mut msk, &mut mpk)?; + // public key contains only the last subkeys + assert_eq!(mpk.subkeys.len(), 20); + // secret key stores the 5 old subkeys // 5 is the size of the security level dimension - assert_eq!(new_partitions_msk.len(), partitions_msk.len() + 5); + assert_eq!(msk.subkeys.count_elements(), 25); - // Clear old rotations will reduce master keys size - policy.clear_old_attribute_values(&Attribute::new("Department", "FIN"))?; - // update the master keys - cover_crypt.update_master_keys(&policy, &mut msk, &mut mpk)?; - let new_partitions_msk: Vec = msk.subkeys.clone().into_keys().collect(); - let new_partitions_mpk: Vec = mpk.subkeys.clone().into_keys().collect(); - assert_eq!(new_partitions_msk.len(), new_partitions_mpk.len()); - for p in &new_partitions_msk { - assert!(new_partitions_mpk.contains(p)); - } - // 5 is the size of the security level dimension - assert_eq!(new_partitions_msk.len(), partitions_msk.len()); + // remove older subkeys for `Department::FIN` + cover_crypt.prune_master_secret_key(&rekey_access_policy, &policy, &mut msk)?; + assert_eq!(mpk.subkeys.len(), 20); + // we only keep the last subkeys in the secret key + assert_eq!(msk.subkeys.count_elements(), 20); + + Ok(()) + } + + #[test] + fn test_master_rekey() -> Result<(), Error> { + let d1 = DimensionBuilder::new( + "D1", + vec![ + ("D1A1", EncryptionHint::Classic), + ("D1A2", EncryptionHint::Classic), + ], + false, + ); + let d2 = DimensionBuilder::new( + "D2", + vec![ + ("D2A1", EncryptionHint::Classic), + ("D2A2", EncryptionHint::Classic), + ], + false, + ); + let mut policy = Policy::new(); + policy.add_dimension(d1)?; + policy.add_dimension(d2)?; + + let cover_crypt = Covercrypt::default(); + let (mut msk, mut mpk) = cover_crypt.generate_master_keys(&policy)?; + assert_eq!(msk.subkeys.count_elements(), 2 * 2); + + let rekey_access_policy = AccessPolicy::Attr(Attribute::new("D1", "D1A1")); + cover_crypt.rekey_master_keys(&rekey_access_policy, &policy, &mut msk, &mut mpk)?; + assert_eq!(msk.subkeys.count_elements(), 4 + 2); // Adding 2 new keys for partitions D1A1':D2A1, D1A1':D2A2 + + let rekey_access_policy = AccessPolicy::Attr(Attribute::new("D1", "D1A2")); + cover_crypt.rekey_master_keys(&rekey_access_policy, &policy, &mut msk, &mut mpk)?; + assert_eq!(msk.subkeys.count_elements(), 6 + 2); // Adding 2 new keys for partitions D1A2':D2A1, D1A2':D2A2 + + let rekey_access_policy = AccessPolicy::Attr(Attribute::new("D2", "D2A1")); + cover_crypt.rekey_master_keys(&rekey_access_policy, &policy, &mut msk, &mut mpk)?; + assert_eq!(msk.subkeys.count_elements(), 8 + 2); // Adding 2 new keys D1A1':D2A1', D1A2':D2A1' Ok(()) } #[test] fn test_refresh_user_key() -> Result<(), Error> { - let mut policy = policy()?; + let policy = policy()?; let cover_crypt = Covercrypt::default(); let (mut msk, mut mpk) = cover_crypt.generate_master_keys(&policy)?; let decryption_policy = AccessPolicy::from_boolean_expression( @@ -120,31 +148,39 @@ mod tests { )?; let mut usk = cover_crypt.generate_user_secret_key(&msk, &decryption_policy, &policy)?; let original_usk = UserSecretKey::deserialize(usk.serialize()?.as_slice())?; - // rotate he FIN department - policy.rotate(&Attribute::new("Department", "MKG"))?; - // update the master keys - cover_crypt.update_master_keys(&policy, &mut msk, &mut mpk)?; + // rekey the MKG department + let rekey_access_policy = AccessPolicy::Attr(Attribute::new("Department", "MKG")); + cover_crypt.rekey_master_keys(&rekey_access_policy, &policy, &mut msk, &mut mpk)?; // refresh the user key and preserve access to old partitions - cover_crypt.refresh_user_secret_key(&mut usk, &decryption_policy, &msk, &policy, true)?; - // 4 partitions accessed by the user were rotated (MKG Protected, Low Secret, + cover_crypt.refresh_user_secret_key(&mut usk, &msk, true)?; + // 4 partitions accessed by the user were rekeyed (MKG Protected, Low Secret, // Medium Secret and High Secret) - assert_eq!(usk.subkeys.len(), original_usk.subkeys.len() + 4); - for x_i in &original_usk.subkeys { - assert!(usk.subkeys.contains(x_i)); + assert_eq!( + usk.subkeys.borrow().count_elements(), + original_usk.subkeys.borrow().count_elements() + 4 + ); + for x_i in original_usk.subkeys.borrow().flat_iter() { + assert!(usk.subkeys.borrow().flat_iter().any(|x| x == x_i)); } // refresh the user key but do NOT preserve access to old partitions - cover_crypt.refresh_user_secret_key(&mut usk, &decryption_policy, &msk, &policy, false)?; + cover_crypt.refresh_user_secret_key(&mut usk, &msk, false)?; // the user should still have access to the same number of partitions - assert_eq!(usk.subkeys.len(), original_usk.subkeys.len()); - for x_i in &original_usk.subkeys { - assert!(!usk.subkeys.contains(x_i)); + assert_eq!( + usk.subkeys.borrow().count_elements(), + original_usk.subkeys.borrow().count_elements() + ); + for x_i in original_usk.subkeys.borrow().flat_iter() { + assert!(!usk.subkeys.borrow().flat_iter().any(|x| x == x_i)); } // try to modify the user key and refresh let part = Partition::from(vec![1, 6]); - usk.subkeys.push(msk.subkeys.get(&part).unwrap().clone()); + usk.subkeys.borrow_mut().create_chain_with_single_value( + part.clone(), + msk.subkeys.get_latest(&part).unwrap().clone(), + ); assert!(cover_crypt - .refresh_user_secret_key(&mut usk, &decryption_policy, &msk, &policy, false) + .refresh_user_secret_key(&mut usk, &msk, false) .is_err()); Ok(()) @@ -156,8 +192,8 @@ mod tests { let cover_crypt = Covercrypt::default(); let (mut msk, mut mpk) = cover_crypt.generate_master_keys(&policy)?; - let partitions_msk: Vec = msk.subkeys.clone().into_keys().collect(); - let partitions_mpk: Vec = mpk.subkeys.clone().into_keys().collect(); + let partitions_msk: Vec = msk.subkeys.keys().cloned().collect(); + let partitions_mpk: Vec = mpk.subkeys.keys().cloned().collect(); assert_eq!(partitions_msk.len(), partitions_mpk.len()); for p in &partitions_msk { assert!(partitions_mpk.contains(p)); @@ -177,8 +213,8 @@ mod tests { )?; // update the master keys cover_crypt.update_master_keys(&policy, &mut msk, &mut mpk)?; - let new_partitions_msk: Vec = msk.subkeys.clone().into_keys().collect(); - let new_partitions_mpk: Vec = mpk.subkeys.clone().into_keys().collect(); + let new_partitions_msk: Vec = msk.subkeys.keys().cloned().collect(); + let new_partitions_mpk: Vec = mpk.subkeys.keys().cloned().collect(); assert_eq!(new_partitions_msk.len(), new_partitions_mpk.len()); for p in &new_partitions_msk { assert!(new_partitions_mpk.contains(p)); @@ -199,17 +235,12 @@ mod tests { .decrypt(&cover_crypt, &low_secret_usk, None) .is_err()); - cover_crypt.refresh_user_secret_key( - &mut low_secret_usk, - &decryption_policy, - &msk, - &policy, - false, - )?; + cover_crypt.refresh_user_secret_key(&mut low_secret_usk, &msk, false)?; + // TODO: fix this behavior? assert!(encrypted_header .decrypt(&cover_crypt, &low_secret_usk, None) - .is_ok()); + .is_err()); Ok(()) } @@ -220,8 +251,8 @@ mod tests { let cover_crypt = Covercrypt::default(); let (mut msk, mut mpk) = cover_crypt.generate_master_keys(&policy)?; - let partitions_msk: Vec = msk.subkeys.clone().into_keys().collect(); - let partitions_mpk: Vec = mpk.subkeys.clone().into_keys().collect(); + let partitions_msk: Vec = msk.subkeys.keys().cloned().collect(); + let partitions_mpk: Vec = mpk.subkeys.keys().cloned().collect(); assert_eq!(partitions_msk.len(), partitions_mpk.len()); for p in &partitions_msk { assert!(partitions_mpk.contains(p)); @@ -247,8 +278,8 @@ mod tests { // update the master keys cover_crypt.update_master_keys(&policy, &mut msk, &mut mpk)?; - let new_partitions_msk: Vec = msk.subkeys.clone().into_keys().collect(); - let new_partitions_mpk: Vec = mpk.subkeys.clone().into_keys().collect(); + let new_partitions_msk: Vec = msk.subkeys.keys().cloned().collect(); + let new_partitions_mpk: Vec = mpk.subkeys.keys().cloned().collect(); assert_eq!(new_partitions_msk.len(), new_partitions_mpk.len()); for p in &new_partitions_msk { assert!(new_partitions_mpk.contains(p)); @@ -261,18 +292,12 @@ mod tests { .is_ok()); // refresh the user key and preserve access to old partitions - let new_decryption_policy = + let _new_decryption_policy = AccessPolicy::from_boolean_expression("Security Level::Top Secret && Department::HR")?; // refreshing the user key will remove access to removed partitions even if we // keep old rotations - cover_crypt.refresh_user_secret_key( - &mut top_secret_fin_usk, - &new_decryption_policy, - &msk, - &policy, - true, - )?; + cover_crypt.refresh_user_secret_key(&mut top_secret_fin_usk, &msk, true)?; assert!(encrypted_header .decrypt(&cover_crypt, &top_secret_fin_usk, None) .is_err()); @@ -286,12 +311,9 @@ mod tests { let cover_crypt = Covercrypt::default(); let (mut msk, mut mpk) = cover_crypt.generate_master_keys(&policy)?; - let partitions_msk: Vec = msk.subkeys.clone().into_keys().collect(); - let partitions_mpk: Vec = mpk.subkeys.clone().into_keys().collect(); + let partitions_msk: Vec = msk.subkeys.keys().cloned().collect(); + let partitions_mpk: Vec = mpk.subkeys.keys().cloned().collect(); assert_eq!(partitions_msk.len(), partitions_mpk.len()); - for p in &partitions_msk { - assert!(partitions_mpk.contains(p)); - } // // New user secret key @@ -313,11 +335,11 @@ mod tests { // update the master keys cover_crypt.update_master_keys(&policy, &mut msk, &mut mpk)?; - let new_partitions_msk: Vec = msk.subkeys.clone().into_keys().collect(); - let new_partitions_mpk: Vec = mpk.subkeys.clone().into_keys().collect(); + let new_partitions_msk: Vec = msk.subkeys.keys().cloned().collect(); + let new_partitions_mpk: Vec = mpk.subkeys.keys().cloned().collect(); // the disabled partition have been removed from mpk assert_eq!(new_partitions_msk.len() - 5, new_partitions_mpk.len()); - // msk hasn't changed + // msk has not changed assert_eq!(new_partitions_msk.len(), partitions_msk.len()); assert!(encrypted_header @@ -334,40 +356,23 @@ mod tests { ); // refresh the user key and preserve access to old partitions - let new_decryption_policy = - AccessPolicy::from_boolean_expression("Security Level::Top Secret && Department::FIN")?; - cover_crypt.refresh_user_secret_key( - &mut top_secret_fin_usk, - &new_decryption_policy, - &msk, - &policy, - true, - )?; + cover_crypt.refresh_user_secret_key(&mut top_secret_fin_usk, &msk, true)?; assert!(encrypted_header .decrypt(&cover_crypt, &top_secret_fin_usk, None) .is_ok()); - // refresh the user key and remove access to old partitions - cover_crypt.refresh_user_secret_key( - &mut top_secret_fin_usk, - &new_decryption_policy, - &msk, - &policy, - false, - )?; + // refresh the user key and remove access to old partitions should still work + cover_crypt.refresh_user_secret_key(&mut top_secret_fin_usk, &msk, false)?; assert!(encrypted_header .decrypt(&cover_crypt, &top_secret_fin_usk, None) .is_ok()); // // Rotating the disabled attribute should only change the msk - policy.rotate(&Attribute::new("Department", "FIN"))?; - cover_crypt.update_master_keys(&policy, &mut msk, &mut mpk)?; - let new_partitions_msk: Vec = msk.subkeys.clone().into_keys().collect(); - let new_partitions_mpk: Vec = mpk.subkeys.clone().into_keys().collect(); + let rekey_ap = AccessPolicy::Attr(Attribute::new("Department", "FIN")); + cover_crypt.rekey_master_keys(&rekey_ap, &policy, &mut msk, &mut mpk)?; // 5 new partitions added to the msk - assert_eq!(new_partitions_msk.len() - 10, new_partitions_mpk.len()); - assert_eq!(new_partitions_msk.len(), partitions_msk.len() + 5); + assert_eq!(msk.subkeys.count_elements() - 10, mpk.subkeys.len()); Ok(()) } @@ -393,7 +398,7 @@ mod tests { EncryptedHeader::generate(&cover_crypt, &policy, &mpk, &top_secret_ap, None, None)?; // remove the FIN department - policy.rename_attribute(&Attribute::new("Department", "FIN"), "Finance")?; + policy.rename_attribute(&Attribute::new("Department", "FIN"), "Finance".to_string())?; // update the master keys cover_crypt.update_master_keys(&policy, &mut msk, &mut mpk)?; @@ -403,16 +408,10 @@ mod tests { .is_ok()); // refresh the user key and preserve access to old partitions - let new_decryption_policy = AccessPolicy::from_boolean_expression( + let _new_decryption_policy = AccessPolicy::from_boolean_expression( "Security Level::Top Secret && Department::Finance", )?; - cover_crypt.refresh_user_secret_key( - &mut top_secret_fin_usk, - &new_decryption_policy, - &msk, - &policy, - false, - )?; + cover_crypt.refresh_user_secret_key(&mut top_secret_fin_usk, &msk, false)?; assert!(encrypted_header .decrypt(&cover_crypt, &top_secret_fin_usk, None) .is_ok()); @@ -422,8 +421,8 @@ mod tests { #[test] fn encrypt_decrypt_sym_key() -> Result<(), Error> { - let mut policy = policy()?; - policy.rotate(&Attribute::new("Department", "FIN"))?; + let policy = policy()?; + //policy.rotate(&Attribute::new("Department", "FIN"))?; let access_policy = (AccessPolicy::new("Department", "R&D") | AccessPolicy::new("Department", "FIN")) & AccessPolicy::new("Security Level", "Top Secret"); @@ -468,7 +467,7 @@ mod tests { fn test_rotate_then_encrypt() -> Result<(), Error> { // // Declare policy - let mut policy = policy()?; + let policy = policy()?; let top_secret_ap = AccessPolicy::from_boolean_expression("Security Level::Top Secret")?; // @@ -502,8 +501,8 @@ mod tests { // // Rotate argument (must update master keys) - policy.rotate(&Attribute::from(("Security Level", "Top Secret")))?; - cover_crypt.update_master_keys(&policy, &mut msk, &mut master_public_key)?; + let rekey_ap = AccessPolicy::Attr(Attribute::from(("Security Level", "Top Secret"))); + cover_crypt.rekey_master_keys(&rekey_ap, &policy, &mut msk, &mut master_public_key)?; // // Encrypt with new attribute @@ -521,15 +520,7 @@ mod tests { .decrypt(&cover_crypt, &top_secret_fin_usk, None) .is_err()); - cover_crypt.refresh_user_secret_key( - &mut top_secret_fin_usk, - &AccessPolicy::from_boolean_expression( - "Security Level::Top Secret && Department::FIN", - )?, - &msk, - &policy, - false, - )?; + cover_crypt.refresh_user_secret_key(&mut top_secret_fin_usk, &msk, false)?; // The refreshed key can decrypt the header assert!(encrypted_header diff --git a/src/test_utils/non_regression.rs b/src/test_utils/non_regression.rs index c2906271..2c7c4c53 100644 --- a/src/test_utils/non_regression.rs +++ b/src/test_utils/non_regression.rs @@ -7,7 +7,7 @@ use cosmian_crypto_core::bytes_ser_de::{Deserializer, Serializable}; use super::policy; use crate::{ - abe_policy::{AccessPolicy, Attribute, Policy}, + abe_policy::{AccessPolicy, Policy}, core::{MasterPublicKey, MasterSecretKey, UserSecretKey}, Covercrypt, EncryptedHeader, Error, }; @@ -155,8 +155,8 @@ impl NonRegressionTestVector { // // Policy settings // - let mut policy = policy()?; - policy.rotate(&Attribute::new("Department", "FIN"))?; + let policy = policy()?; + //policy.rotate(&Attribute::new("Department", "FIN"))?; // // Covercrypt setup diff --git a/src/test_utils/tests_data/non_regression_vector.json b/src/test_utils/tests_data/non_regression_vector.json index 6f4e1ebc..6e7800c1 100644 --- a/src/test_utils/tests_data/non_regression_vector.json +++ b/src/test_utils/tests_data/non_regression_vector.json @@ -1 +1 @@ -{"public_key":"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","master_secret_key":"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","policy":"eyJ2ZXJzaW9uIjoiVjEiLCJsYXN0X2F0dHJpYnV0ZV92YWx1ZSI6MTAsIm1heF9hdHRyaWJ1dGVfY3JlYXRpb25zIjoxMDAsImF4ZXMiOnsiU2VjdXJpdHkgTGV2ZWwiOnsiYXR0cmlidXRlX25hbWVzIjpbIlByb3RlY3RlZCIsIkxvdyBTZWNyZXQiLCJNZWRpdW0gU2VjcmV0IiwiSGlnaCBTZWNyZXQiLCJUb3AgU2VjcmV0Il0sImlzX2hpZXJhcmNoaWNhbCI6dHJ1ZX0sIkRlcGFydG1lbnQiOnsiYXR0cmlidXRlX25hbWVzIjpbIlImRCIsIkhSIiwiTUtHIiwiRklOIl0sImlzX2hpZXJhcmNoaWNhbCI6ZmFsc2V9fSwiYXR0cmlidXRlcyI6eyJTZWN1cml0eSBMZXZlbDo6TG93IFNlY3JldCI6eyJ2YWx1ZXMiOlsyXSwiZW5jcnlwdGlvbl9oaW50IjoiQ2xhc3NpYyJ9LCJTZWN1cml0eSBMZXZlbDo6UHJvdGVjdGVkIjp7InZhbHVlcyI6WzFdLCJlbmNyeXB0aW9uX2hpbnQiOiJDbGFzc2ljIn0sIkRlcGFydG1lbnQ6OkhSIjp7InZhbHVlcyI6WzddLCJlbmNyeXB0aW9uX2hpbnQiOiJDbGFzc2ljIn0sIlNlY3VyaXR5IExldmVsOjpNZWRpdW0gU2VjcmV0Ijp7InZhbHVlcyI6WzNdLCJlbmNyeXB0aW9uX2hpbnQiOiJDbGFzc2ljIn0sIkRlcGFydG1lbnQ6OkZJTiI6eyJ2YWx1ZXMiOls5LDEwXSwiZW5jcnlwdGlvbl9oaW50IjoiQ2xhc3NpYyJ9LCJTZWN1cml0eSBMZXZlbDo6VG9wIFNlY3JldCI6eyJ2YWx1ZXMiOls1XSwiZW5jcnlwdGlvbl9oaW50IjoiSHlicmlkaXplZCJ9LCJTZWN1cml0eSBMZXZlbDo6SGlnaCBTZWNyZXQiOnsidmFsdWVzIjpbNF0sImVuY3J5cHRpb25faGludCI6IkNsYXNzaWMifSwiRGVwYXJ0bWVudDo6UiZEIjp7InZhbHVlcyI6WzZdLCJlbmNyeXB0aW9uX2hpbnQiOiJDbGFzc2ljIn0sIkRlcGFydG1lbnQ6Ok1LRyI6eyJ2YWx1ZXMiOls4XSwiZW5jcnlwdGlvbl9oaW50IjoiQ2xhc3NpYyJ9fX0=","top_secret_mkg_fin_key":{"access_policy":"(Department::MKG || Department:: FIN) && Security Level::Top Secret","key":"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"},"medium_secret_mkg_key":{"access_policy":"Security Level::Medium Secret && Department::MKG","key":"pDBza3zZKU+KYIEWLCfQTyHh4ijersmVPUhbmEFwWQI+mMhnkaoD60BHWwG93zp4zQdpQH74kIFod4A0qy82CAMA9hqXOGMOjXrnBG5935MwP6GZbWK/uffl+SqCdI5MtAkAedRdi2sqMMNQSguMBRTaN7gCoDso180CSXjk6XAb1gIAgrRVtvgzIVYOlp7jnGWMdHuBE542Ig4DQ3VpDfvBHAU="},"top_secret_fin_key":{"access_policy":"Security Level::Top Secret && Department::FIN","key":"6cLPSpfJd6XZgniOpHhOrJWxjPgoJQN5LtOC9HFSDQT8evUsmqFBAcKZMfnOirmTymoc2oJMTuBlGGK6J6FzDQUA10DJwrvIJzs6LaAPRxGzyXyTmhAZI9hgo9B9soTd6gUBy0HESlxBhZAhnIhC0he1roB0XWGj3byPqqIB8+Gt3ibMeAi86vws7Etoaop9C0wFjOZt+qawFoE644wYARgj12WQVKQD6thThAiKXbFivailQnhy56MD1wUyd9MtCCuucWvBC6hYzcDLQXCOsAwwJDW2JqROPyJAIEcrkuJhTHBeUOeUukSdKpilCTsT6KGk0dIIbJfDfRdxSFE/DgMUHPmAGUs38YmSa4J9zLmzMQNDyITLfhSNBQthzyRAoRN4fBN067dWWDJnR+VhXyLLVca96lATYEIzCZsXjPIK4BHO0rsHDsY5MEuzh5C9X/skD5NAx0Ct2tVqT+gp5em7GFEBJGwT+0xLhctzAF1qFhs9mAJhonjO7Fg48PWhpGK6U5oAcCS2xhFaONYrIwMobbjCqwEEXbs2VexwPwS0C6JJa7GcWqFgVPes1ufJ+CEJ/qdnDdi2atA8j2fBW6nDXUV92hlaD5WHs6dRTTaqL7w/+oxrkPaEVySohHClKEcfrGlcayPGesiUE+o57sEEtHgnbUeZFQddU8ovemQC/CE1nKQO24vGVmeNu8UDABYEsolK1JMs08aQkjpE+TBFG3xOvBGZTZCl6YPJfJQ68PIDmjFbmlsNl0I21HBmKUxN6nNBTpBY7eZnkwhy0zaxGxGTuhhd/bo97HMz4VxU3uZABzJeo7lBvCc/WjM61FxB4JBLGFAHvbEKRRIcYWWsyhg7C+EWoyVHBcUIu+avyBaHmmwZbGer3iWKfXOR5tQ4vnlzlim9zwMD0vUAamspUMdzScE/EKUCL+QEmeqcs2xc9AKCsScN4HS3AEVZ6MFJvjApbdKIlGhNpNBzLaA6WEGhZcePYsGo2dhTZsWtACIsFtK8L+qmocuco7xvUXq4EbMCWbK8e3B0XbhvfuwoeZqrs8iQBrtvtLXMQ0wvbnayfBxaE+WNU3onGyRilESZ8/qOKYIQ8jQLp2VZ5gSNR+jCpVAS64oxzriEokh9bmREAItOistpNuGQELpRgSGsXXyVg4ZGaBFbPWgxRHBOzBIDWkx0Avq8+eJZaKEsw9Yt1dkfGzU3rYHOoERW/1nCT5KYAJZv5Og3L+QYzQgyf4LM/dgWQSpR9xViuxQ5syBBJqOtCsM+1sN22rccnSm81wAuOzUqw/vH6BUg4dEw0cWyBSE9lQd/OTKKyvJtMwO3ZUOLZ4lNreCLL7CEFBY6cNOEDWFcwgfK6FAJtNBtMQBA/eYlLnQYmbY59stLGYiKx+IQyKEgMKHKKAcBWsKuSWSUd+QhLnEn6xFIpeWJ4kVzs+KhO4ZlKFiJzVoyGtE4OFsl6BENfNSx02w2ZRWEHYGA/PiFoqMRiKUe/Mgt8IgaBPs6s3ZmvlvEEBlzakOt4/NqpQyZXCWmSYYaw6cieaCwdMq4yvGgsetkR3zOG2GCVEpHdRKshYamEszJW3eFJep7c6yDIGAXdMU8LvepDceKn8xytbhb+Sko/+Vu5StkaHySwGqxqCwd+zxrsYKvMXemf4Mo20M8vFxnY7gNtR8WL5D2AyfYlJq2N0z+LuP+Y39WqFjbCZzkdnOgBwcAmy0HN1GAFzaHfujsWGe0LUiLq3xuhH6uUVeSOe36AQcA/Oz+1JEseLt7t9qFXgmKqAZnRb+zVU83jJZaqUFfIggACJtWpZJCAJxPr6ISQpaD0rCtk0OLpZbGkxecrxI4IQU="},"low_secret_mkg_test_vector":{"encryption_policy":"Department::MKG && Security Level::Low Secret","plaintext":"bG93X3NlY3JldF9ta2dfcGxhaW50ZXh0","ciphertext":"8toCjlGy1UHq8QonJ49iFZiyCT1GqrysoDbXwZkqR0UeSRIBOeYIVuu3xI5UW0wq0ZvpTbx2hW90/3lBdWU7LA4OIUjFIdhrjJ6fJdFMMO4BAKapP94njR2bPUksxKxVBmt4e+BNmWZvEZcSH+Ps7RyVIELUcXhJsKStslUOkFU9Fn/ctoEPvaov5pNHx9LztFXKIM8mFoPxiNLSDKhaOaIB0OzCWPrBMPaeHrd1SlQ8fA38oAA1gVXr0hmTmBD06BkJylg6CA==","header_metadata":"AAAAAQ==","authentication_data":""},"top_secret_mkg_test_vector":{"encryption_policy":"Department::MKG && Security Level::Top Secret","plaintext":"dG9wX3NlY3JldF9ta2dfcGxhaW50ZXh0","ciphertext":"IjMIBenTCBDjOb/JJ/TtQHhwBM/9imU08PcD6HaGRTJobTss8u6FAqzggQLUPsJz2liGOtm43MKQA4/vvXvZXDGDJ5Qu6yJFoG/xOF/j7aQBARicXBhtFUEHxEsyz/gTxMa2d+7F3olt854CUZh/zzTwMVlQ9C+jQTDpMZYJjaA6vybKyz/qsBxiDlCF/Y73CK8E4deTluuo7mF+eXba6n9ZXzxQUniaulM+Ozef+nPuBky630/+ZTl+8h64kSui1O+90mDk0aYZ2L29Z/nbb4eScyWQtTSfAqxxpF2Tl0P4KWo5nnfawCVYO5fvXf4Wn+02eos3m43FVP0HhercbSdIRSXIQdvY9/Scv9wpRCH32ofa6u+6tOReia9tAUT7caFpxodaRKh7H0fmmq7GJ+A7Qgeb2Pp1xq7/+TTAQDGb7d4t91yAxfkGOOVbSn6E0vcEX7n3ocN+z7kJxvYAnDKwZuqYeD6UNMQacTrJjPag7cF3OhEmglUqIEYoL2q4mlU1GJ5Ns204Yz1zmbCtP+mwaIElETVqU2t8LzGM2pE3bRDdwkLk5gb2+bip60SNbN6KOOjHTNyVWpD8U4+/mXeeiuAAcfzbpHoNLSzIbiNnaoN6XhpQ6xf0hz1j6QT1ggOqGfWgS5XMy4bgAKNo0kfbNmVG4P5eTZ3Dz0eRj4qy5XlSOm4TZddOFkhWoViE64+e4ulIrXQlAjCMgPXqSAcQunEHdUu/x0B1bU6nYWMDkWeTlogUX+gfhqdtc5N27YkHzRAbK9iTSC5lD5YFnJnivHfWyuzShFIUwV0p+gHm2oMFRp94SjvqiWvStasNA5znK7NO5T7llhs3zzPPfeI3JCpB3w/2UTQu9Z9Vn7pzM0XisxML/UOjDvI5AkV7fJNfSD2+mkvcwtoMnABg64a2tO90rPexzkmFE5m+CaWF3zZ2+WRGGLYhczhCRgEOsQ04Y2dZWkiG+V7qTtaF4Ydbw/XP5bKvz8xZhIX+9dl/p3dxFuiqheLwOdrxYkQzy4mutdfY1w4wXhLxP+6iXUPdUbHItr8R1D7EuF9hwrGzvvFxJEWK+9J6FpKNWq0itjWiYTdRy/nixpfm+l6p6SeYXJTxO3iKNECsnsKZdRct7YVmZ1QV7kf4ljeZxu9At3u79+c/IWuR7JYBYdK7F3dbBHJQcN4hERFwoqdXvtO4HRviSJgoRNR5L9jDSNVNS1RRCxFoonxvgJoMmjgfBWkb2DtJcrMXB4eiIYoeB6vZGWx2oTJIRZMgd5Pb12Gqw9SBeWl/2Q0NrqMHPhHT2q7U5oP63HRwA2QKdh64au+6atHIQ5tyd+UrdFsxIsts5K9iKz8UDWdH/dPKFSXcBcUAaPEb58WggyxK/Zb6y2yOFD1IlZ3PdE4zloU5preb7QNY7pcY0jJ8SD1esAP4IbAz+ryb1lyAsgY9PDHWpuSzI6B0z+X8PPMpkAZt9r4Jc71pfUwMV7cuEZMrGG4HHTnzjfnKVttWrs8EfLMAHrCdVRMDPrcS6JxooEDeTkGQUx9NO9DD72X2hoEfnwhlJo+QIHhVjrGbjJB6TbTnc/gXuseXMNsdvAtAhxOrET+QIfSrSd9qNZ1Si79PepQDRsFsRHd7qEb9RH4CkNwHlFdD5E8y4l1+dt+8EYiYauZhllmxOX7VHA==","header_metadata":"AAAAAQ==","authentication_data":"AAAAAg=="},"low_secret_fin_test_vector":{"encryption_policy":"Department::FIN && Security Level::Low Secret","plaintext":"bG93X3NlY3JldF9maW5fcGxhaW50ZXh0","ciphertext":"ONHJH8ejrsQihQx8OjSuJR+tYWJ6ZmholHXBeU3+wmO4Z86jnF6Sca4A5q8l8JICEf3WaGv3uBqf1FxJgzLIQ8fYcWhgHsU1QYbc2AtUOOkBAKaTcMmR6oD4u2pGXW+oc17MkD8D7+LlqsOxOrEJzgGMAO5kEr249Rn+OCJtVVNyKhaFOZq0uABbHx+vrouf9xgA1qZIFUeTz77QDWvE9lbFFffBJMY=","header_metadata":"","authentication_data":""}} \ No newline at end of file +{"public_key":"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","master_secret_key":"eZH0q7g1OJJB25u2kXip+6lZ/XAuoFTf8s8WNV2WHACrTJVtBY+I+Mxp8Y4mMi05anjegLuA4I/c3yoeBhjSD/d7AgnPAdDns0U20ji78IJbpuj/3ITYTgXPIAt10xAMFAIECAEAjF6+QIgk4nesWt2ZFlTLlK8X7mRmznhwY8NqbvHQIwACAwYBAANXVxyHpdmuwBBKaNIEP7ZObGPGEdzpNBC+d7GajFcMAgUGAQEGkofbA1yMpQeCgsdYVmeSZqXiClSSmnEQOiukDAO5EZP+U75BWo/aKq2h6xbMaMVex5SctQSShs0P0X1+WjsQvMam1sJ2LEmJ3KwOVoE07Bk72Tc2Qm1nBnEWK1X1C5NUQhSs5TrGJJIi+8RMcHzTOIGevH+4W3ok0G7Pyq5WCE+sFYR6+IeBuLd1wBRjeXBRqXil+iXqCkx7YLxB53Ee9aEExZwpmnEQAq4z2h9wE1GBNHWzU6bE/BzOOIOsQl+sSCRIZixIsl62JpQkxUr+5VUvWmTEWWUqjM53yAPJZHIgYx3O4i5go2yyMicSd20Cqo6wwjoSg4jwHKm48KKAPDr1A7DPWIR4aZrr0qgOTAlJ1KBjezPmuaBmxysuvEqmwZI7dweNtIvfuCgeYZic9ESGW1oYEhArdQI22IiT2i3a1b7qW0jPiWOz+RDTaIlxpkP7F1BGE2rzVcDqKkwlVsgaRxnOOAPk8cdNEUQwG7JdfK/ItzZ59YGo+7qky1+kRJhOcWsQhVZ6iS01sRq8WJQ/6cZjAktBDDlbF6fXiU2qpr7n06kf26woAKtaKqKoy0DaWZcNWMObpqrm5JDt5zz2+z2A83iWEpOXaIS3oSTSHHjp6WV3F767pEuhwrT0C89B48IzcyYZ7F8X0M3gcQ3vWG3r9k0HTB1AFV8p6Gcrqg94gxFZ5yMBUkHeNLVUWEekASazGCA+a7X6jAGlwAkB7IQH9lFyQjn/1gwqioexZ51WNWbpRp+piFsGQi9gpxhE8bAWVyeGdMV8E4IcWWCr8ynpNo0wsQSg44htB26M2R/ZaBM1S2yR6akwCjNrk7ca9H10pbo4+VXv1ERNOFlPm03iOiBZGW2qkGL5E40/QVlK6ix7kUbWWj5o1C885nwjZ3b9iEU2s6AVc2X+OJ6L9HM9KEIOMLcGNra5WijfPGk21p4CKYJHCTGqa5e9KRRgxQ2pU4AkzJ+y6l3I6pWHUxqc0sYwN8x6S3rzE4heeT+ZCaSdnLQZGSHRiKdcRHhw2JZMwj/59n4Gty4k9EkndgIDiU75l8TRwhmewyb+d1u/1gL+uc6nwHWjO5MoCcWctstdEbBHrBtVG5G6GTlEYDyi7IFn6bCAYB1M1YFcZle3kTpv+W8Eo8gY0Qbw8rdlWgsVEYiS6BzVMY/ncjmvo0PBpQjN1nabpairWmgUYLz8xsxTwQal8lwriXa+tShdyQWhy6vLXE59HCIpE0K1G3Ig1kNw02YYqK7j+Jov1YEMnL/XjA6DKiGSi78o+SLtoYnlIqnQZiGnGT6QE19epaTgRIAeSLJhClrI3AxVpWX3UExxl3eJdR24WKq0lkl1QTO+KMAaJZ1TaDH7gCVIzAZoWbQL43CCY0dvdBNzQcNrBEzQV2ZPfG6jiXo0FY4HVHHCZbuJK4Osd1pjFXwI+7JY5B+0NS/0gGf5O4WG8kNgaghc1aGB0Zv0+6kTAnfBqpx/m0TvsnRF4mH3EitzeDKitZ60RAj+kUZF0hSkRYKFcbuxKV6v6X6+UpYoidN9OoQhlQDBHtKptI+U6BLxkPqp3lz/FrpVUUWhAAIEBwEAdbCvXLc3Ayq26c30wWCRlwUtoxJAzKM97GMWv41ThAQCBAYBAP6u1zV15lcEiGf+5uI1Xe/CUxLSHoeIrBYHXIpYHJkGAgEJAQDLKZ8uJkWqQfJdBXEx0KEcmnPMBEP4Ngk9W+YFv/pZCAIBBgEA5sfT9dWl2iGBI6PChUYOQ/niOtBUa27GTUFaArnuagUCBQgBARI7nKuLO3UAqHYqGuJCDTn8idFmbryHSd0YQYZ8rZTJo4dWdZBTsH1yLMKqyjPIMPssFguKZQJFYnqxUyfjP10Yr7tgsHvMGryUz3D1DUTzRuEwcf86K0G4IkvygVt6aDLzVpxscem2v+SVx3IViqZ5Kchperw4FxpBHOHXfl66RJoZKszwW/nmmI2zkXTlYeOoYO/HLPPyhOmWYzvZy9SclquZKuQXGGSRC7vZNtqVNnyYGcu1u0x0VywqD+xohSR8qgFcQR9BW7fbL8xSHM7Ys+yANLsgaG61HlbEe2pHFNfgf0ILMUMzmpu3MBQGLcgIQZpJQFJ1Plf1CfHKSmdrVtnIkna0gFJYejfMpBgiUyrWwYvrH5IJgDmDs8hTvhfTygfms0TxoKS6wjtFZ2sowrYmyQebRp3oMFS4AGQJDv2VdvlULqaYIx+JOf8WJezntnabl914XyWAChV0TiCJwBXbmCxMKo93mEqoIylTnXyHa7ujUtHCWqaQPxD7cPT3nhQxdAwYxcHqGtCVAvwErmcMPjZZQDYbO1KMHTP4GtnrVzTJImvpxvWsbwKIfcAILSaMgwPJYwmBI1DrrBu5YRA4G/vIWtNmNbNbOIflzYRTej/Kj4Rmba/Zf0LXf7rWU5Yrv6rmkH1UVh53cVnwSdGqKarhuED6uTBJtOXEjeiLGKYRRA5bFc75G848ESpyoPMkg4yyxl8pTSkhew/JQOaXIFICpMU6wQcQKdIjFUXlSN8bHb+pjLB5wnLpY5nUUjwleZKHyZL7MHI4MmdXPGVkYEZRmn9Qp+C7l2IVet64J4lJuLdhdjZLfHrjFBNAN24UgBl5qRv0o3ZVMnApK5OshnxqdffVmNXAAHJEzzpTQ4K1gMuTBQIJE5Jzjx/IAzg5vGhiE+kkCFpzWbuguXzjK802F1Zokw5kTnfkvyVxTFHUSwpmqoWLxqeQkoiTI5OYU9pCpAXTaY0rSmNHyQpAo/bIjD3sZQmQCGtyxwjDRkalNB60idsYb56XIe/4mx7YKB5DinGbp+QCrlHVa+oaSrPsgGZhkW51OBa4SSKxk9GRllVwk9ZcGboKqf0jGuZ4r2+rjXnBmpqEq/EXD4Wxa6cZmeuSRGvQQOzHfDwWYXcRxJHjCaOFlUN3umUFhGa6X5x5sTZQsi0iEpEGl0/Ge0ZaUhGQI1SlPowra0WSkNOphQRozf7bP51DAN9EePrbzpIJSgVRDLE5j0qqDngAfS0pdYgkwRRrIuq7WvmwiohCI/FqLEV2HTsnHoFZY63QN9U3laimqYTrbLN1zCenRvC0mepiYlBWWTyJtw2RpETLQPURyBu1Q+LUJwnwEivpHQ23MKa5MUXHITISmAKZf+PlHQ8zN8oXy6qEmHBhhskWDij1LIwHXprDDIpxWLQZj0Q5O8gUfFGjfrJ2zvgAcplpVZe6FHAwUm3QArT6Y410xIJQPY3nimZYkXFkaucFLLbhd1YFir3hTyv7vPhgyAVoNVMJtlLiPBmFLbsTVk+EuCw0YF2zB1ugKG9YSSo0fBg0ZN+zD4RaqTDusS6x6reCxC8ekbO2acgKAgUJAQEqjL/z+mkvSn64+4O8JnOut0M9BGpkuQxB4gB7KGnyGbUsgMdiV3E2uBCGY8KlilI9JcdKxYUr2F1NtaUyE1mJ4Y2Woz4LHJGj9HY2u5b6iFIa1q4voWbY9jPYtVYN2x/JtZn8AoLh1M0Ix2SWVRMPw5nd1i1N25A1Sq/JSzpe+mSkEahQzGwTxpJWbFmVJD4cekVEtD/xh6o28hkHo8kGAUsHjJ/LlLalVn4+xryZprorYa9lmTqHqC9cVToiXGVRW5wdzKAZ91RDl7SGFaqumm8N1BeLc3g5R0pAqWAvBxRDd2PaBmljY0bUmjATYgXpuFxD5bxHtz66e6YDInojiK5DJliSOWcLwbhrICPtnAd4VIV1YJjmVQ4VJYVgU4gOxK5VG4YpjLMr/G2C03U7I2ZzU3E2SKjKC7p2QcWQiDHhyBMNOYRyBm//N6Iy6pgQ+ZMV6wEU/I8NNlNm/LFH6abRaC314AAVK1jkBMzjR74aIb1406ATVrep+qRKuT12wkLADGKapUipMXUWNYepEJE2EAsYskkKbH+76xTW9aXv86Ti2WiYTLaFoFbdOlpnhTu4k0hOoQMb5Ca2MqnxvGPEsV7gwnBI6KNwN6lmcy+r13iD4cP0yLjxNR0t9kJcEAPWyFjMFmrXYXPugGQik71XlUwQnB9Zhm7J6lqyFEzmEFHhIZ8BRyVRZrlCNkuGh5aW50C5Gy6fF8IjRkCRp6IuN0Tg5Yj62GEqCoE/7JB4e5iTyEP9lT97Ek2Jlj0QW46BnLqRV3ont7pmKiLLM6AZkzMoRrs58idp92PhVBn+UJvTZoQbyW5ZxpBOd8d/wXcBpimvhWJq+wuEulTTdgi6iRnPG2LuO8KKoGmYvCMiK0BsrBkIkxqyzLr6Jq60Ob665a0y6QN+ZpolKTidY7jf21z3wKUC/BdKQ0mKVktKVFYItzn92MgYZbLI0kMg1atC5lc0+VhhZBlGixiItxCOknGY4LNmxVOC04AqssGVyScgy5aVgR/NFJyYRTJHyaKWUUsEgVRiMRi5KcFkQ0NVGZhfu472dT24UpkBUUBVE2W/oackuE8/AmO/sr7fIaT993VdQKIP5zhZEL0QAwL4gsnLWxrTxSSiujg4qG3FkEeNY8xK1G5//I+lxWrGYikJrF4g2YI7GM6s9Z8kMhVVfHRZpVfLOqJF5JiBAForoXeJCcg0ZVkNCCQNEnJyOM+RQgNsxoN27KlJeDBkqzWayKw4iqfMlH8bWl3idCZh4kwZDIwWOcsXaToyMR5L1X6RaakvYCTHKCMlhqkQJynF0ra47GsdbMNBMAZZl5pYI2vSpTDB3KsnKETeOhsMl2yD4yO+PKUngKaWBVpymUOolgsoyZvtkEyRUXUXtTAatmV2SIjIjIB7mKpbB11XoC6PgBlUQnCg+GTVSQqK92sXEy1p07ZMd0hi1x64NLEioHgbuwz3iw42eqHkp6vWIJFjRnSw9ZXPImGn4T4zG0NuzDqqqZvKtGQ1bKzvZj+77AczMl4MWmncF6uOIHZTSW8669mo315HjIYc4YEtgcX1UyCDkFEoe1alxS6FDAIDBwEAdTyCFyQCCTEVA1r9+RcAQzGDYWOv4yqZiSAEPnTsoAcCBAkBAOgHIW/OGJP/lyeXn+J4NM1Te5opEXpRCQ1FsFQjQxANAgMJAQCo7uOgLHENsdiHU2nKwEQdJN5MXV7uJAXeIWKCOt+PAgICCAEAX+IfxnbmpCIu3PLsB0kUNRsVJdJoSekynSXWxTHB1gwCAgkBAAOMD6CjzVdDmY/YsO//EQh6Ce3LBDTAZX99Q2Ywv/wFAgEHAQCXQ3Fo3Ko0i26hHFsE6HzTz5YRqmE1SnwCgdBe3T0ACgICBwEALpIsNXU+JABmx3lSpWaEgsndqmrSuuGcIepxVkhtcA8CBQcBAbqTs/3CENK2wFjQnNHgVg/ivFe2DjnKd3G2rSc8BZrqRdPQVArHFXILXdocq5yDLRP7CgABbXemkOa7q8isI/8IRdhlj3E0C2g8YgFyKOIwkq1GybIWABiGUFDURWxApzRGjVXVnlI5x/sXBmxRXsB7SqKxVkBTsqwaB+Z5n3mkGEgDwJvxfmjMfBzFp0TVHuI3guXlGySHQ0s3r4urr7uGSBuJm3DKx21XV1JyuvTQSHBpVhMUZAQEBcBwkN0aHTIZBihAPz+rNKfBsL0cXHP0UxlRXePpO1u0gtTxr7xEZbyUZQDowk53zT/kO8OHQb+XfzcQmiEof42gOLuTXkIxIj1CJjepzj1wzwr7Hz15KCeHQdRsWadzKAyCExW5A1rjC3xxuoX7hGv0BF7yC2+Gb5UwMKxME8pZAi3nnNiyjaCodWoBMk0Yp/rMuQ4xnwjhoIJ8MYGLzJOWZV/SgANQJMexfKTLngDrYoP0tXDTZUkruiEsy682UeBhmWerFFJ3ernir+YbKRxch2/VVCB1GHgmrAuprO3kVwFlMlepIPCaAYWSX4zsK7LQL8m0JJ+KFYCwNdURI4ykLNGnaH9leZ0HsbZarvTbyWllBciVLlF8sHPIhsb8k8GzrggjYBFxFaPHdQuwiH0aWOoyaLFCZ2yYrBJWlNhCGJ1UMInTHvdjuIiCD1OSZoPLK/1WRX2WXGXQfCMoa1N5JsFVfU1mMzQMe43zne/2a9lEAY+5PNOTh6YgXkKXFQKqXmVjJeAJK/IYZ47EJRX2PjYmeprgzHOkpaTARG6BxmOXo9VFB7PYTSo3JsDsbt2TQRU7ynkGltCbPtcpG9IGJiWysJkxMEpMBTfyUpWDwqtVQ+bZNS4rioH8jh04Y+0Ue8a5zVjFCw9ovPiGiSMBCzrTv0iSLQdSzwPhXjZWjn7YG0M0OlawnM2MvYKmOnzGikraKgAzFzBikZ0jgc3UWoikSAaJottCgtGnp43EbIjgmZKLbANJOthbB8UyVd7BvC2ZI+V2fvwUTuFsw5G8DhLDdERIRn45wJeDpq4ltXrFqb7yst+iIUQLY58pceJ0YgcGjcHcW6tbHzbMYj8LtVfFqTbiNK6pWi9jNLNiuZzojxaLdep7oZAhV6dkfq0jQNujQ4/srVHUR+5SmUQre0s2A6MCqCdkN7HaWogEPVKIgAsTr+JnYLR6fDNHIGWotclcW+8XJaxywa0VWT9YOTrsSSKzzB/sFcviAvh5KVAVY9SHauZHi3wjYQhCGVMcj4AaCyFUJl8CXdfjmTspdIUIKqCmy5tyco0ThSvchiC2BBSyqh36rQ/BxJP1UKdGugYEQ7GwN8FaE9lGTgkHaB9jaAFJqOtrqT6lOV7qbS2cFTcBL+jVSE+6mlbbqA83vny2nZ+AQUeoO3RkjS9UOV8KMFJDPD15x5fVkzTqfYzogF+Rxs71N3FAWTtCKwDZOrXck3apW3c6Bbc8Ztc6v0GXr6xnBLGBUCVkv36iYJolZC5Bz+uxJaDKMH3DcQynzCx8Zhg3lt7I9ts+H50cmousrDNinqiS9SNcdqmbxG/Zf7UBAgIGAQAAeCttZOwcg3kyYqEOut/YUBJbyrcAEi9+ZBugtFDYDAIDCAEABoHTeUdVM6k+S+y6sad9Hnf0xsiEnssb3FddXYGkmwcCAQgBAIXS7fU8wwJpE7WvY7In3cReVTEr4nM7M954GHqBEfcFasBlQUFTFbcVQGYa/cQ60Q==","policy":"eyJ2ZXJzaW9uIjoiVjIiLCJsYXN0X2F0dHJpYnV0ZV92YWx1ZSI6OSwiZGltZW5zaW9ucyI6eyJTZWN1cml0eSBMZXZlbCI6eyJPcmRlcmVkIjp7IlByb3RlY3RlZCI6eyJpZCI6MSwiZW5jcnlwdGlvbl9oaW50IjoiQ2xhc3NpYyIsIndyaXRlX3N0YXR1cyI6IkVuY3J5cHREZWNyeXB0In0sIkxvdyBTZWNyZXQiOnsiaWQiOjIsImVuY3J5cHRpb25faGludCI6IkNsYXNzaWMiLCJ3cml0ZV9zdGF0dXMiOiJFbmNyeXB0RGVjcnlwdCJ9LCJNZWRpdW0gU2VjcmV0Ijp7ImlkIjozLCJlbmNyeXB0aW9uX2hpbnQiOiJDbGFzc2ljIiwid3JpdGVfc3RhdHVzIjoiRW5jcnlwdERlY3J5cHQifSwiSGlnaCBTZWNyZXQiOnsiaWQiOjQsImVuY3J5cHRpb25faGludCI6IkNsYXNzaWMiLCJ3cml0ZV9zdGF0dXMiOiJFbmNyeXB0RGVjcnlwdCJ9LCJUb3AgU2VjcmV0Ijp7ImlkIjo1LCJlbmNyeXB0aW9uX2hpbnQiOiJIeWJyaWRpemVkIiwid3JpdGVfc3RhdHVzIjoiRW5jcnlwdERlY3J5cHQifX19LCJEZXBhcnRtZW50Ijp7IlVub3JkZXJlZCI6eyJIUiI6eyJpZCI6NywiZW5jcnlwdGlvbl9oaW50IjoiQ2xhc3NpYyIsIndyaXRlX3N0YXR1cyI6IkVuY3J5cHREZWNyeXB0In0sIkZJTiI6eyJpZCI6OSwiZW5jcnlwdGlvbl9oaW50IjoiQ2xhc3NpYyIsIndyaXRlX3N0YXR1cyI6IkVuY3J5cHREZWNyeXB0In0sIlImRCI6eyJpZCI6NiwiZW5jcnlwdGlvbl9oaW50IjoiQ2xhc3NpYyIsIndyaXRlX3N0YXR1cyI6IkVuY3J5cHREZWNyeXB0In0sIk1LRyI6eyJpZCI6OCwiZW5jcnlwdGlvbl9oaW50IjoiQ2xhc3NpYyIsIndyaXRlX3N0YXR1cyI6IkVuY3J5cHREZWNyeXB0In19fX19","top_secret_mkg_fin_key":{"access_policy":"(Department::MKG || Department:: FIN) && Security Level::Top Secret","key":"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"},"medium_secret_mkg_key":{"access_policy":"Security Level::Medium Secret && Department::MKG","key":"z9nKF/RUO+yNxy8K8xLoUaBEydPA80vWJYvvATzuJAbcXgRvrwK2lVQKtC6L9bjevuK5qH7VOYpvZicqQ/IMAwMCAwgBAAaB03lHVTOpPkvsurGnfR539MbIhJ7LG9xXXV2BpJsHAgIIAQBf4h/GduakIi7c8uwHSRQ1GxUl0mhJ6TKdJdbFMcHWDAIBCAEAhdLt9TzDAmkTta9jsifdxF5VMSviczsz3ngYeoER9wVH5v1340JqJ6yBjCcswWDvCPm9TfYC2iOsN9dKXi+fyw=="},"top_secret_fin_key":{"access_policy":"Security Level::Top Secret && Department::FIN","key":"Zaa+BfghLwcXWfU8+W/M9m1Xy7EC6XACghrXiVbdDwV7YbDl6JWOlSTCRf25rQVTDlX+w5BjUH6S7bYYbh+eAwUCAwkBAKju46AscQ2x2IdTacrARB0k3kxdXu4kBd4hYoI6348CAgQJAQDoByFvzhiT/5cnl5/ieDTNU3uaKRF6UQkNRbBUI0MQDQIBCQEAyymfLiZFqkHyXQVxMdChHJpzzARD+DYJPVvmBb/6WQgCAgkBAAOMD6CjzVdDmY/YsO//EQh6Ce3LBDTAZX99Q2Ywv/wFAgUJAQEqjL/z+mkvSn64+4O8JnOut0M9BGpkuQxB4gB7KGnyGbUsgMdiV3E2uBCGY8KlilI9JcdKxYUr2F1NtaUyE1mJ4Y2Woz4LHJGj9HY2u5b6iFIa1q4voWbY9jPYtVYN2x/JtZn8AoLh1M0Ix2SWVRMPw5nd1i1N25A1Sq/JSzpe+mSkEahQzGwTxpJWbFmVJD4cekVEtD/xh6o28hkHo8kGAUsHjJ/LlLalVn4+xryZprorYa9lmTqHqC9cVToiXGVRW5wdzKAZ91RDl7SGFaqumm8N1BeLc3g5R0pAqWAvBxRDd2PaBmljY0bUmjATYgXpuFxD5bxHtz66e6YDInojiK5DJliSOWcLwbhrICPtnAd4VIV1YJjmVQ4VJYVgU4gOxK5VG4YpjLMr/G2C03U7I2ZzU3E2SKjKC7p2QcWQiDHhyBMNOYRyBm//N6Iy6pgQ+ZMV6wEU/I8NNlNm/LFH6abRaC314AAVK1jkBMzjR74aIb1406ATVrep+qRKuT12wkLADGKapUipMXUWNYepEJE2EAsYskkKbH+76xTW9aXv86Ti2WiYTLaFoFbdOlpnhTu4k0hOoQMb5Ca2MqnxvGPEsV7gwnBI6KNwN6lmcy+r13iD4cP0yLjxNR0t9kJcEAPWyFjMFmrXYXPugGQik71XlUwQnB9Zhm7J6lqyFEzmEFHhIZ8BRyVRZrlCNkuGh5aW50C5Gy6fF8IjRkCRp6IuN0Tg5Yj62GEqCoE/7JB4e5iTyEP9lT97Ek2Jlj0QW46BnLqRV3ont7pmKiLLM6AZkzMoRrs58idp92PhVBn+UJvTZoQbyW5ZxpBOd8d/wXcBpimvhWJq+wuEulTTdgi6iRnPG2LuO8KKoGmYvCMiK0BsrBkIkxqyzLr6Jq60Ob665a0y6QN+ZpolKTidY7jf21z3wKUC/BdKQ0mKVktKVFYItzn92MgYZbLI0kMg1atC5lc0+VhhZBlGixiItxCOknGY4LNmxVOC04AqssGVyScgy5aVgR/NFJyYRTJHyaKWUUsEgVRiMRi5KcFkQ0NVGZhfu472dT24UpkBUUBVE2W/oackuE8/AmO/sr7fIaT993VdQKIP5zhZEL0QAwL4gsnLWxrTxSSiujg4qG3FkEeNY8xK1G5//I+lxWrGYikJrF4g2YI7GM6s9Z8kMhVVfHRZpVfLOqJF5JiBAForoXeJCcg0ZVkNCCQNEnJyOM+RQgNsxoN27KlJeDBkqzWayKw4iqfMlH8bWl3idCZh4kwZDIwWOcsXaToyMR5L1X6RaakvYCTHKCMlhqkQJynF0ra47GsdbMNBMAZZl5pYI2vSpTDB3KsnKETeOhsMl2yD4yO+PKUngKaWBVpymUOolgsoyZvtkEyRUXUXtTAatmV2SIjIjIB7mKpbB11XoC6PgBlUQnCg+GTVSQqK92sXEy1p07ZMd0hi1x64NLEioHgbuwz3iw42eqHkp6vWIJFjRnSw9ZXPImGn4T4zG0NuzDqqqZvKtGQ1bKzvZj+77AczMl4MWmncF6uOIHZTSW8669mo315HjIYc4YEtgcX1UyCDkFEoe1alxS6FDBvwYYj6LGE2p0qw30QqLp39LK61jUHrC+IM9vwhl1zs"},"low_secret_mkg_test_vector":{"encryption_policy":"Department::MKG && Security Level::Low Secret","plaintext":"bG93X3NlY3JldF9ta2dfcGxhaW50ZXh0","ciphertext":"8pmS0RSF03XWSGmnBJ365ajdj7UAebtwJiBtMSvvoDr0ELS3w+a+tkO83MCa3QzyInuijsUZarBrwsEcmSPfbSaIbS7xLyz9scSRpnS0op0BACLnxqX+uAcZHSDOOgWBAzq2VTdnFOXuIz5GFi2jnNTJIK6A6LBStd+eYBma6IfsxqE+5EKcLL/LX0VyB3AD8h3cxWfWBggbFnVwddCwhvKRlx2WUcTFo1ReqUNS4ZsiqVzt9ImGe1mHKSa65J2UMyDNZQpNeA==","header_metadata":"AAAAAQ==","authentication_data":""},"top_secret_mkg_test_vector":{"encryption_policy":"Department::MKG && Security Level::Top Secret","plaintext":"dG9wX3NlY3JldF9ta2dfcGxhaW50ZXh0","ciphertext":"xOgSNb8ClY/EySXmXSFA1GbspILfqGu+HnHTRzhjXRkadlMTugfbEYpWSr93OwTZIvWKgaynYrgnk5eYz+zuZQPf2RuuC7sHSRXPm24VCmABAfYSbj6zyaLE2eideKcmc2zAyqaKRCosbaGL8a/NLOrCDGw1WbT78b7P6KY7Xe3agxnGZ4AF7f/d4dp1JGPPwZ+woDD8W7SbKQExd5KK3lGvkDG3EPGEyzBxm5GMR/sRAjytDDF4Lit+yf3Qwheikgq/GUIXKSO7itTFNbKjLDU7vZd/ctlpjsvrjykqu+B4fU4Qozn2YjHwO/2dkozZo3mLfh1zAP02+HP/+CGZLXenQMLn1BWJHQruYr0+6nrCxXjsF9RCfKn067vYYDFLCTlQWWkPWwjhxWxD4Yqs2K2nYEM0QVlgMeX29XbcbskOrCOww8wJWqKfFRQRxIbAKSL84kyyfojgrEAYghXt8ZrJiiuIfhLbyNu2olJXzYwhZxEISZ4wTqSjJSwz4zi77qLdhqoCGhadkCq/7S3OVcnnFL1IbduVvKcIh8ZXBKUvPX6EWEevlku2bVcGNf3xt3qRl2n3osJnOye5JkYXyz8Op2sb7HBphi/r1vq886uBNRpRT1F4St+d8j8kkfXy0fk6trc+pOOuIpnhxR/CAqO+w4LOdxCwjSh12tmh34EVa6iMgGIgOL+7hZl2VA6lb/Kp/OX5u8xdE9TnlSQBlmI4yQxmtI5sfXEHRioyhQcYfTljRyaNtllc6FIWms4Xm02tHIcZW0exXupcxaqI0iTe7DySWqJGQQAoZXlSdh+jVVA7i2YTD9z0mTGrts7hh3c0OHx3pbus3P0SVG76/GsvW+g/K7QBE64QJl7LN2U5q858QXu5nkrAczrCE9PDufOrNrUT7yNDslFj5xl1XcfXx6k9rjczeY5sUKuA8lw9gKwtABsFeZNrDeMwsmvwmOuZmA1QD6K/4NnF4Oe07bOmPi41v3IQJN946Z6U0dpdnYp6cqFkTxVqhSoWVMxZuMYbSYjsmPKs8M1K/Jo8WO/vMxx9G3Ww3IBy5QNcwDM3GnqpV4ivW9o38y06ySH99XcAEb0kj9eVcJt+y721EeUZPDgH5OE/jE6KNgrXY1lP5m6NnFuS9M0cpbSulS2IA+GQ6OU+n9huUHA4Yd2ncoC5TDG2H0IGBOYPu8awKgB/YlCJZDrR13fvOv0/VctKNiha/lnToznBMOaHxAWKh6YXbgtasy3xkNIdAU67jWrOx3pai9v1JDi/QG/8u3vI4N3q2y9ZSCsrFhQSUrgA7GgtqXhv/8XGzd0zd5f4iuAdRUz5VJttIdI+sk+wPK01YZ6NEbbadkK5lQuRQIboASeUIGkjgU5kFoUcG8C+bcFBuzP2cqzYNQL4aGE/JCMvOjuVUz+R2+SxQl/oFT6gfcAAyVXmsJ3BG3akc4IfamaepWVnbcHo/pi7RusSpwHWFAPtXTVN5tH0p7IU51hjn/Pq+7bBDjpkXyg1l9Zd+w27T2dMOR8XMpFf09JjEnMNhE3va9HA8BW7kgzowRLuhkurIJLXUC784NlFWLJvofDTsAdlxqV7SPn0UGfX/B7mUsAygW3+NnV2Of4U91UuAkQ0mgi6Yjomz9BCGvbYEmCvtKtgnoTawQPGQopx0u/Dr2x9Wm3iFA==","header_metadata":"AAAAAQ==","authentication_data":"AAAAAg=="},"low_secret_fin_test_vector":{"encryption_policy":"Department::FIN && Security Level::Low Secret","plaintext":"bG93X3NlY3JldF9maW5fcGxhaW50ZXh0","ciphertext":"Dn5Sj1av5Dd7OcJEWO4gDtrlwy7jI3ZvgyUE0MWg12L+PHXHn5hERx7nIU3X61kn2rpJswFXki7MKiQkSrVJJEH9TW/l38eh4T3EMgnd6LoBAGQ5MGZYj50tLDxS8/U94pKNmsW/c6DLPI9GD6B1jLsfAMi7b7Qhgz3dSJdPIQkE72WmcgwuKyMd9r+SSrOBGUxiq5n3cxGLccxB5yyeE0qpusk6ZXk=","header_metadata":"","authentication_data":""}} \ No newline at end of file diff --git a/src/test_utils/tests_data/policy_v2.json b/src/test_utils/tests_data/policy_v2.json index 8e942a48..499ecc6d 100644 --- a/src/test_utils/tests_data/policy_v2.json +++ b/src/test_utils/tests_data/policy_v2.json @@ -1 +1 @@ -{"version":"V2","last_attribute_value":9,"dimensions":{"Department":{"order":null,"attributes":{"MKG":{"rotation_values":[8],"encryption_hint":"Classic","write_status":"EncryptDecrypt"},"R&D":{"rotation_values":[6],"encryption_hint":"Classic","write_status":"EncryptDecrypt"},"HR":{"rotation_values":[7],"encryption_hint":"Classic","write_status":"EncryptDecrypt"},"FIN":{"rotation_values":[9],"encryption_hint":"Classic","write_status":"EncryptDecrypt"}}},"Security Level":{"order":["Protected","Low Secret","Medium Secret","High Secret","Top Secret"],"attributes":{"Low Secret":{"rotation_values":[2],"encryption_hint":"Classic","write_status":"EncryptDecrypt"},"High Secret":{"rotation_values":[4],"encryption_hint":"Classic","write_status":"EncryptDecrypt"},"Protected":{"rotation_values":[1],"encryption_hint":"Classic","write_status":"EncryptDecrypt"},"Medium Secret":{"rotation_values":[3],"encryption_hint":"Classic","write_status":"EncryptDecrypt"},"Top Secret":{"rotation_values":[5],"encryption_hint":"Hybridized","write_status":"EncryptDecrypt"}}}}} \ No newline at end of file +{"version":"V2","last_attribute_value":9,"dimensions":{"Department":{"Unordered":{"FIN":{"id":9,"encryption_hint":"Classic","write_status":"EncryptDecrypt"},"R&D":{"id":6,"encryption_hint":"Classic","write_status":"EncryptDecrypt"},"HR":{"id":7,"encryption_hint":"Classic","write_status":"EncryptDecrypt"},"MKG":{"id":8,"encryption_hint":"Classic","write_status":"EncryptDecrypt"}}},"Security Level":{"Ordered":{"Protected":{"id":1,"encryption_hint":"Classic","write_status":"EncryptDecrypt"},"Low Secret":{"id":2,"encryption_hint":"Classic","write_status":"EncryptDecrypt"},"Medium Secret":{"id":3,"encryption_hint":"Classic","write_status":"EncryptDecrypt"},"High Secret":{"id":4,"encryption_hint":"Classic","write_status":"EncryptDecrypt"},"Top Secret":{"id":5,"encryption_hint":"Hybridized","write_status":"EncryptDecrypt"}}}}} \ No newline at end of file