diff --git a/Cargo.lock b/Cargo.lock index 88210f468..b61917e84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/apps/cli/Cargo.toml b/apps/cli/Cargo.toml index 5707542af..43d8895f0 100644 --- a/apps/cli/Cargo.toml +++ b/apps/cli/Cargo.toml @@ -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"] } diff --git a/apps/cli/src/main.rs b/apps/cli/src/main.rs index d35d67a6b..0d5dba93a 100644 --- a/apps/cli/src/main.rs +++ b/apps/cli/src/main.rs @@ -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} diff --git a/core/src/api/keys.rs b/core/src/api/keys.rs index b0fc99872..b8dacec05 100644 --- a/core/src/api/keys.rs +++ b/core/src/api/keys.rs @@ -23,7 +23,7 @@ pub struct KeyAddArgs { } #[derive(Type, Deserialize)] -pub struct SetMasterPasswordArgs { +pub struct UnlockKeyManagerArgs { password: String, secret_key: Option, } @@ -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 diff --git a/core/src/library/library_manager.rs b/core/src/library/library_manager.rs index 1750a4bbc..24d476b14 100644 --- a/core/src/library/library_manager.rs +++ b/core/src/library/library_manager.rs @@ -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); diff --git a/core/src/object/fs/decrypt.rs b/core/src/object/fs/decrypt.rs index abe1fb81f..719828c97 100644 --- a/core/src/object/fs/decrypt.rs +++ b/core/src/object/fs/decrypt.rs @@ -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) diff --git a/core/src/object/fs/encrypt.rs b/core/src/object/fs/encrypt.rs index 6e98a4261..db3c4cb25 100644 --- a/core/src/object/fs/encrypt.rs +++ b/core/src/object/fs/encrypt.rs @@ -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", diff --git a/crates/crypto/Cargo.toml b/crates/crypto/Cargo.toml index c565cc389..dea91ab93 100644 --- a/crates/crypto/Cargo.toml +++ b/crates/crypto/Cargo.toml @@ -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 diff --git a/crates/crypto/benches/aes-256-gcm.rs b/crates/crypto/benches/aes-256-gcm.rs index de57033d7..a58cc790d 100644 --- a/crates/crypto/benches/aes-256-gcm.rs +++ b/crates/crypto/benches/aes-256-gcm.rs @@ -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); diff --git a/crates/crypto/benches/argon2id.rs b/crates/crypto/benches/argon2id.rs index 488bce1c1..88b2adc08 100644 --- a/crates/crypto/benches/argon2id.rs +++ b/crates/crypto/benches/argon2id.rs @@ -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); diff --git a/crates/crypto/benches/xchacha20-poly1305.rs b/crates/crypto/benches/xchacha20-poly1305.rs index 682395390..75fce0ea0 100644 --- a/crates/crypto/benches/xchacha20-poly1305.rs +++ b/crates/crypto/benches/xchacha20-poly1305.rs @@ -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); diff --git a/crates/crypto/examples/single_file.rs b/crates/crypto/examples/single_file.rs index d8a5f1a10..142d6a0e6 100644 --- a/crates/crypto/examples/single_file.rs +++ b/crates/crypto/examples/single_file.rs @@ -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; } diff --git a/crates/crypto/examples/single_file_with_metadata.rs b/crates/crypto/examples/single_file_with_metadata.rs index c61596dea..ee48c1e00 100644 --- a/crates/crypto/examples/single_file_with_metadata.rs +++ b/crates/crypto/examples/single_file_with_metadata.rs @@ -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; } diff --git a/crates/crypto/examples/single_file_with_preview_media.rs b/crates/crypto/examples/single_file_with_preview_media.rs index b6d822e99..032f87447 100644 --- a/crates/crypto/examples/single_file_with_preview_media.rs +++ b/crates/crypto/examples/single_file_with_preview_media.rs @@ -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; } diff --git a/crates/crypto/src/crypto/stream.rs b/crates/crypto/src/crypto/stream.rs index 83e8cef15..e6e431e90 100644 --- a/crates/crypto/src/crypto/stream.rs +++ b/crates/crypto/src/crypto/stream.rs @@ -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 { + pub fn new(key: Protected, nonce: &[u8], algorithm: Algorithm) -> Result { 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(mut self, mut reader: R, mut writer: W, aad: &[u8]) -> Result<()> + pub async fn encrypt_streams( + 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, 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 { + pub fn new(key: Protected, nonce: &[u8], algorithm: Algorithm) -> Result { 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(mut self, mut reader: R, mut writer: W, aad: &[u8]) -> Result<()> + pub async fn decrypt_streams( + 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, nonce: &[u8], algorithm: Algorithm, bytes: &[u8], aad: &[u8], - ) -> Result>> { + ) -> Result> { let mut writer = Cursor::new(Vec::::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()))) } } diff --git a/crates/crypto/src/header/file.rs b/crates/crypto/src/header/file.rs index 79b49c58a..60b3659ef 100644 --- a/crates/crypto/src/header/file.rs +++ b/crates/crypto/src/header/file.rs @@ -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>, - ) -> Result> { + pub async fn decrypt_master_key(&self, password: ProtectedVec) -> Result> { 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::(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>) -> Result { - 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::(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(&self, writer: &mut W) -> Result<()> + pub async fn write(&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>, - ) -> Result> { + hashed_keys: Vec>, + ) -> Result> { 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::(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::(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) -> Result { + 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(reader: &mut R) -> Result<(Self, Vec)> + pub async fn from_reader(reader: &mut R) -> Result<(Self, Vec)> 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 = 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::, Error>(None) - }, - |metadata| Ok(Some(metadata)), - )?; + )) + .await?; + Ok::, 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::, 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::, Error>(None) - }, - |preview_media| Ok(Some(preview_media)), - )?; + Ok(None) + }?; Self { version, diff --git a/crates/crypto/src/header/keyslot.rs b/crates/crypto/src/header/keyslot.rs index 9f8ffa830..e15b1b7c4 100644 --- a/crates/crypto/src/header/keyslot.rs +++ b/crates/crypto/src/header/keyslot.rs @@ -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, } @@ -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, + master_key: Protected, ) -> Result { let nonce = generate_nonce(algorithm); let salt = generate_salt(); - let encrypted_master_key = to_array::(StreamEncryption::encrypt_bytes( - derive_key(hashed_key, salt, FILE_KEY_CONTEXT), - &nonce, - algorithm, - master_key.expose(), - &[], - )?)?; + let encrypted_master_key = to_array::( + 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>) -> Result>> { + pub async fn decrypt_master_key(&self, password: ProtectedVec) -> Result> { 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>> { + key: Protected, + ) -> Result> { 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 diff --git a/crates/crypto/src/header/metadata.rs b/crates/crypto/src/header/metadata.rs index 829349a1d..e6965c9ea 100644 --- a/crates/crypto/src/header/metadata.rs +++ b/crates/crypto/src/header/metadata.rs @@ -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( + pub async fn add_metadata( &mut self, version: MetadataVersion, algorithm: Algorithm, - master_key: Protected<[u8; KEY_LEN]>, + master_key: Protected, 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( + pub async fn decrypt_metadata_from_prehashed( &self, - hashed_keys: Vec>, + hashed_keys: Vec>, ) -> Result 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::(&metadata).map_err(|_| Error::Serialization) - }, - ) + serde_json::from_slice::(&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(&self, password: Protected>) -> Result + pub async fn decrypt_metadata(&self, password: ProtectedVec) -> Result 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::(&metadata).map_err(|_| Error::Serialization) - }, - ) + serde_json::from_slice::(&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(reader: &mut R) -> Result + pub async fn from_reader(reader: &mut R) -> Result 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, diff --git a/crates/crypto/src/header/preview_media.rs b/crates/crypto/src/header/preview_media.rs index aa835da02..4481a5009 100644 --- a/crates/crypto/src/header/preview_media.rs +++ b/crates/crypto/src/header/preview_media.rs @@ -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, 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` 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>, - ) -> Result>> { - let master_key = self.decrypt_master_key_from_prehashed(hashed_keys)?; + hashed_keys: Vec>, + ) -> Result> { + 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` is returned that contains the preview media - pub fn decrypt_preview_media( + pub async fn decrypt_preview_media( &self, - password: Protected>, - ) -> Result>> { - let master_key = self.decrypt_master_key(password)?; + password: ProtectedVec, + ) -> Result> { + 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(reader: &mut R) -> Result + pub async fn from_reader(reader: &mut R) -> Result 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, diff --git a/crates/crypto/src/keys/hashing.rs b/crates/crypto/src/keys/hashing.rs index 11a945043..ccc810156 100644 --- a/crates/crypto/src/keys/hashing.rs +++ b/crates/crypto/src/keys/hashing.rs @@ -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>, - salt: [u8; SALT_LEN], - secret: Option>>, - ) -> Result> { + password: ProtectedVec, + salt: Salt, + secret: Option>, + ) -> Result> { 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>, - salt: [u8; SALT_LEN], - secret: Option>>, + password: ProtectedVec, + salt: Salt, + secret: Option>, params: Params, - ) -> Result> { + ) -> Result> { 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>, - salt: [u8; SALT_LEN], - secret: Option>>, + password: ProtectedVec, + salt: Salt, + secret: Option>, params: Params, - ) -> Result> { + ) -> Result> { let secret = secret.map_or(Protected::new(vec![]), |k| k); let mut key = [0u8; KEY_LEN]; diff --git a/crates/crypto/src/keys/keymanager.rs b/crates/crypto/src/keys/keymanager.rs index 567412b1a..c289e6fd1 100644 --- a/crates/crypto/src/keys/keymanager.rs +++ b/crates/crypto/src/keys/keymanager.rs @@ -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, // nonce for encrypting the master key pub key_nonce: Vec, // nonce used for encrypting the main key pub key: Vec, // 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, // 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>>, // the root key for the vault + root_key: Mutex>>, // the root key for the vault verification_key: Mutex>, keystore: DashMap, keymount: DashMap, @@ -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) -> Result { + pub async fn new(stored_keys: Vec) -> Result { 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 { + pub async fn onboarding(config: OnboardingConfig) -> Result { 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::(StreamEncryption::encrypt_bytes( - derive_key(hashed_password, salt, MASTER_PASSWORD_CONTEXT), - &master_key_nonce, - algorithm, - master_key.expose(), - &[], - )?)?; + let encrypted_master_key = to_array::( + 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) -> Result<()> { + pub async fn populate_keystore(&self, stored_keys: Vec) -> 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, 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::(StreamEncryption::encrypt_bytes( - derive_key(hashed_password, salt, MASTER_PASSWORD_CONTEXT), - &master_key_nonce, - algorithm, - master_key.expose(), - &[], - )?)?; + let encrypted_master_key = to_array::( + 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` 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, // at the time of the backup secret_key: Option>, // 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::(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( + pub async fn unlock( &self, master_password: Protected, secret_key: Option>, 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>> { - 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> { + 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>, + key: ProtectedVec, algorithm: Algorithm, hashing_algorithm: HashingAlgorithm, memory_only: bool, automount: bool, - content_salt: Option<[u8; SALT_LEN]>, + content_salt: Option, ) -> Result { 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::(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::( + 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) -> Protected> { + fn convert_secret_key_string(secret_key: Protected) -> ProtectedVec { 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 { - self.default.lock()?.ok_or(Error::NoDefaultKeySet) + pub async fn get_default(&self) -> Result { + self.default.lock().await.ok_or(Error::NoDefaultKeySet) } /// This should ONLY be used internally. - fn get_root_key(&self) -> Result> { - self.root_key.lock()?.clone().ok_or(Error::NoMasterPassword) + async fn get_root_key(&self) -> Result> { + self.root_key + .lock() + .await + .clone() + .ok_or(Error::NoMasterPassword) } - pub fn get_verification_key(&self) -> Result { + pub async fn get_verification_key(&self) -> Result { 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> { + pub fn enumerate_hashed_keys(&self) -> Vec> { self.keymount .iter() .map(|mounted_key| mounted_key.hashed_key.clone()) - .collect::>>() + .collect::>>() } /// 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 { - Ok(self.root_key.lock()?.is_some()) + pub async fn has_master_password(&self) -> Result { + Ok(self.root_key.lock().await.is_some()) } /// This function is used for unmounting all keys at once. diff --git a/crates/crypto/src/lib.rs b/crates/crypto/src/lib.rs index 87be04556..03b048a79 100644 --- a/crates/crypto/src/lib.rs +++ b/crates/crypto/src/lib.rs @@ -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; diff --git a/crates/crypto/src/primitives.rs b/crates/crypto/src/primitives.rs index 982e7deb0..e15d2cf8f 100644 --- a/crates/crypto/src/primitives.rs +++ b/crates/crypto/src/primitives.rs @@ -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 { /// /// 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 { 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, salt: Salt, context: &str) -> Protected { 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` to an array of bytes /// -/// It's main usage is for converting an encrypted master key from a `Vec` to `[u8; ENCRYPTED_KEY_LEN]` +/// It's main usage is for converting an encrypted master key from a `Vec` to `EncryptedKey` /// /// As the master key is encrypted at this point, it does not need to be `Protected<>` /// diff --git a/crates/crypto/src/protected.rs b/crates/crypto/src/protected.rs index 4755f1cb3..747f1a1bc 100644 --- a/crates/crypto/src/protected.rs +++ b/crates/crypto/src/protected.rs @@ -31,6 +31,7 @@ use std::{fmt::Debug, mem::swap}; use zeroize::Zeroize; +pub type ProtectedVec = Protected>; #[derive(Clone)] pub struct Protected where diff --git a/packages/client/src/core.ts b/packages/client/src/core.ts index 7fbe9283c..56ca4cbd8 100644 --- a/packages/client/src/core.ts +++ b/packages/client/src/core.ts @@ -55,8 +55,8 @@ export type Procedures = { { key: "keys.mount", input: LibraryArgs, result: null } | { key: "keys.restoreKeystore", input: LibraryArgs, result: number } | { key: "keys.setDefault", input: LibraryArgs, result: null } | - { key: "keys.setMasterPassword", input: LibraryArgs, result: null } | { key: "keys.syncKeyToLibrary", input: LibraryArgs, result: null } | + { key: "keys.unlockKeyManager", input: LibraryArgs, result: null } | { key: "keys.unmount", input: LibraryArgs, result: null } | { key: "keys.unmountAll", input: LibraryArgs, result: null } | { key: "keys.updateAutomountStatus", input: LibraryArgs, 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 } diff --git a/packages/interface/src/components/dialog/DecryptFileDialog.tsx b/packages/interface/src/components/dialog/DecryptFileDialog.tsx index 040dea257..c58ff09af 100644 --- a/packages/interface/src/components/dialog/DecryptFileDialog.tsx +++ b/packages/interface/src/components/dialog/DecryptFileDialog.tsx @@ -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 ( { }); 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 > Argon2id (standard) Argon2id (hardened) diff --git a/packages/interface/src/components/dialog/MasterPasswordChangeDialog.tsx b/packages/interface/src/components/dialog/MasterPasswordChangeDialog.tsx index 9c1e7c483..d6c16c778 100644 --- a/packages/interface/src/components/dialog/MasterPasswordChangeDialog.tsx +++ b/packages/interface/src/components/dialog/MasterPasswordChangeDialog.tsx @@ -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 }); } }); diff --git a/packages/interface/src/components/key/KeyManager.tsx b/packages/interface/src/components/key/KeyManager.tsx index e9d594b8b..fbc348129 100644 --- a/packages/interface/src/components/key/KeyManager.tsx +++ b/packages/interface/src/components/key/KeyManager.tsx @@ -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'); diff --git a/packages/interface/src/screens/settings/library/KeysSetting.tsx b/packages/interface/src/screens/settings/library/KeysSetting.tsx index c4c828c49..d0ef16870 100644 --- a/packages/interface/src/screens/settings/library/KeysSetting.tsx +++ b/packages/interface/src/screens/settings/library/KeysSetting.tsx @@ -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',