Merge pull request #549 from spacedriveapp/eng-348-async-crypto

[ENG-348] Asynchronous crypto
This commit is contained in:
Brendan Allan 2023-01-29 03:00:40 -08:00 committed by GitHub
commit 024c838911
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 851 additions and 862 deletions

160
Cargo.lock generated
View file

@ -114,12 +114,6 @@ dependencies = [
"libc",
]
[[package]]
name = "anes"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "anyhow"
version = "1.0.68"
@ -681,12 +675,6 @@ dependencies = [
"toml",
]
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.0.73"
@ -785,33 +773,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "ciborium"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f"
dependencies = [
"ciborium-io",
"ciborium-ll",
"serde",
]
[[package]]
name = "ciborium-io"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369"
[[package]]
name = "ciborium-ll"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b"
dependencies = [
"ciborium-io",
"half 1.8.2",
]
[[package]]
name = "cipher"
version = "0.4.3"
@ -834,18 +795,6 @@ dependencies = [
"libloading",
]
[[package]]
name = "clap"
version = "3.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5"
dependencies = [
"bitflags",
"clap_lex 0.2.4",
"indexmap",
"textwrap",
]
[[package]]
name = "clap"
version = "4.0.32"
@ -854,7 +803,7 @@ checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39"
dependencies = [
"bitflags",
"clap_derive",
"clap_lex 0.3.0",
"clap_lex",
"is-terminal",
"once_cell",
"strsim",
@ -874,15 +823,6 @@ dependencies = [
"syn",
]
[[package]]
name = "clap_lex"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "clap_lex"
version = "0.3.0"
@ -897,10 +837,11 @@ name = "cli"
version = "0.1.0"
dependencies = [
"anyhow",
"clap 4.0.32",
"clap",
"hex",
"indoc",
"sd-crypto",
"tokio",
]
[[package]]
@ -1109,42 +1050,6 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "criterion"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb"
dependencies = [
"anes",
"atty",
"cast",
"ciborium",
"clap 3.2.23",
"criterion-plot",
"itertools",
"lazy_static",
"num-traits",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
dependencies = [
"cast",
"itertools",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.6"
@ -1655,7 +1560,7 @@ checksum = "8eb5f255b5980bb0c8cf676b675d1a99be40f316881444f44e0462eaf5df5ded"
dependencies = [
"bit_field",
"flume",
"half 2.1.0",
"half",
"lebe",
"miniz_oxide 0.6.2",
"smallvec 1.10.0",
@ -2274,12 +2179,6 @@ dependencies = [
"tracing",
]
[[package]]
name = "half"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
[[package]]
name = "half"
version = "2.1.0"
@ -3768,12 +3667,6 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
[[package]]
name = "oorandom"
version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "opaque-debug"
version = "0.3.0"
@ -4251,34 +4144,6 @@ dependencies = [
"xml-rs",
]
[[package]]
name = "plotters"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters-backend"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142"
[[package]]
name = "plotters-svg"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f"
dependencies = [
"plotters-backend",
]
[[package]]
name = "png"
version = "0.11.0"
@ -5521,7 +5386,6 @@ dependencies = [
"balloon-hash",
"blake3",
"chacha20poly1305",
"criterion",
"dashmap",
"hex",
"rand 0.8.5",
@ -6683,12 +6547,6 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
[[package]]
name = "thin-slice"
version = "0.1.1"
@ -6773,16 +6631,6 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "tinyvec"
version = "1.6.0"

View file

@ -11,3 +11,4 @@ clap = { version = "4.0.32", features = ["derive"] }
anyhow = "1.0.68"
hex = "0.4.3"
sd-crypto = { path = "../../crates/crypto" }
tokio = { workspace = true, features = ["io-util", "rt-multi-thread"] }

View file

@ -2,7 +2,8 @@ use anyhow::{Context, Result};
use clap::Parser;
use indoc::printdoc;
use sd_crypto::header::file::FileHeader;
use std::{fs::File, path::PathBuf};
use std::path::PathBuf;
use tokio::fs::File;
#[derive(Parser)]
struct Args {
@ -10,17 +11,18 @@ struct Args {
path: PathBuf,
}
fn main() -> Result<()> {
#[tokio::main]
async fn main() -> Result<()> {
let args = Args::parse();
let mut reader = File::open(args.path).context("unable to open file")?;
let (header, aad) = FileHeader::from_reader(&mut reader)?;
print_details(&header, &aad);
let mut reader = File::open(args.path).await.context("unable to open file")?;
let (header, aad) = FileHeader::from_reader(&mut reader).await?;
print_crypto_details(&header, &aad);
Ok(())
}
fn print_details(header: &FileHeader, aad: &[u8]) {
fn print_crypto_details(header: &FileHeader, aad: &[u8]) {
printdoc! {"
Header version: {version}
Encryption algorithm: {algorithm}

View file

@ -23,7 +23,7 @@ pub struct KeyAddArgs {
}
#[derive(Type, Deserialize)]
pub struct SetMasterPasswordArgs {
pub struct UnlockKeyManagerArgs {
password: String,
secret_key: Option<String>,
}
@ -56,7 +56,9 @@ pub(crate) fn mount() -> RouterBuilder {
})
// do not unlock the key manager until this route returns true
.library_query("hasMasterPassword", |t| {
t(|_, _: (), library| async move { Ok(library.key_manager.has_master_password()?) })
t(
|_, _: (), library| async move { Ok(library.key_manager.has_master_password().await?) },
)
})
// this is so we can show the key as mounted in the UI
.library_query("listMounted", |t| {
@ -64,14 +66,14 @@ pub(crate) fn mount() -> RouterBuilder {
})
.library_query("getKey", |t| {
t(|_, key_uuid: Uuid, library| async move {
let key = library.key_manager.get_key(key_uuid)?;
let key = library.key_manager.get_key(key_uuid).await?;
Ok(String::from_utf8(key.into_inner()).map_err(Error::StringParse)?)
})
})
.library_mutation("mount", |t| {
t(|_, key_uuid: Uuid, library| async move {
library.key_manager.mount(key_uuid)?;
library.key_manager.mount(key_uuid).await?;
// we also need to dispatch jobs that automatically decrypt preview media and metadata here
invalidate_query!(library, "keys.listMounted");
Ok(())
@ -88,7 +90,7 @@ pub(crate) fn mount() -> RouterBuilder {
.library_mutation("clearMasterPassword", |t| {
t(|_, _: (), library| async move {
// This technically clears the root key, but it means the same thing to the frontend
library.key_manager.clear_root_key()?;
library.key_manager.clear_root_key().await?;
invalidate_query!(library, "keys.hasMasterPassword");
Ok(())
@ -139,7 +141,7 @@ pub(crate) fn mount() -> RouterBuilder {
.await?;
}
library.key_manager.remove_key(key_uuid)?;
library.key_manager.remove_key(key_uuid).await?;
// we also need to delete all in-memory decrypted data associated with this key
invalidate_query!(library, "keys.list");
@ -148,14 +150,17 @@ pub(crate) fn mount() -> RouterBuilder {
Ok(())
})
})
.library_mutation("setMasterPassword", |t| {
t(|_, args: SetMasterPasswordArgs, library| async move {
.library_mutation("unlockKeyManager", |t| {
t(|_, args: UnlockKeyManagerArgs, library| async move {
// if this returns an error, the user MUST re-enter the correct password
library.key_manager.set_master_password(
Protected::new(args.password),
args.secret_key.map(Protected::new),
|| invalidate_query!(library, "keys.isKeyManagerUnlocking"),
)?;
library
.key_manager
.unlock(
Protected::new(args.password),
args.secret_key.map(Protected::new),
|| invalidate_query!(library, "keys.isKeyManagerUnlocking"),
)
.await?;
invalidate_query!(library, "keys.hasMasterPassword");
@ -169,7 +174,8 @@ pub(crate) fn mount() -> RouterBuilder {
for key in automount {
library
.key_manager
.mount(Uuid::from_str(&key.uuid).map_err(|_| Error::Serialization)?)?;
.mount(Uuid::from_str(&key.uuid).map_err(|_| Error::Serialization)?)
.await?;
invalidate_query!(library, "keys.listMounted");
}
@ -179,7 +185,7 @@ pub(crate) fn mount() -> RouterBuilder {
})
.library_mutation("setDefault", |t| {
t(|_, key_uuid: Uuid, library| async move {
library.key_manager.set_default(key_uuid)?;
library.key_manager.set_default(key_uuid).await?;
library
.db
@ -206,7 +212,7 @@ pub(crate) fn mount() -> RouterBuilder {
})
})
.library_query("getDefault", |t| {
t(|_, _: (), library| async move { library.key_manager.get_default().ok() })
t(|_, _: (), library| async move { library.key_manager.get_default().await.ok() })
})
.library_query("isKeyManagerUnlocking", |t| {
t(|_, _: (), library| async move { library.key_manager.is_queued(Uuid::nil()) })
@ -222,14 +228,17 @@ pub(crate) fn mount() -> RouterBuilder {
.library_mutation("add", |t| {
t(|_, args: KeyAddArgs, library| async move {
// register the key with the keymanager
let uuid = library.key_manager.add_to_keystore(
Protected::new(args.key.as_bytes().to_vec()),
args.algorithm,
args.hashing_algorithm,
!args.library_sync,
args.automount,
None,
)?;
let uuid = library
.key_manager
.add_to_keystore(
Protected::new(args.key.as_bytes().to_vec()),
args.algorithm,
args.hashing_algorithm,
!args.library_sync,
args.automount,
None,
)
.await?;
if args.library_sync {
write_storedkey_to_db(&library.db, &library.key_manager.access_keystore(uuid)?)
@ -248,7 +257,7 @@ pub(crate) fn mount() -> RouterBuilder {
}
}
library.key_manager.mount(uuid)?;
library.key_manager.mount(uuid).await?;
invalidate_query!(library, "keys.list");
invalidate_query!(library, "keys.listMounted");
@ -261,7 +270,7 @@ pub(crate) fn mount() -> RouterBuilder {
let mut stored_keys = library.key_manager.dump_keystore();
// include the verification key at the time of backup
stored_keys.push(library.key_manager.get_verification_key()?);
stored_keys.push(library.key_manager.get_verification_key().await?);
// exclude all memory-only keys
stored_keys.retain(|k| !k.memory_only);
@ -290,11 +299,10 @@ pub(crate) fn mount() -> RouterBuilder {
let secret_key = args.secret_key.map(Protected::new);
let updated_keys = library.key_manager.import_keystore_backup(
Protected::new(args.password),
secret_key,
&stored_keys,
)?;
let updated_keys = library
.key_manager
.import_keystore_backup(Protected::new(args.password), secret_key, &stored_keys)
.await?;
for key in &updated_keys {
write_storedkey_to_db(&library.db, key).await?;
@ -310,12 +318,15 @@ pub(crate) fn mount() -> RouterBuilder {
t(|_, args: MasterPasswordChangeArgs, library| async move {
let secret_key = args.secret_key.map(Protected::new);
let verification_key = library.key_manager.change_master_password(
Protected::new(args.password),
args.algorithm,
args.hashing_algorithm,
secret_key,
)?;
let verification_key = library
.key_manager
.change_master_password(
Protected::new(args.password),
args.algorithm,
args.hashing_algorithm,
secret_key,
)
.await?;
// remove old nil-id keys if they were set
library

View file

@ -111,7 +111,7 @@ pub async fn seed_keymanager(
.unwrap();
// insert all keys from the DB into the keymanager's keystore
km.populate_keystore(stored_keys)?;
km.populate_keystore(stored_keys).await?;
// if any key had an associated default tag
default.map(|k| km.set_default(k));
@ -197,7 +197,7 @@ impl LibraryManager {
indexer_rules_seeder(&library.db).await?;
// setup master password
let verification_key = KeyManager::onboarding(km_config)?;
let verification_key = KeyManager::onboarding(km_config).await?;
write_storedkey_to_db(&library.db, &verification_key).await?;
@ -329,7 +329,7 @@ impl LibraryManager {
.exec()
.await?;
let key_manager = Arc::new(KeyManager::new(vec![])?);
let key_manager = Arc::new(KeyManager::new(vec![]).await?);
seed_keymanager(&db, &key_manager).await?;
let (sync_manager, _) = SyncManager::new(db.clone(), id);

View file

@ -1,9 +1,8 @@
use sd_crypto::{crypto::stream::StreamDecryption, header::file::FileHeader, Protected};
use serde::{Deserialize, Serialize};
use specta::Type;
use std::{collections::VecDeque, fs::File, path::PathBuf};
use tokio::task;
use std::{collections::VecDeque, path::PathBuf};
use tokio::fs::File;
use crate::job::{JobError, JobReportUpdate, JobResult, JobState, StatefulJob, WorkerContext};
@ -82,10 +81,10 @@ impl StatefulJob for FileDecryptorJob {
|p| p,
);
let mut reader = File::open(info.obj_path.clone())?;
let mut writer = File::create(output_path)?;
let mut reader = File::open(info.obj_path.clone()).await?;
let mut writer = File::create(output_path).await?;
let (header, aad) = FileHeader::from_reader(&mut reader)?;
let (header, aad) = FileHeader::from_reader(&mut reader).await?;
let master_key = if let Some(password) = state.init.password.clone() {
if let Some(save_to_library) = state.init.save_to_library {
@ -93,20 +92,23 @@ impl StatefulJob for FileDecryptorJob {
// we can do this first, as `find_key_index` requires a successful decryption (just like `decrypt_master_key`)
if save_to_library {
let index = header.find_key_index(password.clone())?;
let index = header.find_key_index(password.clone()).await?;
// inherit the encryption algorithm from the keyslot
ctx.library_ctx.key_manager.add_to_keystore(
password.clone(),
header.algorithm,
header.keyslots[index].hashing_algorithm,
false,
false,
Some(header.keyslots[index].salt),
)?;
ctx.library_ctx
.key_manager
.add_to_keystore(
password.clone(),
header.algorithm,
header.keyslots[index].hashing_algorithm,
false,
false,
Some(header.keyslots[index].salt),
)
.await?;
}
header.decrypt_master_key(password)?
header.decrypt_master_key(password).await?
} else {
return Err(JobError::JobDataNotFound(String::from(
"Password decryption selected, but save to library boolean was not included",
@ -115,16 +117,14 @@ impl StatefulJob for FileDecryptorJob {
} else {
let keys = ctx.library_ctx.key_manager.enumerate_hashed_keys();
header.decrypt_master_key_from_prehashed(keys)?
header.decrypt_master_key_from_prehashed(keys).await?
};
task::block_in_place(|| {
let decryptor = StreamDecryption::new(master_key, &header.nonce, header.algorithm)?;
let decryptor = StreamDecryption::new(master_key, &header.nonce, header.algorithm)?;
decryptor.decrypt_streams(&mut reader, &mut writer, &aad)?;
Ok::<(), JobError>(())
})?;
decryptor
.decrypt_streams(&mut reader, &mut writer, &aad)
.await?;
// need to decrypt preview media/metadata, and maybe add an option in the UI so the user can chosoe to restore these values
// for now this can't easily be implemented, as we don't know what the new object id for the file will be (we know the old one, but it may differ)

View file

@ -1,6 +1,6 @@
use std::{collections::VecDeque, fs::File, io::Read, path::PathBuf};
use std::{collections::VecDeque, path::PathBuf};
use tokio::task;
use tokio::{fs::File, io::AsyncReadExt};
use chrono::FixedOffset;
use sd_crypto::{
@ -141,22 +141,25 @@ impl StatefulJob for FileEncryptorJob {
)
.await?;
let mut reader = task::block_in_place(|| File::open(&info.obj_path))?;
let mut writer = task::block_in_place(|| File::create(output_path))?;
let mut reader = File::open(&info.obj_path).await?;
let mut writer = File::create(output_path).await?;
let master_key = generate_master_key();
let mut header = FileHeader::new(
LATEST_FILE_HEADER,
state.init.algorithm,
vec![Keyslot::new(
LATEST_KEYSLOT,
state.init.algorithm,
user_key_details.hashing_algorithm,
user_key_details.content_salt,
user_key,
master_key.clone(),
)?],
vec![
Keyslot::new(
LATEST_KEYSLOT,
state.init.algorithm,
user_key_details.hashing_algorithm,
user_key_details.content_salt,
user_key,
master_key.clone(),
)
.await?,
],
);
if state.init.metadata || state.init.preview_media {
@ -187,12 +190,14 @@ impl StatefulJob for FileEncryptorJob {
date_modified: object.date_modified,
};
header.add_metadata(
LATEST_METADATA,
state.init.algorithm,
master_key.clone(),
&metadata,
)?;
header
.add_metadata(
LATEST_METADATA,
state.init.algorithm,
master_key.clone(),
&metadata,
)
.await?;
}
// if state.init.preview_media
@ -209,18 +214,17 @@ impl StatefulJob for FileEncryptorJob {
if tokio::fs::metadata(&pvm_path).await.is_ok() {
let mut pvm_bytes = Vec::new();
task::block_in_place(|| {
let mut pvm_file = File::open(pvm_path)?;
pvm_file.read_to_end(&mut pvm_bytes)?;
Ok::<_, JobError>(())
})?;
let mut pvm_file = File::open(pvm_path).await?;
pvm_file.read_to_end(&mut pvm_bytes).await?;
header.add_preview_media(
LATEST_PREVIEW_MEDIA,
state.init.algorithm,
master_key.clone(),
&pvm_bytes,
)?;
header
.add_preview_media(
LATEST_PREVIEW_MEDIA,
state.init.algorithm,
master_key.clone(),
&pvm_bytes,
)
.await?;
}
} else {
// should use container encryption if it's a directory
@ -230,15 +234,13 @@ impl StatefulJob for FileEncryptorJob {
}
}
task::block_in_place(|| {
header.write(&mut writer)?;
header.write(&mut writer).await?;
let encryptor =
StreamEncryption::new(master_key, &header.nonce, header.algorithm)?;
let encryptor = StreamEncryption::new(master_key, &header.nonce, header.algorithm)?;
encryptor.encrypt_streams(&mut reader, &mut writer, &header.generate_aad())?;
Ok::<_, JobError>(())
})?;
encryptor
.encrypt_streams(&mut reader, &mut writer, &header.generate_aad())
.await?;
}
_ => warn!(
"encryption is skipping {} as it isn't a file",

View file

@ -45,23 +45,26 @@ hex = "0.4.3"
tokio = { workspace = true, features = ["io-util", "rt-multi-thread"] }
[dev-dependencies]
criterion = "0.4.0"
tokio = { workspace = true, features = [
"fs",
"macros",
] } # features needed for examples
[features]
rspc = ["dep:rspc", "dep:specta"]
serde = ["dep:serde", "dep:serde_json", "dep:serde-big-array", "uuid/serde"]
[[bench]]
name = "aes-256-gcm"
path = "benches/aes-256-gcm.rs"
harness = false
# [[bench]]
# name = "aes-256-gcm"
# path = "benches/aes-256-gcm.rs"
# harness = false
[[bench]]
name = "xchacha20-poly1305"
path = "benches/xchacha20-poly1305.rs"
harness = false
# [[bench]]
# name = "xchacha20-poly1305"
# path = "benches/xchacha20-poly1305.rs"
# harness = false
[[bench]]
name = "argon2id"
path = "benches/argon2id.rs"
harness = false
# [[bench]]
# name = "argon2id"
# path = "benches/argon2id.rs"
# harness = false

View file

@ -1,65 +1,72 @@
use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion};
use sd_crypto::{
crypto::stream::{Algorithm, StreamDecryption, StreamEncryption},
primitives::{generate_master_key, generate_nonce},
};
// use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion};
// use sd_crypto::{
// crypto::stream::{Algorithm, StreamDecryption, StreamEncryption},
// primitives::{generate_master_key, generate_nonce},
// };
const ALGORITHM: Algorithm = Algorithm::Aes256Gcm;
// const ALGORITHM: Algorithm = Algorithm::Aes256Gcm;
const KB: usize = 1024;
// const KB: usize = 1024;
const SIZES: [usize; 8] = [
KB * 16,
KB * 32,
KB * 64,
KB * 128,
KB * 512,
KB * 1024,
KB * 2048,
KB * 4096,
];
// const SIZES: [usize; 8] = [
// KB * 16,
// KB * 32,
// KB * 64,
// KB * 128,
// KB * 512,
// KB * 1024,
// KB * 2048,
// KB * 4096,
// ];
fn bench(c: &mut Criterion) {
let mut group = c.benchmark_group("aes-256-gcm");
// async fn bench(c: &mut Criterion) {
// let mut group = c.benchmark_group("aes-256-gcm");
for size in SIZES {
let buf = vec![0u8; size];
// for size in SIZES {
// let buf = vec![0u8; size];
let key = generate_master_key();
let nonce = generate_nonce(ALGORITHM);
// let key = generate_master_key();
// let nonce = generate_nonce(ALGORITHM);
let encrypted_bytes =
StreamEncryption::encrypt_bytes(key.clone(), &nonce, ALGORITHM, &buf, &[]).unwrap(); // bytes to decrypt
// let encrypted_bytes =
// StreamEncryption::encrypt_bytes(key.clone(), &nonce, ALGORITHM, &buf, &[])
// .await
// .unwrap(); // bytes to decrypt
group.throughput(criterion::Throughput::Bytes(size as u64));
// group.throughput(criterion::Throughput::Bytes(size as u64));
group.bench_function(BenchmarkId::new("encrypt", size), |b| {
b.iter_batched(
|| key.clone(),
|key| StreamEncryption::encrypt_bytes(key, &nonce, ALGORITHM, &buf, &[]).unwrap(),
BatchSize::SmallInput,
)
});
// group.bench_function(BenchmarkId::new("encrypt", size), |b| {
// b.iter_batched(
// || key.clone(),
// |key| async {
// StreamEncryption::encrypt_bytes(key, &nonce, ALGORITHM, &buf, &[])
// .await
// .unwrap()
// },
// BatchSize::SmallInput,
// )
// });
group.bench_function(BenchmarkId::new("decrypt", size), |b| {
b.iter_batched(
|| key.clone(),
|key| {
StreamDecryption::decrypt_bytes(key, &nonce, ALGORITHM, &encrypted_bytes, &[])
.unwrap()
},
BatchSize::SmallInput,
)
});
}
// group.bench_function(BenchmarkId::new("decrypt", size), |b| {
// b.iter_batched(
// || key.clone(),
// |key| async {
// StreamDecryption::decrypt_bytes(key, &nonce, ALGORITHM, &encrypted_bytes, &[])
// .await
// .unwrap()
// },
// BatchSize::SmallInput,
// )
// });
// }
group.finish();
}
// group.finish();
// }
criterion_group!(
name = benches;
config = Criterion::default();
targets = bench
);
// criterion_group!(
// name = benches;
// config = Criterion::default();
// targets = bench;
// );
criterion_main!(benches);
// criterion_main!(benches);

View file

@ -1,36 +1,36 @@
use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion};
use sd_crypto::{
keys::hashing::{HashingAlgorithm, Params},
primitives::{generate_master_key, generate_salt},
Protected,
};
// use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion};
// use sd_crypto::{
// keys::hashing::{HashingAlgorithm, Params},
// primitives::{generate_master_key, generate_salt},
// Protected,
// };
const PARAMS: [Params; 3] = [Params::Standard, Params::Hardened, Params::Paranoid];
// const PARAMS: [Params; 3] = [Params::Standard, Params::Hardened, Params::Paranoid];
fn bench(c: &mut Criterion) {
let mut group = c.benchmark_group("argon2id");
// fn bench(c: &mut Criterion) {
// let mut group = c.benchmark_group("argon2id");
for param in PARAMS {
let key = Protected::new(generate_master_key().expose().to_vec());
let salt = generate_salt();
let hashing_algorithm = HashingAlgorithm::Argon2id(param);
// for param in PARAMS {
// let key = Protected::new(generate_master_key().expose().to_vec());
// let salt = generate_salt();
// let hashing_algorithm = HashingAlgorithm::Argon2id(param);
group.bench_function(BenchmarkId::new("hash", param.argon2id().m_cost()), |b| {
b.iter_batched(
|| (key.clone(), salt),
|(key, salt)| hashing_algorithm.hash(key, salt, None),
BatchSize::SmallInput,
)
});
}
// group.bench_function(BenchmarkId::new("hash", param.argon2id().m_cost()), |b| {
// b.iter_batched(
// || (key.clone(), salt),
// |(key, salt)| hashing_algorithm.hash(key, salt, None),
// BatchSize::SmallInput,
// )
// });
// }
group.finish();
}
// group.finish();
// }
criterion_group!(
name = benches;
config = Criterion::default();
targets = bench
);
// criterion_group!(
// name = benches;
// config = Criterion::default();
// targets = bench
// );
criterion_main!(benches);
// criterion_main!(benches);

View file

@ -1,65 +1,65 @@
use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion};
use sd_crypto::{
crypto::stream::{Algorithm, StreamDecryption, StreamEncryption},
primitives::{generate_master_key, generate_nonce},
};
// use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion};
// use sd_crypto::{
// crypto::stream::{Algorithm, StreamDecryption, StreamEncryption},
// primitives::{generate_master_key, generate_nonce},
// };
const ALGORITHM: Algorithm = Algorithm::XChaCha20Poly1305;
// const ALGORITHM: Algorithm = Algorithm::XChaCha20Poly1305;
const KB: usize = 1024;
// const KB: usize = 1024;
const SIZES: [usize; 8] = [
KB * 16,
KB * 32,
KB * 64,
KB * 128,
KB * 512,
KB * 1024,
KB * 2048,
KB * 4096,
];
// const SIZES: [usize; 8] = [
// KB * 16,
// KB * 32,
// KB * 64,
// KB * 128,
// KB * 512,
// KB * 1024,
// KB * 2048,
// KB * 4096,
// ];
fn bench(c: &mut Criterion) {
let mut group = c.benchmark_group("xchacha20-poly1305");
// fn bench(c: &mut Criterion) {
// let mut group = c.benchmark_group("xchacha20-poly1305");
for size in SIZES {
let buf = vec![0u8; size];
// for size in SIZES {
// let buf = vec![0u8; size];
let key = generate_master_key();
let nonce = generate_nonce(ALGORITHM);
// let key = generate_master_key();
// let nonce = generate_nonce(ALGORITHM);
let encrypted_bytes =
StreamEncryption::encrypt_bytes(key.clone(), &nonce, ALGORITHM, &buf, &[]).unwrap(); // bytes to decrypt
// let encrypted_bytes =
// StreamEncryption::encrypt_bytes(key.clone(), &nonce, ALGORITHM, &buf, &[]).unwrap(); // bytes to decrypt
group.throughput(criterion::Throughput::Bytes(size as u64));
// group.throughput(criterion::Throughput::Bytes(size as u64));
group.bench_function(BenchmarkId::new("encrypt", size), |b| {
b.iter_batched(
|| key.clone(),
|key| StreamEncryption::encrypt_bytes(key, &nonce, ALGORITHM, &buf, &[]).unwrap(),
BatchSize::SmallInput,
)
});
// group.bench_function(BenchmarkId::new("encrypt", size), |b| {
// b.iter_batched(
// || key.clone(),
// |key| StreamEncryption::encrypt_bytes(key, &nonce, ALGORITHM, &buf, &[]).unwrap(),
// BatchSize::SmallInput,
// )
// });
group.bench_function(BenchmarkId::new("decrypt", size), |b| {
b.iter_batched(
|| key.clone(),
|key| {
StreamDecryption::decrypt_bytes(key, &nonce, ALGORITHM, &encrypted_bytes, &[])
.unwrap()
},
BatchSize::SmallInput,
)
});
}
// group.bench_function(BenchmarkId::new("decrypt", size), |b| {
// b.iter_batched(
// || key.clone(),
// |key| {
// StreamDecryption::decrypt_bytes(key, &nonce, ALGORITHM, &encrypted_bytes, &[])
// .unwrap()
// },
// BatchSize::SmallInput,
// )
// });
// }
group.finish();
}
// group.finish();
// }
criterion_group!(
name = benches;
config = Criterion::default();
targets = bench
);
// criterion_group!(
// name = benches;
// config = Criterion::default();
// targets = bench
// );
criterion_main!(benches);
// criterion_main!(benches);

View file

@ -1,4 +1,4 @@
use std::fs::File;
use tokio::fs::File;
use sd_crypto::{
crypto::stream::{Algorithm, StreamDecryption, StreamEncryption},
@ -11,12 +11,12 @@ use sd_crypto::{
const ALGORITHM: Algorithm = Algorithm::XChaCha20Poly1305;
const HASHING_ALGORITHM: HashingAlgorithm = HashingAlgorithm::Argon2id(Params::Standard);
pub fn encrypt() {
async fn encrypt() {
let password = Protected::new(b"password".to_vec());
// Open both the source and the output file
let mut reader = File::open("test").unwrap();
let mut writer = File::create("test.encrypted").unwrap();
let mut reader = File::open("test").await.unwrap();
let mut writer = File::create("test.encrypted").await.unwrap();
// This needs to be generated here, otherwise we won't have access to it for encryption
let master_key = generate_master_key();
@ -36,13 +36,14 @@ pub fn encrypt() {
hashed_password,
master_key.clone(),
)
.await
.unwrap()];
// Create the header for the encrypted file
let header = FileHeader::new(LATEST_FILE_HEADER, ALGORITHM, keyslots);
// Write the header to the file
header.write(&mut writer).unwrap();
header.write(&mut writer).await.unwrap();
// Use the nonce created by the header to initialize a stream encryption object
let encryptor = StreamEncryption::new(master_key, &header.nonce, header.algorithm).unwrap();
@ -51,21 +52,22 @@ pub fn encrypt() {
// Use AAD so the header can be authenticated against every block of data
encryptor
.encrypt_streams(&mut reader, &mut writer, &header.generate_aad())
.await
.unwrap();
}
pub fn decrypt() {
async fn decrypt() {
let password = Protected::new(b"password".to_vec());
// Open both the encrypted file and the output file
let mut reader = File::open("test.encrypted").unwrap();
let mut writer = File::create("test.original").unwrap();
let mut reader = File::open("test.encrypted").await.unwrap();
let mut writer = File::create("test.original").await.unwrap();
// Deserialize the header, keyslots, etc from the encrypted file
let (header, aad) = FileHeader::from_reader(&mut reader).unwrap();
let (header, aad) = FileHeader::from_reader(&mut reader).await.unwrap();
// Decrypt the master key with the user's password
let master_key = header.decrypt_master_key(password).unwrap();
let master_key = header.decrypt_master_key(password).await.unwrap();
// Initialize a stream decryption object using data provided by the header
let decryptor = StreamDecryption::new(master_key, &header.nonce, header.algorithm).unwrap();
@ -73,11 +75,13 @@ pub fn decrypt() {
// Decrypt data the from the writer, and write it to the writer
decryptor
.decrypt_streams(&mut reader, &mut writer, &aad)
.await
.unwrap();
}
fn main() {
encrypt();
#[tokio::main]
async fn main() {
encrypt().await;
decrypt();
decrypt().await;
}

View file

@ -7,7 +7,7 @@ use sd_crypto::{
primitives::{generate_master_key, generate_salt, LATEST_FILE_HEADER, LATEST_KEYSLOT},
Protected,
};
use std::fs::File;
use tokio::fs::File;
const ALGORITHM: Algorithm = Algorithm::XChaCha20Poly1305;
const HASHING_ALGORITHM: HashingAlgorithm = HashingAlgorithm::Argon2id(Params::Standard);
@ -16,7 +16,7 @@ pub struct FileInformation {
pub file_name: String,
}
fn encrypt() {
async fn encrypt() {
let password = Protected::new(b"password".to_vec());
let embedded_metadata = FileInformation {
@ -24,8 +24,8 @@ fn encrypt() {
};
// Open both the source and the output file
let mut reader = File::open("test").unwrap();
let mut writer = File::create("test.encrypted").unwrap();
let mut reader = File::open("test").await.unwrap();
let mut writer = File::create("test.encrypted").await.unwrap();
// This needs to be generated here, otherwise we won't have access to it for encryption
let master_key = generate_master_key();
@ -45,6 +45,7 @@ fn encrypt() {
hashed_password,
master_key.clone(),
)
.await
.unwrap()];
// Create the header for the encrypted file (and include our metadata)
@ -57,10 +58,11 @@ fn encrypt() {
master_key.clone(),
&embedded_metadata,
)
.await
.unwrap();
// Write the header to the file
header.write(&mut writer).unwrap();
header.write(&mut writer).await.unwrap();
// Use the nonce created by the header to initialise a stream encryption object
let encryptor = StreamEncryption::new(master_key, &header.nonce, header.algorithm).unwrap();
@ -69,26 +71,28 @@ fn encrypt() {
// Use AAD so the header can be authenticated against every block of data
encryptor
.encrypt_streams(&mut reader, &mut writer, &header.generate_aad())
.await
.unwrap();
}
pub fn decrypt_metadata() {
async fn decrypt_metadata() {
let password = Protected::new(b"password".to_vec());
// Open the encrypted file
let mut reader = File::open("test.encrypted").unwrap();
let mut reader = File::open("test.encrypted").await.unwrap();
// Deserialize the header, keyslots, etc from the encrypted file
let (header, _) = FileHeader::from_reader(&mut reader).unwrap();
let (header, _) = FileHeader::from_reader(&mut reader).await.unwrap();
// Decrypt the metadata
let file_info: FileInformation = header.decrypt_metadata(password).unwrap();
let file_info: FileInformation = header.decrypt_metadata(password).await.unwrap();
println!("file name: {}", file_info.file_name);
}
fn main() {
encrypt();
#[tokio::main]
async fn main() {
encrypt().await;
decrypt_metadata();
decrypt_metadata().await;
}

View file

@ -1,4 +1,4 @@
use std::fs::File;
use tokio::fs::File;
use sd_crypto::{
crypto::stream::{Algorithm, StreamEncryption},
@ -11,12 +11,12 @@ use sd_crypto::{
const ALGORITHM: Algorithm = Algorithm::XChaCha20Poly1305;
const HASHING_ALGORITHM: HashingAlgorithm = HashingAlgorithm::Argon2id(Params::Standard);
fn encrypt() {
async fn encrypt() {
let password = Protected::new(b"password".to_vec());
// Open both the source and the output file
let mut reader = File::open("test").unwrap();
let mut writer = File::create("test.encrypted").unwrap();
let mut reader = File::open("test").await.unwrap();
let mut writer = File::create("test.encrypted").await.unwrap();
// This needs to be generated here, otherwise we won't have access to it for encryption
let master_key = generate_master_key();
@ -36,6 +36,7 @@ fn encrypt() {
hashed_password,
master_key.clone(),
)
.await
.unwrap()];
let pvm_media = b"a nice mountain".to_vec();
@ -50,10 +51,11 @@ fn encrypt() {
master_key.clone(),
&pvm_media,
)
.await
.unwrap();
// Write the header to the file
header.write(&mut writer).unwrap();
header.write(&mut writer).await.unwrap();
// Use the nonce created by the header to initialise a stream encryption object
let encryptor = StreamEncryption::new(master_key, &header.nonce, header.algorithm).unwrap();
@ -62,26 +64,28 @@ fn encrypt() {
// Use AAD so the header can be authenticated against every block of data
encryptor
.encrypt_streams(&mut reader, &mut writer, &header.generate_aad())
.await
.unwrap();
}
pub fn decrypt_preview_media() {
async fn decrypt_preview_media() {
let password = Protected::new(b"password".to_vec());
// Open the encrypted file
let mut reader = File::open("test.encrypted").unwrap();
let mut reader = File::open("test.encrypted").await.unwrap();
// Deserialize the header, keyslots, etc from the encrypted file
let (header, _) = FileHeader::from_reader(&mut reader).unwrap();
let (header, _) = FileHeader::from_reader(&mut reader).await.unwrap();
// Decrypt the preview media
let media = header.decrypt_preview_media(password).unwrap();
let media = header.decrypt_preview_media(password).await.unwrap();
println!("{:?}", media.expose());
}
fn main() {
encrypt();
#[tokio::main]
async fn main() {
encrypt().await;
decrypt_preview_media();
decrypt_preview_media().await;
}

View file

@ -1,10 +1,11 @@
//! This module contains the crate's STREAM implementation, and wrappers that allow us to support multiple AEADs.
#![allow(clippy::use_self)] // I think: https://github.com/rust-lang/rust-clippy/issues/3909
use std::io::{Cursor, Read, Write};
use std::io::Cursor;
use crate::{
primitives::{AEAD_TAG_SIZE, BLOCK_SIZE, KEY_LEN},
primitives::{Key, AEAD_TAG_SIZE, BLOCK_SIZE},
protected::ProtectedVec,
Error, Protected, Result,
};
use aead::{
@ -13,6 +14,7 @@ use aead::{
};
use aes_gcm::Aes256Gcm;
use chacha20poly1305::XChaCha20Poly1305;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
/// These are all possible algorithms that can be used for encryption and decryption
#[derive(Clone, Copy, Eq, PartialEq, Hash)]
@ -53,7 +55,7 @@ impl StreamEncryption {
///
/// The master key, a suitable nonce, and a specific algorithm should be provided.
#[allow(clippy::needless_pass_by_value)]
pub fn new(key: Protected<[u8; KEY_LEN]>, nonce: &[u8], algorithm: Algorithm) -> Result<Self> {
pub fn new(key: Protected<Key>, nonce: &[u8], algorithm: Algorithm) -> Result<Self> {
if nonce.len() != algorithm.nonce_len() {
return Err(Error::NonceLengthMismatch);
}
@ -105,14 +107,29 @@ impl StreamEncryption {
/// It requires a reader, a writer, and any AAD to go with it.
///
/// The AAD will be authenticated with each block of data.
pub fn encrypt_streams<R, W>(mut self, mut reader: R, mut writer: W, aad: &[u8]) -> Result<()>
pub async fn encrypt_streams<R, W>(
mut self,
mut reader: R,
mut writer: W,
aad: &[u8],
) -> Result<()>
where
R: Read,
W: Write,
R: AsyncReadExt + Unpin + Send,
W: AsyncWriteExt + Unpin + Send,
{
let mut read_buffer = vec![0u8; BLOCK_SIZE].into_boxed_slice();
loop {
let read_count = reader.read(&mut read_buffer)?;
let mut read_count = 0;
loop {
let i = reader.read(&mut read_buffer[read_count..]).await?;
read_count += i;
if i == 0 || read_count == BLOCK_SIZE {
// if we're EOF or the buffer is filled
break;
}
}
if read_count == BLOCK_SIZE {
let payload = Payload {
aad,
@ -121,7 +138,7 @@ impl StreamEncryption {
let encrypted_data = self.encrypt_next(payload).map_err(|_| Error::Encrypt)?;
writer.write_all(&encrypted_data)?;
writer.write_all(&encrypted_data).await?;
} else {
// we use `..read_count` in order to only use the read data, and not zeroes also
let payload = Payload {
@ -130,13 +147,13 @@ impl StreamEncryption {
};
let encrypted_data = self.encrypt_last(payload).map_err(|_| Error::Encrypt)?;
writer.write_all(&encrypted_data)?;
writer.write_all(&encrypted_data).await?;
break;
}
}
writer.flush()?;
writer.flush().await?;
Ok(())
}
@ -145,8 +162,8 @@ impl StreamEncryption {
///
/// It is just a thin wrapper around `encrypt_streams()`, but reduces the amount of code needed elsewhere.
#[allow(unused_mut)]
pub fn encrypt_bytes(
key: Protected<[u8; KEY_LEN]>,
pub async fn encrypt_bytes(
key: Protected<Key>,
nonce: &[u8],
algorithm: Algorithm,
bytes: &[u8],
@ -157,6 +174,7 @@ impl StreamEncryption {
encryptor
.encrypt_streams(bytes, &mut writer, aad)
.await
.map_or_else(Err, |_| Ok(writer.into_inner()))
}
}
@ -166,7 +184,7 @@ impl StreamDecryption {
///
/// The master key, nonce and algorithm that were used for encryption should be provided.
#[allow(clippy::needless_pass_by_value)]
pub fn new(key: Protected<[u8; KEY_LEN]>, nonce: &[u8], algorithm: Algorithm) -> Result<Self> {
pub fn new(key: Protected<Key>, nonce: &[u8], algorithm: Algorithm) -> Result<Self> {
if nonce.len() != algorithm.nonce_len() {
return Err(Error::NonceLengthMismatch);
}
@ -218,15 +236,29 @@ impl StreamDecryption {
/// It requires a reader, a writer, and any AAD that was used.
///
/// The AAD will be authenticated with each block of data - if the AAD doesn't match what was used during encryption, an error will be returned.
pub fn decrypt_streams<R, W>(mut self, mut reader: R, mut writer: W, aad: &[u8]) -> Result<()>
pub async fn decrypt_streams<R, W>(
mut self,
mut reader: R,
mut writer: W,
aad: &[u8],
) -> Result<()>
where
R: Read,
W: Write,
R: AsyncReadExt + Unpin + Send,
W: AsyncWriteExt + Unpin + Send,
{
let mut read_buffer = vec![0u8; BLOCK_SIZE + AEAD_TAG_SIZE].into_boxed_slice();
loop {
let read_count = reader.read(&mut read_buffer)?;
let mut read_count = 0;
loop {
let i = reader.read(&mut read_buffer[read_count..]).await?;
read_count += i;
if i == 0 || read_count == (BLOCK_SIZE + AEAD_TAG_SIZE) {
// if we're EOF or the buffer is filled
break;
}
}
if read_count == (BLOCK_SIZE + AEAD_TAG_SIZE) {
let payload = Payload {
aad,
@ -235,7 +267,7 @@ impl StreamDecryption {
let decrypted_data = self.decrypt_next(payload).map_err(|_| Error::Decrypt)?;
writer.write_all(&decrypted_data)?;
writer.write_all(&decrypted_data).await?;
} else {
let payload = Payload {
aad,
@ -243,13 +275,13 @@ impl StreamDecryption {
};
let decrypted_data = self.decrypt_last(payload).map_err(|_| Error::Decrypt)?;
writer.write_all(&decrypted_data)?;
writer.write_all(&decrypted_data).await?;
break;
}
}
writer.flush()?;
writer.flush().await?;
Ok(())
}
@ -258,18 +290,19 @@ impl StreamDecryption {
///
/// It is just a thin wrapper around `decrypt_streams()`, but reduces the amount of code needed elsewhere.
#[allow(unused_mut)]
pub fn decrypt_bytes(
key: Protected<[u8; KEY_LEN]>,
pub async fn decrypt_bytes(
key: Protected<Key>,
nonce: &[u8],
algorithm: Algorithm,
bytes: &[u8],
aad: &[u8],
) -> Result<Protected<Vec<u8>>> {
) -> Result<ProtectedVec<u8>> {
let mut writer = Cursor::new(Vec::<u8>::new());
let decryptor = Self::new(key, nonce, algorithm)?;
decryptor
.decrypt_streams(bytes, &mut writer, aad)
.await
.map_or_else(Err, |_| Ok(Protected::new(writer.into_inner())))
}
}

View file

@ -29,11 +29,14 @@
//! // Write the header to the file
//! header.write(&mut writer).unwrap();
//! ```
use std::io::{Read, Seek, SeekFrom, Write};
use std::io::SeekFrom;
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
use crate::{
crypto::stream::Algorithm,
primitives::{generate_nonce, to_array, KEY_LEN},
primitives::{generate_nonce, to_array, Key, KEY_LEN},
protected::ProtectedVec,
Error, Protected, Result,
};
@ -98,43 +101,31 @@ impl FileHeader {
///
/// You receive an error if the password doesn't match or if there are no keyslots.
#[allow(clippy::needless_pass_by_value)]
pub fn decrypt_master_key(
&self,
password: Protected<Vec<u8>>,
) -> Result<Protected<[u8; KEY_LEN]>> {
pub async fn decrypt_master_key(&self, password: ProtectedVec<u8>) -> Result<Protected<Key>> {
if self.keyslots.is_empty() {
return Err(Error::NoKeyslots);
}
self.keyslots
.iter()
.find_map(|v| v.decrypt_master_key(password.clone()).ok())
.map(|v| Protected::new(to_array::<KEY_LEN>(v.into_inner()).unwrap()))
.ok_or(Error::IncorrectPassword)
}
/// This is a helper function to find which keyslot a key belongs to.
///
/// You receive an error if the password doesn't match or if there are no keyslots.
#[allow(clippy::needless_pass_by_value)]
pub fn find_key_index(&self, password: Protected<Vec<u8>>) -> Result<usize> {
if self.keyslots.is_empty() {
return Err(Error::NoKeyslots);
for v in &self.keyslots {
if let Some(key) = v
.decrypt_master_key(password.clone())
.await
.ok()
.map(|v| Protected::new(to_array::<KEY_LEN>(v.into_inner()).unwrap()))
{
return Ok(key);
}
}
self.keyslots
.iter()
.enumerate()
.find_map(|(i, v)| v.decrypt_master_key(password.clone()).ok().map(|_| i))
.ok_or(Error::IncorrectPassword)
Err(Error::IncorrectPassword)
}
/// This is a helper function to serialize and write a header to a file.
pub fn write<W>(&self, writer: &mut W) -> Result<()>
pub async fn write<W>(&self, writer: &mut W) -> Result<()>
where
W: Write,
W: AsyncWriteExt + Unpin + Send,
{
writer.write_all(&self.to_bytes()?)?;
writer.write_all(&self.to_bytes()?).await?;
Ok(())
}
@ -144,24 +135,46 @@ impl FileHeader {
///
/// You receive an error if the password doesn't match or if there are no keyslots.
#[allow(clippy::needless_pass_by_value)]
pub fn decrypt_master_key_from_prehashed(
pub async fn decrypt_master_key_from_prehashed(
&self,
hashed_keys: Vec<Protected<[u8; KEY_LEN]>>,
) -> Result<Protected<[u8; KEY_LEN]>> {
hashed_keys: Vec<Protected<Key>>,
) -> Result<Protected<Key>> {
if self.keyslots.is_empty() {
return Err(Error::NoKeyslots);
}
hashed_keys
.iter()
.find_map(|v| {
self.keyslots.iter().find_map(|z| {
z.decrypt_master_key_from_prehashed(v.clone())
.ok()
.map(|x| Protected::new(to_array::<KEY_LEN>(x.into_inner()).unwrap()))
})
})
.ok_or(Error::IncorrectPassword)
for hashed_key in hashed_keys {
for v in &self.keyslots {
if let Some(key) = v
.decrypt_master_key_from_prehashed(hashed_key.clone())
.await
.ok()
.map(|v| Protected::new(to_array::<KEY_LEN>(v.into_inner()).unwrap()))
{
return Ok(key);
}
}
}
Err(Error::IncorrectPassword)
}
/// This is a helper function to find which keyslot a key belongs to.
///
/// You receive an error if the password doesn't match or if there are no keyslots.
#[allow(clippy::needless_pass_by_value)]
pub async fn find_key_index(&self, password: ProtectedVec<u8>) -> Result<usize> {
if self.keyslots.is_empty() {
return Err(Error::NoKeyslots);
}
for (i, v) in self.keyslots.iter().enumerate() {
if let Some(i) = v.decrypt_master_key(password.clone()).await.ok().map(|_| i) {
return Ok(i);
}
}
Err(Error::IncorrectPassword)
}
/// This function should be used for generating AAD before encryption
@ -241,12 +254,12 @@ impl FileHeader {
/// On error, the cursor will not be rewound.
///
/// It returns both the header, and the AAD that should be used for decryption.
pub fn from_reader<R>(reader: &mut R) -> Result<(Self, Vec<u8>)>
pub async fn from_reader<R>(reader: &mut R) -> Result<(Self, Vec<u8>)>
where
R: Read + Seek,
R: AsyncReadExt + AsyncSeekExt + Unpin + Send,
{
let mut magic_bytes = [0u8; MAGIC_BYTES.len()];
reader.read_exact(&mut magic_bytes)?;
reader.read_exact(&mut magic_bytes).await?;
if magic_bytes != MAGIC_BYTES {
return Err(Error::FileHeader);
@ -254,36 +267,38 @@ impl FileHeader {
let mut version = [0u8; 2];
reader.read_exact(&mut version)?;
reader.read_exact(&mut version).await?;
let version = FileHeaderVersion::from_bytes(version)?;
// Rewind so we can get the AAD
reader.rewind()?;
reader.rewind().await?;
// read the aad according to the size
let mut aad = vec![0u8; Self::size(version)];
reader.read_exact(&mut aad)?;
reader.read_exact(&mut aad).await?;
// seek back to the start (plus magic bytes and the two version bytes)
reader.seek(SeekFrom::Start(MAGIC_BYTES.len() as u64 + 2))?;
reader
.seek(SeekFrom::Start(MAGIC_BYTES.len() as u64 + 2))
.await?;
// read the header
let header = match version {
FileHeaderVersion::V1 => {
let mut algorithm = [0u8; 2];
reader.read_exact(&mut algorithm)?;
reader.read_exact(&mut algorithm).await?;
let algorithm = Algorithm::from_bytes(algorithm)?;
let mut nonce = vec![0u8; algorithm.nonce_len()];
reader.read_exact(&mut nonce)?;
reader.read_exact(&mut nonce).await?;
// read and discard the padding
reader.read_exact(&mut vec![0u8; 25 - nonce.len()])?;
reader.read_exact(&mut vec![0u8; 25 - nonce.len()]).await?;
let mut keyslot_bytes = [0u8; (KEYSLOT_SIZE * 2)]; // length of 2x keyslots
let mut keyslots: Vec<Keyslot> = Vec::new();
reader.read_exact(&mut keyslot_bytes)?;
reader.read_exact(&mut keyslot_bytes).await?;
for _ in 0..2 {
Keyslot::from_reader(&mut keyslot_bytes.as_ref())
@ -291,18 +306,21 @@ impl FileHeader {
.ok();
}
let metadata = Metadata::from_reader(reader).map_or_else(
|_| {
reader.seek(SeekFrom::Start(
let metadata = if let Ok(metadata) = Metadata::from_reader(reader).await {
reader
.seek(SeekFrom::Start(
Self::size(version) as u64 + (KEYSLOT_SIZE * 2) as u64,
))?;
Ok::<Option<Metadata>, Error>(None)
},
|metadata| Ok(Some(metadata)),
)?;
))
.await?;
Ok::<Option<Metadata>, Error>(Some(metadata))
} else {
Ok(None)
}?;
let preview_media = PreviewMedia::from_reader(reader).map_or_else(
|_| {
let preview_media =
if let Ok(preview_media) = PreviewMedia::from_reader(reader).await {
Ok::<Option<PreviewMedia>, Error>(Some(preview_media))
} else {
let seek_len = metadata.as_ref().map_or_else(
|| Self::size(version) as u64 + (KEYSLOT_SIZE * 2) as u64,
|metadata| {
@ -311,12 +329,10 @@ impl FileHeader {
},
);
reader.seek(SeekFrom::Start(seek_len))?;
reader.seek(SeekFrom::Start(seek_len)).await?;
Ok::<Option<PreviewMedia>, Error>(None)
},
|preview_media| Ok(Some(preview_media)),
)?;
Ok(None)
}?;
Self {
version,

View file

@ -27,9 +27,10 @@ use crate::{
crypto::stream::{Algorithm, StreamDecryption, StreamEncryption},
keys::hashing::HashingAlgorithm,
primitives::{
derive_key, generate_nonce, generate_salt, to_array, ENCRYPTED_KEY_LEN, FILE_KEY_CONTEXT,
KEY_LEN, SALT_LEN,
derive_key, generate_nonce, generate_salt, to_array, EncryptedKey, Key, Salt,
ENCRYPTED_KEY_LEN, FILE_KEY_CONTEXT, SALT_LEN,
},
protected::ProtectedVec,
Error, Protected, Result,
};
@ -41,9 +42,9 @@ pub struct Keyslot {
pub version: KeyslotVersion,
pub algorithm: Algorithm, // encryption algorithm
pub hashing_algorithm: HashingAlgorithm, // password hashing algorithm
pub salt: [u8; SALT_LEN], // the salt used for deriving a KEK from a (key/content salt) hash
pub content_salt: [u8; SALT_LEN],
pub master_key: [u8; ENCRYPTED_KEY_LEN], // this is encrypted so we can store it
pub salt: Salt, // the salt used for deriving a KEK from a (key/content salt) hash
pub content_salt: Salt,
pub master_key: EncryptedKey, // this is encrypted so we can store it
pub nonce: Vec<u8>,
}
@ -64,25 +65,28 @@ impl Keyslot {
///
/// You will need to provide the password, and a generated master key (this can't generate it, otherwise it can't be used elsewhere)
#[allow(clippy::needless_pass_by_value)]
pub fn new(
pub async fn new(
version: KeyslotVersion,
algorithm: Algorithm,
hashing_algorithm: HashingAlgorithm,
content_salt: [u8; SALT_LEN],
hashed_key: Protected<[u8; KEY_LEN]>,
master_key: Protected<[u8; KEY_LEN]>,
content_salt: Salt,
hashed_key: Protected<Key>,
master_key: Protected<Key>,
) -> Result<Self> {
let nonce = generate_nonce(algorithm);
let salt = generate_salt();
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(StreamEncryption::encrypt_bytes(
derive_key(hashed_key, salt, FILE_KEY_CONTEXT),
&nonce,
algorithm,
master_key.expose(),
&[],
)?)?;
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(
StreamEncryption::encrypt_bytes(
derive_key(hashed_key, salt, FILE_KEY_CONTEXT),
&nonce,
algorithm,
master_key.expose(),
&[],
)
.await?,
)?;
Ok(Self {
version,
@ -101,7 +105,7 @@ impl Keyslot {
///
/// An error will be returned on failure.
#[allow(clippy::needless_pass_by_value)]
pub fn decrypt_master_key(&self, password: Protected<Vec<u8>>) -> Result<Protected<Vec<u8>>> {
pub async fn decrypt_master_key(&self, password: ProtectedVec<u8>) -> Result<ProtectedVec<u8>> {
let key = self
.hashing_algorithm
.hash(password, self.content_salt, None)
@ -114,6 +118,7 @@ impl Keyslot {
&self.master_key,
&[],
)
.await
}
/// This function should not be used directly, use `header.decrypt_master_key()` instead
@ -123,10 +128,10 @@ impl Keyslot {
/// No hashing is done internally.
///
/// An error will be returned on failure.
pub fn decrypt_master_key_from_prehashed(
pub async fn decrypt_master_key_from_prehashed(
&self,
key: Protected<[u8; KEY_LEN]>,
) -> Result<Protected<Vec<u8>>> {
key: Protected<Key>,
) -> Result<ProtectedVec<u8>> {
StreamDecryption::decrypt_bytes(
derive_key(key, self.salt, FILE_KEY_CONTEXT),
&self.nonce,
@ -134,6 +139,7 @@ impl Keyslot {
&self.master_key,
&[],
)
.await
}
/// This function is used to serialize a keyslot into bytes

View file

@ -27,15 +27,16 @@
//! )
//! .unwrap();
//! ```
use std::io::Read;
#[cfg(feature = "serde")]
use crate::{
crypto::stream::{StreamDecryption, StreamEncryption},
primitives::{generate_nonce, KEY_LEN},
Protected,
primitives::{generate_nonce, Key},
Protected, ProtectedVec,
};
use tokio::io::AsyncReadExt;
use crate::{crypto::stream::Algorithm, Error, Result};
use super::file::FileHeader;
@ -68,15 +69,15 @@ impl FileHeader {
/// Metadata needs to be accessed switfly, so a key management system should handle the salt generation.
#[cfg(feature = "serde")]
#[allow(clippy::needless_pass_by_value)]
pub fn add_metadata<T>(
pub async fn add_metadata<T>(
&mut self,
version: MetadataVersion,
algorithm: Algorithm,
master_key: Protected<[u8; KEY_LEN]>,
master_key: Protected<Key>,
metadata: &T,
) -> Result<()>
where
T: ?Sized + serde::Serialize,
T: ?Sized + serde::Serialize + Sync + Send,
{
let metadata_nonce = generate_nonce(algorithm);
@ -86,16 +87,15 @@ impl FileHeader {
algorithm,
&serde_json::to_vec(metadata).map_err(|_| Error::Serialization)?,
&[],
)?;
)
.await?;
let metadata = Metadata {
self.metadata = Some(Metadata {
version,
algorithm,
metadata_nonce,
metadata: encrypted_metadata,
};
self.metadata = Some(metadata);
});
Ok(())
}
@ -106,29 +106,29 @@ impl FileHeader {
///
/// A deserialized data type will be returned from this function
#[cfg(feature = "serde")]
pub fn decrypt_metadata_from_prehashed<T>(
pub async fn decrypt_metadata_from_prehashed<T>(
&self,
hashed_keys: Vec<Protected<[u8; KEY_LEN]>>,
hashed_keys: Vec<Protected<Key>>,
) -> Result<T>
where
T: serde::de::DeserializeOwned,
{
let master_key = self.decrypt_master_key_from_prehashed(hashed_keys)?;
let master_key = self.decrypt_master_key_from_prehashed(hashed_keys).await?;
self.metadata.as_ref().map_or_else(
|| Err(Error::NoMetadata),
|metadata| {
let metadata = StreamDecryption::decrypt_bytes(
master_key,
&metadata.metadata_nonce,
metadata.algorithm,
&metadata.metadata,
&[],
)?;
if let Some(metadata) = self.metadata.as_ref() {
let metadata = StreamDecryption::decrypt_bytes(
master_key,
&metadata.metadata_nonce,
metadata.algorithm,
&metadata.metadata,
&[],
)
.await?;
serde_json::from_slice::<T>(&metadata).map_err(|_| Error::Serialization)
},
)
serde_json::from_slice::<T>(&metadata).map_err(|_| Error::Serialization)
} else {
Err(Error::NoMetadata)
}
}
/// This function should be used to retrieve the metadata for a file
@ -137,26 +137,26 @@ impl FileHeader {
///
/// A deserialized data type will be returned from this function
#[cfg(feature = "serde")]
pub fn decrypt_metadata<T>(&self, password: Protected<Vec<u8>>) -> Result<T>
pub async fn decrypt_metadata<T>(&self, password: ProtectedVec<u8>) -> Result<T>
where
T: serde::de::DeserializeOwned,
{
let master_key = self.decrypt_master_key(password)?;
let master_key = self.decrypt_master_key(password).await?;
self.metadata.as_ref().map_or_else(
|| Err(Error::NoMetadata),
|metadata| {
let metadata = StreamDecryption::decrypt_bytes(
master_key,
&metadata.metadata_nonce,
metadata.algorithm,
&metadata.metadata,
&[],
)?;
if let Some(metadata) = self.metadata.as_ref() {
let metadata = StreamDecryption::decrypt_bytes(
master_key,
&metadata.metadata_nonce,
metadata.algorithm,
&metadata.metadata,
&[],
)
.await?;
serde_json::from_slice::<T>(&metadata).map_err(|_| Error::Serialization)
},
)
serde_json::from_slice::<T>(&metadata).map_err(|_| Error::Serialization)
} else {
Err(Error::NoMetadata)
}
}
}
@ -192,33 +192,35 @@ impl Metadata {
/// The cursor will be left at the end of the metadata item on success
///
/// The cursor will not be rewound on error.
pub fn from_reader<R>(reader: &mut R) -> Result<Self>
pub async fn from_reader<R>(reader: &mut R) -> Result<Self>
where
R: Read,
R: AsyncReadExt + Unpin + Send,
{
let mut version = [0u8; 2];
reader.read_exact(&mut version)?;
reader.read_exact(&mut version).await?;
let version = MetadataVersion::from_bytes(version).map_err(|_| Error::NoMetadata)?;
match version {
MetadataVersion::V1 => {
let mut algorithm = [0u8; 2];
reader.read_exact(&mut algorithm)?;
reader.read_exact(&mut algorithm).await?;
let algorithm = Algorithm::from_bytes(algorithm)?;
let mut metadata_nonce = vec![0u8; algorithm.nonce_len()];
reader.read_exact(&mut metadata_nonce)?;
reader.read_exact(&mut metadata_nonce).await?;
reader.read_exact(&mut vec![0u8; 24 - metadata_nonce.len()])?;
reader
.read_exact(&mut vec![0u8; 24 - metadata_nonce.len()])
.await?;
let mut metadata_length = [0u8; 8];
reader.read_exact(&mut metadata_length)?;
reader.read_exact(&mut metadata_length).await?;
let metadata_length = u64::from_le_bytes(metadata_length);
#[allow(clippy::cast_possible_truncation)]
let mut metadata = vec![0u8; metadata_length as usize];
reader.read_exact(&mut metadata)?;
reader.read_exact(&mut metadata).await?;
let metadata = Self {
version,

View file

@ -20,12 +20,12 @@
//! )
//! .unwrap();
//! ```
use std::io::Read;
use tokio::io::AsyncReadExt;
use crate::{
crypto::stream::{Algorithm, StreamDecryption, StreamEncryption},
primitives::{generate_nonce, KEY_LEN},
Error, Protected, Result,
primitives::{generate_nonce, Key},
Error, Protected, ProtectedVec, Result,
};
use super::file::FileHeader;
@ -57,26 +57,25 @@ impl FileHeader {
///
/// Preview media needs to be accessed switfly, so a key management system should handle the salt generation.
#[allow(clippy::needless_pass_by_value)]
pub fn add_preview_media(
pub async fn add_preview_media(
&mut self,
version: PreviewMediaVersion,
algorithm: Algorithm,
master_key: Protected<[u8; KEY_LEN]>,
master_key: Protected<Key>,
media: &[u8],
) -> Result<()> {
let media_nonce = generate_nonce(algorithm);
let encrypted_media =
StreamEncryption::encrypt_bytes(master_key, &media_nonce, algorithm, media, &[])?;
StreamEncryption::encrypt_bytes(master_key, &media_nonce, algorithm, media, &[])
.await?;
let pvm = PreviewMedia {
self.preview_media = Some(PreviewMedia {
version,
algorithm,
media_nonce,
media: encrypted_media,
};
self.preview_media = Some(pvm);
});
Ok(())
}
@ -86,26 +85,26 @@ impl FileHeader {
/// All it requires is pre-hashed keys returned from the key manager
///
/// Once provided, a `Vec<u8>` is returned that contains the preview media
pub fn decrypt_preview_media_from_prehashed(
pub async fn decrypt_preview_media_from_prehashed(
&self,
hashed_keys: Vec<Protected<[u8; KEY_LEN]>>,
) -> Result<Protected<Vec<u8>>> {
let master_key = self.decrypt_master_key_from_prehashed(hashed_keys)?;
hashed_keys: Vec<Protected<Key>>,
) -> Result<ProtectedVec<u8>> {
let master_key = self.decrypt_master_key_from_prehashed(hashed_keys).await?;
self.preview_media.as_ref().map_or_else(
|| Err(Error::NoPreviewMedia),
|pvm| {
let pvm = StreamDecryption::decrypt_bytes(
master_key,
&pvm.media_nonce,
pvm.algorithm,
&pvm.media,
&[],
)?;
if let Some(pvm) = self.preview_media.as_ref() {
let pvm = StreamDecryption::decrypt_bytes(
master_key,
&pvm.media_nonce,
pvm.algorithm,
&pvm.media,
&[],
)
.await?;
Ok(pvm)
},
)
Ok(pvm)
} else {
Err(Error::NoPreviewMedia)
}
}
/// This function is what you'll want to use to get the preview media for a file
@ -113,26 +112,26 @@ impl FileHeader {
/// All it requires is the user's password. Hashing is handled for you.
///
/// Once provided, a `Vec<u8>` is returned that contains the preview media
pub fn decrypt_preview_media(
pub async fn decrypt_preview_media(
&self,
password: Protected<Vec<u8>>,
) -> Result<Protected<Vec<u8>>> {
let master_key = self.decrypt_master_key(password)?;
password: ProtectedVec<u8>,
) -> Result<ProtectedVec<u8>> {
let master_key = self.decrypt_master_key(password).await?;
self.preview_media.as_ref().map_or_else(
|| Err(Error::NoPreviewMedia),
|pvm| {
let pvm = StreamDecryption::decrypt_bytes(
master_key,
&pvm.media_nonce,
pvm.algorithm,
&pvm.media,
&[],
)?;
if let Some(pvm) = self.preview_media.as_ref() {
let pvm = StreamDecryption::decrypt_bytes(
master_key,
&pvm.media_nonce,
pvm.algorithm,
&pvm.media,
&[],
)
.await?;
Ok(pvm)
},
)
Ok(pvm)
} else {
Err(Error::NoPreviewMedia)
}
}
}
@ -168,34 +167,36 @@ impl PreviewMedia {
/// The cursor will be left at the end of the preview media item on success
///
/// The cursor will not be rewound on error.
pub fn from_reader<R>(reader: &mut R) -> Result<Self>
pub async fn from_reader<R>(reader: &mut R) -> Result<Self>
where
R: Read,
R: AsyncReadExt + Unpin + Send,
{
let mut version = [0u8; 2];
reader.read_exact(&mut version)?;
reader.read_exact(&mut version).await?;
let version =
PreviewMediaVersion::from_bytes(version).map_err(|_| Error::NoPreviewMedia)?;
match version {
PreviewMediaVersion::V1 => {
let mut algorithm = [0u8; 2];
reader.read_exact(&mut algorithm)?;
reader.read_exact(&mut algorithm).await?;
let algorithm = Algorithm::from_bytes(algorithm)?;
let mut media_nonce = vec![0u8; algorithm.nonce_len()];
reader.read_exact(&mut media_nonce)?;
reader.read_exact(&mut media_nonce).await?;
reader.read_exact(&mut vec![0u8; 24 - media_nonce.len()])?;
reader
.read_exact(&mut vec![0u8; 24 - media_nonce.len()])
.await?;
let mut media_length = [0u8; 8];
reader.read_exact(&mut media_length)?;
reader.read_exact(&mut media_length).await?;
let media_length = u64::from_le_bytes(media_length);
#[allow(clippy::cast_possible_truncation)]
let mut media = vec![0u8; media_length as usize];
reader.read_exact(&mut media)?;
reader.read_exact(&mut media).await?;
let preview_media = Self {
version,

View file

@ -11,9 +11,10 @@
//! let hashed_password = hashing_algorithm.hash(password, salt).unwrap();
//! ```
use crate::primitives::KEY_LEN;
use crate::Protected;
use crate::{primitives::SALT_LEN, Error, Result};
use crate::{
primitives::{Key, Salt, KEY_LEN},
Error, Protected, ProtectedVec, Result,
};
use argon2::Argon2;
use balloon_hash::Balloon;
@ -52,10 +53,10 @@ impl HashingAlgorithm {
#[allow(clippy::needless_pass_by_value)]
pub fn hash(
&self,
password: Protected<Vec<u8>>,
salt: [u8; SALT_LEN],
secret: Option<Protected<Vec<u8>>>,
) -> Result<Protected<[u8; KEY_LEN]>> {
password: ProtectedVec<u8>,
salt: Salt,
secret: Option<ProtectedVec<u8>>,
) -> Result<Protected<Key>> {
match self {
Self::Argon2id(params) => PasswordHasher::argon2id(password, salt, secret, *params),
Self::BalloonBlake3(params) => {
@ -106,11 +107,11 @@ struct PasswordHasher;
impl PasswordHasher {
#[allow(clippy::needless_pass_by_value)]
fn argon2id(
password: Protected<Vec<u8>>,
salt: [u8; SALT_LEN],
secret: Option<Protected<Vec<u8>>>,
password: ProtectedVec<u8>,
salt: Salt,
secret: Option<ProtectedVec<u8>>,
params: Params,
) -> Result<Protected<[u8; KEY_LEN]>> {
) -> Result<Protected<Key>> {
let secret = secret.map_or(Protected::new(vec![]), |k| k);
let mut key = [0u8; KEY_LEN];
@ -129,11 +130,11 @@ impl PasswordHasher {
#[allow(clippy::needless_pass_by_value)]
fn balloon_blake3(
password: Protected<Vec<u8>>,
salt: [u8; SALT_LEN],
secret: Option<Protected<Vec<u8>>>,
password: ProtectedVec<u8>,
salt: Salt,
secret: Option<ProtectedVec<u8>>,
params: Params,
) -> Result<Protected<[u8; KEY_LEN]>> {
) -> Result<Protected<Key>> {
let secret = secret.map_or(Protected::new(vec![]), |k| k);
let mut key = [0u8; KEY_LEN];

View file

@ -35,19 +35,21 @@
//! let keys = key_manager.enumerate_hashed_keys();
//! ```
use std::sync::Mutex;
use tokio::sync::Mutex;
use crate::crypto::stream::{StreamDecryption, StreamEncryption};
use crate::primitives::{
derive_key, generate_master_key, generate_nonce, generate_salt, to_array, OnboardingConfig,
KEY_LEN, LATEST_STORED_KEY, MASTER_PASSWORD_CONTEXT, ROOT_KEY_CONTEXT,
};
// use crate::primitives::{
// derive_key, generate_master_key, generate_nonce, generate_salt, to_array, EncryptedKey, Key,
// OnboardingConfig, Salt, KEY_LEN, LATEST_STORED_KEY, MASTER_PASSWORD_CONTEXT, ROOT_KEY_CONTEXT,
// };
use crate::{
crypto::stream::Algorithm,
primitives::{ENCRYPTED_KEY_LEN, SALT_LEN},
Protected,
crypto::stream::{Algorithm, StreamDecryption, StreamEncryption},
primitives::{
derive_key, generate_master_key, generate_nonce, generate_salt, to_array, EncryptedKey,
Key, OnboardingConfig, Salt, ENCRYPTED_KEY_LEN, KEY_LEN, LATEST_STORED_KEY,
MASTER_PASSWORD_CONTEXT, ROOT_KEY_CONTEXT,
},
Error, Protected, ProtectedVec, Result,
};
use crate::{Error, Result};
use dashmap::{DashMap, DashSet};
use uuid::Uuid;
@ -66,13 +68,13 @@ pub struct StoredKey {
pub version: StoredKeyVersion,
pub algorithm: Algorithm, // encryption algorithm for encrypting the master key. can be changed (requires a re-encryption though)
pub hashing_algorithm: HashingAlgorithm, // hashing algorithm used for hashing the key with the content salt
pub content_salt: [u8; SALT_LEN],
pub content_salt: Salt,
#[cfg_attr(feature = "serde", serde(with = "BigArray"))] // salt used for file data
pub master_key: [u8; ENCRYPTED_KEY_LEN], // this is for encrypting the `key`
pub master_key: EncryptedKey, // this is for encrypting the `key`
pub master_key_nonce: Vec<u8>, // nonce for encrypting the master key
pub key_nonce: Vec<u8>, // nonce used for encrypting the main key
pub key: Vec<u8>, // encrypted. the key stored in spacedrive (e.g. generated 64 char key)
pub salt: [u8; SALT_LEN],
pub salt: Salt,
pub memory_only: bool,
pub automount: bool,
}
@ -89,8 +91,8 @@ pub enum StoredKeyVersion {
/// This contains the plaintext key, and the same key hashed with the content salt.
#[derive(Clone)]
pub struct MountedKey {
pub uuid: Uuid, // used for identification. shared with stored keys
pub hashed_key: Protected<[u8; KEY_LEN]>, // this is hashed with the content salt, for instant access
pub uuid: Uuid, // used for identification. shared with stored keys
pub hashed_key: Protected<Key>, // this is hashed with the content salt, for instant access
}
/// This is the key manager itself.
@ -99,7 +101,7 @@ pub struct MountedKey {
///
/// Use the associated functions to interact with it.
pub struct KeyManager {
root_key: Mutex<Option<Protected<[u8; KEY_LEN]>>>, // the root key for the vault
root_key: Mutex<Option<Protected<Key>>>, // the root key for the vault
verification_key: Mutex<Option<StoredKey>>,
keystore: DashMap<Uuid, StoredKey>,
keymount: DashMap<Uuid, MountedKey>,
@ -110,7 +112,7 @@ pub struct KeyManager {
/// The `KeyManager` functions should be used for all key-related management.
impl KeyManager {
/// Initialize the Key Manager with `StoredKeys` retrieved from Prisma
pub fn new(stored_keys: Vec<StoredKey>) -> Result<Self> {
pub async fn new(stored_keys: Vec<StoredKey>) -> Result<Self> {
let keymanager = Self {
root_key: Mutex::new(None),
verification_key: Mutex::new(None),
@ -120,7 +122,7 @@ impl KeyManager {
mounting_queue: DashSet::new(),
};
keymanager.populate_keystore(stored_keys)?;
keymanager.populate_keystore(stored_keys).await?;
Ok(keymanager)
}
@ -131,7 +133,7 @@ impl KeyManager {
///
/// It will also generate a verification key, which should be written to the database.
#[allow(clippy::needless_pass_by_value)]
pub fn onboarding(config: OnboardingConfig) -> Result<StoredKey> {
pub async fn onboarding(config: OnboardingConfig) -> Result<StoredKey> {
let content_salt = generate_salt();
let secret_key = config.secret_key.map(Self::convert_secret_key_string);
@ -156,13 +158,16 @@ impl KeyManager {
let root_key_nonce = generate_nonce(algorithm);
// Encrypt the master key with the hashed master password
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(StreamEncryption::encrypt_bytes(
derive_key(hashed_password, salt, MASTER_PASSWORD_CONTEXT),
&master_key_nonce,
algorithm,
master_key.expose(),
&[],
)?)?;
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(
StreamEncryption::encrypt_bytes(
derive_key(hashed_password, salt, MASTER_PASSWORD_CONTEXT),
&master_key_nonce,
algorithm,
master_key.expose(),
&[],
)
.await?,
)?;
let encrypted_root_key = StreamEncryption::encrypt_bytes(
master_key,
@ -170,7 +175,8 @@ impl KeyManager {
algorithm,
root_key.expose(),
&[],
)?;
)
.await?;
let verification_key = StoredKey {
uuid,
@ -195,14 +201,14 @@ impl KeyManager {
/// It's suitable for when you created the key manager without populating it.
///
/// This also detects the nil-UUID master passphrase verification key
pub fn populate_keystore(&self, stored_keys: Vec<StoredKey>) -> Result<()> {
pub async fn populate_keystore(&self, stored_keys: Vec<StoredKey>) -> Result<()> {
for key in stored_keys {
if self.keystore.contains_key(&key.uuid) {
continue;
}
if key.uuid.is_nil() {
*self.verification_key.lock()? = Some(key);
*self.verification_key.lock().await = Some(key);
} else {
self.keystore.insert(key.uuid, key);
}
@ -212,11 +218,11 @@ impl KeyManager {
}
/// This function removes a key from the keystore, the keymount and it's unset as the default.
pub fn remove_key(&self, uuid: Uuid) -> Result<()> {
pub async fn remove_key(&self, uuid: Uuid) -> Result<()> {
if self.keystore.contains_key(&uuid) {
// if key is default, clear it
// do this manually to prevent deadlocks
let mut default = self.default.lock()?;
let mut default = self.default.lock().await;
if *default == Some(uuid) {
*default = None;
}
@ -235,7 +241,7 @@ impl KeyManager {
}
#[allow(clippy::needless_pass_by_value)]
pub fn change_master_password(
pub async fn change_master_password(
&self,
master_password: Protected<String>,
algorithm: Algorithm,
@ -257,19 +263,22 @@ impl KeyManager {
let master_key = generate_master_key();
let master_key_nonce = generate_nonce(algorithm);
let root_key = self.get_root_key()?;
let root_key = self.get_root_key().await?;
let root_key_nonce = generate_nonce(algorithm);
let salt = generate_salt();
// Encrypt the master key with the hashed master password
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(StreamEncryption::encrypt_bytes(
derive_key(hashed_password, salt, MASTER_PASSWORD_CONTEXT),
&master_key_nonce,
algorithm,
master_key.expose(),
&[],
)?)?;
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(
StreamEncryption::encrypt_bytes(
derive_key(hashed_password, salt, MASTER_PASSWORD_CONTEXT),
&master_key_nonce,
algorithm,
master_key.expose(),
&[],
)
.await?,
)?;
let encrypted_root_key = StreamEncryption::encrypt_bytes(
master_key,
@ -277,7 +286,8 @@ impl KeyManager {
algorithm,
root_key.expose(),
&[],
)?;
)
.await?;
let verification_key = StoredKey {
uuid,
@ -294,7 +304,7 @@ impl KeyManager {
automount: false,
};
*self.verification_key.lock()? = Some(verification_key.clone());
*self.verification_key.lock().await = Some(verification_key.clone());
Ok(verification_key)
}
@ -303,7 +313,7 @@ impl KeyManager {
///
/// It returns a `Vec<StoredKey>` so they can be written to Prisma
#[allow(clippy::needless_pass_by_value)]
pub fn import_keystore_backup(
pub async fn import_keystore_backup(
&self,
master_password: Protected<String>, // at the time of the backup
secret_key: Option<Protected<String>>, // at the time of the backup
@ -347,7 +357,8 @@ impl KeyManager {
old_verification_key.algorithm,
&old_verification_key.master_key,
&[],
)?;
)
.await?;
// get the root key from the backup
let old_root_key = StreamDecryption::decrypt_bytes(
@ -356,7 +367,8 @@ impl KeyManager {
old_verification_key.algorithm,
&old_verification_key.key,
&[],
)?;
)
.await?;
Protected::new(to_array(old_root_key.into_inner())?)
}
@ -379,6 +391,7 @@ impl KeyManager {
&key.master_key,
&[],
)
.await
.map_or(Err(Error::IncorrectPassword), |v| {
Ok(Protected::new(to_array::<KEY_LEN>(v.into_inner())?))
})?;
@ -389,13 +402,16 @@ impl KeyManager {
let salt = generate_salt();
// encrypt the master key with the current root key
let encrypted_master_key = to_array(StreamEncryption::encrypt_bytes(
derive_key(self.get_root_key()?, salt, ROOT_KEY_CONTEXT),
&master_key_nonce,
key.algorithm,
master_key.expose(),
&[],
)?)?;
let encrypted_master_key = to_array(
StreamEncryption::encrypt_bytes(
derive_key(self.get_root_key().await?, salt, ROOT_KEY_CONTEXT),
&master_key_nonce,
key.algorithm,
master_key.expose(),
&[],
)
.await?,
)?;
let mut updated_key = key.clone();
updated_key.master_key_nonce = master_key_nonce;
@ -411,7 +427,7 @@ impl KeyManager {
Ok(reencrypted_keys)
}
/// This requires both the master password and the secret key
/// This is used for unlocking the key manager, and requires both the master password and the secret key.
///
/// The master password and secret key are hashed together.
/// This minimises the risk of an attacker obtaining the master password, as both of these are required to unlock the vault (and both should be stored separately).
@ -422,24 +438,24 @@ impl KeyManager {
///
/// Note: The invalidation function is ran after updating the queue both times, so it isn't required externally.
#[allow(clippy::needless_pass_by_value)]
pub fn set_master_password<F>(
pub async fn unlock<F>(
&self,
master_password: Protected<String>,
secret_key: Option<Protected<String>>,
invalidate: F,
) -> Result<()>
where
F: Fn(),
F: Fn() + Send,
{
let uuid = Uuid::nil();
if self.has_master_password()? {
if self.has_master_password().await? {
return Err(Error::KeyAlreadyMounted);
} else if self.is_queued(uuid) {
return Err(Error::KeyAlreadyQueued);
}
let verification_key = (*self.verification_key.lock()?)
let verification_key = (*self.verification_key.lock().await)
.as_ref()
.map_or(Err(Error::NoVerificationKey), |k| Ok(k.clone()))?;
@ -473,12 +489,13 @@ impl KeyManager {
&verification_key.master_key,
&[],
)
.await
.map_err(|_| {
self.remove_from_queue(uuid).ok();
Error::IncorrectKeymanagerDetails
})?;
*self.root_key.lock()? = Some(Protected::new(
*self.root_key.lock().await = Some(Protected::new(
to_array(
StreamDecryption::decrypt_bytes(
Protected::new(to_array(master_key.into_inner())?),
@ -486,7 +503,8 @@ impl KeyManager {
verification_key.algorithm,
&verification_key.key,
&[],
)?
)
.await?
.expose()
.clone(),
)
@ -512,101 +530,113 @@ impl KeyManager {
/// This is to ensure that only functions which require access to the mounted key receive it.
///
/// We could add a log to this, so that the user can view mounts
pub fn mount(&self, uuid: Uuid) -> Result<()> {
pub async fn mount(&self, uuid: Uuid) -> Result<()> {
if self.keymount.get(&uuid).is_some() {
return Err(Error::KeyAlreadyMounted);
} else if self.is_queued(uuid) {
return Err(Error::KeyAlreadyQueued);
}
self.keystore
.get(&uuid)
.map_or(Err(Error::KeyNotFound), |stored_key| {
match stored_key.version {
StoredKeyVersion::V1 => {
self.mounting_queue.insert(uuid);
if let Some(stored_key) = self.keystore.get(&uuid) {
match stored_key.version {
StoredKeyVersion::V1 => {
self.mounting_queue.insert(uuid);
let master_key = StreamDecryption::decrypt_bytes(
derive_key(self.get_root_key()?, stored_key.salt, ROOT_KEY_CONTEXT),
&stored_key.master_key_nonce,
stored_key.algorithm,
&stored_key.master_key,
&[],
)
.map_or_else(
|_| {
self.remove_from_queue(uuid).ok();
Err(Error::IncorrectPassword)
},
|v| Ok(Protected::new(to_array(v.into_inner())?)),
)?;
// Decrypt the StoredKey using the decrypted master key
let key = StreamDecryption::decrypt_bytes(
master_key,
&stored_key.key_nonce,
stored_key.algorithm,
&stored_key.key,
&[],
)
let master_key = StreamDecryption::decrypt_bytes(
derive_key(
self.get_root_key().await?,
stored_key.salt,
ROOT_KEY_CONTEXT,
),
&stored_key.master_key_nonce,
stored_key.algorithm,
&stored_key.master_key,
&[],
)
.await
.map_or_else(
|_| {
self.remove_from_queue(uuid).ok();
Err(Error::IncorrectPassword)
},
|v| Ok(Protected::new(to_array(v.into_inner())?)),
)?;
// Decrypt the StoredKey using the decrypted master key
let key = StreamDecryption::decrypt_bytes(
master_key,
&stored_key.key_nonce,
stored_key.algorithm,
&stored_key.key,
&[],
)
.await
.map_err(|e| {
self.remove_from_queue(uuid).ok();
e
})?;
// Hash the key once with the parameters/algorithm the user selected during first mount
let hashed_key = stored_key
.hashing_algorithm
.hash(key, stored_key.content_salt, None)
.map_err(|e| {
self.remove_from_queue(uuid).ok();
e
})?;
// Hash the key once with the parameters/algorithm the user selected during first mount
let hashed_key = stored_key
.hashing_algorithm
.hash(key, stored_key.content_salt, None)
.map_err(|e| {
self.remove_from_queue(uuid).ok();
e
})?;
self.keymount.insert(
uuid,
MountedKey {
uuid: stored_key.uuid,
hashed_key,
},
);
self.keymount.insert(
uuid,
MountedKey {
uuid: stored_key.uuid,
hashed_key,
},
);
self.remove_from_queue(uuid)?;
}
self.remove_from_queue(uuid)?;
}
}
Ok(())
})
Ok(())
} else {
Err(Error::KeyNotFound)
}
}
/// This function is used for getting the key value itself, from a given UUID.
///
/// The master password/salt needs to be present, so we are able to decrypt the key itself from the stored key.
pub fn get_key(&self, uuid: Uuid) -> Result<Protected<Vec<u8>>> {
self.keystore
.get(&uuid)
.map_or(Err(Error::KeyNotFound), |stored_key| {
let master_key = StreamDecryption::decrypt_bytes(
derive_key(self.get_root_key()?, stored_key.salt, ROOT_KEY_CONTEXT),
&stored_key.master_key_nonce,
stored_key.algorithm,
&stored_key.master_key,
&[],
)
.map_or(Err(Error::IncorrectPassword), |k| {
Ok(Protected::new(to_array(k.into_inner())?))
})?;
pub async fn get_key(&self, uuid: Uuid) -> Result<ProtectedVec<u8>> {
if let Some(stored_key) = self.keystore.get(&uuid) {
let master_key = StreamDecryption::decrypt_bytes(
derive_key(
self.get_root_key().await?,
stored_key.salt,
ROOT_KEY_CONTEXT,
),
&stored_key.master_key_nonce,
stored_key.algorithm,
&stored_key.master_key,
&[],
)
.await
.map_or(Err(Error::IncorrectPassword), |k| {
Ok(Protected::new(to_array(k.into_inner())?))
})?;
// Decrypt the StoredKey using the decrypted master key
let key = StreamDecryption::decrypt_bytes(
master_key,
&stored_key.key_nonce,
stored_key.algorithm,
&stored_key.key,
&[],
)?;
// Decrypt the StoredKey using the decrypted master key
let key = StreamDecryption::decrypt_bytes(
master_key,
&stored_key.key_nonce,
stored_key.algorithm,
&stored_key.key,
&[],
)
.await?;
Ok(key)
})
Ok(key)
} else {
Err(Error::KeyNotFound)
}
}
/// This function is used to add a new key/password to the keystore.
@ -621,14 +651,14 @@ impl KeyManager {
///
/// You may optionally provide a content salt, if not one will be generated (used primarily for password-based decryption)
#[allow(clippy::needless_pass_by_value)]
pub fn add_to_keystore(
pub async fn add_to_keystore(
&self,
key: Protected<Vec<u8>>,
key: ProtectedVec<u8>,
algorithm: Algorithm,
hashing_algorithm: HashingAlgorithm,
memory_only: bool,
automount: bool,
content_salt: Option<[u8; SALT_LEN]>,
content_salt: Option<Salt>,
) -> Result<Uuid> {
let uuid = uuid::Uuid::new_v4();
@ -643,17 +673,20 @@ impl KeyManager {
let salt = generate_salt();
// Encrypt the master key with a derived key (derived from the root key)
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(StreamEncryption::encrypt_bytes(
derive_key(self.get_root_key()?, salt, ROOT_KEY_CONTEXT),
&master_key_nonce,
algorithm,
master_key.expose(),
&[],
)?)?;
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(
StreamEncryption::encrypt_bytes(
derive_key(self.get_root_key().await?, salt, ROOT_KEY_CONTEXT),
&master_key_nonce,
algorithm,
master_key.expose(),
&[],
)
.await?,
)?;
// Encrypt the actual key (e.g. user-added/autogenerated, text-encodable)
let encrypted_key =
StreamEncryption::encrypt_bytes(master_key, &key_nonce, algorithm, &key, &[])?;
StreamEncryption::encrypt_bytes(master_key, &key_nonce, algorithm, &key, &[]).await?;
// Insert it into the Keystore
self.keystore.insert(
@ -679,7 +712,7 @@ impl KeyManager {
}
#[allow(clippy::needless_pass_by_value)]
fn convert_secret_key_string(secret_key: Protected<String>) -> Protected<Vec<u8>> {
fn convert_secret_key_string(secret_key: Protected<String>) -> ProtectedVec<u8> {
Protected::new(secret_key.expose().as_bytes().to_vec())
}
@ -700,9 +733,9 @@ impl KeyManager {
}
/// This allows you to set the default key
pub fn set_default(&self, uuid: Uuid) -> Result<()> {
pub async fn set_default(&self, uuid: Uuid) -> Result<()> {
if self.keystore.contains_key(&uuid) {
*self.default.lock()? = Some(uuid);
*self.default.lock().await = Some(uuid);
Ok(())
} else {
Err(Error::KeyNotFound)
@ -710,18 +743,23 @@ impl KeyManager {
}
/// This allows you to get the default key's ID
pub fn get_default(&self) -> Result<Uuid> {
self.default.lock()?.ok_or(Error::NoDefaultKeySet)
pub async fn get_default(&self) -> Result<Uuid> {
self.default.lock().await.ok_or(Error::NoDefaultKeySet)
}
/// This should ONLY be used internally.
fn get_root_key(&self) -> Result<Protected<[u8; KEY_LEN]>> {
self.root_key.lock()?.clone().ok_or(Error::NoMasterPassword)
async fn get_root_key(&self) -> Result<Protected<Key>> {
self.root_key
.lock()
.await
.clone()
.ok_or(Error::NoMasterPassword)
}
pub fn get_verification_key(&self) -> Result<StoredKey> {
pub async fn get_verification_key(&self) -> Result<StoredKey> {
self.verification_key
.lock()?
.lock()
.await
.clone()
.ok_or(Error::NoVerificationKey)
}
@ -753,11 +791,11 @@ impl KeyManager {
///
/// This means we don't need to keep super specific track of which key goes to which file, and we can just throw all of them at it.
#[must_use]
pub fn enumerate_hashed_keys(&self) -> Vec<Protected<[u8; KEY_LEN]>> {
pub fn enumerate_hashed_keys(&self) -> Vec<Protected<Key>> {
self.keymount
.iter()
.map(|mounted_key| mounted_key.hashed_key.clone())
.collect::<Vec<Protected<[u8; KEY_LEN]>>>()
.collect::<Vec<Protected<Key>>>()
}
/// This function is for converting a memory-only key to a saved key which syncs to the library.
@ -784,8 +822,8 @@ impl KeyManager {
}
/// This function is for removing a previously-added master password
pub fn clear_root_key(&self) -> Result<()> {
*self.root_key.lock()? = None;
pub async fn clear_root_key(&self) -> Result<()> {
*self.root_key.lock().await = None;
Ok(())
}
@ -793,8 +831,8 @@ impl KeyManager {
/// This function is used for seeing if the key manager has a master password.
///
/// Technically this checks for the root key, but it makes no difference to the front end.
pub fn has_master_password(&self) -> Result<bool> {
Ok(self.root_key.lock()?.is_some())
pub async fn has_master_password(&self) -> Result<bool> {
Ok(self.root_key.lock().await.is_some())
}
/// This function is used for unmounting all keys at once.

View file

@ -24,6 +24,7 @@ pub use aead::Payload;
// Make this easier to use (e.g. `sd_crypto::Protected`)
pub use protected::Protected;
pub use protected::ProtectedVec;
// Re-export zeroize so it can be used elsewhere
pub use zeroize::Zeroize;

View file

@ -44,6 +44,10 @@ pub const MASTER_PASSWORD_CONTEXT: &str =
"spacedrive 2022-12-14 15:35:41 master password hash derivation"; // used for deriving keys from the master password hash
pub const FILE_KEY_CONTEXT: &str = "spacedrive 2022-12-14 12:54:12 file key derivation"; // used for deriving keys from user key/content salt hashes (for file encryption)
pub type Key = [u8; KEY_LEN];
pub type EncryptedKey = [u8; ENCRYPTED_KEY_LEN];
pub type Salt = [u8; SALT_LEN];
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[cfg_attr(feature = "rspc", derive(specta::Type))]
@ -70,7 +74,7 @@ pub fn generate_nonce(algorithm: Algorithm) -> Vec<u8> {
///
/// This function uses `ChaCha20Rng` for generating cryptographically-secure random data
#[must_use]
pub fn generate_salt() -> [u8; SALT_LEN] {
pub fn generate_salt() -> Salt {
let mut salt = [0u8; SALT_LEN];
rand_chacha::ChaCha20Rng::from_entropy().fill_bytes(&mut salt);
salt
@ -82,7 +86,7 @@ pub fn generate_salt() -> [u8; SALT_LEN] {
///
/// This function uses `ChaCha20Rng` for generating cryptographically-secure random data
#[must_use]
pub fn generate_master_key() -> Protected<[u8; KEY_LEN]> {
pub fn generate_master_key() -> Protected<Key> {
let mut master_key = [0u8; KEY_LEN];
rand_chacha::ChaCha20Rng::from_entropy().fill_bytes(&mut master_key);
Protected::new(master_key)
@ -90,11 +94,7 @@ pub fn generate_master_key() -> Protected<[u8; KEY_LEN]> {
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn derive_key(
key: Protected<[u8; KEY_LEN]>,
salt: [u8; SALT_LEN],
context: &str,
) -> Protected<[u8; KEY_LEN]> {
pub fn derive_key(key: Protected<Key>, salt: Salt, context: &str) -> Protected<Key> {
let mut input = key.expose().to_vec();
input.extend_from_slice(&salt);
@ -107,7 +107,7 @@ pub fn derive_key(
/// This is used for converting a `Vec<u8>` to an array of bytes
///
/// It's main usage is for converting an encrypted master key from a `Vec<u8>` to `[u8; ENCRYPTED_KEY_LEN]`
/// It's main usage is for converting an encrypted master key from a `Vec<u8>` to `EncryptedKey`
///
/// As the master key is encrypted at this point, it does not need to be `Protected<>`
///

View file

@ -31,6 +31,7 @@
use std::{fmt::Debug, mem::swap};
use zeroize::Zeroize;
pub type ProtectedVec<T> = Protected<Vec<T>>;
#[derive(Clone)]
pub struct Protected<T>
where

View file

@ -55,8 +55,8 @@ export type Procedures = {
{ key: "keys.mount", input: LibraryArgs<string>, result: null } |
{ key: "keys.restoreKeystore", input: LibraryArgs<RestoreBackupArgs>, result: number } |
{ key: "keys.setDefault", input: LibraryArgs<string>, result: null } |
{ key: "keys.setMasterPassword", input: LibraryArgs<SetMasterPasswordArgs>, result: null } |
{ key: "keys.syncKeyToLibrary", input: LibraryArgs<string>, result: null } |
{ key: "keys.unlockKeyManager", input: LibraryArgs<UnlockKeyManagerArgs>, result: null } |
{ key: "keys.unmount", input: LibraryArgs<string>, result: null } |
{ key: "keys.unmountAll", input: LibraryArgs<null>, result: null } |
{ key: "keys.updateAutomountStatus", input: LibraryArgs<AutomountUpdateArgs>, result: null } |
@ -177,8 +177,6 @@ export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChil
export interface SetFavoriteArgs { id: number, favorite: boolean }
export interface SetMasterPasswordArgs { password: string, secret_key: string | null }
export interface SetNoteArgs { id: number, note: string | null }
export interface Statistics { id: number, date_captured: string, total_object_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string }
@ -195,6 +193,8 @@ export interface TagCreateArgs { name: string, color: string }
export interface TagUpdateArgs { id: number, name: string | null, color: string | null }
export interface UnlockKeyManagerArgs { password: string, secret_key: string | null }
export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean }
export interface FilePathWithObject { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, object_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, object: Object | null }

View file

@ -41,7 +41,7 @@ export const DecryptFileDialog = (props: DecryptDialogProps) => {
const decryptFile = useLibraryMutation('files.decryptFiles', {
onSuccess: () => {
showAlertDialog({
title: 'Info',
title: 'Success',
value:
'The decryption job has started successfully. You may track the progress in the job overview panel.'
});
@ -59,26 +59,24 @@ export const DecryptFileDialog = (props: DecryptDialogProps) => {
const PasswordCurrentEyeIcon = show.password ? EyeSlash : Eye;
const form = useZodForm({
schema,
defaultValues: {
type: hasMountedKeys ? 'key' : 'password',
saveToKeyManager: true
}
saveToKeyManager: true,
outputPath: '',
password: ''
},
schema
});
const onSubmit = form.handleSubmit((data) => {
const output = data.outputPath !== '' ? data.outputPath : null;
const pw = data.type === 'password' ? data.password : null;
const save = data.type === 'password' ? data.saveToKeyManager : null;
return decryptFile.mutateAsync({
const onSubmit = form.handleSubmit((data) =>
decryptFile.mutateAsync({
location_id: props.location_id,
path_id: props.path_id,
output_path: output,
password: pw,
save_to_library: save
});
});
output_path: data.outputPath !== '' ? data.outputPath : null,
password: data.type === 'password' ? data.password : null,
save_to_library: data.type === 'password' ? data.saveToKeyManager : null
})
);
return (
<Dialog

View file

@ -60,6 +60,7 @@ export const EncryptFileDialog = ({ ...props }: EncryptDialogProps) => {
});
const form = useZodForm({
defaultValues: { encryptionAlgo: 'XChaCha20Poly1305', outputPath: '' },
schema
});
@ -147,6 +148,7 @@ export const EncryptFileDialog = ({ ...props }: EncryptDialogProps) => {
className="mt-2 text-gray-400/80"
onChange={() => {}}
value={form.watch('hashingAlgo')}
disabled
>
<SelectOption value="Argon2id-s">Argon2id (standard)</SelectOption>
<SelectOption value="Argon2id-h">Argon2id (hardened)</SelectOption>

View file

@ -52,7 +52,9 @@ export const MasterPasswordChangeDialog = (props: MasterPasswordChangeDialogProp
schema,
defaultValues: {
encryptionAlgo: 'XChaCha20Poly1305',
hashingAlgo: 'Argon2id-s'
hashingAlgo: 'Argon2id-s',
masterPassword: '',
masterPassword2: ''
}
});
@ -64,13 +66,11 @@ export const MasterPasswordChangeDialog = (props: MasterPasswordChangeDialogProp
});
} else {
const hashing_algorithm = getHashingAlgorithmSettings(data.hashingAlgo);
const sk = data.secretKey || null;
return changeMasterPassword.mutateAsync({
algorithm: data.encryptionAlgo as Algorithm,
hashing_algorithm,
password: data.masterPassword,
secret_key: sk
secret_key: data.secretKey || null
});
}
});

View file

@ -2,6 +2,7 @@ import { Eye, EyeSlash, Gear, Lock } from 'phosphor-react';
import { useState } from 'react';
import { useLibraryMutation, useLibraryQuery } from '@sd/client';
import { Button, ButtonLink, Input, Tabs } from '@sd/ui';
import { showAlertDialog } from '~/util/dialog';
import { DefaultProps } from '../primitive/types';
import { KeyList } from './KeyList';
import { KeyMounter } from './KeyMounter';
@ -11,9 +12,12 @@ export type KeyManagerProps = DefaultProps;
export function KeyManager(props: KeyManagerProps) {
const hasMasterPw = useLibraryQuery(['keys.hasMasterPassword']);
const isKeyManagerUnlocking = useLibraryQuery(['keys.isKeyManagerUnlocking']);
const setMasterPasswordMutation = useLibraryMutation('keys.setMasterPassword', {
const setMasterPasswordMutation = useLibraryMutation('keys.unlockKeyManager', {
onError: () => {
alert('Incorrect information provided.');
showAlertDialog({
title: 'Unlock Error',
value: 'The information provided to the key manager was incorrect'
});
}
});
const unmountAll = useLibraryMutation('keys.unmountAll');

View file

@ -76,7 +76,7 @@ export const KeyMounterDropdown = ({
export default function KeysSettings() {
const platform = usePlatform();
const hasMasterPw = useLibraryQuery(['keys.hasMasterPassword']);
const setMasterPasswordMutation = useLibraryMutation('keys.setMasterPassword', {
const setMasterPasswordMutation = useLibraryMutation('keys.unlockKeyManager', {
onError: () => {
showAlertDialog({
title: 'Unlock Error',