diff --git a/crates/lib/src/cryptography/cipher/core.rs b/crates/lib/src/cryptography/cipher/core.rs index 4654ec1..a914c39 100644 --- a/crates/lib/src/cryptography/cipher/core.rs +++ b/crates/lib/src/cryptography/cipher/core.rs @@ -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 + Debug + PartialEq; diff --git a/crates/lib/src/rops_file/core.rs b/crates/lib/src/rops_file/core.rs index cef92f1..d3df570 100644 --- a/crates/lib/src/rops_file/core.rs +++ b/crates/lib/src/rops_file/core.rs @@ -6,7 +6,7 @@ use crate::*; #[serde(bound = "F: FileFormat")] pub struct RopsFile { #[serde(flatten)] - pub map: RopsFileMap, + pub map: RopsFileFormatMap, #[serde(rename = "sops")] pub metadata: RopsFileMetadata, } @@ -17,7 +17,7 @@ mod mock { impl MockTestUtil for RopsFile where - RopsFileMap: MockTestUtil, + RopsFileFormatMap: MockTestUtil, { fn mock() -> Self { Self { diff --git a/crates/lib/src/rops_file/format/map.rs b/crates/lib/src/rops_file/format/map.rs new file mode 100644 index 0000000..a2d5c4e --- /dev/null +++ b/crates/lib/src/rops_file/format/map.rs @@ -0,0 +1,43 @@ +use std::marker::PhantomData; + +use serde::{Deserialize, Serialize}; + +use crate::*; + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[serde(transparent)] +pub struct RopsFileFormatMap { + #[serde(flatten)] + inner: F::Map, + #[serde(skip)] + state_marker: PhantomData, +} + +// 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 RopsFileFormatMap { + 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, + } + } +} diff --git a/crates/lib/src/rops_file/format/mod.rs b/crates/lib/src/rops_file/format/mod.rs index 594efc8..3762667 100644 --- a/crates/lib/src/rops_file/format/mod.rs +++ b/crates/lib/src/rops_file/format/mod.rs @@ -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")] diff --git a/crates/lib/src/rops_file/format/yaml/mock.rs b/crates/lib/src/rops_file/format/yaml/mock.rs index 762f0bf..47ecc9f 100644 --- a/crates/lib/src/rops_file/format/yaml/mock.rs +++ b/crates/lib/src/rops_file/format/yaml/mock.rs @@ -3,14 +3,14 @@ mod rops_file { impl MockFileFormatUtil for RopsFile where - RopsFileMap: MockFileFormatUtil, + RopsFileFormatMap: MockFileFormatUtil, { 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 for RopsFileMap { + impl MockFileFormatUtil for RopsFileFormatMap { fn mock_format_display() -> String { indoc::indoc! {" hello: world! @@ -40,7 +40,7 @@ mod map { } #[cfg(feature = "aes-gcm")] - impl MockFileFormatUtil for RopsFileMap, YamlFileFormat> { + impl MockFileFormatUtil for RopsFileFormatMap, 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 MockTestUtil for RopsFileMap + impl MockTestUtil for RopsFileFormatMap where Self: MockFileFormatUtil, { diff --git a/crates/lib/src/rops_file/format/yaml/mod.rs b/crates/lib/src/rops_file/format/yaml/mod.rs index 8859019..42ba12c 100644 --- a/crates/lib/src/rops_file/format/yaml/mod.rs +++ b/crates/lib/src/rops_file/format/yaml/mod.rs @@ -17,7 +17,7 @@ impl FileFormat for YamlFileFormat { } } -mod map_to_tree; +mod to_internal_map; #[cfg(feature = "test-utils")] mod mock; diff --git a/crates/lib/src/rops_file/format/yaml/map_to_tree.rs b/crates/lib/src/rops_file/format/yaml/to_internal_map.rs similarity index 60% rename from crates/lib/src/rops_file/format/yaml/map_to_tree.rs rename to crates/lib/src/rops_file/format/yaml/to_internal_map.rs index 8abe004..292fed6 100644 --- a/crates/lib/src/rops_file/format/yaml/map_to_tree.rs +++ b/crates/lib/src/rops_file/format/yaml/to_internal_map.rs @@ -6,22 +6,22 @@ use crate::*; mod tree_traversal { use super::*; - pub fn recursive_map_call(yaml_map: YamlMap, recursive_value_fn: F) -> Result, MapToTreeError> + pub fn recursive_map_call(yaml_map: YamlMap, recursive_value_fn: F) -> Result, FormatToInternalMapError> where - F: Fn(YamlValue) -> Result, MapToTreeError>, + F: Fn(YamlValue) -> Result, 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 { + fn validate_key(yaml_value: YamlValue) -> Result { 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,16 +32,16 @@ mod tree_traversal { mod encrypted { use super::*; - impl TryFrom, YamlFileFormat>> for RopsTree> { - type Error = MapToTreeError; + impl TryFrom, YamlFileFormat>> for RopsMap> { + type Error = FormatToInternalMapError; - fn try_from(rops_file_map: RopsFileMap, YamlFileFormat>) -> Result { + fn try_from(rops_file_map: RopsFileFormatMap, YamlFileFormat>) -> Result { return tree_traversal::recursive_map_call(rops_file_map.into_inner_map(), recursive_value_call); - fn recursive_value_call(yaml_value: YamlValue) -> Result>, MapToTreeError> { + fn recursive_value_call(yaml_value: YamlValue) -> Result>, 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::, _>>()?) @@ -49,7 +49,7 @@ mod encrypted { 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,18 +69,20 @@ mod encrypted { #[test] fn transforms_encrypted_yaml_map() { assert_eq!( - RopsTree::mock(), - RopsFileMap::, YamlFileFormat>::mock().try_into().unwrap() + RopsMap::mock(), + RopsFileFormatMap::, YamlFileFormat>::mock() + .try_into() + .unwrap() ) } } #[test] fn dissallows_non_string_keys() { - let file_map = RopsFileMap::from_inner_map(serde_yaml::from_str::("true: xxx").unwrap()); + let file_map = RopsFileFormatMap::from_inner_map(serde_yaml::from_str::("true: xxx").unwrap()); assert!(matches!( - RopsTree::>::try_from(file_map).unwrap_err(), - MapToTreeError::NonStringKey(_) + RopsMap::>::try_from(file_map).unwrap_err(), + FormatToInternalMapError::NonStringKey(_) )) } @@ -88,10 +90,10 @@ mod encrypted { 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::(key_value_str).unwrap()); + let file_map = RopsFileFormatMap::from_inner_map(serde_yaml::from_str::(key_value_str).unwrap()); assert!(matches!( - RopsTree::>::try_from(file_map).unwrap_err(), - MapToTreeError::InvalidValueForEncrypted(_) + RopsMap::>::try_from(file_map).unwrap_err(), + FormatToInternalMapError::InvalidValueForEncrypted(_) )) } @@ -110,19 +112,19 @@ mod encrypted { mod decrypted { use super::*; - impl TryFrom> for RopsTree { - type Error = MapToTreeError; + impl TryFrom> for RopsMap { + type Error = FormatToInternalMapError; - fn try_from(rops_file_map: RopsFileMap) -> Result { + fn try_from(rops_file_map: RopsFileFormatMap) -> Result { return tree_traversal::recursive_map_call(rops_file_map.into_inner_map(), recursive_value_call); - fn recursive_value_call(yaml_value: YamlValue) -> Result, MapToTreeError> { + fn recursive_value_call(yaml_value: YamlValue) -> Result, 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() { @@ -130,7 +132,7 @@ mod decrypted { 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::::mock().try_into().unwrap() + RopsMap::mock(), + RopsFileFormatMap::::mock().try_into().unwrap() ) } #[test] fn dissallows_non_string_keys() { - let file_map = RopsFileMap::from_inner_map(serde_yaml::from_str::("123: 456").unwrap()); + let file_map = RopsFileFormatMap::from_inner_map(serde_yaml::from_str::("123: 456").unwrap()); assert!(matches!( - RopsTree::::try_from(file_map).unwrap_err(), - MapToTreeError::NonStringKey(_) + RopsMap::::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::(&format!("invalid_integer: {}", u64::MAX)).unwrap()); + let file_map = + RopsFileFormatMap::from_inner_map(serde_yaml::from_str::(&format!("invalid_integer: {}", u64::MAX)).unwrap()); assert!(matches!( - RopsTree::::try_from(file_map).unwrap_err(), - MapToTreeError::IntegerOutOfRange(_) + RopsMap::::try_from(file_map).unwrap_err(), + FormatToInternalMapError::IntegerOutOfRange(_) )) } } diff --git a/crates/lib/src/rops_file/map.rs b/crates/lib/src/rops_file/map.rs deleted file mode 100644 index 7082849..0000000 --- a/crates/lib/src/rops_file/map.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::marker::PhantomData; - -use serde::{Deserialize, Serialize}; - -use crate::*; - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[serde(transparent)] -pub struct RopsFileMap { - #[serde(flatten)] - inner: F::Map, - #[serde(skip)] - state_marker: PhantomData, -} - -impl RopsFileMap { - 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, - } - } -} diff --git a/crates/lib/src/rops_file/mod.rs b/crates/lib/src/rops_file/mod.rs index a3e221b..3557289 100644 --- a/crates/lib/src/rops_file/mod.rs +++ b/crates/lib/src/rops_file/mod.rs @@ -1,9 +1,6 @@ mod core; pub use core::RopsFile; -mod map; -pub use map::RopsFileMap; - mod key_path; pub use key_path::KeyPath; @@ -11,7 +8,7 @@ mod value; pub use value::*; mod tree; -pub use tree::{MapToTreeError, RopsTree, SavedRopsTreeNonces}; +pub use tree::{RopsMap, RopsTree, SavedRopsTreeNonces}; mod metadata; pub use metadata::*; diff --git a/crates/lib/src/rops_file/state.rs b/crates/lib/src/rops_file/state.rs index cc7d46f..d75871e 100644 --- a/crates/lib/src/rops_file/state.rs +++ b/crates/lib/src/rops_file/state.rs @@ -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)] diff --git a/crates/lib/src/rops_file/tree.rs b/crates/lib/src/rops_file/tree.rs index b90e284..ad33e62 100644 --- a/crates/lib/src/rops_file/tree.rs +++ b/crates/lib/src/rops_file/tree.rs @@ -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(indexmap::IndexMap>); + #[derive(Debug, PartialEq)] pub enum RopsTree { Sequence(Vec>), - Map(indexmap::IndexMap>), + Map(RopsMap), 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(HashMap<(KeyPath, RopsValue), Nonce>); +#[allow(clippy::complexity)] +pub struct SavedRopsTreeNonces(HashMap<(Cow<'static, KeyPath>, Cow<'static, RopsValue>), Nonce>); + +impl RopsMap { + pub fn encrypt(self, data_key: &DataKey) -> Result>, C::Error> { + Self::encrypt_recursive_impl(self, data_key, &None) + } + + pub fn encrypt_with_saved_nonces( + self, + data_key: &DataKey, + saved_nonces: &SavedRopsTreeNonces, + ) -> Result>, C::Error> { + Self::encrypt_recursive_impl(self, data_key, &Some(saved_nonces)) + } + + fn encrypt_recursive_impl( + self, + data_key: &DataKey, + saved_nonces: &Option<&SavedRopsTreeNonces>, + ) -> Result>, C::Error> { + return encrypt_map_inner(self, data_key, &KeyPath::default(), saved_nonces); + + fn encrypt_map_inner( + decrypted_map: RopsMap, + data_key: &DataKey, + key_path: &KeyPath, + optional_saved_nonces: &Option<&SavedRopsTreeNonces>, + ) -> Result>, 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( + decrypted_tree: RopsTree, + data_key: &DataKey, + key_path: &KeyPath, + optional_saved_nonces: &Option<&SavedRopsTreeNonces>, + ) -> Result>, 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::, _>>()?, + ), + 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 RopsTree> { pub fn decrypt(self, data_key: &DataKey) -> Result, DecryptRopsValueError> { @@ -59,7 +114,7 @@ impl RopsTree> { 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, @@ -67,14 +122,14 @@ impl RopsTree> { ); } - 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 { fn mock() -> Self { - Self::Map(indexmap! { + Self::Map(MockTestUtil::mock()) + } + } + + impl MockTestUtil for RopsTree> + where + RopsMap>: MockTestUtil, + { + fn mock() -> Self { + Self::Map(MockTestUtil::mock()) + } + } + + impl MockTestUtil for RopsMap { + 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> { + impl MockTestUtil for RopsMap> { 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::>::mock(), + RopsMap::::mock() + .encrypt_with_saved_nonces(&DataKey::mock(), &SavedRopsTreeNonces::mock()) + .unwrap() + ) + } + + #[test] + fn encrypts_tree_without_saving_nonces() { + let decrypted_tree_map = RopsMap::::mock(); + let data_key = DataKey::mock(); + let encrypted_tree = decrypted_tree_map.encrypt(&data_key).unwrap(); + + assert_ne!(RopsMap::>::mock(), encrypted_tree); + assert_eq!( + RopsTree::Map(MockTestUtil::mock()), + RopsTree::Map(encrypted_tree).decrypt(&data_key).unwrap() + ) + } } }