rops 0.1.7

SOPS-like library in pure Rust
Documentation
use indexmap::IndexMap;

use crate::*;

impl RopsMap<DecryptedMap> {
    pub fn encrypt<C: Cipher>(
        self,
        data_key: &DataKey,
        optional_partial_encryption: Option<&PartialEncryptionConfig>,
    ) -> Result<RopsMap<EncryptedMap<C>>, C::Error> {
        Self::encrypt_recursive_impl(self, data_key, optional_partial_encryption, &None)
    }

    pub fn encrypt_with_saved_nonces<C: Cipher>(
        self,
        data_key: &DataKey,
        optional_partial_encryption: Option<&PartialEncryptionConfig>,
        saved_nonces: &SavedRopsMapNonces<C>,
    ) -> Result<RopsMap<EncryptedMap<C>>, C::Error> {
        Self::encrypt_recursive_impl(self, data_key, optional_partial_encryption, &Some(saved_nonces))
    }

    fn encrypt_recursive_impl<C: Cipher>(
        self,
        data_key: &DataKey,
        optional_partial_encryption: Option<&PartialEncryptionConfig>,
        saved_nonces: &Option<&SavedRopsMapNonces<C>>,
    ) -> Result<RopsMap<EncryptedMap<C>>, C::Error> {
        return encrypt_map_recursive(
            self,
            data_key,
            optional_partial_encryption.into(),
            &KeyPath::default(),
            saved_nonces,
        );

        fn encrypt_map_recursive<Ci: Cipher>(
            decrypted_map: RopsMap<DecryptedMap>,
            data_key: &DataKey,
            resolved_partial_encryption: ResolvedPartialEncryption,
            key_path: &KeyPath,
            optional_saved_nonces: &Option<&SavedRopsMapNonces<Ci>>,
        ) -> Result<RopsMap<EncryptedMap<Ci>>, 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);
                let mut resolved_partial_encryption = resolved_partial_encryption;

                if let ResolvedPartialEncryption::No(partial_encryption_config) = resolved_partial_encryption {
                    resolved_partial_encryption = partial_encryption_config.resolve(key_path.last())
                }

                encrypted_map.insert(
                    key,
                    encrypt_tree_recursive(
                        decrypted_sub_tree,
                        data_key,
                        resolved_partial_encryption,
                        &key_path,
                        optional_saved_nonces,
                    )?,
                );
            }

            Ok(encrypted_map)
        }

        fn encrypt_tree_recursive<Ci: Cipher>(
            decrypted_tree: RopsTree<DecryptedMap>,
            data_key: &DataKey,
            resolved_partial_encryption: ResolvedPartialEncryption,
            key_path: &KeyPath,
            optional_saved_nonces: &Option<&SavedRopsMapNonces<Ci>>,
        ) -> Result<RopsTree<EncryptedMap<Ci>>, Ci::Error> {
            Ok(match decrypted_tree {
                RopsTree::Sequence(sequence) => RopsTree::Sequence(
                    sequence
                        .into_iter()
                        .map(|sub_tree| {
                            encrypt_tree_recursive(sub_tree, data_key, resolved_partial_encryption, key_path, optional_saved_nonces)
                        })
                        .collect::<Result<Vec<_>, _>>()?,
                ),
                RopsTree::Map(decrypted_map) => RopsTree::Map(encrypt_map_recursive(
                    decrypted_map,
                    data_key,
                    resolved_partial_encryption,
                    key_path,
                    optional_saved_nonces,
                )?),
                RopsTree::Null => RopsTree::Null,
                RopsTree::Leaf(value) => match resolved_partial_encryption.escape_encryption() {
                    true => RopsTree::Leaf(RopsMapEncryptedLeaf::Escaped(value)),
                    false => {
                        let nonce = optional_saved_nonces
                            .and_then(|saved_nonces| saved_nonces.get((key_path, &value)).cloned())
                            .unwrap_or_else(Nonce::new);
                        RopsTree::Leaf(RopsMapEncryptedLeaf::Encrypted(value.encrypt(nonce, data_key, key_path)?))
                    }
                },
            })
        }
    }
}

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

        #[test]
        fn encrypts_map_with_saved_nonces() {
            pretty_assertions::assert_eq!(
                RopsMap::<EncryptedMap<AES256GCM>>::mock(),
                RopsMap::<DecryptedMap>::mock()
                    .encrypt_with_saved_nonces(&DataKey::mock(), MockTestUtil::mock(), &SavedRopsMapNonces::mock())
                    .unwrap()
            )
        }

        #[test]
        fn encrypts_map_without_saving_nonces() {
            let decrypted_map = RopsMap::<DecryptedMap>::mock();
            let data_key = DataKey::mock();
            let encrypted_map = decrypted_map.encrypt(&data_key, None).unwrap();

            pretty_assertions::assert_ne!(RopsMap::<EncryptedMap<AES256GCM>>::mock(), encrypted_map);
            pretty_assertions::assert_eq!(RopsMap::mock(), encrypted_map.decrypt(&data_key).unwrap())
        }
    }
}