Skip to content

Commit

Permalink
Implement ROPS file encryption.
Browse files Browse the repository at this point in the history
  • Loading branch information
gibbz00 committed Dec 24, 2023
1 parent 617fcbd commit 83b4d25
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 41 deletions.
73 changes: 70 additions & 3 deletions crates/lib/src/rops_file/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ where
pub metadata: RopsFileMetadata<S::MetadataState>,
}

#[derive(Debug, thiserror::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(String),
#[error("unable to encrypt metadata: {0}")]
MetadataEncryption(String),
}

#[derive(Debug, thiserror::Error)]
pub enum RopsFileDecryptError {
#[error("invalid encrypted map format; {0}")]
Expand All @@ -38,6 +50,54 @@ where
}
}

impl<H: Hasher, F: FileFormat> RopsFile<DecryptedFile<H>, F> {
pub fn encrypt<C: Cipher, Fo: FileFormat>(self) -> Result<RopsFile<EncryptedFile<C, H>, Fo>, RopsFileEncryptError>
where
RopsFileFormatMap<DecryptedMap, F>: TryInto<RopsMap<DecryptedMap>, Error = FormatToInternalMapError>,
RopsMap<EncryptedMap<C>>: Into<Fo::Map>,
{
let data_key = self.metadata.retrieve_data_key()?;

let encrypted_map = self
.map
.try_into()?
.encrypt::<C>(&data_key)
.map_err(|error| RopsFileEncryptError::MapEncryption(error.to_string()))?;

let encrypted_metadata = self
.metadata
.encrypt::<C>(&data_key)
.map_err(|error| RopsFileEncryptError::MetadataEncryption(error.to_string()))?;

Ok(RopsFile::new(encrypted_map, encrypted_metadata))
}

pub fn encrypt_with_saved_parameters<C: Cipher, Fo: FileFormat>(
self,
saved_parameters: SavedParameters<C, H>,
) -> Result<RopsFile<EncryptedFile<C, H>, Fo>, RopsFileEncryptError>
where
RopsFileFormatMap<DecryptedMap, F>: TryInto<RopsMap<DecryptedMap>, Error = FormatToInternalMapError>,
RopsMap<EncryptedMap<C>>: Into<Fo::Map>,
{
#[rustfmt::skip]
let SavedParameters { data_key, saved_map_nonces, saved_mac_nonce } = saved_parameters;

let encrypted_map = self
.map
.try_into()?
.encrypt_with_saved_nonces(&data_key, &saved_map_nonces)
.map_err(|error| RopsFileEncryptError::MetadataEncryption(error.to_string()))?;

let encrypted_metadata = self
.metadata
.encrypt_with_saved_mac_nonce::<C>(&data_key, saved_mac_nonce)
.map_err(|error| RopsFileEncryptError::MetadataEncryption(error.to_string()))?;

Ok(RopsFile::new(encrypted_map, encrypted_metadata))
}
}

impl<C: Cipher, F: FileFormat, H: Hasher> RopsFile<EncryptedFile<C, H>, F> {
pub fn decrypt<Fo: FileFormat>(self) -> Result<RopsFile<DecryptedFile<H>, Fo>, RopsFileDecryptError>
where
Expand All @@ -53,9 +113,9 @@ impl<C: Cipher, F: FileFormat, H: Hasher> RopsFile<EncryptedFile<C, H>, F> {
}

#[allow(clippy::type_complexity)]
pub fn decrypt_and_save_nonces<Fo: FileFormat>(
pub fn decrypt_and_save_parameters<Fo: FileFormat>(
self,
) -> Result<(RopsFile<DecryptedFile<H>, Fo>, SavedRopsMapNonces<C>, SavedMacNonce<C, H>), RopsFileDecryptError>
) -> Result<(RopsFile<DecryptedFile<H>, Fo>, SavedParameters<C, H>), RopsFileDecryptError>
where
RopsFileFormatMap<EncryptedMap<C>, F>: TryInto<RopsMap<EncryptedMap<C>>, Error = FormatToInternalMapError>,
RopsMap<DecryptedMap>: Into<Fo::Map>,
Expand All @@ -65,7 +125,14 @@ impl<C: Cipher, F: FileFormat, H: Hasher> RopsFile<EncryptedFile<C, H>, F> {

Self::validate_mac(&decrypted_map, &decrypted_metadata.mac)?;

Ok((RopsFile::new(decrypted_map, decrypted_metadata), saved_map_nonces, saved_mac_nonce))
Ok((
RopsFile::new(decrypted_map, decrypted_metadata),
SavedParameters {
data_key,
saved_map_nonces,
saved_mac_nonce,
},
))
}

fn validate_mac(decrypted_map: &RopsMap<DecryptedMap>, stored_mac: &Mac<H>) -> Result<(), RopsFileDecryptError> {
Expand Down
32 changes: 29 additions & 3 deletions crates/lib/src/rops_file/format/yaml/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,32 @@ mod rops_file {
FileFormatTestUtils::assert_deserialization::<YamlFileFormat, EncryptedRopsFile>()
}

#[test]
fn encrypts_rops_file() {
IntegrationsHelper::set_private_keys();

assert_eq!(
DecryptedRopsFile::mock(),
DecryptedRopsFile::mock()
.encrypt::<AES256GCM, YamlFileFormat>()
.unwrap()
.decrypt()
.unwrap()
)
}

#[test]
fn encrypts_rops_file_with_saved_parameters() {
IntegrationsHelper::set_private_keys();

assert_eq!(
EncryptedRopsFile::mock(),
DecryptedRopsFile::mock()
.encrypt_with_saved_parameters(SavedParameters::mock())
.unwrap()
)
}

#[test]
fn decrypts_rops_file() {
IntegrationsHelper::set_private_keys();
Expand All @@ -36,12 +62,12 @@ mod rops_file {
}

#[test]
fn decrypts_rops_file_and_saves_nonces() {
fn decrypts_rops_file_and_saves_parameters() {
IntegrationsHelper::set_private_keys();

assert_eq!(
(DecryptedRopsFile::mock(), SavedRopsMapNonces::mock(), SavedMacNonce::mock()),
EncryptedRopsFile::mock().decrypt_and_save_nonces().unwrap()
(DecryptedRopsFile::mock(), SavedParameters::mock()),
EncryptedRopsFile::mock().decrypt_and_save_parameters().unwrap()
)
}

Expand Down
79 changes: 45 additions & 34 deletions crates/lib/src/rops_file/metadata/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,56 @@ where
pub enum RopsFileMetadataDecryptError {
#[error("unable to decrypt MAC: {0}")]
Mac(String),
#[error("integration returned error during data key retrieval; {0}")]
#[error("unable to retrieve data key: {0}")]
DataKeyRetrieval(#[from] RopsFileMetadataDataKeyRetrievalError),
}

#[derive(Debug, thiserror::Error)]
pub enum RopsFileMetadataDataKeyRetrievalError {
#[error("integration error; {0}")]
Integration(#[from] IntegrationError),
#[error("no data key found in metadata, make sure at least one integration is used")]
MissingDataKey,
}

impl<S: RopsMetadataState> RopsFileMetadata<S>
where
<S::Mac as FromStr>::Err: Display,
{
pub(crate) fn retrieve_data_key(&self) -> Result<DataKey, RopsFileMetadataDataKeyRetrievalError> {
match self.find_data_key()? {
Some(data_key) => Ok(data_key),
None => Err(RopsFileMetadataDataKeyRetrievalError::MissingDataKey),
}
}

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

#[cfg(feature = "age")]
if let Some(data_key) = self.data_key_from_age()? {
return Ok(Some(data_key));
}

Ok(None)
}

#[cfg(feature = "age")]
fn data_key_from_age(&self) -> IntegrationResult<Option<DataKey>> {
let private_keys = AgeIntegration::retrieve_private_keys()?;

for age_metadata in &self.age {
for private_key in &private_keys {
if private_key.to_public() == age_metadata.public_key {
return AgeIntegration::decrypt_data_key(private_key, &age_metadata.encrypted_data_key).map(Some);
}
}
}

Ok(None)
}
}

impl<C: Cipher, H: Hasher> RopsFileMetadata<EncryptedMetadata<C, H>> {
pub fn decrypt(self) -> Result<(RopsFileMetadata<DecryptedMetadata<H>>, DataKey), RopsFileMetadataDecryptError> {
let data_key = self.retrieve_data_key()?;
Expand Down Expand Up @@ -71,39 +115,6 @@ impl<C: Cipher, H: Hasher> RopsFileMetadata<EncryptedMetadata<C, H>> {

Ok((decrypted_metadata, data_key, saved_mac_nonce))
}

fn retrieve_data_key(&self) -> Result<DataKey, RopsFileMetadataDecryptError> {
match self.find_data_key()? {
Some(data_key) => Ok(data_key),
None => Err(RopsFileMetadataDecryptError::MissingDataKey),
}
}

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

#[cfg(feature = "age")]
if let Some(data_key) = self.data_key_from_age()? {
return Ok(Some(data_key));
}

Ok(None)
}

#[cfg(feature = "age")]
fn data_key_from_age(&self) -> IntegrationResult<Option<DataKey>> {
let private_keys = AgeIntegration::retrieve_private_keys()?;

for age_metadata in &self.age {
for private_key in &private_keys {
if private_key.to_public() == age_metadata.public_key {
return AgeIntegration::decrypt_data_key(private_key, &age_metadata.encrypted_data_key).map(Some);
}
}
}

Ok(None)
}
}

impl<H: Hasher> RopsFileMetadata<DecryptedMetadata<H>> {
Expand Down
2 changes: 1 addition & 1 deletion crates/lib/src/rops_file/metadata/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mod core;
pub use core::{RopsFileMetadata, RopsFileMetadataDecryptError};
pub use core::{RopsFileMetadata, RopsFileMetadataDataKeyRetrievalError, RopsFileMetadataDecryptError};

mod last_modified;
pub use last_modified::LastModifiedDateTime;
Expand Down
3 changes: 3 additions & 0 deletions crates/lib/src/rops_file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,8 @@ pub use format::*;
mod mac;
pub use mac::{EncryptedMac, Mac, SavedMacNonce};

mod saved_parameters;
pub use saved_parameters::SavedParameters;

#[cfg(feature = "test-utils")]
mod mock;
29 changes: 29 additions & 0 deletions crates/lib/src/rops_file/saved_parameters.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use crate::*;

// TODO: zeroize?
#[derive(Debug)]
#[impl_tools::autoimpl(PartialEq)]
pub struct SavedParameters<C: Cipher, H: Hasher> {
pub(crate) data_key: DataKey,
pub(crate) saved_map_nonces: SavedRopsMapNonces<C>,
pub(crate) saved_mac_nonce: SavedMacNonce<C, H>,
}

#[cfg(feature = "test-utils")]
mod mock {
use super::*;

impl<C: Cipher, H: Hasher> MockTestUtil for SavedParameters<C, H>
where
SavedRopsMapNonces<C>: MockTestUtil,
SavedMacNonce<C, H>: MockTestUtil,
{
fn mock() -> Self {
Self {
data_key: DataKey::mock(),
saved_map_nonces: SavedRopsMapNonces::mock(),
saved_mac_nonce: SavedMacNonce::mock(),
}
}
}
}

0 comments on commit 83b4d25

Please sign in to comment.