...
 
Commits (4)
......@@ -14,11 +14,11 @@ readme = "README.md"
streams = ["tokio", "futures"]
[dependencies]
fastcdc = "1.0.2"
fastcdc = "1.0.3"
futures = { version = "0.3.4", default-features = false, features = ["std"], optional = true }
rand = "0.7.3"
rand_chacha = "0.2.2"
thiserror = "1.0.11"
thiserror = "1.0.13"
tokio = { version = "0.2.13", features = ["rt-core","rt-threaded", "macros", "sync", "blocking"], optional = true }
[dev-dependencies]
......
......@@ -23,8 +23,8 @@ rpassword = "4.0.5"
tokio = { version = "0.2.13", features = ["full"] }
tracing = "0.1.13"
tracing-subscriber = "0.2.3"
structopt = "0.3.11"
async-trait = "0.1.24"
structopt = "0.3.12"
async-trait = "0.1.26"
[build-dependencies]
vergen = "3.1.0"
......
use std::collections::HashMap;
use std::io::{self, Write};
use std::time::{Duration, Instant};
use asuran::prelude::*;
use anyhow::Result;
use prettytable::{cell, row, Table};
const ONE_MIB: usize = 1_048_576;
const REPETITIONS: usize = 100;
/// Runs each encryption/hmac pair over 1MiB of zeros, 100 times
///
/// Produces output in MiB/s
pub fn bench_with_settings(encryption: Encryption, hmac: HMAC) -> f64 {
let key = Key::random(encryption.key_length());
let compression = Compression::NoCompression;
let bytes = vec![0_u8; ONE_MIB];
let mut total_duration = Duration::new(0, 0);
for _ in 0..REPETITIONS {
// Clone the input
let x = bytes.clone();
// Start the timer
let start = Instant::now();
// Pack the chunk
let _ = Chunk::pack(x, compression, encryption, hmac, &key);
// Stop the timer
let duration = start.elapsed();
total_duration += duration;
}
let elapsed = total_duration.as_secs_f64();
// Convert to MiB/s, which is easy, because we are using 1MiB blocks
(REPETITIONS as f64) / elapsed
}
pub async fn bench_crypto() -> Result<()> {
// Print the info
println!(
" === asuran-cli bench-crypto ===
This command will provide benchmarks of the raw single threaded performance of
Encryption and HMAC operations with each of Asuran's supported crypto
primitives.
These benchmarks are *not* the final throughput of asuran. Compression and
chunker settings are likely to have a far greater impact on final throughput
than any of these.
=== Beginning Benchmarks ===\n"
);
// Flush the output before doing anything
io::stdout().flush()?;
let mut map: HashMap<Encryption, Vec<(HMAC, f64)>> = HashMap::new();
let encryptions = vec![Encryption::new_aes256ctr(), Encryption::new_chacha20()];
let hmacs = vec![
HMAC::SHA256,
HMAC::Blake2b,
HMAC::Blake2bp,
HMAC::Blake3,
HMAC::SHA3,
];
for enc in encryptions.clone() {
let mut results: Vec<(HMAC, f64)> = Vec::new();
for hmac in &hmacs {
results.push((*hmac, bench_with_settings(enc.clone(), *hmac)));
// Print a dot and flush to indicate progress
print!("*");
io::stdout().flush()?;
}
map.insert(enc, results);
}
println!("\n === Results ===\n");
// Make the output table
let mut table = Table::new();
table.set_titles(row!["Encryption Type", "HMAC Type", "Speed"]);
for enc in encryptions {
let mut first = true;
let results = map.remove(&enc).unwrap();
for (hmac, result) in results {
let enc = if first {
format!("{}", encryption_to_str(&enc))
} else {
"".to_string()
};
first = false;
table.add_row(row![
enc,
hmac_to_str(hmac).to_string(),
format!("{:.2} MiB/s", result)
]);
}
}
table.printstd();
println!(
"\n === Authors Note ===
All of the cryptographic primitives that asuran uses are of roughly comparable
cryptographic security, with the possible exception of SHA2. SHA2 is not yet
broken, and the fact that asuran uses an HMAC may or may not mitigate any future
attacks on it.
Simply put, chose the combination that is fastest on your machine, but if a
combination including SHA2 is fastest on your machine, and you do not understand
the above security disclaimer and its implications, you may be well advised to
choose the fastest combination that does not include SHA2."
);
Ok(())
}
fn encryption_to_str(encryption: &Encryption) -> &'static str {
match encryption {
Encryption::AES256CTR { .. } => "AES256-CTR",
Encryption::ChaCha20 { .. } => "ChaCha20",
_ => unimplemented!(),
}
}
fn hmac_to_str(hmac: HMAC) -> &'static str {
match hmac {
HMAC::SHA256 => "SHA2",
HMAC::Blake2b => "BLAKE2b",
HMAC::Blake2bp => "BLAKE2bp",
HMAC::Blake3 => "BLAKE3",
HMAC::SHA3 => "SHA3",
}
}
......@@ -28,7 +28,7 @@ arg_enum! {
///
/// These are a 1-to-1 corrospondance with the name of the struct
/// implementing that backend in the `asuran` crate.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum RepositoryType {
MultiFile,
FlatFile,
......@@ -41,7 +41,7 @@ arg_enum! {
/// These are, more or less, a 1-to-1 corrospondance with the name of the
/// `Encryption` enum variant in the `asuran` crate, but these do not carry
/// an IV with them.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum Encryption {
AES256CBC,
AES256CTR,
......@@ -55,7 +55,7 @@ arg_enum! {
/// These are, more or less, a 1-to-1 corrospondance with the name of the
/// `Compression` enum variant in the `asuran` crate, but these do not carry
/// a compression level with them.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum Compression {
ZStd,
LZ4,
......@@ -69,7 +69,7 @@ arg_enum! {
///
/// These are a 1-to-1 corrospondance with the `HMAC` enum variant in the
/// `asuran` crate
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum HMAC {
SHA256,
Blake2b,
......@@ -83,9 +83,14 @@ arg_enum! {
#[derive(StructOpt, Debug, Clone)]
pub enum Command {
/// Provides a listing of the archives in a repository
List,
List {
#[structopt(flatten)]
repo_opts: RepoOpt,
},
/// Creates a new archive in a repository
Store {
#[structopt(flatten)]
repo_opts: RepoOpt,
/// Location of the directory to store
#[structopt(name = "TARGET")]
target: PathBuf,
......@@ -95,6 +100,8 @@ pub enum Command {
},
/// Extracts an archive from a repository
Extract {
#[structopt(flatten)]
repo_opts: RepoOpt,
/// Location to restore to
#[structopt(name = "TARGET")]
target: PathBuf,
......@@ -103,18 +110,29 @@ pub enum Command {
archive: String,
},
/// Creates a new repository
New,
New {
#[structopt(flatten)]
repo_opts: RepoOpt,
},
/// Runs benchmarks on all combinations of asuran's supported crypto primitives.
BenchCrypto,
}
/// Struct for holding the options the user has selected
#[derive(Debug, StructOpt)]
#[structopt(
name = "Asuran-CLI",
about = "Deduplicating, encrypting, tamper evident archiver",
author = env!("CARGO_PKG_AUTHORS"),
version = VERSION,
global_setting(AppSettings::ColoredHelp),
)]
pub struct Opt {
impl Command {
pub fn repo_opts(&self) -> &RepoOpt {
match self {
Self::List { repo_opts } => repo_opts,
Self::Store { repo_opts, .. } => repo_opts,
Self::Extract { repo_opts, .. } => repo_opts,
Self::New { repo_opts, .. } => repo_opts,
Self::BenchCrypto => unimplemented!("asuran-cli bench does not interact with a repository, and does not have repository options."),
}
}
}
/// Options that are shared among all repository commands
#[derive(Debug, StructOpt, Clone)]
pub struct RepoOpt {
/// Location of the Asuran repository
#[structopt(name = "REPO")]
pub repo: PathBuf,
......@@ -163,12 +181,36 @@ pub struct Opt {
possible_values(&HMAC::variants())
)]
pub hmac: HMAC,
}
/// Struct for holding the options the user has selected
#[derive(Debug, StructOpt)]
#[structopt(
name = "Asuran-CLI",
about = "Deduplicating, encrypting, tamper evident archiver",
author = env!("CARGO_PKG_AUTHORS"),
version = VERSION,
global_setting(AppSettings::ColoredHelp),
)]
pub struct Opt {
/// Operation to perform
#[structopt(subcommand)]
pub command: Command,
}
impl Opt {
pub fn get_chunk_settings(&self) -> repository::ChunkSettings {
self.command.repo_opts().get_chunk_settings()
}
pub async fn open_repo_backend(&self) -> Result<(DynamicBackend, Key)> {
self.command.repo_opts().open_repo_backend().await
}
pub fn repo_opts(&self) -> &RepoOpt {
self.command.repo_opts()
}
}
impl RepoOpt {
/// Generates an `asuran::repostiory::ChunkSettings` from the options the
/// user has selected
pub fn get_chunk_settings(&self) -> repository::ChunkSettings {
......
......@@ -6,6 +6,7 @@ repositories.
mod cli;
mod util;
mod bench;
mod extract;
mod list;
mod new;
......@@ -22,9 +23,12 @@ async fn main() -> Result<()> {
let options = Opt::from_args();
let command = options.command.clone();
match command {
Command::New => new::new(options).await,
Command::Store { target, name } => store::store(options, target, name).await,
Command::List => list::list(options).await,
Command::Extract { target, archive } => extract::extract(options, target, archive).await,
Command::New { .. } => new::new(options).await,
Command::Store { target, name, .. } => store::store(options, target, name).await,
Command::List { .. } => list::list(options).await,
Command::Extract {
target, archive, ..
} => extract::extract(options, target, archive).await,
Command::BenchCrypto => bench::bench_crypto().await,
}
}
......@@ -12,10 +12,10 @@ use std::fs::create_dir_all;
/// specified location
pub async fn new(options: Opt) -> Result<()> {
// Ensure that the repository path does not exist
if options.repo.exists() {
if options.repo_opts().repo.exists() {
return Err(anyhow!(
"Repository location already exists! {:?}",
&options.repo
&options.repo_opts().repo
));
}
......@@ -25,16 +25,19 @@ pub async fn new(options: Opt) -> Result<()> {
// Make them a new random key
let key = Key::random(key_length);
// Attempt to encrypt that key with the user supplied password
let encrypted_key =
EncryptedKey::encrypt_defaults(&key, settings.encryption, options.password.as_bytes());
let encrypted_key = EncryptedKey::encrypt_defaults(
&key,
settings.encryption,
options.repo_opts().password.as_bytes(),
);
// Figure out which type of repository they want, and create it
match options.repository_type {
match options.repo_opts().repository_type {
RepositoryType::MultiFile => {
// Create the directory
create_dir_all(&options.repo)?;
create_dir_all(&options.repo_opts().repo)?;
// Open the repository and set the key
let mut mf = MultiFile::open_defaults(&options.repo, Some(settings), &key)
let mut mf = MultiFile::open_defaults(&options.repo_opts().repo, Some(settings), &key)
.await
.with_context(|| "Unable to create MultiFile directory.")?;
mf.write_key(&encrypted_key)
......@@ -45,8 +48,12 @@ pub async fn new(options: Opt) -> Result<()> {
}
RepositoryType::FlatFile => {
// Open the repository setting the key
let mut ff = FlatFile::new(&options.repo, Some(settings), Some(encrypted_key))
.with_context(|| "Unable to create flatfile.")?;
let mut ff = FlatFile::new(
&options.repo_opts().repo,
Some(settings),
Some(encrypted_key),
)
.with_context(|| "Unable to create flatfile.")?;
ff.close().await;
Ok(())
}
......
......@@ -42,12 +42,13 @@ rand = "0.7.3"
rmp-serde = "0.14.3"
rust-argon2 = "0.8.1"
semver = "0.9.0"
serde = { version = "1.0.104", features = ["derive", "rc"] }
serde = { version = "1.0.105", features = ["derive", "rc"] }
serde_bytes = "0.11.3"
sha2 = { version = "0.8.1", optional = true }
sha3 = { version = "0.8.2", optional = true }
stream-cipher = "0.3.2"
thiserror = "1.0.11"
thiserror = "1.0.13"
tracing = "0.1.13"
uuid = "0.8.1"
xz2 = { version = "0.1.6", optional = true }
zeroize = { version = "1.1.0", features = ["zeroize_derive"] }
......
......@@ -20,7 +20,7 @@ pub enum CompressionError {
type Result<T> = std::result::Result<T, CompressionError>;
/// Marker for the type of compression used by a particular chunk
#[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
pub enum Compression {
NoCompression,
ZStd { level: i32 },
......
......@@ -47,7 +47,7 @@ pub enum EncryptionError {
type Result<T> = std::result::Result<T, EncryptionError>;
/// Tag for the encryption algorthim and IV used by a particular chunk
#[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
pub enum Encryption {
NoEncryption,
AES256CBC { iv: [u8; 16] },
......
......@@ -38,7 +38,7 @@ type HmacSha256 = Hmac<Sha256>;
type HmacSHA3 = Hmac<Sha3_256>;
/// Tag for the HMAC algorithim used by a particular `Chunk`
#[derive(Deserialize, Serialize, Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Deserialize, Serialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum HMAC {
SHA256,
Blake2b,
......
......@@ -14,12 +14,12 @@ readme = "README.md"
[dependencies]
asuran-chunker = { version = "0.0.11-alpha.0", path = "../asuran-chunker/", features = ["streams"] }
asuran-core = { version = "0.0.11-alpha.0", path = "../asuran-core/" }
async-trait = "0.1.24"
async-trait = "0.1.26"
base64 = "0.12.0"
bincode = "1.2.1"
byteorder = "1.3.4"
chrono = { version = "0.4.11", features = ["serde"] }
fastcdc = "1.0.2"
fastcdc = "1.0.3"
futures = { version = "0.3.4", default-features = false, features = ["std"] }
futures-intrusive = "0.3.0"
lazy_static = "1.4.0"
......@@ -29,9 +29,9 @@ petgraph = "0.5.0"
rand = { version = "0.7.3", features = ["small_rng"] }
rmp-serde = "0.14.3"
semver = "0.9.0"
serde = { version = "1.0.104", features = ["derive", "rc"] }
serde = { version = "1.0.105", features = ["derive", "rc"] }
serde_bytes = "0.11.3"
thiserror = "1.0.11"
thiserror = "1.0.13"
tokio = { version = "0.2.13", features = ["rt-core","rt-threaded", "macros", "sync", "blocking"] }
tracing = "0.1.13"
tracing-futures = "0.2.3"
......