Skip to content

Commit

Permalink
Implement RopsMap::encrypt{,_with_saved_nonces}().
Browse files Browse the repository at this point in the history
gibbz00 committed Dec 17, 2023

Verified

This commit was signed with the committer’s verified signature.
wmaxey Wesley Maxey
1 parent a2c1754 commit 164105f
Showing 11 changed files with 233 additions and 121 deletions.
2 changes: 1 addition & 1 deletion crates/lib/src/cryptography/cipher/core.rs
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ use generic_array::ArrayLength;

use crate::*;

pub trait Cipher: Sized {
pub trait Cipher: Sized + Debug + PartialEq {
const NAME: &'static str;

type NonceSize: ArrayLength<u8> + Debug + PartialEq;
4 changes: 2 additions & 2 deletions crates/lib/src/rops_file/core.rs
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ use crate::*;
#[serde(bound = "F: FileFormat")]
pub struct RopsFile<S: RopsFileState, F: FileFormat> {
#[serde(flatten)]
pub map: RopsFileMap<S, F>,
pub map: RopsFileFormatMap<S, F>,
#[serde(rename = "sops")]
pub metadata: RopsFileMetadata,
}
@@ -17,7 +17,7 @@ mod mock {

impl<S: RopsFileState, F: FileFormat> MockTestUtil for RopsFile<S, F>
where
RopsFileMap<S, F>: MockTestUtil,
RopsFileFormatMap<S, F>: MockTestUtil,
{
fn mock() -> Self {
Self {
43 changes: 43 additions & 0 deletions crates/lib/src/rops_file/format/map.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use std::marker::PhantomData;

use serde::{Deserialize, Serialize};

use crate::*;

#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct RopsFileFormatMap<S: RopsFileState, F: FileFormat> {
#[serde(flatten)]
inner: F::Map,
#[serde(skip)]
state_marker: PhantomData<S>,
}

// IMPROVEMENT: Might be worth splitting distinguishing decrypted and
// encrypted map to tree errors by separating then into two enums.
#[derive(Debug, thiserror::Error)]
pub enum FormatToInternalMapError {
#[error("only string keys are supported, found: {0}")]
NonStringKey(String),
#[error("integer out of range, allowed values must fit inside an i64, found: {0}")]
IntegerOutOfRange(u64),
#[error("unable to parse encrypted value components: {0}")]
EncryptedRopsValue(#[from] EncryptedRopsValueFromStrError),
// TEMP: Deprecate once partial encryption feature arrives.
#[error("invalid valid for an encrypted file")]
InvalidValueForEncrypted(String),
}

impl<S: RopsFileState, F: FileFormat> RopsFileFormatMap<S, F> {
pub fn into_inner_map(self) -> F::Map {
self.inner
}

#[cfg(feature = "test-utils")]
pub fn from_inner_map(inner: F::Map) -> Self {
Self {
inner,
state_marker: PhantomData,
}
}
}
3 changes: 3 additions & 0 deletions crates/lib/src/rops_file/format/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
mod core;
pub use core::FileFormat;

mod map;
pub use map::{FormatToInternalMapError, RopsFileFormatMap};

#[cfg(feature = "yaml")]
mod yaml;
#[cfg(feature = "yaml")]
10 changes: 5 additions & 5 deletions crates/lib/src/rops_file/format/yaml/mock.rs
Original file line number Diff line number Diff line change
@@ -3,14 +3,14 @@ mod rops_file {

impl<S: RopsFileState> MockFileFormatUtil<YamlFileFormat> for RopsFile<S, YamlFileFormat>
where
RopsFileMap<S, YamlFileFormat>: MockFileFormatUtil<YamlFileFormat>,
RopsFileFormatMap<S, YamlFileFormat>: MockFileFormatUtil<YamlFileFormat>,
{
fn mock_format_display() -> String {
indoc::formatdoc! {"
{}
sops:
{}",
RopsFileMap::mock_format_display(),
RopsFileFormatMap::mock_format_display(),
textwrap::indent(&RopsFileMetadata::mock_format_display()," ")
}
}
@@ -20,7 +20,7 @@ mod rops_file {
mod map {
use crate::*;

impl MockFileFormatUtil<YamlFileFormat> for RopsFileMap<Decrypted, YamlFileFormat> {
impl MockFileFormatUtil<YamlFileFormat> for RopsFileFormatMap<Decrypted, YamlFileFormat> {
fn mock_format_display() -> String {
indoc::indoc! {"
hello: world!
@@ -40,7 +40,7 @@ mod map {
}

#[cfg(feature = "aes-gcm")]
impl MockFileFormatUtil<YamlFileFormat> for RopsFileMap<Encrypted<AES256GCM>, YamlFileFormat> {
impl MockFileFormatUtil<YamlFileFormat> for RopsFileFormatMap<Encrypted<AES256GCM>, YamlFileFormat> {
fn mock_format_display() -> String {
indoc::indoc! {"
hello: ENC[AES256_GCM,data:3S1E9am/,iv:WUQoQTrRXw/tUgwpmSG69xWtd5dVMfe8qUly1VB8ucM=,tag:nQUDkuh0OR1cjR5hGC5jOw==,type:str]
@@ -59,7 +59,7 @@ mod map {
}
}

impl<S: RopsFileState> MockTestUtil for RopsFileMap<S, YamlFileFormat>
impl<S: RopsFileState> MockTestUtil for RopsFileFormatMap<S, YamlFileFormat>
where
Self: MockFileFormatUtil<YamlFileFormat>,
{
2 changes: 1 addition & 1 deletion crates/lib/src/rops_file/format/yaml/mod.rs
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ impl FileFormat for YamlFileFormat {
}
}

mod map_to_tree;
mod to_internal_map;

#[cfg(feature = "test-utils")]
mod mock;
Original file line number Diff line number Diff line change
@@ -6,22 +6,22 @@ use crate::*;
mod tree_traversal {
use super::*;

pub fn recursive_map_call<F, S: RopsFileState>(yaml_map: YamlMap, recursive_value_fn: F) -> Result<RopsTree<S>, MapToTreeError>
pub fn recursive_map_call<F, S: RopsFileState>(yaml_map: YamlMap, recursive_value_fn: F) -> Result<RopsMap<S>, FormatToInternalMapError>
where
F: Fn(YamlValue) -> Result<RopsTree<S>, MapToTreeError>,
F: Fn(YamlValue) -> Result<RopsTree<S>, FormatToInternalMapError>,
{
let mut inner_map = IndexMap::default();
let mut tree_map = IndexMap::default();

for (yaml_key, value_yaml) in yaml_map {
inner_map.insert(validate_key(yaml_key)?, recursive_value_fn(value_yaml)?);
tree_map.insert(validate_key(yaml_key)?, recursive_value_fn(value_yaml)?);
}

return Ok(RopsTree::Map(inner_map));
return Ok(tree_map.into());

fn validate_key(yaml_value: YamlValue) -> Result<String, MapToTreeError> {
fn validate_key(yaml_value: YamlValue) -> Result<String, FormatToInternalMapError> {
match yaml_value {
YamlValue::String(string) => Ok(string),
other => Err(MapToTreeError::NonStringKey(
other => Err(FormatToInternalMapError::NonStringKey(
serde_yaml::to_string(&other).expect("yaml value not serializable"),
)),
}
@@ -32,24 +32,24 @@ mod tree_traversal {
mod encrypted {
use super::*;

impl<C: Cipher> TryFrom<RopsFileMap<Encrypted<C>, YamlFileFormat>> for RopsTree<Encrypted<C>> {
type Error = MapToTreeError;
impl<C: Cipher> TryFrom<RopsFileFormatMap<Encrypted<C>, YamlFileFormat>> for RopsMap<Encrypted<C>> {
type Error = FormatToInternalMapError;

fn try_from(rops_file_map: RopsFileMap<Encrypted<C>, YamlFileFormat>) -> Result<Self, Self::Error> {
fn try_from(rops_file_map: RopsFileFormatMap<Encrypted<C>, YamlFileFormat>) -> Result<Self, Self::Error> {
return tree_traversal::recursive_map_call(rops_file_map.into_inner_map(), recursive_value_call);

fn recursive_value_call<Ci: Cipher>(yaml_value: YamlValue) -> Result<RopsTree<Encrypted<Ci>>, MapToTreeError> {
fn recursive_value_call<Ci: Cipher>(yaml_value: YamlValue) -> Result<RopsTree<Encrypted<Ci>>, FormatToInternalMapError> {
Ok(match yaml_value {
YamlValue::Tagged(tagged) => recursive_value_call(tagged.value)?,
YamlValue::Mapping(map) => tree_traversal::recursive_map_call(map, recursive_value_call)?,
YamlValue::Mapping(map) => RopsTree::Map(tree_traversal::recursive_map_call(map, recursive_value_call)?),
YamlValue::String(encrypted_string) => RopsTree::Leaf(encrypted_string.parse()?),
YamlValue::Sequence(sequence) => {
RopsTree::Sequence(sequence.into_iter().map(recursive_value_call).collect::<Result<Vec<_>, _>>()?)
}
YamlValue::Null => RopsTree::Null,
YamlValue::Bool(_) | YamlValue::Number(_) => {
// TEMP: handle as hard error until partial encryption support is added
return Err(MapToTreeError::InvalidValueForEncrypted(
return Err(FormatToInternalMapError::InvalidValueForEncrypted(
serde_yaml::to_string(&yaml_value).expect("unable to serialize yaml value"),
));
}
@@ -69,29 +69,31 @@ mod encrypted {
#[test]
fn transforms_encrypted_yaml_map() {
assert_eq!(
RopsTree::mock(),
RopsFileMap::<Encrypted<AES256GCM>, YamlFileFormat>::mock().try_into().unwrap()
RopsMap::mock(),
RopsFileFormatMap::<Encrypted<AES256GCM>, YamlFileFormat>::mock()
.try_into()
.unwrap()
)
}
}

#[test]
fn dissallows_non_string_keys() {
let file_map = RopsFileMap::from_inner_map(serde_yaml::from_str::<YamlMap>("true: xxx").unwrap());
let file_map = RopsFileFormatMap::from_inner_map(serde_yaml::from_str::<YamlMap>("true: xxx").unwrap());
assert!(matches!(
RopsTree::<Encrypted<StubCipher>>::try_from(file_map).unwrap_err(),
MapToTreeError::NonStringKey(_)
RopsMap::<Encrypted<StubCipher>>::try_from(file_map).unwrap_err(),
FormatToInternalMapError::NonStringKey(_)
))
}

/*
TEMP(NOTE): Not necassarily true once partial encryption arrives:
*/
fn assert_disallowed_value_helper(key_value_str: &str) {
let file_map = RopsFileMap::from_inner_map(serde_yaml::from_str::<YamlMap>(key_value_str).unwrap());
let file_map = RopsFileFormatMap::from_inner_map(serde_yaml::from_str::<YamlMap>(key_value_str).unwrap());
assert!(matches!(
RopsTree::<Encrypted<StubCipher>>::try_from(file_map).unwrap_err(),
MapToTreeError::InvalidValueForEncrypted(_)
RopsMap::<Encrypted<StubCipher>>::try_from(file_map).unwrap_err(),
FormatToInternalMapError::InvalidValueForEncrypted(_)
))
}

@@ -110,27 +112,27 @@ mod encrypted {
mod decrypted {
use super::*;

impl TryFrom<RopsFileMap<Decrypted, YamlFileFormat>> for RopsTree<Decrypted> {
type Error = MapToTreeError;
impl TryFrom<RopsFileFormatMap<Decrypted, YamlFileFormat>> for RopsMap<Decrypted> {
type Error = FormatToInternalMapError;

fn try_from(rops_file_map: RopsFileMap<Decrypted, YamlFileFormat>) -> Result<Self, Self::Error> {
fn try_from(rops_file_map: RopsFileFormatMap<Decrypted, YamlFileFormat>) -> Result<Self, Self::Error> {
return tree_traversal::recursive_map_call(rops_file_map.into_inner_map(), recursive_value_call);

fn recursive_value_call(yaml_value: YamlValue) -> Result<RopsTree<Decrypted>, MapToTreeError> {
fn recursive_value_call(yaml_value: YamlValue) -> Result<RopsTree<Decrypted>, FormatToInternalMapError> {
Ok(match yaml_value {
// SOPS simply throws away tags, so do we for now.
// It can, however, deserialize manually added tags to encrypted documents,
// so we could in theory keep the tags somewhere without breaking SOPS compatability.
YamlValue::Tagged(tagged) => recursive_value_call(tagged.value)?,
YamlValue::Mapping(map) => tree_traversal::recursive_map_call(map, recursive_value_call)?,
YamlValue::Mapping(map) => RopsTree::Map(tree_traversal::recursive_map_call(map, recursive_value_call)?),
YamlValue::Bool(boolean) => RopsTree::Leaf(RopsValue::Boolean(boolean)),
YamlValue::String(string) => RopsTree::Leaf(RopsValue::String(string)),
YamlValue::Number(number) => RopsTree::Leaf(match number.is_f64() {
true => RopsValue::Float(number.as_f64().expect("number not a f64").to_string()),
false => RopsValue::Integer(
number
.as_i64()
.ok_or_else(|| MapToTreeError::IntegerOutOfRange(number.as_u64().expect("number not an u64")))?,
.ok_or_else(|| FormatToInternalMapError::IntegerOutOfRange(number.as_u64().expect("number not an u64")))?,
),
}),
YamlValue::Sequence(sequence) => {
@@ -149,26 +151,27 @@ mod decrypted {
#[test]
fn transforms_decrypted_yaml_map() {
assert_eq!(
RopsTree::mock(),
RopsFileMap::<Decrypted, YamlFileFormat>::mock().try_into().unwrap()
RopsMap::mock(),
RopsFileFormatMap::<Decrypted, YamlFileFormat>::mock().try_into().unwrap()
)
}

#[test]
fn dissallows_non_string_keys() {
let file_map = RopsFileMap::from_inner_map(serde_yaml::from_str::<YamlMap>("123: 456").unwrap());
let file_map = RopsFileFormatMap::from_inner_map(serde_yaml::from_str::<YamlMap>("123: 456").unwrap());
assert!(matches!(
RopsTree::<Decrypted>::try_from(file_map).unwrap_err(),
MapToTreeError::NonStringKey(_)
RopsMap::<Decrypted>::try_from(file_map).unwrap_err(),
FormatToInternalMapError::NonStringKey(_)
))
}

#[test]
fn dissallows_out_of_range_integers() {
let file_map = RopsFileMap::from_inner_map(serde_yaml::from_str::<YamlMap>(&format!("invalid_integer: {}", u64::MAX)).unwrap());
let file_map =
RopsFileFormatMap::from_inner_map(serde_yaml::from_str::<YamlMap>(&format!("invalid_integer: {}", u64::MAX)).unwrap());
assert!(matches!(
RopsTree::<Decrypted>::try_from(file_map).unwrap_err(),
MapToTreeError::IntegerOutOfRange(_)
RopsMap::<Decrypted>::try_from(file_map).unwrap_err(),
FormatToInternalMapError::IntegerOutOfRange(_)
))
}
}
28 changes: 0 additions & 28 deletions crates/lib/src/rops_file/map.rs

This file was deleted.

5 changes: 1 addition & 4 deletions crates/lib/src/rops_file/mod.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
mod core;
pub use core::RopsFile;

mod map;
pub use map::RopsFileMap;

mod key_path;
pub use key_path::KeyPath;

mod value;
pub use value::*;

mod tree;
pub use tree::{MapToTreeError, RopsTree, SavedRopsTreeNonces};
pub use tree::{RopsMap, RopsTree, SavedRopsTreeNonces};

mod metadata;
pub use metadata::*;
6 changes: 3 additions & 3 deletions crates/lib/src/rops_file/state.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::marker::PhantomData;
use std::{fmt::Debug, marker::PhantomData};

use crate::*;

pub trait RopsFileState {
type RopsTreeLeaf;
pub trait RopsFileState: Debug + PartialEq {
type RopsTreeLeaf: Debug + PartialEq;
}

#[derive(Debug, PartialEq)]
178 changes: 136 additions & 42 deletions crates/lib/src/rops_file/tree.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,90 @@
use std::collections::HashMap;
use std::{borrow::Cow, collections::HashMap};

use derive_more::{Deref, DerefMut};
use derive_more::{Deref, DerefMut, From, Into};
use indexmap::IndexMap;

use crate::*;

#[derive(Debug, PartialEq, From, Into, Deref, DerefMut)]
pub struct RopsMap<S: RopsFileState>(indexmap::IndexMap<String, RopsTree<S>>);

#[derive(Debug, PartialEq)]
pub enum RopsTree<S: RopsFileState> {
Sequence(Vec<RopsTree<S>>),
Map(indexmap::IndexMap<String, RopsTree<S>>),
Map(RopsMap<S>),
Null,
Leaf(S::RopsTreeLeaf),
}

// IMPROVEMENT: Might be worth splitting distinguishing decrypted and
// encrypted map to tree errors by separating then into two enums.
#[derive(Debug, thiserror::Error)]
pub enum MapToTreeError {
#[error("only string keys are supported, found: {0}")]
NonStringKey(String),
#[error("integer out of range, allowed values must fit inside an i64, found: {0}")]
IntegerOutOfRange(u64),
#[error("unable to parse encrypted value components: {0}")]
EncryptedRopsValue(#[from] EncryptedRopsValueFromStrError),
// TEMP: Deprecate once partial encryption feature arrives.
#[error("invalid valid for an encrypted file")]
InvalidValueForEncrypted(String),
}

// WORKAROUND: Non-cow tuple key doesn't allow saved_nounces.get((&key, &value))
#[derive(Debug, PartialEq, Deref, DerefMut)]
pub struct SavedRopsTreeNonces<C: Cipher>(HashMap<(KeyPath, RopsValue), Nonce<C::NonceSize>>);
#[allow(clippy::complexity)]
pub struct SavedRopsTreeNonces<C: Cipher>(HashMap<(Cow<'static, KeyPath>, Cow<'static, RopsValue>), Nonce<C::NonceSize>>);

impl RopsMap<Decrypted> {
pub fn encrypt<C: Cipher>(self, data_key: &DataKey) -> Result<RopsMap<Encrypted<C>>, C::Error> {
Self::encrypt_recursive_impl(self, data_key, &None)
}

pub fn encrypt_with_saved_nonces<C: Cipher>(
self,
data_key: &DataKey,
saved_nonces: &SavedRopsTreeNonces<C>,
) -> Result<RopsMap<Encrypted<C>>, C::Error> {
Self::encrypt_recursive_impl(self, data_key, &Some(saved_nonces))
}

fn encrypt_recursive_impl<C: Cipher>(
self,
data_key: &DataKey,
saved_nonces: &Option<&SavedRopsTreeNonces<C>>,
) -> Result<RopsMap<Encrypted<C>>, C::Error> {
return encrypt_map_inner(self, data_key, &KeyPath::default(), saved_nonces);

fn encrypt_map_inner<Ci: Cipher>(
decrypted_map: RopsMap<Decrypted>,
data_key: &DataKey,
key_path: &KeyPath,
optional_saved_nonces: &Option<&SavedRopsTreeNonces<Ci>>,
) -> Result<RopsMap<Encrypted<Ci>>, Ci::Error> {
let mut encrypted_map = RopsMap(IndexMap::with_capacity(decrypted_map.len()));
for (key, decrypted_sub_tree) in decrypted_map.0 {
let key_path = key_path.join(&key);
encrypted_map.insert(
key,
encrypt_recursive_inner(decrypted_sub_tree, data_key, &key_path, optional_saved_nonces)?,
);
}

Ok(encrypted_map)
}

fn encrypt_recursive_inner<Ci: Cipher>(
decrypted_tree: RopsTree<Decrypted>,
data_key: &DataKey,
key_path: &KeyPath,
optional_saved_nonces: &Option<&SavedRopsTreeNonces<Ci>>,
) -> Result<RopsTree<Encrypted<Ci>>, Ci::Error> {
Ok(match decrypted_tree {
RopsTree::Sequence(sequence) => RopsTree::Sequence(
sequence
.into_iter()
.map(|sub_tree| encrypt_recursive_inner(sub_tree, data_key, key_path, optional_saved_nonces))
.collect::<Result<Vec<_>, _>>()?,
),
RopsTree::Map(decrypted_map) => RopsTree::Map(encrypt_map_inner(decrypted_map, data_key, key_path, optional_saved_nonces)?),
RopsTree::Null => RopsTree::Null,
RopsTree::Leaf(value) => {
let nonce = optional_saved_nonces
.map(|saved_nonces| saved_nonces.get(&(Cow::Borrowed(key_path), Cow::Borrowed(&value))).cloned())
.flatten()
.unwrap_or_else(Nonce::new);
RopsTree::Leaf(value.encrypt(nonce, data_key, key_path)?)
}
})
}
}
}

impl<C: Cipher> RopsTree<Encrypted<C>> {
pub fn decrypt(self, data_key: &DataKey) -> Result<RopsTree<Decrypted>, DecryptRopsValueError> {
@@ -59,22 +114,22 @@ impl<C: Cipher> RopsTree<Encrypted<C>> {
RopsTree::Map(encrypted_map) => {
let mut decrypted_map = IndexMap::with_capacity(encrypted_map.len());

for (key, sub_tree) in encrypted_map {
for (key, sub_tree) in encrypted_map.0 {
let sub_key_path = key_path.join(&key);
decrypted_map.insert(
key,
Self::recursive_decrypt_impl(sub_tree, data_key, &sub_key_path, optional_saved_nonces)?,
);
}

RopsTree::Map(decrypted_map)
RopsTree::Map(decrypted_map.into())
}
RopsTree::Null => RopsTree::Null,
RopsTree::Leaf(encrypted_value) => RopsTree::Leaf(match optional_saved_nonces {
Some(saved_nonces) => {
let nonce = encrypted_value.nonce.clone();
let decrypted_value = encrypted_value.decrypt(data_key, key_path)?;
saved_nonces.insert((key_path.clone(), decrypted_value.clone()), nonce);
saved_nonces.insert((Cow::Owned(key_path.clone()), Cow::Owned(decrypted_value.clone())), nonce);
decrypted_value
}
None => encrypted_value.decrypt(data_key, key_path)?,
@@ -91,23 +146,38 @@ mod mock {

impl MockTestUtil for RopsTree<Decrypted> {
fn mock() -> Self {
Self::Map(indexmap! {
Self::Map(MockTestUtil::mock())
}
}

impl<C: Cipher> MockTestUtil for RopsTree<Encrypted<C>>
where
RopsMap<Encrypted<C>>: MockTestUtil,
{
fn mock() -> Self {
Self::Map(MockTestUtil::mock())
}
}

impl MockTestUtil for RopsMap<Decrypted> {
fn mock() -> Self {
Self(indexmap! {
"hello".to_string() => RopsTree::Leaf(RopsValue::String("world!".to_string())),
"nested_map".to_string() => RopsTree::Map(indexmap! {
"nested_map".to_string() => RopsTree::Map(Self(indexmap! {
"null_key".to_string() => RopsTree::Null,
"array".to_string() => RopsTree::Sequence(vec![
RopsTree::Leaf(RopsValue::String("string".to_string())),
RopsTree::Map(indexmap! {
"nested_map_in_array".to_string() => RopsTree::Map(indexmap!{
RopsTree::Map(Self(indexmap! {
"nested_map_in_array".to_string() => RopsTree::Map(Self(indexmap!{
"integer".to_string() => RopsTree::Leaf(RopsValue::Integer(1234))
}),
}),
RopsTree::Map(indexmap!{
})),
})),
RopsTree::Map(Self(indexmap!{
"float".to_string() => RopsTree::Leaf(RopsValue::Float(1234.56789.to_string()))
}),
})),
]),
}
),
)),
"booleans".to_string() => RopsTree::Sequence(vec![
RopsTree::Leaf(RopsValue::Boolean(true)),
RopsTree::Leaf(RopsValue::Boolean(false))
@@ -136,13 +206,14 @@ mod mock {
.into_iter()
.for_each(|sub_tree| recurive_build(sub_tree, saved_nonces, data_key, key_path)),
RopsTree::Map(map) => map
.0
.into_iter()
.for_each(|(key, sub_tree)| recurive_build(sub_tree, saved_nonces, data_key, &key_path.join(&key))),
RopsTree::Null => (),
RopsTree::Leaf(encrypted_value) => {
let nonce = encrypted_value.nonce.clone();
let decrypted = encrypted_value.decrypt(data_key, key_path).unwrap();
saved_nonces.insert((key_path.clone(), decrypted), nonce);
saved_nonces.insert((Cow::Owned(key_path.clone()), Cow::Owned(decrypted)), nonce);
}
}
}
@@ -153,24 +224,24 @@ mod mock {
mod aes_gcm {
use super::*;

impl MockTestUtil for RopsTree<Encrypted<AES256GCM>> {
impl MockTestUtil for RopsMap<Encrypted<AES256GCM>> {
fn mock() -> Self {
return Self::Map(indexmap! {
return Self(indexmap! {
"hello".to_string() => leaf("ENC[AES256_GCM,data:3S1E9am/,iv:WUQoQTrRXw/tUgwpmSG69xWtd5dVMfe8qUly1VB8ucM=,tag:nQUDkuh0OR1cjR5hGC5jOw==,type:str]"),
"nested_map".to_string() => RopsTree::Map(indexmap! {
"nested_map".to_string() => RopsTree::Map(Self(indexmap! {
"null_key".to_string() => RopsTree::Null,
"array".to_string() => RopsTree::Sequence(vec![
leaf("ENC[AES256_GCM,data:ANbeNrGp,iv:PRWGCPdOttPr5dlzT9te7WWCZ90J7+CvfY1vp60aADM=,tag:PvSLx4pLT5zRKOU0df8Xlg==,type:str]"),
RopsTree::Map(indexmap! {
"nested_map_in_array".to_string() => RopsTree::Map(indexmap!{
RopsTree::Map(Self(indexmap! {
"nested_map_in_array".to_string() => RopsTree::Map(Self(indexmap!{
"integer".to_string() => leaf("ENC[AES256_GCM,data:qTW5qw==,iv:ugMxvR8YPwDgn2MbBpDX0lpCqzJY3GerhbA5jEKUbwE=,tag:d8utfA76C4XPzJyDfgE4Pw==,type:int]")
}),
}),
RopsTree::Map(indexmap!{
})),
})),
RopsTree::Map(Self(indexmap!{
"float".to_string() => leaf("ENC[AES256_GCM,data:/MTg0fCennyN8g==,iv:+/8+Ljm+cls7BbDYZnlg6NVFkrkw4GkEfWU2aGW57qE=,tag:26uMp2JmVAckySIaL2BLCg==,type:float]")
}),
})),
]),
}
})
),
"booleans".to_string() => RopsTree::Sequence(vec![
leaf("ENC[AES256_GCM,data:bCdz2A==,iv:8kD+h1jClyVHBj9o2WZuAkjk+uD6A2lgNpcGljpQEhk=,tag:u3/fktl5HfFrVLERVvLRGw==,type:bool]"),
@@ -209,5 +280,28 @@ mod tests {
.unwrap()
)
}

#[test]
fn encrypts_tree_with_saved_nonces() {
assert_eq!(
RopsMap::<Encrypted<AES256GCM>>::mock(),
RopsMap::<Decrypted>::mock()
.encrypt_with_saved_nonces(&DataKey::mock(), &SavedRopsTreeNonces::mock())
.unwrap()
)
}

#[test]
fn encrypts_tree_without_saving_nonces() {
let decrypted_tree_map = RopsMap::<Decrypted>::mock();
let data_key = DataKey::mock();
let encrypted_tree = decrypted_tree_map.encrypt(&data_key).unwrap();

assert_ne!(RopsMap::<Encrypted<AES256GCM>>::mock(), encrypted_tree);
assert_eq!(
RopsTree::Map(MockTestUtil::mock()),
RopsTree::Map(encrypted_tree).decrypt(&data_key).unwrap()
)
}
}
}

0 comments on commit 164105f

Please sign in to comment.