Skip to content

Commit

Permalink
Encrypted YAML RopsFileMap to RopsTree.
Browse files Browse the repository at this point in the history
  • Loading branch information
gibbz00 committed Dec 16, 2023
1 parent e998d39 commit 7777925
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 94 deletions.
5 changes: 5 additions & 0 deletions crates/lib/src/cryptography/cipher/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@ pub use core::AeadCipher;
mod aes256_gcm;
#[cfg(feature = "aes-gcm")]
pub use aes256_gcm::AES256GCM;

#[cfg(feature = "test-utils")]
mod stub_cipher;
#[cfg(feature = "test-utils")]
pub use stub_cipher::StubCipher;
23 changes: 23 additions & 0 deletions crates/lib/src/cryptography/cipher/stub_cipher.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use generic_array::typenum::U32;

use crate::*;

#[derive(Debug, PartialEq)]
pub struct StubCipher;

impl AeadCipher for StubCipher {
const NAME: &'static str = "STUB";

type NonceSize = U32;
type AuthorizationTagSize = U32;
type EncryptError = ();

fn encrypt(
_nonce: &Nonce<Self::NonceSize>,
_data_key: &DataKey,
_in_place_buffer: &mut [u8],
_associated_data: &[u8],
) -> Result<AuthorizationTag<Self>, Self::EncryptError> {
unimplemented!()
}
}
17 changes: 17 additions & 0 deletions crates/lib/src/rops_file/decrypt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use crate::*;

#[derive(Debug, thiserror::Error)]
pub enum RopsFileDecryptError {
#[error("invalid map: {0}")]
MapToTree(#[from] MapToTreeError),
}

impl<C: AeadCipher, F: FileFormat> RopsFile<Encrypted<C>, F> {
pub fn decrypt(self) -> Result<(), RopsFileDecryptError>
where
RopsTree<Encrypted<C>>: TryFrom<RopsFileMap<Encrypted<C>, F>, Error = MapToTreeError>,
{
let _tree = RopsTree::<Encrypted<C>>::try_from(self.map)?;
todo!()
}
}
86 changes: 0 additions & 86 deletions crates/lib/src/rops_file/format/yaml/decrypted_map_to_tree.rs

This file was deleted.

175 changes: 175 additions & 0 deletions crates/lib/src/rops_file/format/yaml/map_to_tree.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
use indexmap::IndexMap;
use serde_yaml::{Mapping as YamlMap, Value as YamlValue};

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>
where
F: Fn(YamlValue) -> Result<RopsTree<S>, MapToTreeError>,
{
let mut inner_map = IndexMap::default();

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

return Ok(RopsTree::Map(inner_map));

fn validate_key(yaml_value: YamlValue) -> Result<String, MapToTreeError> {
match yaml_value {
YamlValue::String(string) => Ok(string),
other => Err(MapToTreeError::NonStringKey(
serde_yaml::to_string(&other).expect("yaml value not serializable"),
)),
}
}
}
}

mod encrypted {
use super::*;

impl<C: AeadCipher> TryFrom<RopsFileMap<Encrypted<C>, YamlFileFormat>> for RopsTree<Encrypted<C>> {
type Error = MapToTreeError;

fn try_from(rops_file_map: RopsFileMap<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: AeadCipher>(yaml_value: YamlValue) -> Result<RopsTree<Encrypted<Ci>>, MapToTreeError> {
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::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(
serde_yaml::to_string(&yaml_value).expect("unable to serialize yaml value"),
));
}
})
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[cfg(feature = "aes-gcm")]
mod aes_gcm {
use super::*;

#[test]
fn transforms_encrypted_yaml_map() {
assert_eq!(
RopsTree::mock(),
RopsFileMap::<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());
assert!(matches!(
RopsTree::<Encrypted<StubCipher>>::try_from(file_map).unwrap_err(),
MapToTreeError::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());
assert!(matches!(
RopsTree::<Encrypted<StubCipher>>::try_from(file_map).unwrap_err(),
MapToTreeError::InvalidValueForEncrypted(_)
))
}

#[test]
fn dissallows_boolean_values() {
assert_disallowed_value_helper("disallowed_boolean: true")
}

#[test]
fn dissallows_integer_values() {
assert_disallowed_value_helper("disallowed_integer: 1")
}
}
}

mod decrypted {
use super::*;

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

fn try_from(rops_file_map: RopsFileMap<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> {
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::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")),
false => RopsValue::Integer(
number
.as_i64()
.ok_or_else(|| MapToTreeError::IntegerOutOfRange(number.as_u64().expect("number not an u64")))?,
),
}),
YamlValue::Sequence(sequence) => {
RopsTree::Sequence(sequence.into_iter().map(recursive_value_call).collect::<Result<Vec<_>, _>>()?)
}
YamlValue::Null => RopsTree::Null,
})
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn transforms_decrypted_yaml_map() {
assert_eq!(
RopsTree::mock(),
RopsFileMap::<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());
assert!(matches!(
RopsTree::<Decrypted>::try_from(file_map).unwrap_err(),
MapToTreeError::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());
assert!(matches!(
RopsTree::<Decrypted>::try_from(file_map).unwrap_err(),
MapToTreeError::IntegerOutOfRange(_)
))
}
}
}
4 changes: 1 addition & 3 deletions crates/lib/src/rops_file/format/yaml/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ impl FileFormat for YamlFileFormat {
}
}

mod encrypted_map_to_tree {}

mod decrypted_map_to_tree;
mod map_to_tree;

#[cfg(feature = "test-utils")]
mod mock;
Expand Down
4 changes: 3 additions & 1 deletion crates/lib/src/rops_file/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
mod core;
pub use core::RopsFile;

mod decrypt;

mod map;
pub use map::RopsFileMap;

Expand All @@ -11,7 +13,7 @@ mod value;
pub use value::*;

mod tree;
pub use tree::{DecryptedMapToTreeError, EncryptedMapToTreeError, RopsTree};
pub use tree::{MapToTreeError, RopsTree};

mod metadata;
pub use metadata::*;
Expand Down
Loading

0 comments on commit 7777925

Please sign in to comment.