Skip to content

Commit

Permalink
test: Add unit tests for extra verification
Browse files Browse the repository at this point in the history
  • Loading branch information
aawsome committed Feb 29, 2024
1 parent e7104de commit 05cf25e
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 34 deletions.
1 change: 1 addition & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ xattr = "1"

[dev-dependencies]
expect-test = "1.4.1"
mockall = "0.12.1"
pretty_assertions = "1.4.0"
public-api = "0.33.1"
quickcheck = "1.0.3"
Expand Down
27 changes: 27 additions & 0 deletions crates/core/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use std::{io::Read, ops::Deref, path::PathBuf, sync::Arc};
use anyhow::Result;
use bytes::Bytes;
use log::trace;
#[cfg(test)]
use mockall::*;
use serde_derive::{Deserialize, Serialize};

use crate::{
Expand Down Expand Up @@ -306,6 +308,31 @@ pub trait WriteBackend: ReadBackend {
fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> Result<()>;
}

#[cfg(test)]
mock! {
Backend {}

impl ReadBackend for Backend{
fn location(&self) -> String;
fn list_with_size(&self, tpe: FileType) -> Result<Vec<(Id, u32)>>;
fn read_full(&self, tpe: FileType, id: &Id) -> Result<Bytes>;
fn read_partial(
&self,
tpe: FileType,
id: &Id,
cacheable: bool,
offset: u32,
length: u32,
) -> Result<Bytes>;
}

impl WriteBackend for Backend {
fn create(&self) -> Result<()>;
fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> Result<()>;
fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> Result<()>;
}
}

impl WriteBackend for Arc<dyn WriteBackend> {
fn create(&self) -> Result<()> {
self.deref().create()
Expand Down
175 changes: 141 additions & 34 deletions crates/core/src/backend/decrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -387,34 +387,9 @@ impl<C: CryptoKey> DecryptBackend<C> {
_ => return Err(CryptBackendErrorKind::DecryptionNotSupportedForBackend)?,
})
}
}

impl<C: CryptoKey> DecryptWriteBackend for DecryptBackend<C> {
/// The type of the key.
type Key = C;

/// Gets the key.
fn key(&self) -> &Self::Key {
&self.key
}

/// Writes the given data to the backend and returns the id of the data.
///
/// # Arguments
///
/// * `tpe` - The type of the file.
/// * `data` - The data to write.
///
/// # Errors
///
/// * [`CryptBackendErrorKind::CopyEncodingDataFailed`] - If the data could not be encoded.
///
/// # Returns
///
/// The id of the data.
///
/// [`CryptBackendErrorKind::CopyEncodingDataFailed`]: crate::error::CryptBackendErrorKind::CopyEncodingDataFailed
fn hash_write_full(&self, tpe: FileType, data: &[u8]) -> RusticResult<Id> {
/// encrypt and potentially compress a repository file
fn encrypt_file(&self, data: &[u8]) -> RusticResult<Vec<u8>> {
let data_encrypted = match self.zstd {
Some(level) => {
let mut out = vec![2_u8];
Expand All @@ -424,19 +399,21 @@ impl<C: CryptoKey> DecryptWriteBackend for DecryptBackend<C> {
}
None => self.key().encrypt_data(data)?,
};
Ok(data_encrypted)
}

fn very_file(&self, data_encrypted: &[u8], data: &[u8]) -> RusticResult<()> {
if self.extra_verify {
let check_data = self.decrypt_file(&data_encrypted)?;
let check_data = self.decrypt_file(data_encrypted)?;
if data != check_data {
return Err(CryptBackendErrorKind::ExtraVerificationFailed.into());
}
}
let id = hash(&data_encrypted);
self.write_bytes(tpe, &id, false, data_encrypted.into())
.map_err(RusticErrorKind::Backend)?;
Ok(id)
Ok(())
}

fn process_data(&self, data: &[u8]) -> RusticResult<(Vec<u8>, u32, Option<NonZeroU32>)> {
/// encrypt and potentially compress some data
fn encrypt_data(&self, data: &[u8]) -> RusticResult<(Vec<u8>, u32, Option<NonZeroU32>)> {
let data_len: u32 = data
.len()
.try_into()
Expand All @@ -449,13 +426,63 @@ impl<C: CryptoKey> DecryptWriteBackend for DecryptBackend<C> {
NonZeroU32::new(data_len),
),
};
Ok((data_encrypted, data_len, uncompressed_length))
}

fn very_data(
&self,
data_encrypted: &[u8],
uncompressed_length: Option<NonZeroU32>,
data: &[u8],
) -> RusticResult<()> {
if self.extra_verify {
let data_check =
self.read_encrypted_from_partial(&data_encrypted, uncompressed_length)?;
self.read_encrypted_from_partial(data_encrypted, uncompressed_length)?;
if data != data_check {
return Err(CryptBackendErrorKind::ExtraVerificationFailed.into());
}
}
Ok(())
}
}

impl<C: CryptoKey> DecryptWriteBackend for DecryptBackend<C> {
/// The type of the key.
type Key = C;

/// Gets the key.
fn key(&self) -> &Self::Key {
&self.key
}

/// Writes the given data to the backend and returns the id of the data.
///
/// # Arguments
///
/// * `tpe` - The type of the file.
/// * `data` - The data to write.
///
/// # Errors
///
/// * [`CryptBackendErrorKind::CopyEncodingDataFailed`] - If the data could not be encoded.
///
/// # Returns
///
/// The id of the data.
///
/// [`CryptBackendErrorKind::CopyEncodingDataFailed`]: crate::error::CryptBackendErrorKind::CopyEncodingDataFailed
fn hash_write_full(&self, tpe: FileType, data: &[u8]) -> RusticResult<Id> {
let data_encrypted = self.encrypt_file(data)?;
self.very_file(&data_encrypted, data)?;
let id = hash(&data_encrypted);
self.write_bytes(tpe, &id, false, data_encrypted.into())
.map_err(RusticErrorKind::Backend)?;
Ok(id)
}

fn process_data(&self, data: &[u8]) -> RusticResult<(Vec<u8>, u32, Option<NonZeroU32>)> {
let (data_encrypted, data_len, uncompressed_length) = self.encrypt_data(data)?;
self.very_data(&data_encrypted, uncompressed_length, data)?;
Ok((data_encrypted, data_len, uncompressed_length))
}

Expand Down Expand Up @@ -554,3 +581,83 @@ impl<C: CryptoKey> WriteBackend for DecryptBackend<C> {
self.be.remove(tpe, id, cacheable)
}
}

#[cfg(test)]
mod tests {
use crate::{backend::MockBackend, crypto::aespoly1305::Key};
use anyhow::Result;

use super::*;

fn init() -> (DecryptBackend<Key>, &'static [u8]) {
let be = Arc::new(MockBackend::new());
let key = Key::new();
let be = DecryptBackend::new(be, key);
let data = "{test}".as_bytes(); // braces as this should be detected as json
(be, data)
}

#[test]
fn verify_encrypt_file_ok() -> Result<()> {
let (mut be, data) = init();
be.set_extra_verify(true);
let data_encrypted = be.encrypt_file(data)?;
be.very_file(&data_encrypted, data)?;
Ok(())
}

#[test]
fn verify_encrypt_file_no_test() -> Result<()> {
let (be, data) = init();
let mut data_encrypted = be.encrypt_file(data)?;
// modify some data
data_encrypted[0] = !data_encrypted[0];
// won't be detected
be.very_file(&data_encrypted, data)?;
Ok(())
}

#[test]
fn verify_encrypt_file_nok() -> Result<()> {
let (mut be, data) = init();
be.set_extra_verify(true);
let mut data_encrypted = be.encrypt_file(data)?;
// modify some data
data_encrypted[5] = !data_encrypted[5];
// will be detected
assert!(be.very_file(&data_encrypted, data).is_err());
Ok(())
}

#[test]
fn verify_encrypt_data_ok() -> Result<()> {
let (mut be, data) = init();
be.set_extra_verify(true);
let (data_encrypted, _, ul) = be.encrypt_data(data)?;
be.very_data(&data_encrypted, ul, data)?;
Ok(())
}

#[test]
fn verify_encrypt_data_no_test() -> Result<()> {
let (be, data) = init();
let (mut data_encrypted, _, ul) = be.encrypt_data(data)?;
// modify some data
data_encrypted[0] = !data_encrypted[0];
// won't be detected
be.very_data(&data_encrypted, ul, data)?;
Ok(())
}

#[test]
fn verify_encrypt_data_nok() -> Result<()> {
let (mut be, data) = init();
be.set_extra_verify(true);
let (mut data_encrypted, _, ul) = be.encrypt_data(data)?;
// modify some data
data_encrypted[0] = !data_encrypted[0];
// will be detected
assert!(be.very_data(&data_encrypted, ul, data).is_err());
Ok(())
}
}

0 comments on commit 05cf25e

Please sign in to comment.