Skip to content

Commit

Permalink
Introduce RopsFileBuilder for aiding in the initial rops file encry…
Browse files Browse the repository at this point in the history
…ption.
  • Loading branch information
gibbz00 committed Dec 31, 2023
1 parent 2cf6da2 commit a9e6039
Show file tree
Hide file tree
Showing 14 changed files with 233 additions and 49 deletions.
4 changes: 4 additions & 0 deletions crates/rops/src/cryptography/data_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ impl DataKey {
DataKeySize::USIZE
}

pub fn new() -> Self {
DataKey(RngKey::new())
}

pub fn empty() -> Self {
Self(RngKey::empty())
}
Expand Down
18 changes: 18 additions & 0 deletions crates/rops/src/integration/age.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ impl Integration for AgeIntegration {

Ok(Some(decrypted_data_key_buffer))
}

fn append_to_metadata(integration_metadata: &mut IntegrationMetadata, integration_metadata_unit: IntegrationMetadataUnit<Self>) {
integration_metadata.age.push(integration_metadata_unit)
}
}

mod error {
Expand All @@ -94,6 +98,16 @@ mod error {
}
}

mod key_id {
use super::*;

impl IntegrationKeyId<AgeIntegration> for age::x25519::Recipient {
fn append_to_builder<F: FileFormat>(self, rops_file_builder: &mut RopsFileBuilder<F>) {
rops_file_builder.age_key_ids.push(self)
}
}
}

pub use config::AgeConfig;
mod config {
use serde::{Deserialize, Serialize};
Expand All @@ -112,6 +126,10 @@ mod config {
impl IntegrationConfig<AgeIntegration> for AgeConfig {
const INCLUDE_DATA_KEY_CREATED_AT: bool = false;

fn new(key_id: <AgeIntegration as Integration>::KeyId) -> Self {
Self { key_id }
}

fn key_id(&self) -> &<AgeIntegration as Integration>::KeyId {
&self.key_id
}
Expand Down
4 changes: 4 additions & 0 deletions crates/rops/src/integration/aws_kms/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ pub struct AwsKmsConfig {
impl IntegrationConfig<AwsKmsIntegration> for AwsKmsConfig {
const INCLUDE_DATA_KEY_CREATED_AT: bool = true;

fn new(key_id: <AwsKmsIntegration as Integration>::KeyId) -> Self {
Self { key_id }
}

fn key_id(&self) -> &<AwsKmsIntegration as Integration>::KeyId {
&self.key_id
}
Expand Down
4 changes: 4 additions & 0 deletions crates/rops/src/integration/aws_kms/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ impl Integration for AwsKmsIntegration {
.map(Some)
.map_err(|error| IntegrationError::Decryption(error.into()))
}

fn append_to_metadata(integration_metadata: &mut IntegrationMetadata, integration_metadata_unit: IntegrationMetadataUnit<Self>) {
integration_metadata.kms.push(integration_metadata_unit)
}
}

fn tokio_blocking<O>(future: impl Future<Output = O>) -> O {
Expand Down
6 changes: 6 additions & 0 deletions crates/rops/src/integration/aws_kms/key_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ impl FromStr for AwsKeyId {
}
}

impl IntegrationKeyId<AwsKmsIntegration> for AwsKeyId {
fn append_to_builder<F: FileFormat>(self, rops_file_builder: &mut RopsFileBuilder<F>) {
rops_file_builder.aws_kms_key_ids.push(self)
}
}

#[cfg(feature = "test-utils")]
mod mock {
use super::*;
Expand Down
6 changes: 5 additions & 1 deletion crates/rops/src/integration/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const ROPS_APPLICATION_NAME: &str = "rops";

pub trait Integration: Sized {
const NAME: &'static str;
type KeyId;
type KeyId: IntegrationKeyId<Self>;
type PrivateKey;
type Config: IntegrationConfig<Self>;

Expand Down Expand Up @@ -66,11 +66,15 @@ pub trait Integration: Sized {
fn encrypt_data_key(key_id: &Self::KeyId, data_key: &DataKey) -> IntegrationResult<String>;

fn decrypt_data_key(key_id: &Self::KeyId, encrypted_data_key: &str) -> IntegrationResult<Option<DataKey>>;

fn append_to_metadata(integration_metadata: &mut IntegrationMetadata, integration_metadata_unit: IntegrationMetadataUnit<Self>);
}

pub trait IntegrationConfig<I: Integration>: Debug + PartialEq {
const INCLUDE_DATA_KEY_CREATED_AT: bool;

fn new(key_id: I::KeyId) -> Self;

fn key_id(&self) -> &I::KeyId;
}

Expand Down
5 changes: 5 additions & 0 deletions crates/rops/src/integration/key_id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use crate::*;

pub trait IntegrationKeyId<I: Integration> {
fn append_to_builder<F: FileFormat>(self, rops_file_builder: &mut RopsFileBuilder<F>);
}
3 changes: 3 additions & 0 deletions crates/rops/src/integration/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
mod core;
pub use core::{Integration, IntegrationConfig};

mod key_id;
pub use key_id::IntegrationKeyId;

mod error;
pub use error::{IntegrationError, IntegrationResult};

Expand Down
18 changes: 16 additions & 2 deletions crates/rops/src/integration/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ mod stub_integration {

impl Integration for StubIntegration {
const NAME: &'static str = "stub";
type KeyId = ();
type KeyId = String;
type PrivateKey = String;
type Config = StubIntegrationConfig;

Expand All @@ -48,6 +48,16 @@ mod stub_integration {
fn decrypt_data_key(_key_id: &Self::KeyId, _encrypted_data_key: &str) -> IntegrationResult<Option<DataKey>> {
unimplemented!()
}

fn append_to_metadata(_integration_metadata: &mut IntegrationMetadata, _integration_metadata_unit: IntegrationMetadataUnit<Self>) {
unimplemented!()
}
}

impl IntegrationKeyId<StubIntegration> for String {
fn append_to_builder<F: FileFormat>(self, _rops_file_builder: &mut RopsFileBuilder<F>) {
unimplemented!()
}
}

#[derive(Debug, PartialEq)]
Expand All @@ -56,8 +66,12 @@ mod stub_integration {
impl IntegrationConfig<StubIntegration> for StubIntegrationConfig {
const INCLUDE_DATA_KEY_CREATED_AT: bool = false;

fn new(key_id: <StubIntegration as Integration>::KeyId) -> Self {
Self(key_id)
}

fn key_id(&self) -> &<StubIntegration as Integration>::KeyId {
&()
&self.0
}
}
}
96 changes: 96 additions & 0 deletions crates/rops/src/rops_file/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use crate::*;

pub struct RopsFileBuilder<F: FileFormat> {
plaintext_map: F::Map,
partial_encryption: Option<PartialEncryptionConfig>,
mac_only_encrypted: Option<bool>,
#[cfg(feature = "age")]
pub(crate) age_key_ids: Vec<<AgeIntegration as Integration>::KeyId>,
#[cfg(feature = "aws-kms")]
pub(crate) aws_kms_key_ids: Vec<<AwsKmsIntegration as Integration>::KeyId>,
}

impl<F: FileFormat> RopsFileBuilder<F> {
pub fn new(plaintext_map: F::Map) -> Self {
Self {
plaintext_map,
partial_encryption: None,
mac_only_encrypted: None,
age_key_ids: Vec::new(),
aws_kms_key_ids: Vec::new(),
}
}

pub fn with_partial_encryption(mut self, partial_encryption: PartialEncryptionConfig) -> Self {
self.partial_encryption = Some(partial_encryption);
self
}

pub fn mac_only_encrypted(mut self) -> Self {
self.mac_only_encrypted = Some(true);
self
}

pub fn add_integration_key<I: Integration>(mut self, key_id: I::KeyId) -> Self {
key_id.append_to_builder(&mut self);
self
}

pub fn encrypt<C: Cipher, H: Hasher>(self) -> Result<RopsFile<EncryptedFile<C, H>, F>, RopsFileEncryptError> {
#[rustfmt::skip]
let Self { plaintext_map, partial_encryption, mac_only_encrypted, age_key_ids, aws_kms_key_ids } = self;

let data_key = DataKey::new();

let decrypted_map = plaintext_map.decrypted_to_internal()?;

let mac = Mac::<H>::compute(
MacOnlyEncryptedConfig::new(mac_only_encrypted, partial_encryption.as_ref()),
&decrypted_map,
);

let encrypted_map_result = decrypted_map.encrypt(&data_key, partial_encryption.as_ref());

let mut integration_metadata = IntegrationMetadata::default();
#[cfg(feature = "age")]
integration_metadata.add_integration_keys::<AgeIntegration>(age_key_ids, &data_key)?;
#[cfg(feature = "aws-kms")]
integration_metadata.add_integration_keys::<AwsKmsIntegration>(aws_kms_key_ids, &data_key)?;

let encrypted_metadata_result = RopsFileMetadata {
intregation: integration_metadata,
last_modified: LastModifiedDateTime::now(),
mac,
partial_encryption,
mac_only_encrypted,
}
.encrypt(&data_key);

RopsFile::from_parts_results(encrypted_map_result, encrypted_metadata_result)
}
}

// Redundant to test combinations of file formats, integrations, ciphers and hashers if the
// respective trait implementations are well tested.
#[cfg(all(test, feature = "yaml", feature = "age", feature = "aes-gcm", feature = "sha2"))]
mod tests {
use super::*;

#[test]
fn encrypts_with_builder() {
AgeIntegration::set_mock_private_key_env_var();

let builder_rops_file =
RopsFileBuilder::<YamlFileFormat>::new(RopsFileFormatMap::<DecryptedMap, YamlFileFormat>::mock().into_inner_map())
.with_partial_encryption(MockTestUtil::mock())
.mac_only_encrypted()
.add_integration_key::<AgeIntegration>(AgeIntegration::mock_key_id())
.encrypt::<AES256GCM, SHA512>()
.unwrap()
.decrypt::<YamlFileFormat>()
.unwrap();

assert_eq!(RopsFileFormatMap::mock(), builder_rops_file.map);
assert_ne!(RopsFileMetadata::mock(), builder_rops_file.metadata);
}
}
56 changes: 19 additions & 37 deletions crates/rops/src/rops_file/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,6 @@ where
pub metadata: RopsFileMetadata<S::MetadataState>,
}

#[derive(Debug, Error)]
pub enum RopsFileEncryptError {
#[error("invalid decrypted map format: {0}")]
FormatToIntenrnalMap(#[from] FormatToInternalMapError),
#[error("unable to retrieve data key: {0}")]
DataKeyRetrieval(#[from] RopsFileMetadataDataKeyRetrievalError),
#[error("unable to encrypt map: {0}")]
MapEncryption(anyhow::Error),
#[error("unable to encrypt metadata: {0}")]
MetadataEncryption(anyhow::Error),
}

#[derive(Debug, Error)]
pub enum RopsFileDecryptError {
#[error("invalid encrypted map format; {0}")]
FormatToIntenrnalMap(#[from] FormatToInternalMapError),
#[error("unable to decrypt map value: {0}")]
DecryptValue(#[from] DecryptRopsValueError),
#[error("unable to decrypt file metadata")]
Metadata(#[from] RopsFileMetadataDecryptError),
#[error("invalid MAC, computed {0}, stored {0}")]
MacMismatch(String, String),
}

impl<S: RopsFileState, F: FileFormat> RopsFile<S, F>
where
<<S::MetadataState as RopsMetadataState>::Mac as FromStr>::Err: Display,
Expand Down Expand Up @@ -88,7 +64,7 @@ impl<H: Hasher, F: FileFormat> RopsFile<DecryptedFile<H>, F> {
.to_internal()?
.encrypt::<C>(&data_key, self.metadata.partial_encryption.as_ref());
let encrypted_metadata = self.metadata.encrypt::<C>(&data_key);
Self::file_from_parts_results(encrypted_map, encrypted_metadata)
RopsFile::from_parts_results(encrypted_map, encrypted_metadata)
}

pub fn encrypt_with_saved_parameters<C: Cipher, Fo: FileFormat>(
Expand All @@ -104,16 +80,7 @@ impl<H: Hasher, F: FileFormat> RopsFile<DecryptedFile<H>, F> {
.encrypt_with_saved_nonces(&data_key, self.metadata.partial_encryption.as_ref(), &saved_map_nonces);

let encrypted_metadata = self.metadata.encrypt_with_saved_mac_nonce::<C>(&data_key, saved_mac_nonce);
Self::file_from_parts_results(encrypted_map, encrypted_metadata)
}

fn file_from_parts_results<C: Cipher, Fo: FileFormat>(
encrypted_map_result: Result<RopsMap<EncryptedMap<C>>, C::Error>,
encrypted_metadata_result: Result<RopsFileMetadata<EncryptedMetadata<C, H>>, C::Error>,
) -> Result<RopsFile<EncryptedFile<C, H>, Fo>, RopsFileEncryptError> {
let encrypted_map = encrypted_map_result.map_err(|error| RopsFileEncryptError::MetadataEncryption(error.into()))?;
let encrypted_metadata = encrypted_metadata_result.map_err(|error| RopsFileEncryptError::MetadataEncryption(error.into()))?;
Ok(RopsFile::new(encrypted_map, encrypted_metadata))
RopsFile::from_parts_results(encrypted_map, encrypted_metadata)
}
}

Expand Down Expand Up @@ -154,17 +121,32 @@ impl<C: Cipher, F: FileFormat, H: Hasher> RopsFile<EncryptedFile<C, H>, F> {
decrypted_map: &RopsMap<DecryptedMap>,
decrypted_metadata: &RopsFileMetadata<DecryptedMetadata<H>>,
) -> Result<(), RopsFileDecryptError> {
let computed_mac = Mac::<H>::compute(MacOnlyEncryptedConfig::new(decrypted_metadata), decrypted_map);
let computed_mac = Mac::<H>::compute(
MacOnlyEncryptedConfig::new(
decrypted_metadata.mac_only_encrypted,
decrypted_metadata.partial_encryption.as_ref(),
),
decrypted_map,
);
let stored_mac = &decrypted_metadata.mac;

match &computed_mac != stored_mac {
true => Err(RopsFileDecryptError::MacMismatch(computed_mac.to_string(), stored_mac.to_string())),
false => Ok(()),
}
}

pub(crate) fn from_parts_results(
encrypted_map_result: Result<RopsMap<EncryptedMap<C>>, C::Error>,
encrypted_metadata_result: Result<RopsFileMetadata<EncryptedMetadata<C, H>>, C::Error>,
) -> Result<Self, RopsFileEncryptError> {
let encrypted_map = encrypted_map_result.map_err(|error| RopsFileEncryptError::MetadataEncryption(error.into()))?;
let encrypted_metadata = encrypted_metadata_result.map_err(|error| RopsFileEncryptError::MetadataEncryption(error.into()))?;
Ok(RopsFile::new(encrypted_map, encrypted_metadata))
}
}

// Reduntant to test combinations of file formats, integrations, ciphers and hashers if the
// Redundant to test combinations of file formats, integrations, ciphers and hashers if the
// respective trait implementations are well tested.
#[cfg(all(test, feature = "yaml", feature = "age", feature = "aes-gcm", feature = "sha2"))]
mod tests {
Expand Down
10 changes: 10 additions & 0 deletions crates/rops/src/rops_file/metadata/integration/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ pub struct IntegrationMetadata {
}

impl IntegrationMetadata {
pub fn add_integration_keys<I: Integration>(&mut self, key_ids: Vec<I::KeyId>, data_key: &DataKey) -> IntegrationResult<()> {
key_ids
.into_iter()
.map(|key_id| I::Config::new(key_id))
.map(|integration_config| IntegrationMetadataUnit::<I>::new(integration_config, data_key))
.try_for_each(|integation_metada_unit_result| {
integation_metada_unit_result.map(|integration_metadata| I::append_to_metadata(self, integration_metadata))
})
}

pub fn find_data_key(&self) -> IntegrationResult<Option<DataKey>> {
// In order of what is assumed to be quickest:

Expand Down
14 changes: 6 additions & 8 deletions crates/rops/src/rops_file/metadata/mac/mac_only_encrypted.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::{fmt::Display, str::FromStr};

use crate::*;

#[derive(Clone, Copy)]
Expand All @@ -9,13 +7,13 @@ pub struct MacOnlyEncryptedConfig<'a> {
}

impl MacOnlyEncryptedConfig<'_> {
pub fn new<'a, S: RopsMetadataState>(metadata: &'a RopsFileMetadata<S>) -> MacOnlyEncryptedConfig<'a>
where
<S::Mac as FromStr>::Err: Display,
{
pub fn new<'a>(
mac_only_encrypted: Option<bool>,
partial_encryption: Option<&'a PartialEncryptionConfig>,
) -> MacOnlyEncryptedConfig<'a> {
MacOnlyEncryptedConfig::<'a> {
mac_only_encrypted: metadata.mac_only_encrypted.unwrap_or_default(),
resolved_partial_encryption: metadata.partial_encryption.as_ref().into(),
mac_only_encrypted: mac_only_encrypted.unwrap_or_default(),
resolved_partial_encryption: partial_encryption.into(),
}
}
}
Expand Down
Loading

0 comments on commit a9e6039

Please sign in to comment.