Skip to content

Commit

Permalink
Feature/add attribute rotation to pyo3 (#16)
Browse files Browse the repository at this point in the history
* [pyo3 + JS/bindgen + FFI] Add CoverCrypt attributes rotation mechanism
  • Loading branch information
Manuthor authored Jun 27, 2022
1 parent a8c26a7 commit fbe42e3
Show file tree
Hide file tree
Showing 17 changed files with 512 additions and 149 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/target*
.cargo_check
non_regression_vector.json
*nix*
16 changes: 16 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ clippy:
stage: prebuild
cache: {}
script:
# no feature activated
- cargo clippy --all-targets -- -D warnings
# all features activated
- cargo clippy-all

# Security check
Expand Down Expand Up @@ -122,3 +125,16 @@ build_osx:
- target/release/*.so
- target/*.h
expire_in: 3 mos

build_android:
<<: *base_compile
stage: build
image: gitlab.cosmian.com:5000/core/ci-rust-android:latest
before_script:
- rustup target add i686-linux-android x86_64-linux-android armv7-linux-androideabi aarch64-linux-android
script:
- cargo ndk -t x86 -t x86_64 -t armeabi-v7a -t arm64-v8a -o jniLibs build --release --features ffi --lib
artifacts:
paths:
- jniLibs
expire_in: 3 mos
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file.

---
## [3.0.1] - 2022-06-21
### Added
- [pyo3 + JS/bindgen + FFI] Add CoverCrypt attributes rotation mechanism
### Changed
### Fixed
- Fix access policy to partitions
### Removed
---

## [3.0.0] - 2022-06-14
### Added
- Add Gitlab CI .gitlab-ci.yml
Expand Down
26 changes: 13 additions & 13 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cover_crypt"
version = "3.0.0"
version = "3.0.1"
edition = "2021"
authors = [
"Théophile Brezot <[email protected]>",
Expand Down
2 changes: 1 addition & 1 deletion benches/benches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ unsafe fn unwrap_ffi_error(val: i32) -> Result<(), Error> {
let mut message_bytes_len = message_bytes_key.len() as c_int;
get_last_error(message_bytes_ptr, &mut message_bytes_len);
let cstr = CStr::from_ptr(message_bytes_ptr);
return Err(Error::Other(format!("FFI ERROR: {}", cstr.to_str()?)));
Err(Error::Other(format!("FFI ERROR: {}", cstr.to_str()?)))
} else {
Ok(())
}
Expand Down
103 changes: 55 additions & 48 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,47 +269,42 @@ fn access_policy_to_partitions(
access_policy: &AccessPolicy,
policy: &Policy,
) -> Result<HashSet<Partition>, Error> {
let combinations = to_attribute_combinations(access_policy, policy)?;
let mut set: HashSet<Partition> = HashSet::with_capacity(combinations.len());
for combination in combinations {
set.insert(Partition::from_attributes(combination)?);
let attr_combinations = to_attribute_combinations(access_policy, policy)?;
let mut set = HashSet::with_capacity(attr_combinations.len());
for attr_combination in &attr_combinations {
for partition in to_partitions(attr_combination, policy)? {
let is_unique = set.insert(partition);
if !is_unique {
return Err(Error::ExistingCombination(format!("{attr_combination:?}")));
}
}
}
Ok(set)
}

/// Returns the list of partitions that can be built using the values of
/// each attribute in the given access policy. This corresponds to an OR
/// expression of AND expressions.
/// Returns the list of attribute combinations that can be built using the
/// values of each attribute in the given access policy. This corresponds to
/// an OR expression of AND expressions.
///
/// - `policy` : global policy
fn to_attribute_combinations(
access_policy: &AccessPolicy,
policy: &Policy,
) -> Result<Vec<Vec<u32>>, Error> {
) -> Result<Vec<Vec<Attribute>>, Error> {
match access_policy {
AccessPolicy::Attr(attr) => {
let (attribute_names, is_hierarchical) = policy
.as_map()
.get(&attr.axis())
.ok_or_else(|| Error::UnknownPartition(attr.axis()))?;
let mut res = policy
.attribute_values(attr)?
.iter()
.map(|&value| vec![value])
.collect::<Vec<Vec<u32>>>();
let mut res = vec![vec![attr.clone()]];
if *is_hierarchical {
// add attribute values for all attributes below the given one
for name in attribute_names.iter() {
if *name == attr.name() {
break;
}
res.extend(
policy
.attribute_values(&Attribute::new(&attr.axis(), name))?
.iter()
.map(|&value| vec![value])
.collect::<Vec<Vec<u32>>>(),
);
res.push(vec![Attribute::new(&attr.axis(), name)]);
}
}
Ok(res)
Expand Down Expand Up @@ -474,39 +469,51 @@ mod tests {
}

#[test]
fn test_to_attribute_combinations() -> Result<(), Error> {
fn test_access_policy_to_partition() -> Result<(), Error> {
//
// create policy
let mut policy = policy()?;

policy.rotate(&Attribute::new("Department", "FIN"))?;
let access_policy = (AccessPolicy::new("Department", "HR")
| AccessPolicy::new("Department", "FIN"))
& AccessPolicy::new("Security Level", "Confidential");
let combinations = to_attribute_combinations(&access_policy, &policy)?;
let mut partitions_: HashSet<Partition> = HashSet::with_capacity(combinations.len());
for combination in combinations {
partitions_.insert(Partition::from_attributes(combination)?);
}

// combine attribute values to verify
let mut map: HashMap<String, Vec<u32>> = HashMap::new();
let mut dpt_axis_attributes =
policy.attribute_values(&Attribute::new("Department", "FIN"))?;
dpt_axis_attributes.extend(policy.attribute_values(&Attribute::new("Department", "HR"))?);
map.insert("Department".to_owned(), dpt_axis_attributes);
let mut lvl_axis_attributes =
policy.attribute_values(&Attribute::new("Security Level", "Confidential"))?;
lvl_axis_attributes
.extend(policy.attribute_values(&Attribute::new("Security Level", "Protected"))?);
map.insert("Security Level".to_owned(), lvl_axis_attributes);

let axes: Vec<String> = policy.as_map().keys().cloned().collect();

let combinations = combine_attribute_values(0, axes.as_slice(), &map)?;
let mut partitions: HashSet<Partition> = HashSet::with_capacity(combinations.len());
for combination in combinations {
partitions.insert(Partition::from_attributes(combination)?);
//
// create access policy
let access_policy = AccessPolicy::new("Department", "HR")
| (AccessPolicy::new("Department", "FIN")
& AccessPolicy::new("Security Level", "Confidential"));

//
// create partitions from access policy
let partitions = access_policy_to_partitions(&access_policy, &policy)?;

//
// manually create the partitions
let mut partitions_ = HashSet::new();
// add the partitions associated with the HR department: combine with
// all attributes of the Security Level axis
let hr_value = policy.attribute_current_value(&Attribute::new("Department", "HR"))?;
let (security_levels, _) = policy.as_map().get("Security Level").unwrap();
for attr_name in security_levels {
let attr_value =
policy.attribute_current_value(&Attribute::new("Security Level", attr_name))?;
let mut partition = vec![hr_value, attr_value];
partition.sort_unstable();
partitions_.insert(Partition::from_attributes(partition)?);
}

// add the other attribute combination: FIN && Confidential
let fin_value = policy.attribute_current_value(&Attribute::new("Department", "FIN"))?;
let conf_value =
policy.attribute_current_value(&Attribute::new("Security Level", "Confidential"))?;
let mut partition = vec![fin_value, conf_value];
partition.sort_unstable();
partitions_.insert(Partition::from_attributes(partition)?);
// since this is a hyerachical axis, add the lower values: here only protected
let prot_value =
policy.attribute_current_value(&Attribute::new("Security Level", "Protected"))?;
let mut partition = vec![fin_value, prot_value];
partition.sort_unstable();
partitions_.insert(Partition::from_attributes(partition)?);

assert_eq!(partitions, partitions_);
Ok(())
}
Expand Down
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ pub enum Error {
ExistingAttribute(String, String),
#[error("policy {0} already exists")]
ExistingPolicy(String),
#[error("Combination {0} already exists")]
ExistingCombination(String),
#[error("invalid size")]
InvalidSize(String),
#[error("Empty private key")]
Expand Down
82 changes: 80 additions & 2 deletions src/interfaces/ffi/generate_cc_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
api::{CoverCrypt, PrivateKey},
ffi_bail, ffi_not_null, ffi_unwrap,
interfaces::ffi::error::{set_last_error, FfiError},
policies::{AccessPolicy, Policy},
policies::{AccessPolicy, Attribute, Policy},
};

#[no_mangle]
Expand Down Expand Up @@ -161,7 +161,7 @@ pub unsafe extern "C" fn h_generate_user_private_key(
let policy: Policy = ffi_unwrap!(serde_json::from_str(&policy));

//
// Generate master keys
// Generate user private key
let user_key = ffi_unwrap!(
CoverCrypt::<X25519Crypto>::default().generate_user_private_key(
&master_private_key,
Expand Down Expand Up @@ -191,3 +191,81 @@ pub unsafe extern "C" fn h_generate_user_private_key(

0
}

#[no_mangle]
/// Generate the user private key matching the given access policy
///
/// - `new_policy_ptr`: Output buffer containing new policy
/// - `new_policy_len`: Size of the output buffer
/// - `attributes_ptr`: Attributes to rotate (JSON)
/// - `policy_ptr`: Policy to use to generate the keys (JSON)
/// # Safety
pub unsafe extern "C" fn h_rotate_attributes(
new_policy_ptr: *mut c_char,
new_policy_len: *mut c_int,
attributes_ptr: *const c_char,
policy_ptr: *const c_char,
) -> c_int {
//
// Checks inputs
ffi_not_null!(
new_policy_ptr,
"New policy pointer should point to pre-allocated memory"
);
if *new_policy_len == 0 {
ffi_bail!("The new policy buffer should not be empty");
}
ffi_not_null!(attributes_ptr, "Attributes pointer should not be null");
ffi_not_null!(policy_ptr, "Policy pointer should not be null");

//
// Attributes
let attributes = match CStr::from_ptr(attributes_ptr).to_str() {
Ok(msg) => msg.to_owned(),
Err(e) => {
set_last_error(FfiError::Generic(format!(
"CoverCrypt attributes rotation: invalid Attributes: {e}"
)));
return 1;
}
};
let attributes: Vec<Attribute> = ffi_unwrap!(serde_json::from_str(&attributes));

//
// Policy
let policy = match CStr::from_ptr(policy_ptr).to_str() {
Ok(msg) => msg.to_owned(),
Err(e) => {
set_last_error(FfiError::Generic(format!(
"CoverCrypt keys generation: invalid Policy: {e}"
)));
return 1;
}
};
let mut policy: Policy = ffi_unwrap!(serde_json::from_str(&policy));

//
// Rotate attributes of the current policy
for attr in &attributes {
ffi_unwrap!(policy.rotate(attr));
}

//
// Serialize new policy
let new_policy_string = policy.to_string();

//
// Prepare output
let allocated = *new_policy_len;
let len = new_policy_string.len();
if (allocated as usize) < len {
ffi_bail!(
"The pre-allocated output policy buffer is too small; need {len} bytes, allocated {allocated}"
);
}
std::slice::from_raw_parts_mut(new_policy_ptr as *mut u8, len)
.copy_from_slice(new_policy_string.as_bytes());
*new_policy_len = len as c_int;

0
}
Loading

0 comments on commit fbe42e3

Please sign in to comment.