diff --git a/.gitignore b/.gitignore index dcd74930..62f1ff88 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ /*.sh /.vscode Cargo.lock +**/.#* +**/#*# \ No newline at end of file diff --git a/benches/benches.rs b/benches/benches.rs index 394e1a39..132931c2 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -21,7 +21,7 @@ fn policy() -> Result { DimensionBuilder::new( "Department", vec![ - ("R&D", EncryptionHint::Classic), + ("RD", EncryptionHint::Classic), ("HR", EncryptionHint::Classic), ("MKG", EncryptionHint::Classic), ("FIN", EncryptionHint::Classic), @@ -46,7 +46,7 @@ fn policy() -> Result { DimensionBuilder::new( "Department", vec![ - ("R&D", EncryptionHint::Hybridized), + ("RD", EncryptionHint::Hybridized), ("HR", EncryptionHint::Hybridized), ("MKG", EncryptionHint::Hybridized), ("FIN", EncryptionHint::Hybridized), @@ -118,16 +118,13 @@ fn get_access_policies() -> (Vec, Vec) { // Access policy with 1 partition #[allow(unused_mut)] let mut access_policies = - vec![ - AccessPolicy::from_boolean_expression("Department::FIN && Security Level::Protected") - .unwrap(), - ]; + vec![AccessPolicy::parse("Department::FIN && Security Level::Protected").unwrap()]; #[cfg(feature = "full_bench")] { // Access policy with 2 partition access_policies.push( - AccessPolicy::from_boolean_expression( + AccessPolicy::parse( "(Department::FIN && Security Level::Protected) || (Department::HR && Security \ Level::Confidential)", ) @@ -136,7 +133,7 @@ fn get_access_policies() -> (Vec, Vec) { // Access policy with 3 partition access_policies.push( - AccessPolicy::from_boolean_expression( + AccessPolicy::parse( "(Department::FIN && Security Level::Protected) || ((Department::HR || \ Department::MKG) && Security Level::Confidential)", ) @@ -145,18 +142,18 @@ fn get_access_policies() -> (Vec, Vec) { // Access policy with 4 partition access_policies.push( - AccessPolicy::from_boolean_expression( + AccessPolicy::parse( "(Department::FIN && Security Level::Protected) || ((Department::HR || \ - Department::MKG || Department::R&D) && Security Level::Confidential)", + Department::MKG || Department::RD) && Security Level::Confidential)", ) .unwrap(), ); // Access policy with 5 partition access_policies.push( - AccessPolicy::from_boolean_expression( + AccessPolicy::parse( "(Department::FIN && Security Level::Protected) || ((Department::HR || \ - Department::MKG || Department::R&D) && Security Level::Confidential) || \ + Department::MKG || Department::RD) && Security Level::Confidential) || \ (Department::HR && Security Level::Top Secret)", ) .unwrap(), @@ -167,36 +164,33 @@ fn get_access_policies() -> (Vec, Vec) { // policies is always "Department::FIN && Security Level::Protected" only. #[allow(unused_mut)] let mut user_access_policies = - vec![ - AccessPolicy::from_boolean_expression("Department::FIN && Security Level::Protected") - .unwrap(), - ]; + vec![AccessPolicy::parse("Department::FIN && Security Level::Protected").unwrap()]; #[cfg(feature = "full_bench")] { user_access_policies.push( - AccessPolicy::from_boolean_expression( + AccessPolicy::parse( "(Department::FIN && Department::MKG) && Security Level::Protected", ) .unwrap(), ); user_access_policies.push( - AccessPolicy::from_boolean_expression( + AccessPolicy::parse( "(Department::FIN && Department::MKG && Department::HR) && Security \ Level::Protected", ) .unwrap(), ); user_access_policies.push( - AccessPolicy::from_boolean_expression( - "(Department::R&D && Department::FIN && Department::MKG && Department::HR) && \ + AccessPolicy::parse( + "(Department::RD && Department::FIN && Department::MKG && Department::HR) && \ Security Level::Protected", ) .unwrap(), ); user_access_policies.push( - AccessPolicy::from_boolean_expression( - "(Department::R&D && Department::FIN && Department::MKG && Department::HR && \ + AccessPolicy::parse( + "(Department::RD && Department::FIN && Department::MKG && Department::HR && \ Department::CYBER) && Security Level::Protected", ) .unwrap(), diff --git a/examples/runme.rs b/examples/runme.rs index 70f0f1df..6a7786c6 100644 --- a/examples/runme.rs +++ b/examples/runme.rs @@ -47,8 +47,7 @@ fn main() { // The user has a security clearance `Security Level::Top Secret`, // and belongs to the finance department (`Department::FIN`). let access_policy = - AccessPolicy::from_boolean_expression("Security Level::Top Secret && Department::FIN") - .unwrap(); + AccessPolicy::parse("Security Level::Top Secret && Department::FIN").unwrap(); let mut usk = cover_crypt .generate_user_secret_key(&msk, &access_policy, &policy) .unwrap(); diff --git a/src/abe_policy/access_policy.rs b/src/abe_policy/access_policy.rs index 5d9b9667..5b9fbe5d 100644 --- a/src/abe_policy/access_policy.rs +++ b/src/abe_policy/access_policy.rs @@ -1,80 +1,37 @@ +//! This module defines methods to parse and manipulate access policies. +//! +//! Access policies are boolean equations of *attributes*. Attributes are +//! defined as a combination of a dimension name and a component name (belonging +//! to the named dimension). +//! + use std::{ - collections::HashMap, + collections::LinkedList, fmt::Debug, ops::{BitAnd, BitOr}, }; use crate::{abe_policy::Attribute, Error}; -/// An `AccessPolicy` is a boolean expression over attributes. +/// An access policy is a boolean expression of attributes. +/// +/// TODO: is this a subset-cover limitation? It seems possible to subtract +/// coordinates from the set of positively generated ones. /// /// Only `positive` literals are allowed (no negation). -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum AccessPolicy { Attr(Attribute), And(Box, Box), Or(Box, Box), -} - -impl PartialEq for AccessPolicy { - fn eq(&self, other: &Self) -> bool { - let mut attributes_mapping = HashMap::::new(); - let left_to_u32 = self.to_u32(&mut attributes_mapping); - let right_to_u32 = other.to_u32(&mut attributes_mapping); - if left_to_u32 != right_to_u32 { - false - } else { - self.ordered_attributes() == other.ordered_attributes() - } - } + Any, } impl AccessPolicy { - /// Creates an Access Policy based on a single Policy Attribute. - /// - /// Shortcut for - /// ```ignore - /// AccessPolicy::Attr(Attribute::new(dimension, attribute)) - /// ``` - /// - /// Access Policies can easily be created using it - /// ```ignore - /// let access_policy = - /// AccessPolicy::new("Security Level", "level 4") - /// & (AccessPolicy::new("Department", "MKG") | AccessPolicy::new("Department", "FIN")); - /// ``` - #[must_use] - pub fn new(dimension: &str, attribute: &str) -> Self { - Self::Attr(Attribute::new(dimension, attribute)) - } - - /// Converts policy to integer value (for comparison). - /// Each attribute is mapped to an integer value and the algebraic - /// expression is applied with those values. - /// We must keep a mapping of each attribute to the corresponding integer - /// value in order to avoid having 2 different attributes with same integer - /// value - fn to_u32(&self, attribute_mapping: &mut HashMap) -> u32 { - match self { - Self::Attr(attr) => { - if let Some(integer_value) = attribute_mapping.get(attr) { - *integer_value - } else { - let max = (attribute_mapping.len() + 1) as u32; - attribute_mapping.insert(attr.clone(), max); - max - } - } - Self::And(l, r) => l.to_u32(attribute_mapping) * r.to_u32(attribute_mapping), - Self::Or(l, r) => l.to_u32(attribute_mapping) + r.to_u32(attribute_mapping), - } - } - - /// This function is finding the right closing parenthesis in the boolean - /// expression given as a string + /// Finds the corresponding closing parenthesis in the boolean expression + /// given as a string. fn find_next_parenthesis(boolean_expression: &str) -> Result { let mut count = 0; - let mut right_closing_parenthesis = None; for (index, c) in boolean_expression.chars().enumerate() { match c { '(' => count += 1, @@ -82,264 +39,137 @@ impl AccessPolicy { _ => {} }; if count < 0 { - right_closing_parenthesis = Some(index); - break; + return Ok(index); } } - - right_closing_parenthesis.ok_or_else(|| { - Error::InvalidBooleanExpression(format!( - "Missing closing parenthesis in boolean expression {boolean_expression}" - )) - }) + Err(Error::InvalidBooleanExpression(format!( + "Missing closing parenthesis in boolean expression {boolean_expression}" + ))) } - /// Sanitizes spaces in boolean expression around parenthesis and operators - /// but keep spaces inside dimension & attribute names. + /// Parses the given string into an access policy. /// - /// Useless spaces are removed: - /// - before and after operator. Example: `A && B` --> `A&&B` - /// - before and after parenthesis. Example: `(A && B)` --> `(A&&B)` - /// - But keep these spaces: `(A::b c || d e::F)` --> `(A::b c||d e::F)` - fn sanitize_spaces(boolean_expression: &str) -> String { - let trim_closure = |expr: &str, separator: &str| -> String { - let expression = expr - .split(separator) - .collect::>() - .into_iter() - .map(str::trim) - .collect::>(); - let mut expression_chars = Vec::::new(); - for (i, s) in expression.iter().enumerate() { - if i == 0 && s.is_empty() { - expression_chars.append(&mut separator.chars().collect::>()); - } else { - expression_chars.append(&mut s.chars().collect::>()); - if i != expression.len() - 1 { - expression_chars.append(&mut separator.chars().collect::>()); - } - } - } - expression_chars.iter().collect::() - }; - - // Remove successively spaces around `special` substrings - let mut output = boolean_expression.to_string(); - for sep in ["(", ")", "||", "&&", "::"] { - output = trim_closure(output.as_str(), sep); - } - - output - } - - /// This function takes a boolean expression and splits it into a left part, - /// an operator and a right part. + /// # Abstract grammar /// - /// Example: "`Department::HR` && `Level::level_2`" will be decomposed in: - /// - `Department::HR` - /// - && - /// - `Level::level_2` - fn decompose_expression( - boolean_expression: &str, - split_position: usize, - ) -> Result<(String, Option, Option), Error> { - /// Number of characters of an `AccessPolicy` operator. - /// Possible operators are: '||' and '&&'. - const OPERATOR_SIZE: usize = 2; - - if split_position > boolean_expression.len() { - return Err(Error::InvalidBooleanExpression(format!( - "Cannot split boolean expression {boolean_expression} at position \ - {split_position} since {split_position} is greater than the size of \ - {boolean_expression}" - ))); - } - - // Put aside `Department::HR` from `Department::HR && Level::level_2` - let left_part = &boolean_expression[..split_position]; - if split_position == boolean_expression.len() { - return Ok((left_part.to_string(), None, None)); - } - - // Put aside `&&` from `Department::HR && Level::level_2` - let next_char = boolean_expression - .chars() - .nth(split_position) - .unwrap_or_default(); - let mut split_position = split_position; - if next_char == ')' { - split_position += 1; - } - if split_position == boolean_expression.len() { - return Ok((left_part.to_string(), None, None)); - } - if split_position + OPERATOR_SIZE > boolean_expression.len() { - return Err(Error::InvalidBooleanExpression(format!( - "Cannot split boolean expression {boolean_expression} at position {} since it is \ - greater than the size of {boolean_expression}", - split_position + OPERATOR_SIZE - ))); - } - - let operator = &boolean_expression[split_position..split_position + OPERATOR_SIZE]; - - // Put aside `Level::level_2` from `Department::HR && Level::level_2` - let right_part = &boolean_expression[split_position + OPERATOR_SIZE..]; - Ok(( - left_part.to_string(), - Some(operator.to_string()), - Some(right_part.to_string()), - )) - } - - /// Converts a boolean expression into `AccessPolicy`. + /// The following abstract grammar describes the valid access policies + /// syntax. The brackets are used to denote optional elements, the pipes + /// ('|') to denote options, slashes to denote REGEXP, and spaces to denote + /// concatenation. Spaces may be interleaved with elements. They are simply + /// ignored by the parser. /// - /// # Arguments + /// - access_policy: [ attribute | group [ operator access_policy ]] + /// - attribute: dimension [ separator component ] + /// - group: ( access_policy ) + /// - operator: OR | AND + /// - OR: || + /// - AND: && + /// - separator: :: + /// - dimension: /[^&|: ]+/ + /// - component: /[^&|: ]+/ /// - /// - `boolean_expression`: expression with operators && and || + /// The REGEXP used to describe the dimension and the component stands for: + /// "at least one character that is neither '&', '|', ':' nor ' '". /// - /// # Returns + /// # Precedence rule /// - /// the corresponding `AccessPolicy` + /// Note that the usual precedence rules hold in expressing an access + /// policy: + /// 1. grouping takes precedence over all operators; + /// 2. the logical AND takes precedence over the logical OR. /// /// # Examples /// - /// ``` - /// use cosmian_cover_crypt::abe_policy::AccessPolicy; + /// The following expressions define valid access policies: /// - /// let boolean_expression = "(Department::HR || Department::RnD) && Level::level_2"; - /// let access_policy = AccessPolicy::from_boolean_expression(boolean_expression); - /// assert_eq!( - /// access_policy.unwrap(), - /// (AccessPolicy::new("Department", "HR") | AccessPolicy::new("Department", "RnD")) & AccessPolicy::new("Level", "level_2"), - /// ); - /// ``` - /// # Errors + /// - "Department::MKG && (Country::FR || Country::DE)" + /// - "" + /// - "Security Level::Low Secret && Country::FR" /// - /// Missing parenthesis or bad operators - pub fn from_boolean_expression(boolean_expression: &str) -> Result { - let boolean_expression_example = "(Department::HR || Department::RnD) && Level::level_2"; - - // Remove spaces around parenthesis and operators - let boolean_expression = Self::sanitize_spaces(boolean_expression); - - if !boolean_expression.contains("::") { - return Err(Error::InvalidBooleanExpression(format!( - "'{boolean_expression}' does not contain any attribute separator '::'. Example: \ - {boolean_expression_example}" - ))); - } - - // if first char is parenthesis - let first_char = boolean_expression.chars().next().unwrap_or_default(); - if first_char == '(' { - // Skip first parenthesis - let boolean_expression = &boolean_expression[1..]; - // Check if formula contains a closing parenthesis - let c = boolean_expression.matches(')').count(); - if c == 0 { - return Err(Error::InvalidBooleanExpression(format!( - "closing parenthesis missing in {boolean_expression}" - ))); - } - // Search right closing parenthesis, avoiding false positive - let matching_closing_parenthesis = Self::find_next_parenthesis(boolean_expression)?; - let (left_part, operator, right_part) = - Self::decompose_expression(boolean_expression, matching_closing_parenthesis)?; - if operator.is_none() { - return Self::from_boolean_expression(left_part.as_str()); - } - - let operator = operator.unwrap_or_default(); - let right_part = right_part.unwrap_or_default(); - let ap1 = Box::new(Self::from_boolean_expression(left_part.as_str())?); - let ap2 = Box::new(Self::from_boolean_expression(right_part.as_str())?); - let ap = match operator.as_str() { - "&&" => Ok(Self::And(ap1, ap2)), - "||" => Ok(Self::Or(ap1, ap2)), - _ => Err(Error::UnsupportedOperator(operator.to_string())), - }?; - Ok(ap) - } else { - let or_position = boolean_expression.find("||"); - let and_position = boolean_expression.find("&&"); - - // Get position of next operator - let position = if or_position.is_none() && and_position.is_none() { - 0 - } else if or_position.is_none() { - and_position.unwrap_or_default() - } else if and_position.is_none() { - or_position.unwrap_or_default() + /// Notice that the arity of the operators is two. Therefore the following + /// access policy is *invalid*: + /// + /// - "Department::MKG (&& Country::FR || Country::DE)" + /// + /// It is not possible to concatenate attributes. Therefore the following + /// access policy is *invalid*: + /// + /// - "Department::MKG Department::FIN" + pub fn parse(mut e: &str) -> Result { + let seeker = |c: &char| !"()|&".contains(*c); + let mut q = LinkedList::::new(); + loop { + e = e.trim(); + if e.is_empty() { + return Ok(Self::conjugate(Self::Any, q.into_iter())); } else { - std::cmp::min( - or_position.unwrap_or_default(), - and_position.unwrap_or_default(), - ) - }; - - if position == 0 { - let attribute_vec = boolean_expression.split("::").collect::>(); - - if attribute_vec.len() != 2 - || attribute_vec[0].is_empty() - || attribute_vec[1].is_empty() - { - return Err(Error::InvalidBooleanExpression(format!( - "'{boolean_expression}' does not respect the format . \ - Example: {boolean_expression_example}" - ))); + match &e[..1] { + "(" => { + let offset = Self::find_next_parenthesis(&e[1..])?; + q.push_back(Self::parse(&e[1..1 + offset]).map_err(|err| { + Error::InvalidBooleanExpression(format!( + "error while parsing '{e}': {err}" + )) + })?); + e = &e[2 + offset..]; + } + "|" => { + if e[1..].is_empty() || &e[1..2] != "|" { + return Err(Error::InvalidBooleanExpression(format!( + "invalid separator in: '{e}'" + ))); + } + let base = q.pop_front().ok_or_else(|| { + Error::InvalidBooleanExpression(format!("leading OR operand in '{e}'")) + })?; + let lhs = Self::conjugate(base, q.into_iter()); + return Ok(lhs | Self::parse(&e[2..])?); + } + "&" => { + if e[1..].is_empty() || &e[1..2] != "&" { + return Err(Error::InvalidBooleanExpression(format!( + "invalid leading separator in: '{e}'" + ))); + } + if q.is_empty() { + return Err(Error::InvalidBooleanExpression(format!( + "leading AND operand in '{e}'" + ))); + } + e = &e[2..]; + } + ")" => { + return Err(Error::InvalidBooleanExpression(format!( + "unmatched closing parenthesis in '{e}'" + ))) + } + _ => { + let attr: String = e.chars().take_while(seeker).collect(); + q.push_back(Self::Attr(Attribute::try_from(attr.as_str())?)); + e = &e[attr.len()..]; + } } - return Ok(Self::new(attribute_vec[0], attribute_vec[1])); - } - - // Remove operator from input string - let (left_part, operator, right_part) = - Self::decompose_expression(&boolean_expression, position)?; - if operator.is_none() { - return Self::from_boolean_expression(left_part.as_str()); } - let operator = operator.unwrap_or_default(); - let right_part = right_part.unwrap_or_default(); - - let ap1 = Box::new(Self::from_boolean_expression(left_part.as_str())?); - let ap2 = Box::new(Self::from_boolean_expression(right_part.as_str())?); - let ap = match operator.as_str() { - "&&" => Ok(Self::And(ap1, ap2)), - "||" => Ok(Self::Or(ap1, ap2)), - _ => Err(Error::UnsupportedOperator(operator.to_string())), - }?; - - Ok(ap) } } - /// Returns the sorted sequence of attributes used in the access policy. - #[must_use] - pub fn ordered_attributes(&self) -> Vec { - let mut attributes = self.clone().into_attributes(); - attributes.sort_unstable(); - attributes - } - - /// Returns the sequence of attributes used in the access policy. - pub fn into_attributes(self) -> Vec { - match self { - Self::Attr(att) => vec![att], - Self::And(lhs, rhs) | Self::Or(lhs, rhs) => { - [lhs.into_attributes(), rhs.into_attributes()].concat() - } - } + /// Conjugate the access policies from the given iterator. + fn conjugate(first: Self, policies: impl Iterator) -> Self { + policies.fold(first, |mut res, operand| { + res = res & operand; + res + }) } - /// Converts the access policy into the Disjunctive Normal Form (DNF) of its attributes. + /// Converts the access policy into the Disjunctive Normal Form (DNF) of its + /// attributes. Returns the DNF as the list of its conjunctions, themselves + /// represented as the list of their attributes. #[must_use] - pub fn into_dnf(self) -> Vec> { + pub fn to_dnf(&self) -> Vec> { match self { - Self::Attr(attr) => vec![vec![attr]], + Self::Attr(attr) => vec![vec![attr.clone()]], Self::And(lhs, rhs) => { - let combinations_left = lhs.into_dnf(); - let combinations_right = rhs.into_dnf(); + let combinations_left = lhs.to_dnf(); + let combinations_right = rhs.to_dnf(); let mut res = Vec::with_capacity(combinations_left.len() * combinations_right.len()); for value_left in combinations_left { @@ -349,31 +179,61 @@ impl AccessPolicy { } res } - Self::Or(lhs, rhs) => [lhs.into_dnf(), rhs.into_dnf()].concat(), + Self::Or(lhs, rhs) => [lhs.to_dnf(), rhs.to_dnf()].concat(), + Self::Any => vec![vec![]], } } } -// use A & B to construct And(A, B) impl BitAnd for AccessPolicy { type Output = Self; fn bitand(self, rhs: Self) -> Self::Output { - Self::And(Box::new(self), Box::new(rhs)) + if self == Self::Any { + rhs + } else if rhs == Self::Any { + self + } else { + Self::And(Box::new(self), Box::new(rhs)) + } } } -// use A | B to construct Or(A, B) impl BitOr for AccessPolicy { type Output = Self; fn bitor(self, rhs: Self) -> Self::Output { - Self::Or(Box::new(self), Box::new(rhs)) + if self == Self::Any { + self + } else if rhs == Self::Any { + rhs + } else { + Self::Or(Box::new(self), Box::new(rhs)) + } } } -impl From for AccessPolicy { - fn from(attribute: Attribute) -> Self { - Self::Attr(attribute) +#[cfg(test)] +mod tests { + use super::AccessPolicy; + + #[test] + fn test_access_policy_parsing() { + // These are valid access policies. + let ap = AccessPolicy::parse("(D1::A && (D2::A) || D2::B)").unwrap(); + println!("{ap:#?}"); + let ap = AccessPolicy::parse("D1::A && D2::A || D2::B").unwrap(); + println!("{ap:#?}"); + let ap = AccessPolicy::parse("D1::A && (D2::A || D2::B)").unwrap(); + println!("{ap:#?}"); + let ap = AccessPolicy::parse("D1::A (D2::A || D2::B)").unwrap(); + println!("{ap:#?}"); + assert_eq!(AccessPolicy::parse("").unwrap(), AccessPolicy::Any); + + // These are invalid access policies. + // TODO: make this one valid (change the parsing rule of the attribute). + assert!(AccessPolicy::parse("D1").is_err()); + assert!(AccessPolicy::parse("D1::A (&& D2::A || D2::B)").is_err()); + assert!(AccessPolicy::parse("|| D2::B").is_err()); } } diff --git a/src/abe_policy/attribute.rs b/src/abe_policy/attribute.rs index 348310fe..8c75031e 100644 --- a/src/abe_policy/attribute.rs +++ b/src/abe_policy/attribute.rs @@ -67,14 +67,14 @@ impl BitOr for AttributeStatus { } #[derive(Debug, Clone, Serialize, Deserialize)] -/// Attribute representation used to create a `Dimension` and add it to a -/// `Policy`. +/// Attribute representation used to create an attribute and add it to a dimension. pub struct AttributeBuilder { pub name: String, pub encryption_hint: EncryptionHint, } -/// An attribute in a policy group is characterized by the dimension policy name -/// and its unique name within this dimension. + +/// A policy attribute is composed of a dimension name and a component +/// name. #[derive(Hash, PartialEq, Eq, Clone, PartialOrd, Ord, Serialize, Deserialize)] #[serde(try_from = "&str", into = "String")] pub struct Attribute { @@ -124,23 +124,23 @@ impl TryFrom<&str> for Attribute { type Error = Error; fn try_from(s: &str) -> Result { - let (dimension, name) = s.trim().split_once("::").ok_or_else(|| { + let (dimension, component) = s.split_once("::").ok_or_else(|| { Error::InvalidAttribute(format!("at least one separator '::' expected in {s}")) })?; - if name.contains("::") { + if component.contains("::") { return Err(Error::InvalidAttribute(format!( "separator '::' expected only once in {s}" ))); } - if dimension.is_empty() || name.is_empty() { + if dimension.is_empty() || component.is_empty() { return Err(Error::InvalidAttribute(format!( "empty dimension or empty name in {s}" ))); } - Ok(Self::new(dimension, name)) + Ok(Self::new(dimension.trim(), component.trim())) } } diff --git a/src/abe_policy/policy.rs b/src/abe_policy/policy.rs index ad92177a..99253203 100644 --- a/src/abe_policy/policy.rs +++ b/src/abe_policy/policy.rs @@ -201,7 +201,7 @@ impl Policy { &self, ap: AccessPolicy, ) -> Result, Error> { - let dnf = ap.into_dnf(); + let dnf = ap.to_dnf(); let mut coordinates = HashSet::new(); for conjunction in dnf { let semantic_space = conjunction @@ -233,7 +233,7 @@ impl Policy { &self, ap: AccessPolicy, ) -> Result, Error> { - let dnf = ap.into_dnf(); + let dnf = ap.to_dnf(); let mut coordinates = HashSet::with_capacity(dnf.len()); for conjunction in dnf { let coo = Partition::from_attribute_ids( @@ -348,8 +348,8 @@ mod tests { let ap = "(Department::HR || Department::FIN) && Security Level::Low Secret"; - let semantic_space_coordinates = policy - .generate_semantic_space_coordinates(AccessPolicy::from_boolean_expression(ap)?)?; + let semantic_space_coordinates = + policy.generate_semantic_space_coordinates(AccessPolicy::parse(ap)?)?; // Check the number of coordinates is correct. assert_eq!(semantic_space_coordinates.len(), (2 + 1) * (2 + 1)); @@ -446,9 +446,7 @@ mod tests { assert_eq!( policy - .generate_semantic_space_coordinates(AccessPolicy::from_boolean_expression( - ap - )?)? + .generate_semantic_space_coordinates(AccessPolicy::parse(ap)?)? .len(), // remove (2 + 1) not to count "Security Level::Protected" -> "Security Level::Low Secret" twice 2 * (1 + 1) * (2 + 1) - (2 + 1) @@ -458,9 +456,7 @@ mod tests { || (Department::MKG && Security Level::Medium Secret)"; assert_eq!( policy - .generate_semantic_space_coordinates(AccessPolicy::from_boolean_expression( - ap - )?)? + .generate_semantic_space_coordinates(AccessPolicy::parse(ap)?)? .len(), // remove (2 + 1) not to count "Security Level::Protected" -> "Security Level::Low Secret" twice (1 + 1) * (2 + 1) + (1 + 1) * (3 + 1) - (2 + 1) diff --git a/src/abe_policy/tests.rs b/src/abe_policy/tests.rs index bf7bad30..8b069c61 100644 --- a/src/abe_policy/tests.rs +++ b/src/abe_policy/tests.rs @@ -1,5 +1,5 @@ use crate::{ - abe_policy::{AccessPolicy, Attribute, DimensionBuilder, EncryptionHint, Policy}, + abe_policy::{Attribute, DimensionBuilder, EncryptionHint, Policy}, error::Error, }; @@ -166,16 +166,3 @@ fn test_edit_policy_attributes() -> Result<(), Error> { Ok(()) } - -#[test] -fn test_access_policy_equality() { - let ap1 = "(Department::FIN || Department::MKG) && Security Level::Top Secret"; - let ap2 = "Security Level::Top Secret && (Department::FIN || Department::MKG)"; - let ap3 = "Security Level::Top Secret && (Department::FIN || Department::HR)"; - let ap1 = AccessPolicy::from_boolean_expression(ap1).unwrap(); - let ap2 = AccessPolicy::from_boolean_expression(ap2).unwrap(); - let ap3 = AccessPolicy::from_boolean_expression(ap3).unwrap(); - assert_eq!(ap1, ap2); - assert_eq!(ap2, ap2); - assert_ne!(ap2, ap3); -} diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs index fc5251c5..34ee17a5 100644 --- a/src/test_utils/mod.rs +++ b/src/test_utils/mod.rs @@ -149,9 +149,7 @@ mod tests { let cover_crypt = Covercrypt::default(); let (mut msk, mut mpk) = cover_crypt.generate_master_keys(&policy)?; - let ap = AccessPolicy::from_boolean_expression( - "Department::MKG && Security Level::High Secret", - )?; + let ap = AccessPolicy::parse("Department::MKG && Security Level::High Secret")?; let mut usk = cover_crypt.generate_user_secret_key(&msk, &ap, &policy)?; let original_usk = UserSecretKey::deserialize(usk.serialize()?.as_slice())?; @@ -205,8 +203,7 @@ mod tests { // // User secret key - let decryption_policy = - AccessPolicy::from_boolean_expression("Security Level::Low Secret")?; + let decryption_policy = AccessPolicy::parse("Security Level::Low Secret")?; let mut low_secret_usk = cover_crypt.generate_user_secret_key(&msk, &decryption_policy, &policy)?; @@ -227,9 +224,8 @@ mod tests { // // Encrypt - let secret_sales_ap = AccessPolicy::from_boolean_expression( - "Security Level::Low Secret && Department::Sales", - )?; + let secret_sales_ap = + AccessPolicy::parse("Security Level::Low Secret && Department::Sales")?; let (_, encrypted_header) = EncryptedHeader::generate(&cover_crypt, &policy, &mpk, &secret_sales_ap, None, None)?; @@ -263,7 +259,7 @@ mod tests { // // New user secret key - let decryption_policy = AccessPolicy::from_boolean_expression( + let decryption_policy = AccessPolicy::parse( "Security Level::Top Secret && (Department::FIN || Department::HR)", )?; let mut top_secret_fin_usk = @@ -271,8 +267,7 @@ mod tests { // // Encrypt - let top_secret_ap = - AccessPolicy::from_boolean_expression("Security Level::Top Secret && Department::FIN")?; + let top_secret_ap = AccessPolicy::parse("Security Level::Top Secret && Department::FIN")?; let (_, encrypted_header) = EncryptedHeader::generate(&cover_crypt, &policy, &mpk, &top_secret_ap, None, None)?; @@ -296,7 +291,7 @@ 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::HR")?; + AccessPolicy::parse("Security Level::Top Secret && Department::HR")?; // refreshing the user key will remove access to removed partitions even if we // keep old rotations @@ -320,7 +315,7 @@ mod tests { // // New user secret key - let decryption_policy = AccessPolicy::from_boolean_expression( + let decryption_policy = AccessPolicy::parse( "Security Level::Top Secret && (Department::FIN || Department::HR)", )?; let mut top_secret_fin_usk = @@ -328,8 +323,7 @@ mod tests { // // Encrypt - let top_secret_ap = - AccessPolicy::from_boolean_expression("Security Level::Top Secret && Department::FIN")?; + let top_secret_ap = AccessPolicy::parse("Security Level::Top Secret && Department::FIN")?; let (_, encrypted_header) = EncryptedHeader::generate(&cover_crypt, &policy, &mpk, &top_secret_ap, None, None)?; @@ -350,8 +344,7 @@ mod tests { .is_ok()); // Can not encrypt using deactivated attribute - let top_secret_ap = - AccessPolicy::from_boolean_expression("Security Level::Top Secret && Department::FIN")?; + let top_secret_ap = AccessPolicy::parse("Security Level::Top Secret && Department::FIN")?; assert!( EncryptedHeader::generate(&cover_crypt, &policy, &mpk, &top_secret_ap, None, None) @@ -388,14 +381,13 @@ mod tests { // // New user secret key let decryption_policy = - AccessPolicy::from_boolean_expression("Security Level::Top Secret && Department::FIN")?; + AccessPolicy::parse("Security Level::Top Secret && Department::FIN")?; let mut top_secret_fin_usk = cover_crypt.generate_user_secret_key(&msk, &decryption_policy, &policy)?; // // Encrypt - let top_secret_ap = - AccessPolicy::from_boolean_expression("Security Level::Top Secret && Department::FIN")?; + let top_secret_ap = AccessPolicy::parse("Security Level::Top Secret && Department::FIN")?; let (_, encrypted_header) = EncryptedHeader::generate(&cover_crypt, &policy, &mpk, &top_secret_ap, None, None)?; @@ -410,9 +402,8 @@ mod tests { .is_ok()); // refresh the user key and preserve access to old partitions - let _new_decryption_policy = AccessPolicy::from_boolean_expression( - "Security Level::Top Secret && Department::Finance", - )?; + let _new_decryption_policy = + AccessPolicy::parse("Security Level::Top Secret && Department::Finance")?; cover_crypt.refresh_user_secret_key(&mut top_secret_fin_usk, &msk, false)?; assert!(encrypted_header .decrypt(&cover_crypt, &top_secret_fin_usk, None) @@ -424,16 +415,16 @@ mod tests { #[test] fn encrypt_decrypt_sym_key() -> Result<(), Error> { 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"); + let access_policy = AccessPolicy::parse( + "(Department::MKG || Department::FIN) && Security Level::Top Secret", + ) + .unwrap(); let cover_crypt = Covercrypt::default(); let (msk, mpk) = cover_crypt.generate_master_keys(&policy)?; let (sym_key, encrypted_key) = cover_crypt.encaps( &policy, &mpk, - AccessPolicy::from_boolean_expression("Department::R&D && Security Level::Top Secret")?, + AccessPolicy::parse("Department::MKG && Security Level::Top Secret")?, )?; let usk = cover_crypt.generate_user_secret_key(&msk, &access_policy, &policy)?; let recovered_key = cover_crypt.decaps(&usk, &encrypted_key)?; @@ -456,7 +447,7 @@ mod tests { // New user secret key let _user_key = cover_crypt.generate_user_secret_key( &msk, - &AccessPolicy::from_boolean_expression("Security Level::Top Secret")?, + &AccessPolicy::parse("Security Level::Top Secret")?, &policy, )?; @@ -468,7 +459,7 @@ mod tests { // // Declare policy let policy = policy()?; - let top_secret_ap = AccessPolicy::from_boolean_expression("Security Level::Top Secret")?; + let top_secret_ap = AccessPolicy::parse("Security Level::Top Secret")?; // // Setup Covercrypt @@ -479,9 +470,7 @@ mod tests { // New user secret key let mut top_secret_fin_usk = cover_crypt.generate_user_secret_key( &msk, - &AccessPolicy::from_boolean_expression( - "Security Level::Top Secret && Department::FIN", - )?, + &AccessPolicy::parse("Security Level::Top Secret && Department::FIN")?, &policy, )?; diff --git a/src/test_utils/non_regression.rs b/src/test_utils/non_regression.rs index 2c7c4c53..21fda71f 100644 --- a/src/test_utils/non_regression.rs +++ b/src/test_utils/non_regression.rs @@ -82,7 +82,7 @@ impl EncryptionTestVector { &cover_crypt, policy, mpk, - &AccessPolicy::from_boolean_expression(encryption_policy)?, + &AccessPolicy::parse(encryption_policy)?, header_metadata, authentication_data, )?; @@ -122,11 +122,7 @@ impl UserSecretKeyTestVector { Ok(Self { key: transcoder.encode( Covercrypt::default() - .generate_user_secret_key( - msk, - &AccessPolicy::from_boolean_expression(access_policy)?, - policy, - )? + .generate_user_secret_key(msk, &AccessPolicy::parse(access_policy)?, policy)? .serialize()?, ), access_policy: access_policy.to_string(),