diff --git a/crates/lib/src/rops_file/core.rs b/crates/lib/src/rops_file/core.rs index 32e1199..89e68dc 100644 --- a/crates/lib/src/rops_file/core.rs +++ b/crates/lib/src/rops_file/core.rs @@ -17,6 +17,18 @@ where pub metadata: RopsFileMetadata, } +#[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}")] @@ -38,6 +50,54 @@ where } } +impl RopsFile, F> { + pub fn encrypt(self) -> Result, Fo>, RopsFileEncryptError> + where + RopsFileFormatMap: TryInto, Error = FormatToInternalMapError>, + RopsMap>: Into, + { + let data_key = self.metadata.retrieve_data_key()?; + + let encrypted_map = self + .map + .try_into()? + .encrypt::(&data_key) + .map_err(|error| RopsFileEncryptError::MapEncryption(error.to_string()))?; + + let encrypted_metadata = self + .metadata + .encrypt::(&data_key) + .map_err(|error| RopsFileEncryptError::MetadataEncryption(error.to_string()))?; + + Ok(RopsFile::new(encrypted_map, encrypted_metadata)) + } + + pub fn encrypt_with_saved_parameters( + self, + saved_parameters: SavedParameters, + ) -> Result, Fo>, RopsFileEncryptError> + where + RopsFileFormatMap: TryInto, Error = FormatToInternalMapError>, + RopsMap>: Into, + { + #[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::(&data_key, saved_mac_nonce) + .map_err(|error| RopsFileEncryptError::MetadataEncryption(error.to_string()))?; + + Ok(RopsFile::new(encrypted_map, encrypted_metadata)) + } +} + impl RopsFile, F> { pub fn decrypt(self) -> Result, Fo>, RopsFileDecryptError> where @@ -53,9 +113,9 @@ impl RopsFile, F> { } #[allow(clippy::type_complexity)] - pub fn decrypt_and_save_nonces( + pub fn decrypt_and_save_parameters( self, - ) -> Result<(RopsFile, Fo>, SavedRopsMapNonces, SavedMacNonce), RopsFileDecryptError> + ) -> Result<(RopsFile, Fo>, SavedParameters), RopsFileDecryptError> where RopsFileFormatMap, F>: TryInto>, Error = FormatToInternalMapError>, RopsMap: Into, @@ -65,7 +125,14 @@ impl RopsFile, 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, stored_mac: &Mac) -> Result<(), RopsFileDecryptError> { diff --git a/crates/lib/src/rops_file/format/yaml/tests.rs b/crates/lib/src/rops_file/format/yaml/tests.rs index ffc890a..f1a271e 100644 --- a/crates/lib/src/rops_file/format/yaml/tests.rs +++ b/crates/lib/src/rops_file/format/yaml/tests.rs @@ -28,6 +28,32 @@ mod rops_file { FileFormatTestUtils::assert_deserialization::() } + #[test] + fn encrypts_rops_file() { + IntegrationsHelper::set_private_keys(); + + assert_eq!( + DecryptedRopsFile::mock(), + DecryptedRopsFile::mock() + .encrypt::() + .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(); @@ -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() ) } diff --git a/crates/lib/src/rops_file/metadata/core.rs b/crates/lib/src/rops_file/metadata/core.rs index af8a1e5..8bcea13 100644 --- a/crates/lib/src/rops_file/metadata/core.rs +++ b/crates/lib/src/rops_file/metadata/core.rs @@ -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 RopsFileMetadata +where + ::Err: Display, +{ + pub(crate) fn retrieve_data_key(&self) -> Result { + match self.find_data_key()? { + Some(data_key) => Ok(data_key), + None => Err(RopsFileMetadataDataKeyRetrievalError::MissingDataKey), + } + } + + fn find_data_key(&self) -> IntegrationResult> { + // 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> { + 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 RopsFileMetadata> { pub fn decrypt(self) -> Result<(RopsFileMetadata>, DataKey), RopsFileMetadataDecryptError> { let data_key = self.retrieve_data_key()?; @@ -71,39 +115,6 @@ impl RopsFileMetadata> { Ok((decrypted_metadata, data_key, saved_mac_nonce)) } - - fn retrieve_data_key(&self) -> Result { - match self.find_data_key()? { - Some(data_key) => Ok(data_key), - None => Err(RopsFileMetadataDecryptError::MissingDataKey), - } - } - - fn find_data_key(&self) -> IntegrationResult> { - // 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> { - 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 RopsFileMetadata> { diff --git a/crates/lib/src/rops_file/metadata/mod.rs b/crates/lib/src/rops_file/metadata/mod.rs index 5755fbe..8efea99 100644 --- a/crates/lib/src/rops_file/metadata/mod.rs +++ b/crates/lib/src/rops_file/metadata/mod.rs @@ -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; diff --git a/crates/lib/src/rops_file/mod.rs b/crates/lib/src/rops_file/mod.rs index c1a611f..148c1d1 100644 --- a/crates/lib/src/rops_file/mod.rs +++ b/crates/lib/src/rops_file/mod.rs @@ -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; diff --git a/crates/lib/src/rops_file/saved_parameters.rs b/crates/lib/src/rops_file/saved_parameters.rs new file mode 100644 index 0000000..3753243 --- /dev/null +++ b/crates/lib/src/rops_file/saved_parameters.rs @@ -0,0 +1,29 @@ +use crate::*; + +// TODO: zeroize? +#[derive(Debug)] +#[impl_tools::autoimpl(PartialEq)] +pub struct SavedParameters { + pub(crate) data_key: DataKey, + pub(crate) saved_map_nonces: SavedRopsMapNonces, + pub(crate) saved_mac_nonce: SavedMacNonce, +} + +#[cfg(feature = "test-utils")] +mod mock { + use super::*; + + impl MockTestUtil for SavedParameters + where + SavedRopsMapNonces: MockTestUtil, + SavedMacNonce: MockTestUtil, + { + fn mock() -> Self { + Self { + data_key: DataKey::mock(), + saved_map_nonces: SavedRopsMapNonces::mock(), + saved_mac_nonce: SavedMacNonce::mock(), + } + } + } +}