Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rekey: fix policy parsing #130

Merged
merged 8 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .cargo/audit.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[advisories]
# Waiting for https://github.com/Argyle-Software/kyber/pull/110
ignore = [
"RUSTSEC-2023-0079", # pqc-kyber
]
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ base64 = { version = "0.21.0", optional = true }
cosmian_crypto_core = { version = "9.2.0", default-features = false, features = ["ser", "sha3", "aes", "curve25519"] }
pqc_kyber = { version = "0.4", features = ["std", "hazmat"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_json = { version = "1.0", features = ["preserve_order"] }
tiny-keccak = { version = "2.0.2", features = ["shake", "kmac"] }
zeroize = "1.6.0"

[dev-dependencies]
base64 = { version = "0.21.0" }
criterion = { version = "0.4", features = ["html_reports"], default_features = false }
criterion = { version = "0.5", features = ["html_reports"], default_features = false }
5 changes: 3 additions & 2 deletions src/abe_policy/mod.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
//! This crate defines the `Policy` logic, the basis for Attribute Based
//! Encryption (ABE).
//!
//! A `Policy` is a set of axes. Each dimension is defined by its name and its list
//! of associated attribute names.
//! A `Policy` is a set of axes. Each dimension is defined by its name and its
//! list of associated attribute names.
//!
//! An `Attribute` is composed by an dimension name and an attribute name within
//! this dimension.

mod access_policy;
mod attribute;
mod dimension;
mod parser;
mod partitions;
mod policy;
mod policy_versions;
Expand Down
267 changes: 267 additions & 0 deletions src/abe_policy/parser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
use std::collections::HashMap;

use serde_json::Value;

use super::{
AttributeParameters, Dimension, DimensionBuilder, EncryptionHint, LegacyPolicy, Policy,
PolicyV1, PolicyVersion,
};
use crate::Error;

impl Policy {
/// Converts the given bytes into a Policy, supports legacy policy versions.
pub fn parse_and_convert(bytes: &[u8]) -> Result<Self, Error> {
let json_policy: Value =
serde_json::from_slice(bytes).map_err(Error::DeserializationError)?;

if let Some(policy_version) = json_policy.get("version") {
match serde_json::from_value::<PolicyVersion>(policy_version.clone()) {
Ok(PolicyVersion::V1) => Ok(serde_json::from_slice::<PolicyV1>(bytes)
.map_err(Error::DeserializationError)?
.into()),
Ok(PolicyVersion::V2) => {
serde_json::from_value::<Self>(json_policy).map_err(Error::DeserializationError)
}
Err(e) => Err(Error::DeserializationError(e)),
}
} else {
// Legacy Policy
Ok(serde_json::from_slice::<LegacyPolicy>(bytes)
.map_err(Error::DeserializationError)?
.into())
}
}
}

impl TryFrom<&[u8]> for Policy {
type Error = Error;

fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
Self::parse_and_convert(bytes)
}
}

impl TryFrom<&Policy> for Vec<u8> {
type Error = Error;

fn try_from(policy: &Policy) -> Result<Self, Self::Error> {
serde_json::to_vec(policy).map_err(Self::Error::DeserializationError)
}
}

impl TryFrom<HashMap<String, Vec<String>>> for Policy {
type Error = Error;

/// Create a policy object from policy specifications
///
/// The policy specifications must be passed as a mapping object:
/// ```json
/// {
/// "Security Level::<": [
Manuthor marked this conversation as resolved.
Show resolved Hide resolved
/// "Protected",
/// "Confidential",
/// "Top Secret::+"
/// ],
/// "Department": [
/// "R&D",
/// "HR",
/// "MKG",
/// "FIN"
/// ]
/// }
/// ```
fn try_from(value: HashMap<String, Vec<String>>) -> Result<Self, Self::Error> {
let mut policy = Self::new();

for (axis, attributes) in &value {
ackRow marked this conversation as resolved.
Show resolved Hide resolved
// Split the axis into axis name and hierarchy flag
let (axis_name, hierarchical) = match axis.split_once("::") {
Some((name, specs)) => {
// If the axis contains the hierarchy flag, parse it
let hierarchical = match specs {
"<" => true,
x => {
return Err(Error::ConversionFailed(format!("unknown axis spec {x}")));
ackRow marked this conversation as resolved.
Show resolved Hide resolved
}
};
(name, hierarchical)
}
// If there is no hierarchy flag, assume the axis is non-hierarchical
None => (axis.as_str(), false),
};

let mut attributes_properties: Vec<(&str, EncryptionHint)> =
Vec::with_capacity(attributes.len());

// Parse each attribute and its encryption hint
for att in attributes {
let (att_name, encryption_hint) = match att.split_once("::") {
Some((name, specs)) => {
let encryption_hint = match specs {
"+" => EncryptionHint::Hybridized,
x => {
return Err(Error::ConversionFailed(format!(
"unknown attribute spec {x}"
ackRow marked this conversation as resolved.
Show resolved Hide resolved
)));
}
};
(name, encryption_hint)
}
// If there is no encryption hint, assume the attribute is non-hybridized
None => (att.as_str(), EncryptionHint::Classic),
};
attributes_properties.push((att_name, encryption_hint));
}

// Add the axis to the policy
policy.add_dimension(DimensionBuilder::new(
axis_name,
attributes_properties,
hierarchical,
))?;
}
Ok(policy)
}
}

fn convert_attribute(attribute: (String, AttributeParameters)) -> String {
let (name, params) = attribute;
match params.get_encryption_hint() {
EncryptionHint::Hybridized => name + "::+",
EncryptionHint::Classic => name,
}
}
ackRow marked this conversation as resolved.
Show resolved Hide resolved

impl TryFrom<Policy> for HashMap<String, Vec<String>> {
type Error = Error;

fn try_from(policy: Policy) -> Result<Self, Self::Error> {
let mut result: Self = Self::with_capacity(policy.dimensions.len());

for (dim_name, dimension) in policy.dimensions {
let (dim_full_name, attributes_list) = match dimension {
Dimension::Unordered(attributes) => {
let attributes_list: Vec<String> =
attributes.into_iter().map(convert_attribute).collect();
(dim_name, attributes_list)
}
Dimension::Ordered(attributes) => {
let dim_name = dim_name + "::<";
let attributes_list: Vec<String> =
attributes.into_iter().map(convert_attribute).collect();
(dim_name, attributes_list)
}
};
result.insert(dim_full_name, attributes_list);
}
Ok(result)
}
}

#[cfg(test)]
mod tests {
use std::collections::HashMap;

use crate::{
abe_policy::{Attribute, Dimension, DimensionBuilder, EncryptionHint, Policy},
Error,
};

#[test]
pub fn test_parse_policy_from_bytes() -> Result<(), Error> {
let mut policy = Policy::new();
policy.add_dimension(DimensionBuilder::new(
"Security Level",
vec![
("Protected", EncryptionHint::Classic),
("Confidential", EncryptionHint::Classic),
("Top Secret", EncryptionHint::Hybridized),
],
true,
))?;
policy.add_dimension(DimensionBuilder::new(
"Department",
vec![
("R&D", EncryptionHint::Classic),
("HR", EncryptionHint::Classic),
("MKG", EncryptionHint::Classic),
("FIN", EncryptionHint::Classic),
],
false,
))?;
let serialized_policy = <Vec<u8>>::try_from(&policy)?;
let parsed_policy = Policy::parse_and_convert(&serialized_policy)?;
// check policy size
assert_eq!(parsed_policy.attributes().len(), policy.attributes().len());

// check order
let orig_ordered_dim = policy.dimensions.get("Security Level").unwrap();
let parsed_ordered_dim = parsed_policy.dimensions.get("Security Level").unwrap();
assert_eq!(
parsed_ordered_dim.get_attributes_name().collect::<Vec<_>>(),
orig_ordered_dim.get_attributes_name().collect::<Vec<_>>(),
);

Ok(())
}

#[test]
pub fn test_create_policy_from_spec() -> Result<(), Error> {
let json = r#"
{
"Security Level::<": [
"Protected",
"Confidential",
"Top Secret::+"
],
"Department": [
"R&D",
"HR",
"MKG",
"FIN"
]
}
"#;

let policy_json: HashMap<String, Vec<String>> = serde_json::from_str(json).unwrap();
let policy: Policy = policy_json.try_into()?;
assert_eq!(policy.dimensions.len(), 2);
assert!(matches!(
policy.dimensions.get("Security Level").unwrap(),
Dimension::Ordered(_)
));
assert!(matches!(
policy.dimensions.get("Department").unwrap(),
Dimension::Unordered(_)
));
assert_eq!(
policy
.dimensions
.get("Security Level")
.unwrap()
.attributes()
.count(),
3
);
assert_eq!(
policy
.get_attribute_hybridization_hint(&Attribute::new("Department", "MKG"))
.unwrap(),
EncryptionHint::Classic
);
assert_eq!(
policy
.get_attribute_hybridization_hint(&Attribute::new("Security Level", "Protected"))
.unwrap(),
EncryptionHint::Classic
);
assert_eq!(
policy
.get_attribute_hybridization_hint(&Attribute::new("Security Level", "Top Secret"))
.unwrap(),
EncryptionHint::Hybridized
);

Ok(())
}
}
Loading
Loading