mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-05 08:03:28 +00:00
Merge pull request #549 from spacedriveapp/eng-348-async-crypto
[ENG-348] Asynchronous crypto
This commit is contained in:
commit
024c838911
160
Cargo.lock
generated
160
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -23,7 +23,7 @@ pub struct KeyAddArgs {
|
|||
}
|
||||
|
||||
#[derive(Type, Deserialize)]
|
||||
pub struct SetMasterPasswordArgs {
|
||||
pub struct UnlockKeyManagerArgs {
|
||||
password: String,
|
||||
secret_key: Option<String>,
|
||||
}
|
||||
|
@ -56,7 +56,9 @@ pub(crate) fn mount() -> RouterBuilder {
|
|||
})
|
||||
// do not unlock the key manager until this route returns true
|
||||
.library_query("hasMasterPassword", |t| {
|
||||
t(|_, _: (), library| async move { Ok(library.key_manager.has_master_password()?) })
|
||||
t(
|
||||
|_, _: (), library| async move { Ok(library.key_manager.has_master_password().await?) },
|
||||
)
|
||||
})
|
||||
// this is so we can show the key as mounted in the UI
|
||||
.library_query("listMounted", |t| {
|
||||
|
@ -64,14 +66,14 @@ pub(crate) fn mount() -> RouterBuilder {
|
|||
})
|
||||
.library_query("getKey", |t| {
|
||||
t(|_, key_uuid: Uuid, library| async move {
|
||||
let key = library.key_manager.get_key(key_uuid)?;
|
||||
let key = library.key_manager.get_key(key_uuid).await?;
|
||||
|
||||
Ok(String::from_utf8(key.into_inner()).map_err(Error::StringParse)?)
|
||||
})
|
||||
})
|
||||
.library_mutation("mount", |t| {
|
||||
t(|_, key_uuid: Uuid, library| async move {
|
||||
library.key_manager.mount(key_uuid)?;
|
||||
library.key_manager.mount(key_uuid).await?;
|
||||
// we also need to dispatch jobs that automatically decrypt preview media and metadata here
|
||||
invalidate_query!(library, "keys.listMounted");
|
||||
Ok(())
|
||||
|
@ -88,7 +90,7 @@ pub(crate) fn mount() -> RouterBuilder {
|
|||
.library_mutation("clearMasterPassword", |t| {
|
||||
t(|_, _: (), library| async move {
|
||||
// This technically clears the root key, but it means the same thing to the frontend
|
||||
library.key_manager.clear_root_key()?;
|
||||
library.key_manager.clear_root_key().await?;
|
||||
|
||||
invalidate_query!(library, "keys.hasMasterPassword");
|
||||
Ok(())
|
||||
|
@ -139,7 +141,7 @@ pub(crate) fn mount() -> RouterBuilder {
|
|||
.await?;
|
||||
}
|
||||
|
||||
library.key_manager.remove_key(key_uuid)?;
|
||||
library.key_manager.remove_key(key_uuid).await?;
|
||||
|
||||
// we also need to delete all in-memory decrypted data associated with this key
|
||||
invalidate_query!(library, "keys.list");
|
||||
|
@ -148,14 +150,17 @@ pub(crate) fn mount() -> RouterBuilder {
|
|||
Ok(())
|
||||
})
|
||||
})
|
||||
.library_mutation("setMasterPassword", |t| {
|
||||
t(|_, args: SetMasterPasswordArgs, library| async move {
|
||||
.library_mutation("unlockKeyManager", |t| {
|
||||
t(|_, args: UnlockKeyManagerArgs, library| async move {
|
||||
// if this returns an error, the user MUST re-enter the correct password
|
||||
library.key_manager.set_master_password(
|
||||
Protected::new(args.password),
|
||||
args.secret_key.map(Protected::new),
|
||||
|| invalidate_query!(library, "keys.isKeyManagerUnlocking"),
|
||||
)?;
|
||||
library
|
||||
.key_manager
|
||||
.unlock(
|
||||
Protected::new(args.password),
|
||||
args.secret_key.map(Protected::new),
|
||||
|| invalidate_query!(library, "keys.isKeyManagerUnlocking"),
|
||||
)
|
||||
.await?;
|
||||
|
||||
invalidate_query!(library, "keys.hasMasterPassword");
|
||||
|
||||
|
@ -169,7 +174,8 @@ pub(crate) fn mount() -> RouterBuilder {
|
|||
for key in automount {
|
||||
library
|
||||
.key_manager
|
||||
.mount(Uuid::from_str(&key.uuid).map_err(|_| Error::Serialization)?)?;
|
||||
.mount(Uuid::from_str(&key.uuid).map_err(|_| Error::Serialization)?)
|
||||
.await?;
|
||||
|
||||
invalidate_query!(library, "keys.listMounted");
|
||||
}
|
||||
|
@ -179,7 +185,7 @@ pub(crate) fn mount() -> RouterBuilder {
|
|||
})
|
||||
.library_mutation("setDefault", |t| {
|
||||
t(|_, key_uuid: Uuid, library| async move {
|
||||
library.key_manager.set_default(key_uuid)?;
|
||||
library.key_manager.set_default(key_uuid).await?;
|
||||
|
||||
library
|
||||
.db
|
||||
|
@ -206,7 +212,7 @@ pub(crate) fn mount() -> RouterBuilder {
|
|||
})
|
||||
})
|
||||
.library_query("getDefault", |t| {
|
||||
t(|_, _: (), library| async move { library.key_manager.get_default().ok() })
|
||||
t(|_, _: (), library| async move { library.key_manager.get_default().await.ok() })
|
||||
})
|
||||
.library_query("isKeyManagerUnlocking", |t| {
|
||||
t(|_, _: (), library| async move { library.key_manager.is_queued(Uuid::nil()) })
|
||||
|
@ -222,14 +228,17 @@ pub(crate) fn mount() -> RouterBuilder {
|
|||
.library_mutation("add", |t| {
|
||||
t(|_, args: KeyAddArgs, library| async move {
|
||||
// register the key with the keymanager
|
||||
let uuid = library.key_manager.add_to_keystore(
|
||||
Protected::new(args.key.as_bytes().to_vec()),
|
||||
args.algorithm,
|
||||
args.hashing_algorithm,
|
||||
!args.library_sync,
|
||||
args.automount,
|
||||
None,
|
||||
)?;
|
||||
let uuid = library
|
||||
.key_manager
|
||||
.add_to_keystore(
|
||||
Protected::new(args.key.as_bytes().to_vec()),
|
||||
args.algorithm,
|
||||
args.hashing_algorithm,
|
||||
!args.library_sync,
|
||||
args.automount,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if args.library_sync {
|
||||
write_storedkey_to_db(&library.db, &library.key_manager.access_keystore(uuid)?)
|
||||
|
@ -248,7 +257,7 @@ pub(crate) fn mount() -> RouterBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
library.key_manager.mount(uuid)?;
|
||||
library.key_manager.mount(uuid).await?;
|
||||
|
||||
invalidate_query!(library, "keys.list");
|
||||
invalidate_query!(library, "keys.listMounted");
|
||||
|
@ -261,7 +270,7 @@ pub(crate) fn mount() -> RouterBuilder {
|
|||
let mut stored_keys = library.key_manager.dump_keystore();
|
||||
|
||||
// include the verification key at the time of backup
|
||||
stored_keys.push(library.key_manager.get_verification_key()?);
|
||||
stored_keys.push(library.key_manager.get_verification_key().await?);
|
||||
|
||||
// exclude all memory-only keys
|
||||
stored_keys.retain(|k| !k.memory_only);
|
||||
|
@ -290,11 +299,10 @@ pub(crate) fn mount() -> RouterBuilder {
|
|||
|
||||
let secret_key = args.secret_key.map(Protected::new);
|
||||
|
||||
let updated_keys = library.key_manager.import_keystore_backup(
|
||||
Protected::new(args.password),
|
||||
secret_key,
|
||||
&stored_keys,
|
||||
)?;
|
||||
let updated_keys = library
|
||||
.key_manager
|
||||
.import_keystore_backup(Protected::new(args.password), secret_key, &stored_keys)
|
||||
.await?;
|
||||
|
||||
for key in &updated_keys {
|
||||
write_storedkey_to_db(&library.db, key).await?;
|
||||
|
@ -310,12 +318,15 @@ pub(crate) fn mount() -> RouterBuilder {
|
|||
t(|_, args: MasterPasswordChangeArgs, library| async move {
|
||||
let secret_key = args.secret_key.map(Protected::new);
|
||||
|
||||
let verification_key = library.key_manager.change_master_password(
|
||||
Protected::new(args.password),
|
||||
args.algorithm,
|
||||
args.hashing_algorithm,
|
||||
secret_key,
|
||||
)?;
|
||||
let verification_key = library
|
||||
.key_manager
|
||||
.change_master_password(
|
||||
Protected::new(args.password),
|
||||
args.algorithm,
|
||||
args.hashing_algorithm,
|
||||
secret_key,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// remove old nil-id keys if they were set
|
||||
library
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
//! This module contains the crate's STREAM implementation, and wrappers that allow us to support multiple AEADs.
|
||||
#![allow(clippy::use_self)] // I think: https://github.com/rust-lang/rust-clippy/issues/3909
|
||||
|
||||
use std::io::{Cursor, Read, Write};
|
||||
use std::io::Cursor;
|
||||
|
||||
use crate::{
|
||||
primitives::{AEAD_TAG_SIZE, BLOCK_SIZE, KEY_LEN},
|
||||
primitives::{Key, AEAD_TAG_SIZE, BLOCK_SIZE},
|
||||
protected::ProtectedVec,
|
||||
Error, Protected, Result,
|
||||
};
|
||||
use aead::{
|
||||
|
@ -13,6 +14,7 @@ use aead::{
|
|||
};
|
||||
use aes_gcm::Aes256Gcm;
|
||||
use chacha20poly1305::XChaCha20Poly1305;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
/// These are all possible algorithms that can be used for encryption and decryption
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Hash)]
|
||||
|
@ -53,7 +55,7 @@ impl StreamEncryption {
|
|||
///
|
||||
/// The master key, a suitable nonce, and a specific algorithm should be provided.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn new(key: Protected<[u8; KEY_LEN]>, nonce: &[u8], algorithm: Algorithm) -> Result<Self> {
|
||||
pub fn new(key: Protected<Key>, nonce: &[u8], algorithm: Algorithm) -> Result<Self> {
|
||||
if nonce.len() != algorithm.nonce_len() {
|
||||
return Err(Error::NonceLengthMismatch);
|
||||
}
|
||||
|
@ -105,14 +107,29 @@ impl StreamEncryption {
|
|||
/// It requires a reader, a writer, and any AAD to go with it.
|
||||
///
|
||||
/// The AAD will be authenticated with each block of data.
|
||||
pub fn encrypt_streams<R, W>(mut self, mut reader: R, mut writer: W, aad: &[u8]) -> Result<()>
|
||||
pub async fn encrypt_streams<R, W>(
|
||||
mut self,
|
||||
mut reader: R,
|
||||
mut writer: W,
|
||||
aad: &[u8],
|
||||
) -> Result<()>
|
||||
where
|
||||
R: Read,
|
||||
W: Write,
|
||||
R: AsyncReadExt + Unpin + Send,
|
||||
W: AsyncWriteExt + Unpin + Send,
|
||||
{
|
||||
let mut read_buffer = vec![0u8; BLOCK_SIZE].into_boxed_slice();
|
||||
|
||||
loop {
|
||||
let read_count = reader.read(&mut read_buffer)?;
|
||||
let mut read_count = 0;
|
||||
loop {
|
||||
let i = reader.read(&mut read_buffer[read_count..]).await?;
|
||||
read_count += i;
|
||||
if i == 0 || read_count == BLOCK_SIZE {
|
||||
// if we're EOF or the buffer is filled
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if read_count == BLOCK_SIZE {
|
||||
let payload = Payload {
|
||||
aad,
|
||||
|
@ -121,7 +138,7 @@ impl StreamEncryption {
|
|||
|
||||
let encrypted_data = self.encrypt_next(payload).map_err(|_| Error::Encrypt)?;
|
||||
|
||||
writer.write_all(&encrypted_data)?;
|
||||
writer.write_all(&encrypted_data).await?;
|
||||
} else {
|
||||
// we use `..read_count` in order to only use the read data, and not zeroes also
|
||||
let payload = Payload {
|
||||
|
@ -130,13 +147,13 @@ impl StreamEncryption {
|
|||
};
|
||||
|
||||
let encrypted_data = self.encrypt_last(payload).map_err(|_| Error::Encrypt)?;
|
||||
writer.write_all(&encrypted_data)?;
|
||||
writer.write_all(&encrypted_data).await?;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
writer.flush()?;
|
||||
writer.flush().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -145,8 +162,8 @@ impl StreamEncryption {
|
|||
///
|
||||
/// It is just a thin wrapper around `encrypt_streams()`, but reduces the amount of code needed elsewhere.
|
||||
#[allow(unused_mut)]
|
||||
pub fn encrypt_bytes(
|
||||
key: Protected<[u8; KEY_LEN]>,
|
||||
pub async fn encrypt_bytes(
|
||||
key: Protected<Key>,
|
||||
nonce: &[u8],
|
||||
algorithm: Algorithm,
|
||||
bytes: &[u8],
|
||||
|
@ -157,6 +174,7 @@ impl StreamEncryption {
|
|||
|
||||
encryptor
|
||||
.encrypt_streams(bytes, &mut writer, aad)
|
||||
.await
|
||||
.map_or_else(Err, |_| Ok(writer.into_inner()))
|
||||
}
|
||||
}
|
||||
|
@ -166,7 +184,7 @@ impl StreamDecryption {
|
|||
///
|
||||
/// The master key, nonce and algorithm that were used for encryption should be provided.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn new(key: Protected<[u8; KEY_LEN]>, nonce: &[u8], algorithm: Algorithm) -> Result<Self> {
|
||||
pub fn new(key: Protected<Key>, nonce: &[u8], algorithm: Algorithm) -> Result<Self> {
|
||||
if nonce.len() != algorithm.nonce_len() {
|
||||
return Err(Error::NonceLengthMismatch);
|
||||
}
|
||||
|
@ -218,15 +236,29 @@ impl StreamDecryption {
|
|||
/// It requires a reader, a writer, and any AAD that was used.
|
||||
///
|
||||
/// The AAD will be authenticated with each block of data - if the AAD doesn't match what was used during encryption, an error will be returned.
|
||||
pub fn decrypt_streams<R, W>(mut self, mut reader: R, mut writer: W, aad: &[u8]) -> Result<()>
|
||||
pub async fn decrypt_streams<R, W>(
|
||||
mut self,
|
||||
mut reader: R,
|
||||
mut writer: W,
|
||||
aad: &[u8],
|
||||
) -> Result<()>
|
||||
where
|
||||
R: Read,
|
||||
W: Write,
|
||||
R: AsyncReadExt + Unpin + Send,
|
||||
W: AsyncWriteExt + Unpin + Send,
|
||||
{
|
||||
let mut read_buffer = vec![0u8; BLOCK_SIZE + AEAD_TAG_SIZE].into_boxed_slice();
|
||||
|
||||
loop {
|
||||
let read_count = reader.read(&mut read_buffer)?;
|
||||
let mut read_count = 0;
|
||||
loop {
|
||||
let i = reader.read(&mut read_buffer[read_count..]).await?;
|
||||
read_count += i;
|
||||
if i == 0 || read_count == (BLOCK_SIZE + AEAD_TAG_SIZE) {
|
||||
// if we're EOF or the buffer is filled
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if read_count == (BLOCK_SIZE + AEAD_TAG_SIZE) {
|
||||
let payload = Payload {
|
||||
aad,
|
||||
|
@ -235,7 +267,7 @@ impl StreamDecryption {
|
|||
|
||||
let decrypted_data = self.decrypt_next(payload).map_err(|_| Error::Decrypt)?;
|
||||
|
||||
writer.write_all(&decrypted_data)?;
|
||||
writer.write_all(&decrypted_data).await?;
|
||||
} else {
|
||||
let payload = Payload {
|
||||
aad,
|
||||
|
@ -243,13 +275,13 @@ impl StreamDecryption {
|
|||
};
|
||||
|
||||
let decrypted_data = self.decrypt_last(payload).map_err(|_| Error::Decrypt)?;
|
||||
writer.write_all(&decrypted_data)?;
|
||||
writer.write_all(&decrypted_data).await?;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
writer.flush()?;
|
||||
writer.flush().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -258,18 +290,19 @@ impl StreamDecryption {
|
|||
///
|
||||
/// It is just a thin wrapper around `decrypt_streams()`, but reduces the amount of code needed elsewhere.
|
||||
#[allow(unused_mut)]
|
||||
pub fn decrypt_bytes(
|
||||
key: Protected<[u8; KEY_LEN]>,
|
||||
pub async fn decrypt_bytes(
|
||||
key: Protected<Key>,
|
||||
nonce: &[u8],
|
||||
algorithm: Algorithm,
|
||||
bytes: &[u8],
|
||||
aad: &[u8],
|
||||
) -> Result<Protected<Vec<u8>>> {
|
||||
) -> Result<ProtectedVec<u8>> {
|
||||
let mut writer = Cursor::new(Vec::<u8>::new());
|
||||
let decryptor = Self::new(key, nonce, algorithm)?;
|
||||
|
||||
decryptor
|
||||
.decrypt_streams(bytes, &mut writer, aad)
|
||||
.await
|
||||
.map_or_else(Err, |_| Ok(Protected::new(writer.into_inner())))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,11 +29,14 @@
|
|||
//! // Write the header to the file
|
||||
//! header.write(&mut writer).unwrap();
|
||||
//! ```
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::io::SeekFrom;
|
||||
|
||||
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
|
||||
|
||||
use crate::{
|
||||
crypto::stream::Algorithm,
|
||||
primitives::{generate_nonce, to_array, KEY_LEN},
|
||||
primitives::{generate_nonce, to_array, Key, KEY_LEN},
|
||||
protected::ProtectedVec,
|
||||
Error, Protected, Result,
|
||||
};
|
||||
|
||||
|
@ -98,43 +101,31 @@ impl FileHeader {
|
|||
///
|
||||
/// You receive an error if the password doesn't match or if there are no keyslots.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn decrypt_master_key(
|
||||
&self,
|
||||
password: Protected<Vec<u8>>,
|
||||
) -> Result<Protected<[u8; KEY_LEN]>> {
|
||||
pub async fn decrypt_master_key(&self, password: ProtectedVec<u8>) -> Result<Protected<Key>> {
|
||||
if self.keyslots.is_empty() {
|
||||
return Err(Error::NoKeyslots);
|
||||
}
|
||||
|
||||
self.keyslots
|
||||
.iter()
|
||||
.find_map(|v| v.decrypt_master_key(password.clone()).ok())
|
||||
.map(|v| Protected::new(to_array::<KEY_LEN>(v.into_inner()).unwrap()))
|
||||
.ok_or(Error::IncorrectPassword)
|
||||
}
|
||||
|
||||
/// This is a helper function to find which keyslot a key belongs to.
|
||||
///
|
||||
/// You receive an error if the password doesn't match or if there are no keyslots.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn find_key_index(&self, password: Protected<Vec<u8>>) -> Result<usize> {
|
||||
if self.keyslots.is_empty() {
|
||||
return Err(Error::NoKeyslots);
|
||||
for v in &self.keyslots {
|
||||
if let Some(key) = v
|
||||
.decrypt_master_key(password.clone())
|
||||
.await
|
||||
.ok()
|
||||
.map(|v| Protected::new(to_array::<KEY_LEN>(v.into_inner()).unwrap()))
|
||||
{
|
||||
return Ok(key);
|
||||
}
|
||||
}
|
||||
|
||||
self.keyslots
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(i, v)| v.decrypt_master_key(password.clone()).ok().map(|_| i))
|
||||
.ok_or(Error::IncorrectPassword)
|
||||
Err(Error::IncorrectPassword)
|
||||
}
|
||||
|
||||
/// This is a helper function to serialize and write a header to a file.
|
||||
pub fn write<W>(&self, writer: &mut W) -> Result<()>
|
||||
pub async fn write<W>(&self, writer: &mut W) -> Result<()>
|
||||
where
|
||||
W: Write,
|
||||
W: AsyncWriteExt + Unpin + Send,
|
||||
{
|
||||
writer.write_all(&self.to_bytes()?)?;
|
||||
writer.write_all(&self.to_bytes()?).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -144,24 +135,46 @@ impl FileHeader {
|
|||
///
|
||||
/// You receive an error if the password doesn't match or if there are no keyslots.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn decrypt_master_key_from_prehashed(
|
||||
pub async fn decrypt_master_key_from_prehashed(
|
||||
&self,
|
||||
hashed_keys: Vec<Protected<[u8; KEY_LEN]>>,
|
||||
) -> Result<Protected<[u8; KEY_LEN]>> {
|
||||
hashed_keys: Vec<Protected<Key>>,
|
||||
) -> Result<Protected<Key>> {
|
||||
if self.keyslots.is_empty() {
|
||||
return Err(Error::NoKeyslots);
|
||||
}
|
||||
|
||||
hashed_keys
|
||||
.iter()
|
||||
.find_map(|v| {
|
||||
self.keyslots.iter().find_map(|z| {
|
||||
z.decrypt_master_key_from_prehashed(v.clone())
|
||||
.ok()
|
||||
.map(|x| Protected::new(to_array::<KEY_LEN>(x.into_inner()).unwrap()))
|
||||
})
|
||||
})
|
||||
.ok_or(Error::IncorrectPassword)
|
||||
for hashed_key in hashed_keys {
|
||||
for v in &self.keyslots {
|
||||
if let Some(key) = v
|
||||
.decrypt_master_key_from_prehashed(hashed_key.clone())
|
||||
.await
|
||||
.ok()
|
||||
.map(|v| Protected::new(to_array::<KEY_LEN>(v.into_inner()).unwrap()))
|
||||
{
|
||||
return Ok(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::IncorrectPassword)
|
||||
}
|
||||
|
||||
/// This is a helper function to find which keyslot a key belongs to.
|
||||
///
|
||||
/// You receive an error if the password doesn't match or if there are no keyslots.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub async fn find_key_index(&self, password: ProtectedVec<u8>) -> Result<usize> {
|
||||
if self.keyslots.is_empty() {
|
||||
return Err(Error::NoKeyslots);
|
||||
}
|
||||
|
||||
for (i, v) in self.keyslots.iter().enumerate() {
|
||||
if let Some(i) = v.decrypt_master_key(password.clone()).await.ok().map(|_| i) {
|
||||
return Ok(i);
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::IncorrectPassword)
|
||||
}
|
||||
|
||||
/// This function should be used for generating AAD before encryption
|
||||
|
@ -241,12 +254,12 @@ impl FileHeader {
|
|||
/// On error, the cursor will not be rewound.
|
||||
///
|
||||
/// It returns both the header, and the AAD that should be used for decryption.
|
||||
pub fn from_reader<R>(reader: &mut R) -> Result<(Self, Vec<u8>)>
|
||||
pub async fn from_reader<R>(reader: &mut R) -> Result<(Self, Vec<u8>)>
|
||||
where
|
||||
R: Read + Seek,
|
||||
R: AsyncReadExt + AsyncSeekExt + Unpin + Send,
|
||||
{
|
||||
let mut magic_bytes = [0u8; MAGIC_BYTES.len()];
|
||||
reader.read_exact(&mut magic_bytes)?;
|
||||
reader.read_exact(&mut magic_bytes).await?;
|
||||
|
||||
if magic_bytes != MAGIC_BYTES {
|
||||
return Err(Error::FileHeader);
|
||||
|
@ -254,36 +267,38 @@ impl FileHeader {
|
|||
|
||||
let mut version = [0u8; 2];
|
||||
|
||||
reader.read_exact(&mut version)?;
|
||||
reader.read_exact(&mut version).await?;
|
||||
let version = FileHeaderVersion::from_bytes(version)?;
|
||||
|
||||
// Rewind so we can get the AAD
|
||||
reader.rewind()?;
|
||||
reader.rewind().await?;
|
||||
|
||||
// read the aad according to the size
|
||||
let mut aad = vec![0u8; Self::size(version)];
|
||||
reader.read_exact(&mut aad)?;
|
||||
reader.read_exact(&mut aad).await?;
|
||||
|
||||
// seek back to the start (plus magic bytes and the two version bytes)
|
||||
reader.seek(SeekFrom::Start(MAGIC_BYTES.len() as u64 + 2))?;
|
||||
reader
|
||||
.seek(SeekFrom::Start(MAGIC_BYTES.len() as u64 + 2))
|
||||
.await?;
|
||||
|
||||
// read the header
|
||||
let header = match version {
|
||||
FileHeaderVersion::V1 => {
|
||||
let mut algorithm = [0u8; 2];
|
||||
reader.read_exact(&mut algorithm)?;
|
||||
reader.read_exact(&mut algorithm).await?;
|
||||
let algorithm = Algorithm::from_bytes(algorithm)?;
|
||||
|
||||
let mut nonce = vec![0u8; algorithm.nonce_len()];
|
||||
reader.read_exact(&mut nonce)?;
|
||||
reader.read_exact(&mut nonce).await?;
|
||||
|
||||
// read and discard the padding
|
||||
reader.read_exact(&mut vec![0u8; 25 - nonce.len()])?;
|
||||
reader.read_exact(&mut vec![0u8; 25 - nonce.len()]).await?;
|
||||
|
||||
let mut keyslot_bytes = [0u8; (KEYSLOT_SIZE * 2)]; // length of 2x keyslots
|
||||
let mut keyslots: Vec<Keyslot> = Vec::new();
|
||||
|
||||
reader.read_exact(&mut keyslot_bytes)?;
|
||||
reader.read_exact(&mut keyslot_bytes).await?;
|
||||
|
||||
for _ in 0..2 {
|
||||
Keyslot::from_reader(&mut keyslot_bytes.as_ref())
|
||||
|
@ -291,18 +306,21 @@ impl FileHeader {
|
|||
.ok();
|
||||
}
|
||||
|
||||
let metadata = Metadata::from_reader(reader).map_or_else(
|
||||
|_| {
|
||||
reader.seek(SeekFrom::Start(
|
||||
let metadata = if let Ok(metadata) = Metadata::from_reader(reader).await {
|
||||
reader
|
||||
.seek(SeekFrom::Start(
|
||||
Self::size(version) as u64 + (KEYSLOT_SIZE * 2) as u64,
|
||||
))?;
|
||||
Ok::<Option<Metadata>, Error>(None)
|
||||
},
|
||||
|metadata| Ok(Some(metadata)),
|
||||
)?;
|
||||
))
|
||||
.await?;
|
||||
Ok::<Option<Metadata>, Error>(Some(metadata))
|
||||
} else {
|
||||
Ok(None)
|
||||
}?;
|
||||
|
||||
let preview_media = PreviewMedia::from_reader(reader).map_or_else(
|
||||
|_| {
|
||||
let preview_media =
|
||||
if let Ok(preview_media) = PreviewMedia::from_reader(reader).await {
|
||||
Ok::<Option<PreviewMedia>, Error>(Some(preview_media))
|
||||
} else {
|
||||
let seek_len = metadata.as_ref().map_or_else(
|
||||
|| Self::size(version) as u64 + (KEYSLOT_SIZE * 2) as u64,
|
||||
|metadata| {
|
||||
|
@ -311,12 +329,10 @@ impl FileHeader {
|
|||
},
|
||||
);
|
||||
|
||||
reader.seek(SeekFrom::Start(seek_len))?;
|
||||
reader.seek(SeekFrom::Start(seek_len)).await?;
|
||||
|
||||
Ok::<Option<PreviewMedia>, Error>(None)
|
||||
},
|
||||
|preview_media| Ok(Some(preview_media)),
|
||||
)?;
|
||||
Ok(None)
|
||||
}?;
|
||||
|
||||
Self {
|
||||
version,
|
||||
|
|
|
@ -27,9 +27,10 @@ use crate::{
|
|||
crypto::stream::{Algorithm, StreamDecryption, StreamEncryption},
|
||||
keys::hashing::HashingAlgorithm,
|
||||
primitives::{
|
||||
derive_key, generate_nonce, generate_salt, to_array, ENCRYPTED_KEY_LEN, FILE_KEY_CONTEXT,
|
||||
KEY_LEN, SALT_LEN,
|
||||
derive_key, generate_nonce, generate_salt, to_array, EncryptedKey, Key, Salt,
|
||||
ENCRYPTED_KEY_LEN, FILE_KEY_CONTEXT, SALT_LEN,
|
||||
},
|
||||
protected::ProtectedVec,
|
||||
Error, Protected, Result,
|
||||
};
|
||||
|
||||
|
@ -41,9 +42,9 @@ pub struct Keyslot {
|
|||
pub version: KeyslotVersion,
|
||||
pub algorithm: Algorithm, // encryption algorithm
|
||||
pub hashing_algorithm: HashingAlgorithm, // password hashing algorithm
|
||||
pub salt: [u8; SALT_LEN], // the salt used for deriving a KEK from a (key/content salt) hash
|
||||
pub content_salt: [u8; SALT_LEN],
|
||||
pub master_key: [u8; ENCRYPTED_KEY_LEN], // this is encrypted so we can store it
|
||||
pub salt: Salt, // the salt used for deriving a KEK from a (key/content salt) hash
|
||||
pub content_salt: Salt,
|
||||
pub master_key: EncryptedKey, // this is encrypted so we can store it
|
||||
pub nonce: Vec<u8>,
|
||||
}
|
||||
|
||||
|
@ -64,25 +65,28 @@ impl Keyslot {
|
|||
///
|
||||
/// You will need to provide the password, and a generated master key (this can't generate it, otherwise it can't be used elsewhere)
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn new(
|
||||
pub async fn new(
|
||||
version: KeyslotVersion,
|
||||
algorithm: Algorithm,
|
||||
hashing_algorithm: HashingAlgorithm,
|
||||
content_salt: [u8; SALT_LEN],
|
||||
hashed_key: Protected<[u8; KEY_LEN]>,
|
||||
master_key: Protected<[u8; KEY_LEN]>,
|
||||
content_salt: Salt,
|
||||
hashed_key: Protected<Key>,
|
||||
master_key: Protected<Key>,
|
||||
) -> Result<Self> {
|
||||
let nonce = generate_nonce(algorithm);
|
||||
|
||||
let salt = generate_salt();
|
||||
|
||||
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(StreamEncryption::encrypt_bytes(
|
||||
derive_key(hashed_key, salt, FILE_KEY_CONTEXT),
|
||||
&nonce,
|
||||
algorithm,
|
||||
master_key.expose(),
|
||||
&[],
|
||||
)?)?;
|
||||
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(
|
||||
StreamEncryption::encrypt_bytes(
|
||||
derive_key(hashed_key, salt, FILE_KEY_CONTEXT),
|
||||
&nonce,
|
||||
algorithm,
|
||||
master_key.expose(),
|
||||
&[],
|
||||
)
|
||||
.await?,
|
||||
)?;
|
||||
|
||||
Ok(Self {
|
||||
version,
|
||||
|
@ -101,7 +105,7 @@ impl Keyslot {
|
|||
///
|
||||
/// An error will be returned on failure.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn decrypt_master_key(&self, password: Protected<Vec<u8>>) -> Result<Protected<Vec<u8>>> {
|
||||
pub async fn decrypt_master_key(&self, password: ProtectedVec<u8>) -> Result<ProtectedVec<u8>> {
|
||||
let key = self
|
||||
.hashing_algorithm
|
||||
.hash(password, self.content_salt, None)
|
||||
|
@ -114,6 +118,7 @@ impl Keyslot {
|
|||
&self.master_key,
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// This function should not be used directly, use `header.decrypt_master_key()` instead
|
||||
|
@ -123,10 +128,10 @@ impl Keyslot {
|
|||
/// No hashing is done internally.
|
||||
///
|
||||
/// An error will be returned on failure.
|
||||
pub fn decrypt_master_key_from_prehashed(
|
||||
pub async fn decrypt_master_key_from_prehashed(
|
||||
&self,
|
||||
key: Protected<[u8; KEY_LEN]>,
|
||||
) -> Result<Protected<Vec<u8>>> {
|
||||
key: Protected<Key>,
|
||||
) -> Result<ProtectedVec<u8>> {
|
||||
StreamDecryption::decrypt_bytes(
|
||||
derive_key(key, self.salt, FILE_KEY_CONTEXT),
|
||||
&self.nonce,
|
||||
|
@ -134,6 +139,7 @@ impl Keyslot {
|
|||
&self.master_key,
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// This function is used to serialize a keyslot into bytes
|
||||
|
|
|
@ -27,15 +27,16 @@
|
|||
//! )
|
||||
//! .unwrap();
|
||||
//! ```
|
||||
use std::io::Read;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use crate::{
|
||||
crypto::stream::{StreamDecryption, StreamEncryption},
|
||||
primitives::{generate_nonce, KEY_LEN},
|
||||
Protected,
|
||||
primitives::{generate_nonce, Key},
|
||||
Protected, ProtectedVec,
|
||||
};
|
||||
|
||||
use tokio::io::AsyncReadExt;
|
||||
|
||||
use crate::{crypto::stream::Algorithm, Error, Result};
|
||||
|
||||
use super::file::FileHeader;
|
||||
|
@ -68,15 +69,15 @@ impl FileHeader {
|
|||
/// Metadata needs to be accessed switfly, so a key management system should handle the salt generation.
|
||||
#[cfg(feature = "serde")]
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn add_metadata<T>(
|
||||
pub async fn add_metadata<T>(
|
||||
&mut self,
|
||||
version: MetadataVersion,
|
||||
algorithm: Algorithm,
|
||||
master_key: Protected<[u8; KEY_LEN]>,
|
||||
master_key: Protected<Key>,
|
||||
metadata: &T,
|
||||
) -> Result<()>
|
||||
where
|
||||
T: ?Sized + serde::Serialize,
|
||||
T: ?Sized + serde::Serialize + Sync + Send,
|
||||
{
|
||||
let metadata_nonce = generate_nonce(algorithm);
|
||||
|
||||
|
@ -86,16 +87,15 @@ impl FileHeader {
|
|||
algorithm,
|
||||
&serde_json::to_vec(metadata).map_err(|_| Error::Serialization)?,
|
||||
&[],
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
let metadata = Metadata {
|
||||
self.metadata = Some(Metadata {
|
||||
version,
|
||||
algorithm,
|
||||
metadata_nonce,
|
||||
metadata: encrypted_metadata,
|
||||
};
|
||||
|
||||
self.metadata = Some(metadata);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -106,29 +106,29 @@ impl FileHeader {
|
|||
///
|
||||
/// A deserialized data type will be returned from this function
|
||||
#[cfg(feature = "serde")]
|
||||
pub fn decrypt_metadata_from_prehashed<T>(
|
||||
pub async fn decrypt_metadata_from_prehashed<T>(
|
||||
&self,
|
||||
hashed_keys: Vec<Protected<[u8; KEY_LEN]>>,
|
||||
hashed_keys: Vec<Protected<Key>>,
|
||||
) -> Result<T>
|
||||
where
|
||||
T: serde::de::DeserializeOwned,
|
||||
{
|
||||
let master_key = self.decrypt_master_key_from_prehashed(hashed_keys)?;
|
||||
let master_key = self.decrypt_master_key_from_prehashed(hashed_keys).await?;
|
||||
|
||||
self.metadata.as_ref().map_or_else(
|
||||
|| Err(Error::NoMetadata),
|
||||
|metadata| {
|
||||
let metadata = StreamDecryption::decrypt_bytes(
|
||||
master_key,
|
||||
&metadata.metadata_nonce,
|
||||
metadata.algorithm,
|
||||
&metadata.metadata,
|
||||
&[],
|
||||
)?;
|
||||
if let Some(metadata) = self.metadata.as_ref() {
|
||||
let metadata = StreamDecryption::decrypt_bytes(
|
||||
master_key,
|
||||
&metadata.metadata_nonce,
|
||||
metadata.algorithm,
|
||||
&metadata.metadata,
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
|
||||
serde_json::from_slice::<T>(&metadata).map_err(|_| Error::Serialization)
|
||||
},
|
||||
)
|
||||
serde_json::from_slice::<T>(&metadata).map_err(|_| Error::Serialization)
|
||||
} else {
|
||||
Err(Error::NoMetadata)
|
||||
}
|
||||
}
|
||||
|
||||
/// This function should be used to retrieve the metadata for a file
|
||||
|
@ -137,26 +137,26 @@ impl FileHeader {
|
|||
///
|
||||
/// A deserialized data type will be returned from this function
|
||||
#[cfg(feature = "serde")]
|
||||
pub fn decrypt_metadata<T>(&self, password: Protected<Vec<u8>>) -> Result<T>
|
||||
pub async fn decrypt_metadata<T>(&self, password: ProtectedVec<u8>) -> Result<T>
|
||||
where
|
||||
T: serde::de::DeserializeOwned,
|
||||
{
|
||||
let master_key = self.decrypt_master_key(password)?;
|
||||
let master_key = self.decrypt_master_key(password).await?;
|
||||
|
||||
self.metadata.as_ref().map_or_else(
|
||||
|| Err(Error::NoMetadata),
|
||||
|metadata| {
|
||||
let metadata = StreamDecryption::decrypt_bytes(
|
||||
master_key,
|
||||
&metadata.metadata_nonce,
|
||||
metadata.algorithm,
|
||||
&metadata.metadata,
|
||||
&[],
|
||||
)?;
|
||||
if let Some(metadata) = self.metadata.as_ref() {
|
||||
let metadata = StreamDecryption::decrypt_bytes(
|
||||
master_key,
|
||||
&metadata.metadata_nonce,
|
||||
metadata.algorithm,
|
||||
&metadata.metadata,
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
|
||||
serde_json::from_slice::<T>(&metadata).map_err(|_| Error::Serialization)
|
||||
},
|
||||
)
|
||||
serde_json::from_slice::<T>(&metadata).map_err(|_| Error::Serialization)
|
||||
} else {
|
||||
Err(Error::NoMetadata)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,33 +192,35 @@ impl Metadata {
|
|||
/// The cursor will be left at the end of the metadata item on success
|
||||
///
|
||||
/// The cursor will not be rewound on error.
|
||||
pub fn from_reader<R>(reader: &mut R) -> Result<Self>
|
||||
pub async fn from_reader<R>(reader: &mut R) -> Result<Self>
|
||||
where
|
||||
R: Read,
|
||||
R: AsyncReadExt + Unpin + Send,
|
||||
{
|
||||
let mut version = [0u8; 2];
|
||||
reader.read_exact(&mut version)?;
|
||||
reader.read_exact(&mut version).await?;
|
||||
let version = MetadataVersion::from_bytes(version).map_err(|_| Error::NoMetadata)?;
|
||||
|
||||
match version {
|
||||
MetadataVersion::V1 => {
|
||||
let mut algorithm = [0u8; 2];
|
||||
reader.read_exact(&mut algorithm)?;
|
||||
reader.read_exact(&mut algorithm).await?;
|
||||
let algorithm = Algorithm::from_bytes(algorithm)?;
|
||||
|
||||
let mut metadata_nonce = vec![0u8; algorithm.nonce_len()];
|
||||
reader.read_exact(&mut metadata_nonce)?;
|
||||
reader.read_exact(&mut metadata_nonce).await?;
|
||||
|
||||
reader.read_exact(&mut vec![0u8; 24 - metadata_nonce.len()])?;
|
||||
reader
|
||||
.read_exact(&mut vec![0u8; 24 - metadata_nonce.len()])
|
||||
.await?;
|
||||
|
||||
let mut metadata_length = [0u8; 8];
|
||||
reader.read_exact(&mut metadata_length)?;
|
||||
reader.read_exact(&mut metadata_length).await?;
|
||||
|
||||
let metadata_length = u64::from_le_bytes(metadata_length);
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let mut metadata = vec![0u8; metadata_length as usize];
|
||||
reader.read_exact(&mut metadata)?;
|
||||
reader.read_exact(&mut metadata).await?;
|
||||
|
||||
let metadata = Self {
|
||||
version,
|
||||
|
|
|
@ -20,12 +20,12 @@
|
|||
//! )
|
||||
//! .unwrap();
|
||||
//! ```
|
||||
use std::io::Read;
|
||||
use tokio::io::AsyncReadExt;
|
||||
|
||||
use crate::{
|
||||
crypto::stream::{Algorithm, StreamDecryption, StreamEncryption},
|
||||
primitives::{generate_nonce, KEY_LEN},
|
||||
Error, Protected, Result,
|
||||
primitives::{generate_nonce, Key},
|
||||
Error, Protected, ProtectedVec, Result,
|
||||
};
|
||||
|
||||
use super::file::FileHeader;
|
||||
|
@ -57,26 +57,25 @@ impl FileHeader {
|
|||
///
|
||||
/// Preview media needs to be accessed switfly, so a key management system should handle the salt generation.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn add_preview_media(
|
||||
pub async fn add_preview_media(
|
||||
&mut self,
|
||||
version: PreviewMediaVersion,
|
||||
algorithm: Algorithm,
|
||||
master_key: Protected<[u8; KEY_LEN]>,
|
||||
master_key: Protected<Key>,
|
||||
media: &[u8],
|
||||
) -> Result<()> {
|
||||
let media_nonce = generate_nonce(algorithm);
|
||||
|
||||
let encrypted_media =
|
||||
StreamEncryption::encrypt_bytes(master_key, &media_nonce, algorithm, media, &[])?;
|
||||
StreamEncryption::encrypt_bytes(master_key, &media_nonce, algorithm, media, &[])
|
||||
.await?;
|
||||
|
||||
let pvm = PreviewMedia {
|
||||
self.preview_media = Some(PreviewMedia {
|
||||
version,
|
||||
algorithm,
|
||||
media_nonce,
|
||||
media: encrypted_media,
|
||||
};
|
||||
|
||||
self.preview_media = Some(pvm);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -86,26 +85,26 @@ impl FileHeader {
|
|||
/// All it requires is pre-hashed keys returned from the key manager
|
||||
///
|
||||
/// Once provided, a `Vec<u8>` is returned that contains the preview media
|
||||
pub fn decrypt_preview_media_from_prehashed(
|
||||
pub async fn decrypt_preview_media_from_prehashed(
|
||||
&self,
|
||||
hashed_keys: Vec<Protected<[u8; KEY_LEN]>>,
|
||||
) -> Result<Protected<Vec<u8>>> {
|
||||
let master_key = self.decrypt_master_key_from_prehashed(hashed_keys)?;
|
||||
hashed_keys: Vec<Protected<Key>>,
|
||||
) -> Result<ProtectedVec<u8>> {
|
||||
let master_key = self.decrypt_master_key_from_prehashed(hashed_keys).await?;
|
||||
|
||||
self.preview_media.as_ref().map_or_else(
|
||||
|| Err(Error::NoPreviewMedia),
|
||||
|pvm| {
|
||||
let pvm = StreamDecryption::decrypt_bytes(
|
||||
master_key,
|
||||
&pvm.media_nonce,
|
||||
pvm.algorithm,
|
||||
&pvm.media,
|
||||
&[],
|
||||
)?;
|
||||
if let Some(pvm) = self.preview_media.as_ref() {
|
||||
let pvm = StreamDecryption::decrypt_bytes(
|
||||
master_key,
|
||||
&pvm.media_nonce,
|
||||
pvm.algorithm,
|
||||
&pvm.media,
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(pvm)
|
||||
},
|
||||
)
|
||||
Ok(pvm)
|
||||
} else {
|
||||
Err(Error::NoPreviewMedia)
|
||||
}
|
||||
}
|
||||
|
||||
/// This function is what you'll want to use to get the preview media for a file
|
||||
|
@ -113,26 +112,26 @@ impl FileHeader {
|
|||
/// All it requires is the user's password. Hashing is handled for you.
|
||||
///
|
||||
/// Once provided, a `Vec<u8>` is returned that contains the preview media
|
||||
pub fn decrypt_preview_media(
|
||||
pub async fn decrypt_preview_media(
|
||||
&self,
|
||||
password: Protected<Vec<u8>>,
|
||||
) -> Result<Protected<Vec<u8>>> {
|
||||
let master_key = self.decrypt_master_key(password)?;
|
||||
password: ProtectedVec<u8>,
|
||||
) -> Result<ProtectedVec<u8>> {
|
||||
let master_key = self.decrypt_master_key(password).await?;
|
||||
|
||||
self.preview_media.as_ref().map_or_else(
|
||||
|| Err(Error::NoPreviewMedia),
|
||||
|pvm| {
|
||||
let pvm = StreamDecryption::decrypt_bytes(
|
||||
master_key,
|
||||
&pvm.media_nonce,
|
||||
pvm.algorithm,
|
||||
&pvm.media,
|
||||
&[],
|
||||
)?;
|
||||
if let Some(pvm) = self.preview_media.as_ref() {
|
||||
let pvm = StreamDecryption::decrypt_bytes(
|
||||
master_key,
|
||||
&pvm.media_nonce,
|
||||
pvm.algorithm,
|
||||
&pvm.media,
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(pvm)
|
||||
},
|
||||
)
|
||||
Ok(pvm)
|
||||
} else {
|
||||
Err(Error::NoPreviewMedia)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,34 +167,36 @@ impl PreviewMedia {
|
|||
/// The cursor will be left at the end of the preview media item on success
|
||||
///
|
||||
/// The cursor will not be rewound on error.
|
||||
pub fn from_reader<R>(reader: &mut R) -> Result<Self>
|
||||
pub async fn from_reader<R>(reader: &mut R) -> Result<Self>
|
||||
where
|
||||
R: Read,
|
||||
R: AsyncReadExt + Unpin + Send,
|
||||
{
|
||||
let mut version = [0u8; 2];
|
||||
reader.read_exact(&mut version)?;
|
||||
reader.read_exact(&mut version).await?;
|
||||
let version =
|
||||
PreviewMediaVersion::from_bytes(version).map_err(|_| Error::NoPreviewMedia)?;
|
||||
|
||||
match version {
|
||||
PreviewMediaVersion::V1 => {
|
||||
let mut algorithm = [0u8; 2];
|
||||
reader.read_exact(&mut algorithm)?;
|
||||
reader.read_exact(&mut algorithm).await?;
|
||||
let algorithm = Algorithm::from_bytes(algorithm)?;
|
||||
|
||||
let mut media_nonce = vec![0u8; algorithm.nonce_len()];
|
||||
reader.read_exact(&mut media_nonce)?;
|
||||
reader.read_exact(&mut media_nonce).await?;
|
||||
|
||||
reader.read_exact(&mut vec![0u8; 24 - media_nonce.len()])?;
|
||||
reader
|
||||
.read_exact(&mut vec![0u8; 24 - media_nonce.len()])
|
||||
.await?;
|
||||
|
||||
let mut media_length = [0u8; 8];
|
||||
reader.read_exact(&mut media_length)?;
|
||||
reader.read_exact(&mut media_length).await?;
|
||||
|
||||
let media_length = u64::from_le_bytes(media_length);
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let mut media = vec![0u8; media_length as usize];
|
||||
reader.read_exact(&mut media)?;
|
||||
reader.read_exact(&mut media).await?;
|
||||
|
||||
let preview_media = Self {
|
||||
version,
|
||||
|
|
|
@ -11,9 +11,10 @@
|
|||
//! let hashed_password = hashing_algorithm.hash(password, salt).unwrap();
|
||||
//! ```
|
||||
|
||||
use crate::primitives::KEY_LEN;
|
||||
use crate::Protected;
|
||||
use crate::{primitives::SALT_LEN, Error, Result};
|
||||
use crate::{
|
||||
primitives::{Key, Salt, KEY_LEN},
|
||||
Error, Protected, ProtectedVec, Result,
|
||||
};
|
||||
use argon2::Argon2;
|
||||
use balloon_hash::Balloon;
|
||||
|
||||
|
@ -52,10 +53,10 @@ impl HashingAlgorithm {
|
|||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn hash(
|
||||
&self,
|
||||
password: Protected<Vec<u8>>,
|
||||
salt: [u8; SALT_LEN],
|
||||
secret: Option<Protected<Vec<u8>>>,
|
||||
) -> Result<Protected<[u8; KEY_LEN]>> {
|
||||
password: ProtectedVec<u8>,
|
||||
salt: Salt,
|
||||
secret: Option<ProtectedVec<u8>>,
|
||||
) -> Result<Protected<Key>> {
|
||||
match self {
|
||||
Self::Argon2id(params) => PasswordHasher::argon2id(password, salt, secret, *params),
|
||||
Self::BalloonBlake3(params) => {
|
||||
|
@ -106,11 +107,11 @@ struct PasswordHasher;
|
|||
impl PasswordHasher {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn argon2id(
|
||||
password: Protected<Vec<u8>>,
|
||||
salt: [u8; SALT_LEN],
|
||||
secret: Option<Protected<Vec<u8>>>,
|
||||
password: ProtectedVec<u8>,
|
||||
salt: Salt,
|
||||
secret: Option<ProtectedVec<u8>>,
|
||||
params: Params,
|
||||
) -> Result<Protected<[u8; KEY_LEN]>> {
|
||||
) -> Result<Protected<Key>> {
|
||||
let secret = secret.map_or(Protected::new(vec![]), |k| k);
|
||||
|
||||
let mut key = [0u8; KEY_LEN];
|
||||
|
@ -129,11 +130,11 @@ impl PasswordHasher {
|
|||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn balloon_blake3(
|
||||
password: Protected<Vec<u8>>,
|
||||
salt: [u8; SALT_LEN],
|
||||
secret: Option<Protected<Vec<u8>>>,
|
||||
password: ProtectedVec<u8>,
|
||||
salt: Salt,
|
||||
secret: Option<ProtectedVec<u8>>,
|
||||
params: Params,
|
||||
) -> Result<Protected<[u8; KEY_LEN]>> {
|
||||
) -> Result<Protected<Key>> {
|
||||
let secret = secret.map_or(Protected::new(vec![]), |k| k);
|
||||
|
||||
let mut key = [0u8; KEY_LEN];
|
||||
|
|
|
@ -35,19 +35,21 @@
|
|||
//! let keys = key_manager.enumerate_hashed_keys();
|
||||
//! ```
|
||||
|
||||
use std::sync::Mutex;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::crypto::stream::{StreamDecryption, StreamEncryption};
|
||||
use crate::primitives::{
|
||||
derive_key, generate_master_key, generate_nonce, generate_salt, to_array, OnboardingConfig,
|
||||
KEY_LEN, LATEST_STORED_KEY, MASTER_PASSWORD_CONTEXT, ROOT_KEY_CONTEXT,
|
||||
};
|
||||
// use crate::primitives::{
|
||||
// derive_key, generate_master_key, generate_nonce, generate_salt, to_array, EncryptedKey, Key,
|
||||
// OnboardingConfig, Salt, KEY_LEN, LATEST_STORED_KEY, MASTER_PASSWORD_CONTEXT, ROOT_KEY_CONTEXT,
|
||||
// };
|
||||
use crate::{
|
||||
crypto::stream::Algorithm,
|
||||
primitives::{ENCRYPTED_KEY_LEN, SALT_LEN},
|
||||
Protected,
|
||||
crypto::stream::{Algorithm, StreamDecryption, StreamEncryption},
|
||||
primitives::{
|
||||
derive_key, generate_master_key, generate_nonce, generate_salt, to_array, EncryptedKey,
|
||||
Key, OnboardingConfig, Salt, ENCRYPTED_KEY_LEN, KEY_LEN, LATEST_STORED_KEY,
|
||||
MASTER_PASSWORD_CONTEXT, ROOT_KEY_CONTEXT,
|
||||
},
|
||||
Error, Protected, ProtectedVec, Result,
|
||||
};
|
||||
use crate::{Error, Result};
|
||||
|
||||
use dashmap::{DashMap, DashSet};
|
||||
use uuid::Uuid;
|
||||
|
@ -66,13 +68,13 @@ pub struct StoredKey {
|
|||
pub version: StoredKeyVersion,
|
||||
pub algorithm: Algorithm, // encryption algorithm for encrypting the master key. can be changed (requires a re-encryption though)
|
||||
pub hashing_algorithm: HashingAlgorithm, // hashing algorithm used for hashing the key with the content salt
|
||||
pub content_salt: [u8; SALT_LEN],
|
||||
pub content_salt: Salt,
|
||||
#[cfg_attr(feature = "serde", serde(with = "BigArray"))] // salt used for file data
|
||||
pub master_key: [u8; ENCRYPTED_KEY_LEN], // this is for encrypting the `key`
|
||||
pub master_key: EncryptedKey, // this is for encrypting the `key`
|
||||
pub master_key_nonce: Vec<u8>, // nonce for encrypting the master key
|
||||
pub key_nonce: Vec<u8>, // nonce used for encrypting the main key
|
||||
pub key: Vec<u8>, // encrypted. the key stored in spacedrive (e.g. generated 64 char key)
|
||||
pub salt: [u8; SALT_LEN],
|
||||
pub salt: Salt,
|
||||
pub memory_only: bool,
|
||||
pub automount: bool,
|
||||
}
|
||||
|
@ -89,8 +91,8 @@ pub enum StoredKeyVersion {
|
|||
/// This contains the plaintext key, and the same key hashed with the content salt.
|
||||
#[derive(Clone)]
|
||||
pub struct MountedKey {
|
||||
pub uuid: Uuid, // used for identification. shared with stored keys
|
||||
pub hashed_key: Protected<[u8; KEY_LEN]>, // this is hashed with the content salt, for instant access
|
||||
pub uuid: Uuid, // used for identification. shared with stored keys
|
||||
pub hashed_key: Protected<Key>, // this is hashed with the content salt, for instant access
|
||||
}
|
||||
|
||||
/// This is the key manager itself.
|
||||
|
@ -99,7 +101,7 @@ pub struct MountedKey {
|
|||
///
|
||||
/// Use the associated functions to interact with it.
|
||||
pub struct KeyManager {
|
||||
root_key: Mutex<Option<Protected<[u8; KEY_LEN]>>>, // the root key for the vault
|
||||
root_key: Mutex<Option<Protected<Key>>>, // the root key for the vault
|
||||
verification_key: Mutex<Option<StoredKey>>,
|
||||
keystore: DashMap<Uuid, StoredKey>,
|
||||
keymount: DashMap<Uuid, MountedKey>,
|
||||
|
@ -110,7 +112,7 @@ pub struct KeyManager {
|
|||
/// The `KeyManager` functions should be used for all key-related management.
|
||||
impl KeyManager {
|
||||
/// Initialize the Key Manager with `StoredKeys` retrieved from Prisma
|
||||
pub fn new(stored_keys: Vec<StoredKey>) -> Result<Self> {
|
||||
pub async fn new(stored_keys: Vec<StoredKey>) -> Result<Self> {
|
||||
let keymanager = Self {
|
||||
root_key: Mutex::new(None),
|
||||
verification_key: Mutex::new(None),
|
||||
|
@ -120,7 +122,7 @@ impl KeyManager {
|
|||
mounting_queue: DashSet::new(),
|
||||
};
|
||||
|
||||
keymanager.populate_keystore(stored_keys)?;
|
||||
keymanager.populate_keystore(stored_keys).await?;
|
||||
|
||||
Ok(keymanager)
|
||||
}
|
||||
|
@ -131,7 +133,7 @@ impl KeyManager {
|
|||
///
|
||||
/// It will also generate a verification key, which should be written to the database.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn onboarding(config: OnboardingConfig) -> Result<StoredKey> {
|
||||
pub async fn onboarding(config: OnboardingConfig) -> Result<StoredKey> {
|
||||
let content_salt = generate_salt();
|
||||
let secret_key = config.secret_key.map(Self::convert_secret_key_string);
|
||||
|
||||
|
@ -156,13 +158,16 @@ impl KeyManager {
|
|||
let root_key_nonce = generate_nonce(algorithm);
|
||||
|
||||
// Encrypt the master key with the hashed master password
|
||||
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(StreamEncryption::encrypt_bytes(
|
||||
derive_key(hashed_password, salt, MASTER_PASSWORD_CONTEXT),
|
||||
&master_key_nonce,
|
||||
algorithm,
|
||||
master_key.expose(),
|
||||
&[],
|
||||
)?)?;
|
||||
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(
|
||||
StreamEncryption::encrypt_bytes(
|
||||
derive_key(hashed_password, salt, MASTER_PASSWORD_CONTEXT),
|
||||
&master_key_nonce,
|
||||
algorithm,
|
||||
master_key.expose(),
|
||||
&[],
|
||||
)
|
||||
.await?,
|
||||
)?;
|
||||
|
||||
let encrypted_root_key = StreamEncryption::encrypt_bytes(
|
||||
master_key,
|
||||
|
@ -170,7 +175,8 @@ impl KeyManager {
|
|||
algorithm,
|
||||
root_key.expose(),
|
||||
&[],
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
let verification_key = StoredKey {
|
||||
uuid,
|
||||
|
@ -195,14 +201,14 @@ impl KeyManager {
|
|||
/// It's suitable for when you created the key manager without populating it.
|
||||
///
|
||||
/// This also detects the nil-UUID master passphrase verification key
|
||||
pub fn populate_keystore(&self, stored_keys: Vec<StoredKey>) -> Result<()> {
|
||||
pub async fn populate_keystore(&self, stored_keys: Vec<StoredKey>) -> Result<()> {
|
||||
for key in stored_keys {
|
||||
if self.keystore.contains_key(&key.uuid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if key.uuid.is_nil() {
|
||||
*self.verification_key.lock()? = Some(key);
|
||||
*self.verification_key.lock().await = Some(key);
|
||||
} else {
|
||||
self.keystore.insert(key.uuid, key);
|
||||
}
|
||||
|
@ -212,11 +218,11 @@ impl KeyManager {
|
|||
}
|
||||
|
||||
/// This function removes a key from the keystore, the keymount and it's unset as the default.
|
||||
pub fn remove_key(&self, uuid: Uuid) -> Result<()> {
|
||||
pub async fn remove_key(&self, uuid: Uuid) -> Result<()> {
|
||||
if self.keystore.contains_key(&uuid) {
|
||||
// if key is default, clear it
|
||||
// do this manually to prevent deadlocks
|
||||
let mut default = self.default.lock()?;
|
||||
let mut default = self.default.lock().await;
|
||||
if *default == Some(uuid) {
|
||||
*default = None;
|
||||
}
|
||||
|
@ -235,7 +241,7 @@ impl KeyManager {
|
|||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn change_master_password(
|
||||
pub async fn change_master_password(
|
||||
&self,
|
||||
master_password: Protected<String>,
|
||||
algorithm: Algorithm,
|
||||
|
@ -257,19 +263,22 @@ impl KeyManager {
|
|||
let master_key = generate_master_key();
|
||||
let master_key_nonce = generate_nonce(algorithm);
|
||||
|
||||
let root_key = self.get_root_key()?;
|
||||
let root_key = self.get_root_key().await?;
|
||||
let root_key_nonce = generate_nonce(algorithm);
|
||||
|
||||
let salt = generate_salt();
|
||||
|
||||
// Encrypt the master key with the hashed master password
|
||||
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(StreamEncryption::encrypt_bytes(
|
||||
derive_key(hashed_password, salt, MASTER_PASSWORD_CONTEXT),
|
||||
&master_key_nonce,
|
||||
algorithm,
|
||||
master_key.expose(),
|
||||
&[],
|
||||
)?)?;
|
||||
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(
|
||||
StreamEncryption::encrypt_bytes(
|
||||
derive_key(hashed_password, salt, MASTER_PASSWORD_CONTEXT),
|
||||
&master_key_nonce,
|
||||
algorithm,
|
||||
master_key.expose(),
|
||||
&[],
|
||||
)
|
||||
.await?,
|
||||
)?;
|
||||
|
||||
let encrypted_root_key = StreamEncryption::encrypt_bytes(
|
||||
master_key,
|
||||
|
@ -277,7 +286,8 @@ impl KeyManager {
|
|||
algorithm,
|
||||
root_key.expose(),
|
||||
&[],
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
let verification_key = StoredKey {
|
||||
uuid,
|
||||
|
@ -294,7 +304,7 @@ impl KeyManager {
|
|||
automount: false,
|
||||
};
|
||||
|
||||
*self.verification_key.lock()? = Some(verification_key.clone());
|
||||
*self.verification_key.lock().await = Some(verification_key.clone());
|
||||
|
||||
Ok(verification_key)
|
||||
}
|
||||
|
@ -303,7 +313,7 @@ impl KeyManager {
|
|||
///
|
||||
/// It returns a `Vec<StoredKey>` so they can be written to Prisma
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn import_keystore_backup(
|
||||
pub async fn import_keystore_backup(
|
||||
&self,
|
||||
master_password: Protected<String>, // at the time of the backup
|
||||
secret_key: Option<Protected<String>>, // at the time of the backup
|
||||
|
@ -347,7 +357,8 @@ impl KeyManager {
|
|||
old_verification_key.algorithm,
|
||||
&old_verification_key.master_key,
|
||||
&[],
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
// get the root key from the backup
|
||||
let old_root_key = StreamDecryption::decrypt_bytes(
|
||||
|
@ -356,7 +367,8 @@ impl KeyManager {
|
|||
old_verification_key.algorithm,
|
||||
&old_verification_key.key,
|
||||
&[],
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
Protected::new(to_array(old_root_key.into_inner())?)
|
||||
}
|
||||
|
@ -379,6 +391,7 @@ impl KeyManager {
|
|||
&key.master_key,
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.map_or(Err(Error::IncorrectPassword), |v| {
|
||||
Ok(Protected::new(to_array::<KEY_LEN>(v.into_inner())?))
|
||||
})?;
|
||||
|
@ -389,13 +402,16 @@ impl KeyManager {
|
|||
let salt = generate_salt();
|
||||
|
||||
// encrypt the master key with the current root key
|
||||
let encrypted_master_key = to_array(StreamEncryption::encrypt_bytes(
|
||||
derive_key(self.get_root_key()?, salt, ROOT_KEY_CONTEXT),
|
||||
&master_key_nonce,
|
||||
key.algorithm,
|
||||
master_key.expose(),
|
||||
&[],
|
||||
)?)?;
|
||||
let encrypted_master_key = to_array(
|
||||
StreamEncryption::encrypt_bytes(
|
||||
derive_key(self.get_root_key().await?, salt, ROOT_KEY_CONTEXT),
|
||||
&master_key_nonce,
|
||||
key.algorithm,
|
||||
master_key.expose(),
|
||||
&[],
|
||||
)
|
||||
.await?,
|
||||
)?;
|
||||
|
||||
let mut updated_key = key.clone();
|
||||
updated_key.master_key_nonce = master_key_nonce;
|
||||
|
@ -411,7 +427,7 @@ impl KeyManager {
|
|||
Ok(reencrypted_keys)
|
||||
}
|
||||
|
||||
/// This requires both the master password and the secret key
|
||||
/// This is used for unlocking the key manager, and requires both the master password and the secret key.
|
||||
///
|
||||
/// The master password and secret key are hashed together.
|
||||
/// This minimises the risk of an attacker obtaining the master password, as both of these are required to unlock the vault (and both should be stored separately).
|
||||
|
@ -422,24 +438,24 @@ impl KeyManager {
|
|||
///
|
||||
/// Note: The invalidation function is ran after updating the queue both times, so it isn't required externally.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn set_master_password<F>(
|
||||
pub async fn unlock<F>(
|
||||
&self,
|
||||
master_password: Protected<String>,
|
||||
secret_key: Option<Protected<String>>,
|
||||
invalidate: F,
|
||||
) -> Result<()>
|
||||
where
|
||||
F: Fn(),
|
||||
F: Fn() + Send,
|
||||
{
|
||||
let uuid = Uuid::nil();
|
||||
|
||||
if self.has_master_password()? {
|
||||
if self.has_master_password().await? {
|
||||
return Err(Error::KeyAlreadyMounted);
|
||||
} else if self.is_queued(uuid) {
|
||||
return Err(Error::KeyAlreadyQueued);
|
||||
}
|
||||
|
||||
let verification_key = (*self.verification_key.lock()?)
|
||||
let verification_key = (*self.verification_key.lock().await)
|
||||
.as_ref()
|
||||
.map_or(Err(Error::NoVerificationKey), |k| Ok(k.clone()))?;
|
||||
|
||||
|
@ -473,12 +489,13 @@ impl KeyManager {
|
|||
&verification_key.master_key,
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.map_err(|_| {
|
||||
self.remove_from_queue(uuid).ok();
|
||||
Error::IncorrectKeymanagerDetails
|
||||
})?;
|
||||
|
||||
*self.root_key.lock()? = Some(Protected::new(
|
||||
*self.root_key.lock().await = Some(Protected::new(
|
||||
to_array(
|
||||
StreamDecryption::decrypt_bytes(
|
||||
Protected::new(to_array(master_key.into_inner())?),
|
||||
|
@ -486,7 +503,8 @@ impl KeyManager {
|
|||
verification_key.algorithm,
|
||||
&verification_key.key,
|
||||
&[],
|
||||
)?
|
||||
)
|
||||
.await?
|
||||
.expose()
|
||||
.clone(),
|
||||
)
|
||||
|
@ -512,101 +530,113 @@ impl KeyManager {
|
|||
/// This is to ensure that only functions which require access to the mounted key receive it.
|
||||
///
|
||||
/// We could add a log to this, so that the user can view mounts
|
||||
pub fn mount(&self, uuid: Uuid) -> Result<()> {
|
||||
pub async fn mount(&self, uuid: Uuid) -> Result<()> {
|
||||
if self.keymount.get(&uuid).is_some() {
|
||||
return Err(Error::KeyAlreadyMounted);
|
||||
} else if self.is_queued(uuid) {
|
||||
return Err(Error::KeyAlreadyQueued);
|
||||
}
|
||||
|
||||
self.keystore
|
||||
.get(&uuid)
|
||||
.map_or(Err(Error::KeyNotFound), |stored_key| {
|
||||
match stored_key.version {
|
||||
StoredKeyVersion::V1 => {
|
||||
self.mounting_queue.insert(uuid);
|
||||
if let Some(stored_key) = self.keystore.get(&uuid) {
|
||||
match stored_key.version {
|
||||
StoredKeyVersion::V1 => {
|
||||
self.mounting_queue.insert(uuid);
|
||||
|
||||
let master_key = StreamDecryption::decrypt_bytes(
|
||||
derive_key(self.get_root_key()?, stored_key.salt, ROOT_KEY_CONTEXT),
|
||||
&stored_key.master_key_nonce,
|
||||
stored_key.algorithm,
|
||||
&stored_key.master_key,
|
||||
&[],
|
||||
)
|
||||
.map_or_else(
|
||||
|_| {
|
||||
self.remove_from_queue(uuid).ok();
|
||||
Err(Error::IncorrectPassword)
|
||||
},
|
||||
|v| Ok(Protected::new(to_array(v.into_inner())?)),
|
||||
)?;
|
||||
// Decrypt the StoredKey using the decrypted master key
|
||||
let key = StreamDecryption::decrypt_bytes(
|
||||
master_key,
|
||||
&stored_key.key_nonce,
|
||||
stored_key.algorithm,
|
||||
&stored_key.key,
|
||||
&[],
|
||||
)
|
||||
let master_key = StreamDecryption::decrypt_bytes(
|
||||
derive_key(
|
||||
self.get_root_key().await?,
|
||||
stored_key.salt,
|
||||
ROOT_KEY_CONTEXT,
|
||||
),
|
||||
&stored_key.master_key_nonce,
|
||||
stored_key.algorithm,
|
||||
&stored_key.master_key,
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.map_or_else(
|
||||
|_| {
|
||||
self.remove_from_queue(uuid).ok();
|
||||
Err(Error::IncorrectPassword)
|
||||
},
|
||||
|v| Ok(Protected::new(to_array(v.into_inner())?)),
|
||||
)?;
|
||||
// Decrypt the StoredKey using the decrypted master key
|
||||
let key = StreamDecryption::decrypt_bytes(
|
||||
master_key,
|
||||
&stored_key.key_nonce,
|
||||
stored_key.algorithm,
|
||||
&stored_key.key,
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
self.remove_from_queue(uuid).ok();
|
||||
e
|
||||
})?;
|
||||
|
||||
// Hash the key once with the parameters/algorithm the user selected during first mount
|
||||
let hashed_key = stored_key
|
||||
.hashing_algorithm
|
||||
.hash(key, stored_key.content_salt, None)
|
||||
.map_err(|e| {
|
||||
self.remove_from_queue(uuid).ok();
|
||||
e
|
||||
})?;
|
||||
|
||||
// Hash the key once with the parameters/algorithm the user selected during first mount
|
||||
let hashed_key = stored_key
|
||||
.hashing_algorithm
|
||||
.hash(key, stored_key.content_salt, None)
|
||||
.map_err(|e| {
|
||||
self.remove_from_queue(uuid).ok();
|
||||
e
|
||||
})?;
|
||||
self.keymount.insert(
|
||||
uuid,
|
||||
MountedKey {
|
||||
uuid: stored_key.uuid,
|
||||
hashed_key,
|
||||
},
|
||||
);
|
||||
|
||||
self.keymount.insert(
|
||||
uuid,
|
||||
MountedKey {
|
||||
uuid: stored_key.uuid,
|
||||
hashed_key,
|
||||
},
|
||||
);
|
||||
|
||||
self.remove_from_queue(uuid)?;
|
||||
}
|
||||
self.remove_from_queue(uuid)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::KeyNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
/// This function is used for getting the key value itself, from a given UUID.
|
||||
///
|
||||
/// The master password/salt needs to be present, so we are able to decrypt the key itself from the stored key.
|
||||
pub fn get_key(&self, uuid: Uuid) -> Result<Protected<Vec<u8>>> {
|
||||
self.keystore
|
||||
.get(&uuid)
|
||||
.map_or(Err(Error::KeyNotFound), |stored_key| {
|
||||
let master_key = StreamDecryption::decrypt_bytes(
|
||||
derive_key(self.get_root_key()?, stored_key.salt, ROOT_KEY_CONTEXT),
|
||||
&stored_key.master_key_nonce,
|
||||
stored_key.algorithm,
|
||||
&stored_key.master_key,
|
||||
&[],
|
||||
)
|
||||
.map_or(Err(Error::IncorrectPassword), |k| {
|
||||
Ok(Protected::new(to_array(k.into_inner())?))
|
||||
})?;
|
||||
pub async fn get_key(&self, uuid: Uuid) -> Result<ProtectedVec<u8>> {
|
||||
if let Some(stored_key) = self.keystore.get(&uuid) {
|
||||
let master_key = StreamDecryption::decrypt_bytes(
|
||||
derive_key(
|
||||
self.get_root_key().await?,
|
||||
stored_key.salt,
|
||||
ROOT_KEY_CONTEXT,
|
||||
),
|
||||
&stored_key.master_key_nonce,
|
||||
stored_key.algorithm,
|
||||
&stored_key.master_key,
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.map_or(Err(Error::IncorrectPassword), |k| {
|
||||
Ok(Protected::new(to_array(k.into_inner())?))
|
||||
})?;
|
||||
|
||||
// Decrypt the StoredKey using the decrypted master key
|
||||
let key = StreamDecryption::decrypt_bytes(
|
||||
master_key,
|
||||
&stored_key.key_nonce,
|
||||
stored_key.algorithm,
|
||||
&stored_key.key,
|
||||
&[],
|
||||
)?;
|
||||
// Decrypt the StoredKey using the decrypted master key
|
||||
let key = StreamDecryption::decrypt_bytes(
|
||||
master_key,
|
||||
&stored_key.key_nonce,
|
||||
stored_key.algorithm,
|
||||
&stored_key.key,
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(key)
|
||||
})
|
||||
Ok(key)
|
||||
} else {
|
||||
Err(Error::KeyNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
/// This function is used to add a new key/password to the keystore.
|
||||
|
@ -621,14 +651,14 @@ impl KeyManager {
|
|||
///
|
||||
/// You may optionally provide a content salt, if not one will be generated (used primarily for password-based decryption)
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn add_to_keystore(
|
||||
pub async fn add_to_keystore(
|
||||
&self,
|
||||
key: Protected<Vec<u8>>,
|
||||
key: ProtectedVec<u8>,
|
||||
algorithm: Algorithm,
|
||||
hashing_algorithm: HashingAlgorithm,
|
||||
memory_only: bool,
|
||||
automount: bool,
|
||||
content_salt: Option<[u8; SALT_LEN]>,
|
||||
content_salt: Option<Salt>,
|
||||
) -> Result<Uuid> {
|
||||
let uuid = uuid::Uuid::new_v4();
|
||||
|
||||
|
@ -643,17 +673,20 @@ impl KeyManager {
|
|||
let salt = generate_salt();
|
||||
|
||||
// Encrypt the master key with a derived key (derived from the root key)
|
||||
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(StreamEncryption::encrypt_bytes(
|
||||
derive_key(self.get_root_key()?, salt, ROOT_KEY_CONTEXT),
|
||||
&master_key_nonce,
|
||||
algorithm,
|
||||
master_key.expose(),
|
||||
&[],
|
||||
)?)?;
|
||||
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(
|
||||
StreamEncryption::encrypt_bytes(
|
||||
derive_key(self.get_root_key().await?, salt, ROOT_KEY_CONTEXT),
|
||||
&master_key_nonce,
|
||||
algorithm,
|
||||
master_key.expose(),
|
||||
&[],
|
||||
)
|
||||
.await?,
|
||||
)?;
|
||||
|
||||
// Encrypt the actual key (e.g. user-added/autogenerated, text-encodable)
|
||||
let encrypted_key =
|
||||
StreamEncryption::encrypt_bytes(master_key, &key_nonce, algorithm, &key, &[])?;
|
||||
StreamEncryption::encrypt_bytes(master_key, &key_nonce, algorithm, &key, &[]).await?;
|
||||
|
||||
// Insert it into the Keystore
|
||||
self.keystore.insert(
|
||||
|
@ -679,7 +712,7 @@ impl KeyManager {
|
|||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn convert_secret_key_string(secret_key: Protected<String>) -> Protected<Vec<u8>> {
|
||||
fn convert_secret_key_string(secret_key: Protected<String>) -> ProtectedVec<u8> {
|
||||
Protected::new(secret_key.expose().as_bytes().to_vec())
|
||||
}
|
||||
|
||||
|
@ -700,9 +733,9 @@ impl KeyManager {
|
|||
}
|
||||
|
||||
/// This allows you to set the default key
|
||||
pub fn set_default(&self, uuid: Uuid) -> Result<()> {
|
||||
pub async fn set_default(&self, uuid: Uuid) -> Result<()> {
|
||||
if self.keystore.contains_key(&uuid) {
|
||||
*self.default.lock()? = Some(uuid);
|
||||
*self.default.lock().await = Some(uuid);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::KeyNotFound)
|
||||
|
@ -710,18 +743,23 @@ impl KeyManager {
|
|||
}
|
||||
|
||||
/// This allows you to get the default key's ID
|
||||
pub fn get_default(&self) -> Result<Uuid> {
|
||||
self.default.lock()?.ok_or(Error::NoDefaultKeySet)
|
||||
pub async fn get_default(&self) -> Result<Uuid> {
|
||||
self.default.lock().await.ok_or(Error::NoDefaultKeySet)
|
||||
}
|
||||
|
||||
/// This should ONLY be used internally.
|
||||
fn get_root_key(&self) -> Result<Protected<[u8; KEY_LEN]>> {
|
||||
self.root_key.lock()?.clone().ok_or(Error::NoMasterPassword)
|
||||
async fn get_root_key(&self) -> Result<Protected<Key>> {
|
||||
self.root_key
|
||||
.lock()
|
||||
.await
|
||||
.clone()
|
||||
.ok_or(Error::NoMasterPassword)
|
||||
}
|
||||
|
||||
pub fn get_verification_key(&self) -> Result<StoredKey> {
|
||||
pub async fn get_verification_key(&self) -> Result<StoredKey> {
|
||||
self.verification_key
|
||||
.lock()?
|
||||
.lock()
|
||||
.await
|
||||
.clone()
|
||||
.ok_or(Error::NoVerificationKey)
|
||||
}
|
||||
|
@ -753,11 +791,11 @@ impl KeyManager {
|
|||
///
|
||||
/// This means we don't need to keep super specific track of which key goes to which file, and we can just throw all of them at it.
|
||||
#[must_use]
|
||||
pub fn enumerate_hashed_keys(&self) -> Vec<Protected<[u8; KEY_LEN]>> {
|
||||
pub fn enumerate_hashed_keys(&self) -> Vec<Protected<Key>> {
|
||||
self.keymount
|
||||
.iter()
|
||||
.map(|mounted_key| mounted_key.hashed_key.clone())
|
||||
.collect::<Vec<Protected<[u8; KEY_LEN]>>>()
|
||||
.collect::<Vec<Protected<Key>>>()
|
||||
}
|
||||
|
||||
/// This function is for converting a memory-only key to a saved key which syncs to the library.
|
||||
|
@ -784,8 +822,8 @@ impl KeyManager {
|
|||
}
|
||||
|
||||
/// This function is for removing a previously-added master password
|
||||
pub fn clear_root_key(&self) -> Result<()> {
|
||||
*self.root_key.lock()? = None;
|
||||
pub async fn clear_root_key(&self) -> Result<()> {
|
||||
*self.root_key.lock().await = None;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -793,8 +831,8 @@ impl KeyManager {
|
|||
/// This function is used for seeing if the key manager has a master password.
|
||||
///
|
||||
/// Technically this checks for the root key, but it makes no difference to the front end.
|
||||
pub fn has_master_password(&self) -> Result<bool> {
|
||||
Ok(self.root_key.lock()?.is_some())
|
||||
pub async fn has_master_password(&self) -> Result<bool> {
|
||||
Ok(self.root_key.lock().await.is_some())
|
||||
}
|
||||
|
||||
/// This function is used for unmounting all keys at once.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -44,6 +44,10 @@ pub const MASTER_PASSWORD_CONTEXT: &str =
|
|||
"spacedrive 2022-12-14 15:35:41 master password hash derivation"; // used for deriving keys from the master password hash
|
||||
pub const FILE_KEY_CONTEXT: &str = "spacedrive 2022-12-14 12:54:12 file key derivation"; // used for deriving keys from user key/content salt hashes (for file encryption)
|
||||
|
||||
pub type Key = [u8; KEY_LEN];
|
||||
pub type EncryptedKey = [u8; ENCRYPTED_KEY_LEN];
|
||||
pub type Salt = [u8; SALT_LEN];
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
|
||||
#[cfg_attr(feature = "rspc", derive(specta::Type))]
|
||||
|
@ -70,7 +74,7 @@ pub fn generate_nonce(algorithm: Algorithm) -> Vec<u8> {
|
|||
///
|
||||
/// This function uses `ChaCha20Rng` for generating cryptographically-secure random data
|
||||
#[must_use]
|
||||
pub fn generate_salt() -> [u8; SALT_LEN] {
|
||||
pub fn generate_salt() -> Salt {
|
||||
let mut salt = [0u8; SALT_LEN];
|
||||
rand_chacha::ChaCha20Rng::from_entropy().fill_bytes(&mut salt);
|
||||
salt
|
||||
|
@ -82,7 +86,7 @@ pub fn generate_salt() -> [u8; SALT_LEN] {
|
|||
///
|
||||
/// This function uses `ChaCha20Rng` for generating cryptographically-secure random data
|
||||
#[must_use]
|
||||
pub fn generate_master_key() -> Protected<[u8; KEY_LEN]> {
|
||||
pub fn generate_master_key() -> Protected<Key> {
|
||||
let mut master_key = [0u8; KEY_LEN];
|
||||
rand_chacha::ChaCha20Rng::from_entropy().fill_bytes(&mut master_key);
|
||||
Protected::new(master_key)
|
||||
|
@ -90,11 +94,7 @@ pub fn generate_master_key() -> Protected<[u8; KEY_LEN]> {
|
|||
|
||||
#[must_use]
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn derive_key(
|
||||
key: Protected<[u8; KEY_LEN]>,
|
||||
salt: [u8; SALT_LEN],
|
||||
context: &str,
|
||||
) -> Protected<[u8; KEY_LEN]> {
|
||||
pub fn derive_key(key: Protected<Key>, salt: Salt, context: &str) -> Protected<Key> {
|
||||
let mut input = key.expose().to_vec();
|
||||
input.extend_from_slice(&salt);
|
||||
|
||||
|
@ -107,7 +107,7 @@ pub fn derive_key(
|
|||
|
||||
/// This is used for converting a `Vec<u8>` to an array of bytes
|
||||
///
|
||||
/// It's main usage is for converting an encrypted master key from a `Vec<u8>` to `[u8; ENCRYPTED_KEY_LEN]`
|
||||
/// It's main usage is for converting an encrypted master key from a `Vec<u8>` to `EncryptedKey`
|
||||
///
|
||||
/// As the master key is encrypted at this point, it does not need to be `Protected<>`
|
||||
///
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
use std::{fmt::Debug, mem::swap};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
pub type ProtectedVec<T> = Protected<Vec<T>>;
|
||||
#[derive(Clone)]
|
||||
pub struct Protected<T>
|
||||
where
|
||||
|
|
|
@ -55,8 +55,8 @@ export type Procedures = {
|
|||
{ key: "keys.mount", input: LibraryArgs<string>, result: null } |
|
||||
{ key: "keys.restoreKeystore", input: LibraryArgs<RestoreBackupArgs>, result: number } |
|
||||
{ key: "keys.setDefault", input: LibraryArgs<string>, result: null } |
|
||||
{ key: "keys.setMasterPassword", input: LibraryArgs<SetMasterPasswordArgs>, result: null } |
|
||||
{ key: "keys.syncKeyToLibrary", input: LibraryArgs<string>, result: null } |
|
||||
{ key: "keys.unlockKeyManager", input: LibraryArgs<UnlockKeyManagerArgs>, result: null } |
|
||||
{ key: "keys.unmount", input: LibraryArgs<string>, result: null } |
|
||||
{ key: "keys.unmountAll", input: LibraryArgs<null>, result: null } |
|
||||
{ key: "keys.updateAutomountStatus", input: LibraryArgs<AutomountUpdateArgs>, result: null } |
|
||||
|
@ -177,8 +177,6 @@ export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChil
|
|||
|
||||
export interface SetFavoriteArgs { id: number, favorite: boolean }
|
||||
|
||||
export interface SetMasterPasswordArgs { password: string, secret_key: string | null }
|
||||
|
||||
export interface SetNoteArgs { id: number, note: string | null }
|
||||
|
||||
export interface Statistics { id: number, date_captured: string, total_object_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string }
|
||||
|
@ -195,6 +193,8 @@ export interface TagCreateArgs { name: string, color: string }
|
|||
|
||||
export interface TagUpdateArgs { id: number, name: string | null, color: string | null }
|
||||
|
||||
export interface UnlockKeyManagerArgs { password: string, secret_key: string | null }
|
||||
|
||||
export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean }
|
||||
|
||||
export interface FilePathWithObject { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, object_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, object: Object | null }
|
||||
|
|
|
@ -41,7 +41,7 @@ export const DecryptFileDialog = (props: DecryptDialogProps) => {
|
|||
const decryptFile = useLibraryMutation('files.decryptFiles', {
|
||||
onSuccess: () => {
|
||||
showAlertDialog({
|
||||
title: 'Info',
|
||||
title: 'Success',
|
||||
value:
|
||||
'The decryption job has started successfully. You may track the progress in the job overview panel.'
|
||||
});
|
||||
|
@ -59,26 +59,24 @@ export const DecryptFileDialog = (props: DecryptDialogProps) => {
|
|||
const PasswordCurrentEyeIcon = show.password ? EyeSlash : Eye;
|
||||
|
||||
const form = useZodForm({
|
||||
schema,
|
||||
defaultValues: {
|
||||
type: hasMountedKeys ? 'key' : 'password',
|
||||
saveToKeyManager: true
|
||||
}
|
||||
saveToKeyManager: true,
|
||||
outputPath: '',
|
||||
password: ''
|
||||
},
|
||||
schema
|
||||
});
|
||||
|
||||
const onSubmit = form.handleSubmit((data) => {
|
||||
const output = data.outputPath !== '' ? data.outputPath : null;
|
||||
const pw = data.type === 'password' ? data.password : null;
|
||||
const save = data.type === 'password' ? data.saveToKeyManager : null;
|
||||
|
||||
return decryptFile.mutateAsync({
|
||||
const onSubmit = form.handleSubmit((data) =>
|
||||
decryptFile.mutateAsync({
|
||||
location_id: props.location_id,
|
||||
path_id: props.path_id,
|
||||
output_path: output,
|
||||
password: pw,
|
||||
save_to_library: save
|
||||
});
|
||||
});
|
||||
output_path: data.outputPath !== '' ? data.outputPath : null,
|
||||
password: data.type === 'password' ? data.password : null,
|
||||
save_to_library: data.type === 'password' ? data.saveToKeyManager : null
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
|
|
|
@ -60,6 +60,7 @@ export const EncryptFileDialog = ({ ...props }: EncryptDialogProps) => {
|
|||
});
|
||||
|
||||
const form = useZodForm({
|
||||
defaultValues: { encryptionAlgo: 'XChaCha20Poly1305', outputPath: '' },
|
||||
schema
|
||||
});
|
||||
|
||||
|
@ -147,6 +148,7 @@ export const EncryptFileDialog = ({ ...props }: EncryptDialogProps) => {
|
|||
className="mt-2 text-gray-400/80"
|
||||
onChange={() => {}}
|
||||
value={form.watch('hashingAlgo')}
|
||||
disabled
|
||||
>
|
||||
<SelectOption value="Argon2id-s">Argon2id (standard)</SelectOption>
|
||||
<SelectOption value="Argon2id-h">Argon2id (hardened)</SelectOption>
|
||||
|
|
|
@ -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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Reference in a new issue