Merge branch 'main' into non-unique-objects

This commit is contained in:
Brendan Allan committed 2023-01-30 12:11:35 +08:00
commit d91bb9aa1d
40 files changed
+884 -736

No files matched your search

Generated
BIN
View File
Binary file not shown.
+4
View File
@@ -34,6 +34,8 @@ specta = { version = "0.0.4" }
swift-rs = { git = "https://github.com/Brendonovich/swift-rs.git", rev = "833e29ba333f1dfe303eaa21de78c4f8c5a3f2ff" }
tokio = { version = "1.24.2" }
[patch.crates-io]
# We use this patch so we can compile for the IOS simulator on M1
openssl-sys = { git = "https://github.com/spacedriveapp/rust-openssl", rev = "92c3dec225a9e984884d5b30a517e5d44a24d03b" }
@@ -41,3 +43,5 @@ openssl-sys = { git = "https://github.com/spacedriveapp/rust-openssl", rev = "92
rspc = { git = "https://github.com/oscartbeaumont/rspc", rev = "6243b5b6a1376940a40318340e5eaef22e4a2c22" } # TODO: Move back to crates.io when new jsonrpc executor + `tokio::spawn` in the Tauri IPC plugin is released
normi = { git = "https://github.com/oscartbeaumont/rspc", rev = "6243b5b6a1376940a40318340e5eaef22e4a2c22" } # TODO: When normi is released on crates.io
specta = { git = "https://github.com/oscartbeaumont/rspc", rev = "6243b5b6a1376940a40318340e5eaef22e4a2c22" } # TODO: When normi is released on crates.io
tokio = { git = "https://github.com/spacedriveapp/tokio", rev = "d3936848ccb32ac7df41614acfabe5139a6a7d75" } # TODO: move back once tokio raise their internal read buffer size (ENG-351 for more)
+1
View File
@@ -11,3 +11,4 @@ clap = { version = "4.0.32", features = ["derive"] }
anyhow = "1.0.68"
hex = "0.4.3"
sd-crypto = { path = "../../crates/crypto" }
tokio = { workspace = true, features = ["io-util", "rt-multi-thread"] }
+8 -6
View File
@@ -2,7 +2,8 @@ use anyhow::{Context, Result};
use clap::Parser;
use indoc::printdoc;
use sd_crypto::header::file::FileHeader;
use std::{fs::File, path::PathBuf};
use std::path::PathBuf;
use tokio::fs::File;
#[derive(Parser)]
struct Args {
@@ -10,17 +11,18 @@ struct Args {
path: PathBuf,
}
fn main() -> Result<()> {
#[tokio::main]
async fn main() -> Result<()> {
let args = Args::parse();
let mut reader = File::open(args.path).context("unable to open file")?;
let (header, aad) = FileHeader::from_reader(&mut reader)?;
print_details(&header, &aad);
let mut reader = File::open(args.path).await.context("unable to open file")?;
let (header, aad) = FileHeader::from_reader(&mut reader).await?;
print_crypto_details(&header, &aad);
Ok(())
}
fn print_details(header: &FileHeader, aad: &[u8]) {
fn print_crypto_details(header: &FileHeader, aad: &[u8]) {
printdoc! {"
Header version: {version}
Encryption algorithm: {algorithm}
+2 -4
View File
@@ -13,7 +13,7 @@ build = "build.rs"
tauri = { version = "1.1.1", features = ["api-all", "macos-private-api"] }
rspc = { workspace = true, features = ["tauri"] }
sd-core = { path = "../../../core", features = ["ffmpeg", "location-watcher"] }
tokio = { version = "1.21.2", features = ["sync"] }
tokio = { workspace = true, features = ["sync"] }
window-shadows = "0.2.0"
tracing = "0.1.36"
serde = "1.0.145"
@@ -25,9 +25,7 @@ swift-rs.workspace = true
tauri-build = { version = "1.1.1", features = [] }
[target.'cfg(target_os = "macos")'.build-dependencies]
swift-rs = { workspace = true, features = [
"build",
] }
swift-rs = { workspace = true, features = ["build"] }
[features]
default = ["custom-protocol"]
+3 -3
View File
@@ -6,16 +6,16 @@ rust-version = "1.64.0"
[features]
android = ["sd-core/android"]
[dependencies]
once_cell = "1.15.0"
sd-core = { path = "../../../../core", features = [
"mobile",
"p2p",
], default-features = false }
rspc.workspace= true
rspc.workspace = true
serde_json = "1.0.85"
tokio = "1.21.2"
tokio = { workspace = true }
openssl = { version = "0.10.42", features = [
"vendored",
] } # Override features of transitive dependencies
+1 -1
View File
@@ -7,6 +7,6 @@ edition = "2021"
sd-core = { path = "../../core", features = ["ffmpeg"] }
rspc = { workspace = true, features = ["axum"] }
axum = "0.5.16"
tokio = { version = "1.21.2", features = ["sync", "rt-multi-thread", "signal"] }
tokio = { workspace = true, features = ["sync", "rt-multi-thread", "signal"] }
tracing = "0.1.36"
ctrlc = "3.2.3"
+8 -6
View File
@@ -19,9 +19,7 @@ ffmpeg = [
"dep:ffmpeg-next",
"dep:sd-ffmpeg",
] # This feature controls whether the Spacedrive Core contains functionality which requires FFmpeg.
location-watcher = [
"dep:notify"
]
location-watcher = ["dep:notify"]
[dependencies]
hostname = "0.3.1"
@@ -46,10 +44,12 @@ uuid = { version = "1.1.2", features = ["v4", "serde"] }
sysinfo = "0.26.4"
thiserror = "1.0.37"
tokio = { version = "1.21.2", features = [
tokio = { workspace = true, features = [
"sync",
"rt-multi-thread",
"io-util",
"macros",
"time",
] }
include_dir = { version = "0.7.2", features = ["glob"] }
async-trait = "^0.1.57"
@@ -58,7 +58,7 @@ webp = "0.2.2"
ffmpeg-next = { version = "5.1.1", optional = true, features = [] }
sd-ffmpeg = { path = "../crates/ffmpeg", optional = true }
sd-crypto = { path = "../crates/crypto", features = ["rspc", "serde"] }
sd-file-ext = { path = "../crates/file-ext"}
sd-file-ext = { path = "../crates/file-ext" }
sd-sync = { path = "../crates/sync" }
fs_extra = "1.2.0"
tracing = "0.1.36"
@@ -70,7 +70,9 @@ ctor = "0.1.23"
globset = { version = "^0.4.9", features = ["serde1"] }
itertools = "^0.10.5"
enumflags2 = "0.7.5"
notify = { version = "5.0.0", default-features = false, features = ["macos_fsevent"], optional = true }
notify = { version = "5.0.0", default-features = false, features = [
"macos_fsevent",
], optional = true }
uhlc = "0.5.1"
[dev-dependencies]
+48 -37
View File
@@ -23,7 +23,7 @@ pub struct KeyAddArgs {
}
#[derive(Type, Deserialize)]
pub struct SetMasterPasswordArgs {
pub struct UnlockKeyManagerArgs {
password: String,
secret_key: Option<String>,
}
@@ -56,7 +56,9 @@ pub(crate) fn mount() -> RouterBuilder {
})
// do not unlock the key manager until this route returns true
.library_query("hasMasterPassword", |t| {
t(|_, _: (), library| async move { Ok(library.key_manager.has_master_password()?) })
t(
|_, _: (), library| async move { Ok(library.key_manager.has_master_password().await?) },
)
})
// this is so we can show the key as mounted in the UI
.library_query("listMounted", |t| {
@@ -64,14 +66,14 @@ pub(crate) fn mount() -> RouterBuilder {
})
.library_query("getKey", |t| {
t(|_, key_uuid: Uuid, library| async move {
let key = library.key_manager.get_key(key_uuid)?;
let key = library.key_manager.get_key(key_uuid).await?;
Ok(String::from_utf8(key.into_inner()).map_err(Error::StringParse)?)
})
})
.library_mutation("mount", |t| {
t(|_, key_uuid: Uuid, library| async move {
library.key_manager.mount(key_uuid)?;
library.key_manager.mount(key_uuid).await?;
// we also need to dispatch jobs that automatically decrypt preview media and metadata here
invalidate_query!(library, "keys.listMounted");
Ok(())
@@ -88,7 +90,7 @@ pub(crate) fn mount() -> RouterBuilder {
.library_mutation("clearMasterPassword", |t| {
t(|_, _: (), library| async move {
// This technically clears the root key, but it means the same thing to the frontend
library.key_manager.clear_root_key()?;
library.key_manager.clear_root_key().await?;
invalidate_query!(library, "keys.hasMasterPassword");
Ok(())
@@ -139,7 +141,7 @@ pub(crate) fn mount() -> RouterBuilder {
.await?;
}
library.key_manager.remove_key(key_uuid)?;
library.key_manager.remove_key(key_uuid).await?;
// we also need to delete all in-memory decrypted data associated with this key
invalidate_query!(library, "keys.list");
@@ -148,14 +150,17 @@ pub(crate) fn mount() -> RouterBuilder {
Ok(())
})
})
.library_mutation("setMasterPassword", |t| {
t(|_, args: SetMasterPasswordArgs, library| async move {
.library_mutation("unlockKeyManager", |t| {
t(|_, args: UnlockKeyManagerArgs, library| async move {
// if this returns an error, the user MUST re-enter the correct password
library.key_manager.set_master_password(
Protected::new(args.password),
args.secret_key.map(Protected::new),
|| invalidate_query!(library, "keys.isKeyManagerUnlocking"),
)?;
library
.key_manager
.unlock(
Protected::new(args.password),
args.secret_key.map(Protected::new),
|| invalidate_query!(library, "keys.isKeyManagerUnlocking"),
)
.await?;
invalidate_query!(library, "keys.hasMasterPassword");
@@ -169,7 +174,8 @@ pub(crate) fn mount() -> RouterBuilder {
for key in automount {
library
.key_manager
.mount(Uuid::from_str(&key.uuid).map_err(|_| Error::Serialization)?)?;
.mount(Uuid::from_str(&key.uuid).map_err(|_| Error::Serialization)?)
.await?;
invalidate_query!(library, "keys.listMounted");
}
@@ -179,7 +185,7 @@ pub(crate) fn mount() -> RouterBuilder {
})
.library_mutation("setDefault", |t| {
t(|_, key_uuid: Uuid, library| async move {
library.key_manager.set_default(key_uuid)?;
library.key_manager.set_default(key_uuid).await?;
library
.db
@@ -206,7 +212,7 @@ pub(crate) fn mount() -> RouterBuilder {
})
})
.library_query("getDefault", |t| {
t(|_, _: (), library| async move { library.key_manager.get_default().ok() })
t(|_, _: (), library| async move { library.key_manager.get_default().await.ok() })
})
.library_query("isKeyManagerUnlocking", |t| {
t(|_, _: (), library| async move { library.key_manager.is_queued(Uuid::nil()) })
@@ -222,14 +228,17 @@ pub(crate) fn mount() -> RouterBuilder {
.library_mutation("add", |t| {
t(|_, args: KeyAddArgs, library| async move {
// register the key with the keymanager
let uuid = library.key_manager.add_to_keystore(
Protected::new(args.key.as_bytes().to_vec()),
args.algorithm,
args.hashing_algorithm,
!args.library_sync,
args.automount,
None,
)?;
let uuid = library
.key_manager
.add_to_keystore(
Protected::new(args.key.as_bytes().to_vec()),
args.algorithm,
args.hashing_algorithm,
!args.library_sync,
args.automount,
None,
)
.await?;
if args.library_sync {
write_storedkey_to_db(&library.db, &library.key_manager.access_keystore(uuid)?)
@@ -248,7 +257,7 @@ pub(crate) fn mount() -> RouterBuilder {
}
}
library.key_manager.mount(uuid)?;
library.key_manager.mount(uuid).await?;
invalidate_query!(library, "keys.list");
invalidate_query!(library, "keys.listMounted");
@@ -261,7 +270,7 @@ pub(crate) fn mount() -> RouterBuilder {
let mut stored_keys = library.key_manager.dump_keystore();
// include the verification key at the time of backup
stored_keys.push(library.key_manager.get_verification_key()?);
stored_keys.push(library.key_manager.get_verification_key().await?);
// exclude all memory-only keys
stored_keys.retain(|k| !k.memory_only);
@@ -290,11 +299,10 @@ pub(crate) fn mount() -> RouterBuilder {
let secret_key = args.secret_key.map(Protected::new);
let updated_keys = library.key_manager.import_keystore_backup(
Protected::new(args.password),
secret_key,
&stored_keys,
)?;
let updated_keys = library
.key_manager
.import_keystore_backup(Protected::new(args.password), secret_key, &stored_keys)
.await?;
for key in &updated_keys {
write_storedkey_to_db(&library.db, key).await?;
@@ -310,12 +318,15 @@ pub(crate) fn mount() -> RouterBuilder {
t(|_, args: MasterPasswordChangeArgs, library| async move {
let secret_key = args.secret_key.map(Protected::new);
let verification_key = library.key_manager.change_master_password(
Protected::new(args.password),
args.algorithm,
args.hashing_algorithm,
secret_key,
)?;
let verification_key = library
.key_manager
.change_master_password(
Protected::new(args.password),
args.algorithm,
args.hashing_algorithm,
secret_key,
)
.await?;
// remove old nil-id keys if they were set
library
+3 -3
View File
@@ -111,7 +111,7 @@ pub async fn seed_keymanager(
.unwrap();
// insert all keys from the DB into the keymanager's keystore
km.populate_keystore(stored_keys)?;
km.populate_keystore(stored_keys).await?;
// if any key had an associated default tag
default.map(|k| km.set_default(k));
@@ -197,7 +197,7 @@ impl LibraryManager {
indexer_rules_seeder(&library.db).await?;
// setup master password
let verification_key = KeyManager::onboarding(km_config)?;
let verification_key = KeyManager::onboarding(km_config).await?;
write_storedkey_to_db(&library.db, &verification_key).await?;
@@ -329,7 +329,7 @@ impl LibraryManager {
.exec()
.await?;
let key_manager = Arc::new(KeyManager::new(vec![])?);
let key_manager = Arc::new(KeyManager::new(vec![]).await?);
seed_keymanager(&db, &key_manager).await?;
let (sync_manager, _) = SyncManager::new(db.clone(), id);
+23 -23
View File
@@ -1,9 +1,8 @@
use sd_crypto::{crypto::stream::StreamDecryption, header::file::FileHeader, Protected};
use serde::{Deserialize, Serialize};
use specta::Type;
use std::{collections::VecDeque, fs::File, path::PathBuf};
use tokio::task;
use std::{collections::VecDeque, path::PathBuf};
use tokio::fs::File;
use crate::job::{JobError, JobReportUpdate, JobResult, JobState, StatefulJob, WorkerContext};
@@ -82,10 +81,10 @@ impl StatefulJob for FileDecryptorJob {
|p| p,
);
let mut reader = File::open(info.fs_path.clone())?;
let mut writer = File::create(output_path)?;
let mut reader = File::open(info.fs_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)
+39 -39
View File
@@ -1,6 +1,6 @@
use super::{context_menu_fs_info, FsInfo};
use crate::{job::*, library::LibraryContext};
use chrono::FixedOffset;
use sd_crypto::{
crypto::stream::{Algorithm, StreamEncryption},
header::{file::FileHeader, keyslot::Keyslot},
@@ -9,12 +9,10 @@ use sd_crypto::{
LATEST_PREVIEW_MEDIA,
},
};
use chrono::FixedOffset;
use serde::{Deserialize, Serialize};
use specta::Type;
use std::{fs::File, io::Read, path::PathBuf};
use tokio::task;
use std::path::PathBuf;
use tokio::{fs::File, io::AsyncReadExt};
use tracing::warn;
pub struct FileEncryptorJob;
@@ -127,22 +125,25 @@ impl StatefulJob for FileEncryptorJob {
)
.await?;
let mut reader = task::block_in_place(|| File::open(&info.fs_path))?;
let mut writer = task::block_in_place(|| File::create(output_path))?;
let mut reader = File::open(&info.fs_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 {
@@ -160,12 +161,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
@@ -183,18 +186,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
@@ -204,15 +206,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",
+17 -14
View File
@@ -42,26 +42,29 @@ specta = { workspace = true, optional = true }
hex = "0.4.3"
tokio = { version = "1.21.2", features = ["io-util", "rt-multi-thread"] }
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
+59 -52
View File
@@ -1,65 +1,72 @@
use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion};
use sd_crypto::{
crypto::stream::{Algorithm, StreamDecryption, StreamEncryption},
primitives::{generate_master_key, generate_nonce},
};
// use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion};
// use sd_crypto::{
// crypto::stream::{Algorithm, StreamDecryption, StreamEncryption},
// primitives::{generate_master_key, generate_nonce},
// };
const ALGORITHM: Algorithm = Algorithm::Aes256Gcm;
// const ALGORITHM: Algorithm = Algorithm::Aes256Gcm;
const KB: usize = 1024;
// const KB: usize = 1024;
const SIZES: [usize; 8] = [
KB * 16,
KB * 32,
KB * 64,
KB * 128,
KB * 512,
KB * 1024,
KB * 2048,
KB * 4096,
];
// const SIZES: [usize; 8] = [
// KB * 16,
// KB * 32,
// KB * 64,
// KB * 128,
// KB * 512,
// KB * 1024,
// KB * 2048,
// KB * 4096,
// ];
fn bench(c: &mut Criterion) {
let mut group = c.benchmark_group("aes-256-gcm");
// async fn bench(c: &mut Criterion) {
// let mut group = c.benchmark_group("aes-256-gcm");
for size in SIZES {
let buf = vec![0u8; size];
// for size in SIZES {
// let buf = vec![0u8; size];
let key = generate_master_key();
let nonce = generate_nonce(ALGORITHM);
// let key = generate_master_key();
// let nonce = generate_nonce(ALGORITHM);
let encrypted_bytes =
StreamEncryption::encrypt_bytes(key.clone(), &nonce, ALGORITHM, &buf, &[]).unwrap(); // bytes to decrypt
// let encrypted_bytes =
// StreamEncryption::encrypt_bytes(key.clone(), &nonce, ALGORITHM, &buf, &[])
// .await
// .unwrap(); // bytes to decrypt
group.throughput(criterion::Throughput::Bytes(size as u64));
// group.throughput(criterion::Throughput::Bytes(size as u64));
group.bench_function(BenchmarkId::new("encrypt", size), |b| {
b.iter_batched(
|| key.clone(),
|key| StreamEncryption::encrypt_bytes(key, &nonce, ALGORITHM, &buf, &[]).unwrap(),
BatchSize::SmallInput,
)
});
// group.bench_function(BenchmarkId::new("encrypt", size), |b| {
// b.iter_batched(
// || key.clone(),
// |key| async {
// StreamEncryption::encrypt_bytes(key, &nonce, ALGORITHM, &buf, &[])
// .await
// .unwrap()
// },
// BatchSize::SmallInput,
// )
// });
group.bench_function(BenchmarkId::new("decrypt", size), |b| {
b.iter_batched(
|| key.clone(),
|key| {
StreamDecryption::decrypt_bytes(key, &nonce, ALGORITHM, &encrypted_bytes, &[])
.unwrap()
},
BatchSize::SmallInput,
)
});
}
// group.bench_function(BenchmarkId::new("decrypt", size), |b| {
// b.iter_batched(
// || key.clone(),
// |key| async {
// StreamDecryption::decrypt_bytes(key, &nonce, ALGORITHM, &encrypted_bytes, &[])
// .await
// .unwrap()
// },
// BatchSize::SmallInput,
// )
// });
// }
group.finish();
}
// group.finish();
// }
criterion_group!(
name = benches;
config = Criterion::default();
targets = bench
);
// criterion_group!(
// name = benches;
// config = Criterion::default();
// targets = bench;
// );
criterion_main!(benches);
// criterion_main!(benches);
+29 -29
View File
@@ -1,36 +1,36 @@
use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion};
use sd_crypto::{
keys::hashing::{HashingAlgorithm, Params},
primitives::{generate_master_key, generate_salt},
Protected,
};
// use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion};
// use sd_crypto::{
// keys::hashing::{HashingAlgorithm, Params},
// primitives::{generate_master_key, generate_salt},
// Protected,
// };
const PARAMS: [Params; 3] = [Params::Standard, Params::Hardened, Params::Paranoid];
// const PARAMS: [Params; 3] = [Params::Standard, Params::Hardened, Params::Paranoid];
fn bench(c: &mut Criterion) {
let mut group = c.benchmark_group("argon2id");
// fn bench(c: &mut Criterion) {
// let mut group = c.benchmark_group("argon2id");
for param in PARAMS {
let key = Protected::new(generate_master_key().expose().to_vec());
let salt = generate_salt();
let hashing_algorithm = HashingAlgorithm::Argon2id(param);
// for param in PARAMS {
// let key = Protected::new(generate_master_key().expose().to_vec());
// let salt = generate_salt();
// let hashing_algorithm = HashingAlgorithm::Argon2id(param);
group.bench_function(BenchmarkId::new("hash", param.argon2id().m_cost()), |b| {
b.iter_batched(
|| (key.clone(), salt),
|(key, salt)| hashing_algorithm.hash(key, salt, None),
BatchSize::SmallInput,
)
});
}
// group.bench_function(BenchmarkId::new("hash", param.argon2id().m_cost()), |b| {
// b.iter_batched(
// || (key.clone(), salt),
// |(key, salt)| hashing_algorithm.hash(key, salt, None),
// BatchSize::SmallInput,
// )
// });
// }
group.finish();
}
// group.finish();
// }
criterion_group!(
name = benches;
config = Criterion::default();
targets = bench
);
// criterion_group!(
// name = benches;
// config = Criterion::default();
// targets = bench
// );
criterion_main!(benches);
// criterion_main!(benches);
+52 -52
View File
@@ -1,65 +1,65 @@
use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion};
use sd_crypto::{
crypto::stream::{Algorithm, StreamDecryption, StreamEncryption},
primitives::{generate_master_key, generate_nonce},
};
// use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion};
// use sd_crypto::{
// crypto::stream::{Algorithm, StreamDecryption, StreamEncryption},
// primitives::{generate_master_key, generate_nonce},
// };
const ALGORITHM: Algorithm = Algorithm::XChaCha20Poly1305;
// const ALGORITHM: Algorithm = Algorithm::XChaCha20Poly1305;
const KB: usize = 1024;
// const KB: usize = 1024;
const SIZES: [usize; 8] = [
KB * 16,
KB * 32,
KB * 64,
KB * 128,
KB * 512,
KB * 1024,
KB * 2048,
KB * 4096,
];
// const SIZES: [usize; 8] = [
// KB * 16,
// KB * 32,
// KB * 64,
// KB * 128,
// KB * 512,
// KB * 1024,
// KB * 2048,
// KB * 4096,
// ];
fn bench(c: &mut Criterion) {
let mut group = c.benchmark_group("xchacha20-poly1305");
// fn bench(c: &mut Criterion) {
// let mut group = c.benchmark_group("xchacha20-poly1305");
for size in SIZES {
let buf = vec![0u8; size];
// for size in SIZES {
// let buf = vec![0u8; size];
let key = generate_master_key();
let nonce = generate_nonce(ALGORITHM);
// let key = generate_master_key();
// let nonce = generate_nonce(ALGORITHM);
let encrypted_bytes =
StreamEncryption::encrypt_bytes(key.clone(), &nonce, ALGORITHM, &buf, &[]).unwrap(); // bytes to decrypt
// let encrypted_bytes =
// StreamEncryption::encrypt_bytes(key.clone(), &nonce, ALGORITHM, &buf, &[]).unwrap(); // bytes to decrypt
group.throughput(criterion::Throughput::Bytes(size as u64));
// group.throughput(criterion::Throughput::Bytes(size as u64));
group.bench_function(BenchmarkId::new("encrypt", size), |b| {
b.iter_batched(
|| key.clone(),
|key| StreamEncryption::encrypt_bytes(key, &nonce, ALGORITHM, &buf, &[]).unwrap(),
BatchSize::SmallInput,
)
});
// group.bench_function(BenchmarkId::new("encrypt", size), |b| {
// b.iter_batched(
// || key.clone(),
// |key| StreamEncryption::encrypt_bytes(key, &nonce, ALGORITHM, &buf, &[]).unwrap(),
// BatchSize::SmallInput,
// )
// });
group.bench_function(BenchmarkId::new("decrypt", size), |b| {
b.iter_batched(
|| key.clone(),
|key| {
StreamDecryption::decrypt_bytes(key, &nonce, ALGORITHM, &encrypted_bytes, &[])
.unwrap()
},
BatchSize::SmallInput,
)
});
}
// group.bench_function(BenchmarkId::new("decrypt", size), |b| {
// b.iter_batched(
// || key.clone(),
// |key| {
// StreamDecryption::decrypt_bytes(key, &nonce, ALGORITHM, &encrypted_bytes, &[])
// .unwrap()
// },
// BatchSize::SmallInput,
// )
// });
// }
group.finish();
}
// group.finish();
// }
criterion_group!(
name = benches;
config = Criterion::default();
targets = bench
);
// criterion_group!(
// name = benches;
// config = Criterion::default();
// targets = bench
// );
criterion_main!(benches);
// criterion_main!(benches);
+17 -13
View File
@@ -1,4 +1,4 @@
use std::fs::File;
use tokio::fs::File;
use sd_crypto::{
crypto::stream::{Algorithm, StreamDecryption, StreamEncryption},
@@ -11,12 +11,12 @@ use sd_crypto::{
const ALGORITHM: Algorithm = Algorithm::XChaCha20Poly1305;
const HASHING_ALGORITHM: HashingAlgorithm = HashingAlgorithm::Argon2id(Params::Standard);
pub fn encrypt() {
async fn encrypt() {
let password = Protected::new(b"password".to_vec());
// Open both the source and the output file
let mut reader = File::open("test").unwrap();
let mut writer = File::create("test.encrypted").unwrap();
let mut reader = File::open("test").await.unwrap();
let mut writer = File::create("test.encrypted").await.unwrap();
// This needs to be generated here, otherwise we won't have access to it for encryption
let master_key = generate_master_key();
@@ -36,13 +36,14 @@ pub fn encrypt() {
hashed_password,
master_key.clone(),
)
.await
.unwrap()];
// Create the header for the encrypted file
let header = FileHeader::new(LATEST_FILE_HEADER, ALGORITHM, keyslots);
// Write the header to the file
header.write(&mut writer).unwrap();
header.write(&mut writer).await.unwrap();
// Use the nonce created by the header to initialize a stream encryption object
let encryptor = StreamEncryption::new(master_key, &header.nonce, header.algorithm).unwrap();
@@ -51,21 +52,22 @@ pub fn encrypt() {
// Use AAD so the header can be authenticated against every block of data
encryptor
.encrypt_streams(&mut reader, &mut writer, &header.generate_aad())
.await
.unwrap();
}
pub fn decrypt() {
async fn decrypt() {
let password = Protected::new(b"password".to_vec());
// Open both the encrypted file and the output file
let mut reader = File::open("test.encrypted").unwrap();
let mut writer = File::create("test.original").unwrap();
let mut reader = File::open("test.encrypted").await.unwrap();
let mut writer = File::create("test.original").await.unwrap();
// Deserialize the header, keyslots, etc from the encrypted file
let (header, aad) = FileHeader::from_reader(&mut reader).unwrap();
let (header, aad) = FileHeader::from_reader(&mut reader).await.unwrap();
// Decrypt the master key with the user's password
let master_key = header.decrypt_master_key(password).unwrap();
let master_key = header.decrypt_master_key(password).await.unwrap();
// Initialize a stream decryption object using data provided by the header
let decryptor = StreamDecryption::new(master_key, &header.nonce, header.algorithm).unwrap();
@@ -73,11 +75,13 @@ pub fn decrypt() {
// Decrypt data the from the writer, and write it to the writer
decryptor
.decrypt_streams(&mut reader, &mut writer, &aad)
.await
.unwrap();
}
fn main() {
encrypt();
#[tokio::main]
async fn main() {
encrypt().await;
decrypt();
decrypt().await;
}
@@ -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;
}
+56 -23
View File
@@ -1,10 +1,11 @@
//! This module contains the crate's STREAM implementation, and wrappers that allow us to support multiple AEADs.
#![allow(clippy::use_self)] // I think: https://github.com/rust-lang/rust-clippy/issues/3909
use std::io::{Cursor, Read, Write};
use std::io::Cursor;
use crate::{
primitives::{AEAD_TAG_SIZE, BLOCK_SIZE, KEY_LEN},
primitives::{Key, AEAD_TAG_SIZE, BLOCK_SIZE},
protected::ProtectedVec,
Error, Protected, Result,
};
use aead::{
@@ -13,6 +14,7 @@ use aead::{
};
use aes_gcm::Aes256Gcm;
use chacha20poly1305::XChaCha20Poly1305;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
/// These are all possible algorithms that can be used for encryption and decryption
#[derive(Clone, Copy, Eq, PartialEq, Hash)]
@@ -53,7 +55,7 @@ impl StreamEncryption {
///
/// The master key, a suitable nonce, and a specific algorithm should be provided.
#[allow(clippy::needless_pass_by_value)]
pub fn new(key: Protected<[u8; KEY_LEN]>, nonce: &[u8], algorithm: Algorithm) -> Result<Self> {
pub fn new(key: Protected<Key>, nonce: &[u8], algorithm: Algorithm) -> Result<Self> {
if nonce.len() != algorithm.nonce_len() {
return Err(Error::NonceLengthMismatch);
}
@@ -105,14 +107,29 @@ impl StreamEncryption {
/// It requires a reader, a writer, and any AAD to go with it.
///
/// The AAD will be authenticated with each block of data.
pub fn encrypt_streams<R, W>(mut self, mut reader: R, mut writer: W, aad: &[u8]) -> Result<()>
pub async fn encrypt_streams<R, W>(
mut self,
mut reader: R,
mut writer: W,
aad: &[u8],
) -> Result<()>
where
R: Read,
W: Write,
R: AsyncReadExt + Unpin + Send,
W: AsyncWriteExt + Unpin + Send,
{
let mut read_buffer = vec![0u8; BLOCK_SIZE].into_boxed_slice();
loop {
let read_count = reader.read(&mut read_buffer)?;
let mut read_count = 0;
loop {
let i = reader.read(&mut read_buffer[read_count..]).await?;
read_count += i;
if i == 0 || read_count == BLOCK_SIZE {
// if we're EOF or the buffer is filled
break;
}
}
if read_count == BLOCK_SIZE {
let payload = Payload {
aad,
@@ -121,7 +138,7 @@ impl StreamEncryption {
let encrypted_data = self.encrypt_next(payload).map_err(|_| Error::Encrypt)?;
writer.write_all(&encrypted_data)?;
writer.write_all(&encrypted_data).await?;
} else {
// we use `..read_count` in order to only use the read data, and not zeroes also
let payload = Payload {
@@ -130,13 +147,13 @@ impl StreamEncryption {
};
let encrypted_data = self.encrypt_last(payload).map_err(|_| Error::Encrypt)?;
writer.write_all(&encrypted_data)?;
writer.write_all(&encrypted_data).await?;
break;
}
}
writer.flush()?;
writer.flush().await?;
Ok(())
}
@@ -145,8 +162,8 @@ impl StreamEncryption {
///
/// It is just a thin wrapper around `encrypt_streams()`, but reduces the amount of code needed elsewhere.
#[allow(unused_mut)]
pub fn encrypt_bytes(
key: Protected<[u8; KEY_LEN]>,
pub async fn encrypt_bytes(
key: Protected<Key>,
nonce: &[u8],
algorithm: Algorithm,
bytes: &[u8],
@@ -157,6 +174,7 @@ impl StreamEncryption {
encryptor
.encrypt_streams(bytes, &mut writer, aad)
.await
.map_or_else(Err, |_| Ok(writer.into_inner()))
}
}
@@ -166,7 +184,7 @@ impl StreamDecryption {
///
/// The master key, nonce and algorithm that were used for encryption should be provided.
#[allow(clippy::needless_pass_by_value)]
pub fn new(key: Protected<[u8; KEY_LEN]>, nonce: &[u8], algorithm: Algorithm) -> Result<Self> {
pub fn new(key: Protected<Key>, nonce: &[u8], algorithm: Algorithm) -> Result<Self> {
if nonce.len() != algorithm.nonce_len() {
return Err(Error::NonceLengthMismatch);
}
@@ -218,15 +236,29 @@ impl StreamDecryption {
/// It requires a reader, a writer, and any AAD that was used.
///
/// The AAD will be authenticated with each block of data - if the AAD doesn't match what was used during encryption, an error will be returned.
pub fn decrypt_streams<R, W>(mut self, mut reader: R, mut writer: W, aad: &[u8]) -> Result<()>
pub async fn decrypt_streams<R, W>(
mut self,
mut reader: R,
mut writer: W,
aad: &[u8],
) -> Result<()>
where
R: Read,
W: Write,
R: AsyncReadExt + Unpin + Send,
W: AsyncWriteExt + Unpin + Send,
{
let mut read_buffer = vec![0u8; BLOCK_SIZE + AEAD_TAG_SIZE].into_boxed_slice();
loop {
let read_count = reader.read(&mut read_buffer)?;
let mut read_count = 0;
loop {
let i = reader.read(&mut read_buffer[read_count..]).await?;
read_count += i;
if i == 0 || read_count == (BLOCK_SIZE + AEAD_TAG_SIZE) {
// if we're EOF or the buffer is filled
break;
}
}
if read_count == (BLOCK_SIZE + AEAD_TAG_SIZE) {
let payload = Payload {
aad,
@@ -235,7 +267,7 @@ impl StreamDecryption {
let decrypted_data = self.decrypt_next(payload).map_err(|_| Error::Decrypt)?;
writer.write_all(&decrypted_data)?;
writer.write_all(&decrypted_data).await?;
} else {
let payload = Payload {
aad,
@@ -243,13 +275,13 @@ impl StreamDecryption {
};
let decrypted_data = self.decrypt_last(payload).map_err(|_| Error::Decrypt)?;
writer.write_all(&decrypted_data)?;
writer.write_all(&decrypted_data).await?;
break;
}
}
writer.flush()?;
writer.flush().await?;
Ok(())
}
@@ -258,18 +290,19 @@ impl StreamDecryption {
///
/// It is just a thin wrapper around `decrypt_streams()`, but reduces the amount of code needed elsewhere.
#[allow(unused_mut)]
pub fn decrypt_bytes(
key: Protected<[u8; KEY_LEN]>,
pub async fn decrypt_bytes(
key: Protected<Key>,
nonce: &[u8],
algorithm: Algorithm,
bytes: &[u8],
aad: &[u8],
) -> Result<Protected<Vec<u8>>> {
) -> Result<ProtectedVec<u8>> {
let mut writer = Cursor::new(Vec::<u8>::new());
let decryptor = Self::new(key, nonce, algorithm)?;
decryptor
.decrypt_streams(bytes, &mut writer, aad)
.await
.map_or_else(Err, |_| Ok(Protected::new(writer.into_inner())))
}
}
+83 -67
View File
@@ -29,11 +29,14 @@
//! // Write the header to the file
//! header.write(&mut writer).unwrap();
//! ```
use std::io::{Read, Seek, SeekFrom, Write};
use std::io::SeekFrom;
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
use crate::{
crypto::stream::Algorithm,
primitives::{generate_nonce, to_array, KEY_LEN},
primitives::{generate_nonce, to_array, Key, KEY_LEN},
protected::ProtectedVec,
Error, Protected, Result,
};
@@ -98,43 +101,31 @@ impl FileHeader {
///
/// You receive an error if the password doesn't match or if there are no keyslots.
#[allow(clippy::needless_pass_by_value)]
pub fn decrypt_master_key(
&self,
password: Protected<Vec<u8>>,
) -> Result<Protected<[u8; KEY_LEN]>> {
pub async fn decrypt_master_key(&self, password: ProtectedVec<u8>) -> Result<Protected<Key>> {
if self.keyslots.is_empty() {
return Err(Error::NoKeyslots);
}
self.keyslots
.iter()
.find_map(|v| v.decrypt_master_key(password.clone()).ok())
.map(|v| Protected::new(to_array::<KEY_LEN>(v.into_inner()).unwrap()))
.ok_or(Error::IncorrectPassword)
}
/// This is a helper function to find which keyslot a key belongs to.
///
/// You receive an error if the password doesn't match or if there are no keyslots.
#[allow(clippy::needless_pass_by_value)]
pub fn find_key_index(&self, password: Protected<Vec<u8>>) -> Result<usize> {
if self.keyslots.is_empty() {
return Err(Error::NoKeyslots);
for v in &self.keyslots {
if let Some(key) = v
.decrypt_master_key(password.clone())
.await
.ok()
.map(|v| Protected::new(to_array::<KEY_LEN>(v.into_inner()).unwrap()))
{
return Ok(key);
}
}
self.keyslots
.iter()
.enumerate()
.find_map(|(i, v)| v.decrypt_master_key(password.clone()).ok().map(|_| i))
.ok_or(Error::IncorrectPassword)
Err(Error::IncorrectPassword)
}
/// This is a helper function to serialize and write a header to a file.
pub fn write<W>(&self, writer: &mut W) -> Result<()>
pub async fn write<W>(&self, writer: &mut W) -> Result<()>
where
W: Write,
W: AsyncWriteExt + Unpin + Send,
{
writer.write_all(&self.to_bytes()?)?;
writer.write_all(&self.to_bytes()?).await?;
Ok(())
}
@@ -144,24 +135,46 @@ impl FileHeader {
///
/// You receive an error if the password doesn't match or if there are no keyslots.
#[allow(clippy::needless_pass_by_value)]
pub fn decrypt_master_key_from_prehashed(
pub async fn decrypt_master_key_from_prehashed(
&self,
hashed_keys: Vec<Protected<[u8; KEY_LEN]>>,
) -> Result<Protected<[u8; KEY_LEN]>> {
hashed_keys: Vec<Protected<Key>>,
) -> Result<Protected<Key>> {
if self.keyslots.is_empty() {
return Err(Error::NoKeyslots);
}
hashed_keys
.iter()
.find_map(|v| {
self.keyslots.iter().find_map(|z| {
z.decrypt_master_key_from_prehashed(v.clone())
.ok()
.map(|x| Protected::new(to_array::<KEY_LEN>(x.into_inner()).unwrap()))
})
})
.ok_or(Error::IncorrectPassword)
for hashed_key in hashed_keys {
for v in &self.keyslots {
if let Some(key) = v
.decrypt_master_key_from_prehashed(hashed_key.clone())
.await
.ok()
.map(|v| Protected::new(to_array::<KEY_LEN>(v.into_inner()).unwrap()))
{
return Ok(key);
}
}
}
Err(Error::IncorrectPassword)
}
/// This is a helper function to find which keyslot a key belongs to.
///
/// You receive an error if the password doesn't match or if there are no keyslots.
#[allow(clippy::needless_pass_by_value)]
pub async fn find_key_index(&self, password: ProtectedVec<u8>) -> Result<usize> {
if self.keyslots.is_empty() {
return Err(Error::NoKeyslots);
}
for (i, v) in self.keyslots.iter().enumerate() {
if let Some(i) = v.decrypt_master_key(password.clone()).await.ok().map(|_| i) {
return Ok(i);
}
}
Err(Error::IncorrectPassword)
}
/// This function should be used for generating AAD before encryption
@@ -241,12 +254,12 @@ impl FileHeader {
/// On error, the cursor will not be rewound.
///
/// It returns both the header, and the AAD that should be used for decryption.
pub fn from_reader<R>(reader: &mut R) -> Result<(Self, Vec<u8>)>
pub async fn from_reader<R>(reader: &mut R) -> Result<(Self, Vec<u8>)>
where
R: Read + Seek,
R: AsyncReadExt + AsyncSeekExt + Unpin + Send,
{
let mut magic_bytes = [0u8; MAGIC_BYTES.len()];
reader.read_exact(&mut magic_bytes)?;
reader.read_exact(&mut magic_bytes).await?;
if magic_bytes != MAGIC_BYTES {
return Err(Error::FileHeader);
@@ -254,36 +267,38 @@ impl FileHeader {
let mut version = [0u8; 2];
reader.read_exact(&mut version)?;
reader.read_exact(&mut version).await?;
let version = FileHeaderVersion::from_bytes(version)?;
// Rewind so we can get the AAD
reader.rewind()?;
reader.rewind().await?;
// read the aad according to the size
let mut aad = vec![0u8; Self::size(version)];
reader.read_exact(&mut aad)?;
reader.read_exact(&mut aad).await?;
// seek back to the start (plus magic bytes and the two version bytes)
reader.seek(SeekFrom::Start(MAGIC_BYTES.len() as u64 + 2))?;
reader
.seek(SeekFrom::Start(MAGIC_BYTES.len() as u64 + 2))
.await?;
// read the header
let header = match version {
FileHeaderVersion::V1 => {
let mut algorithm = [0u8; 2];
reader.read_exact(&mut algorithm)?;
reader.read_exact(&mut algorithm).await?;
let algorithm = Algorithm::from_bytes(algorithm)?;
let mut nonce = vec![0u8; algorithm.nonce_len()];
reader.read_exact(&mut nonce)?;
reader.read_exact(&mut nonce).await?;
// read and discard the padding
reader.read_exact(&mut vec![0u8; 25 - nonce.len()])?;
reader.read_exact(&mut vec![0u8; 25 - nonce.len()]).await?;
let mut keyslot_bytes = [0u8; (KEYSLOT_SIZE * 2)]; // length of 2x keyslots
let mut keyslots: Vec<Keyslot> = Vec::new();
reader.read_exact(&mut keyslot_bytes)?;
reader.read_exact(&mut keyslot_bytes).await?;
for _ in 0..2 {
Keyslot::from_reader(&mut keyslot_bytes.as_ref())
@@ -291,18 +306,21 @@ impl FileHeader {
.ok();
}
let metadata = Metadata::from_reader(reader).map_or_else(
|_| {
reader.seek(SeekFrom::Start(
let metadata = if let Ok(metadata) = Metadata::from_reader(reader).await {
reader
.seek(SeekFrom::Start(
Self::size(version) as u64 + (KEYSLOT_SIZE * 2) as u64,
))?;
Ok::<Option<Metadata>, Error>(None)
},
|metadata| Ok(Some(metadata)),
)?;
))
.await?;
Ok::<Option<Metadata>, Error>(Some(metadata))
} else {
Ok(None)
}?;
let preview_media = PreviewMedia::from_reader(reader).map_or_else(
|_| {
let preview_media =
if let Ok(preview_media) = PreviewMedia::from_reader(reader).await {
Ok::<Option<PreviewMedia>, Error>(Some(preview_media))
} else {
let seek_len = metadata.as_ref().map_or_else(
|| Self::size(version) as u64 + (KEYSLOT_SIZE * 2) as u64,
|metadata| {
@@ -311,12 +329,10 @@ impl FileHeader {
},
);
reader.seek(SeekFrom::Start(seek_len))?;
reader.seek(SeekFrom::Start(seek_len)).await?;
Ok::<Option<PreviewMedia>, Error>(None)
},
|preview_media| Ok(Some(preview_media)),
)?;
Ok(None)
}?;
Self {
version,
+26 -20
View File
@@ -27,9 +27,10 @@ use crate::{
crypto::stream::{Algorithm, StreamDecryption, StreamEncryption},
keys::hashing::HashingAlgorithm,
primitives::{
derive_key, generate_nonce, generate_salt, to_array, ENCRYPTED_KEY_LEN, FILE_KEY_CONTEXT,
KEY_LEN, SALT_LEN,
derive_key, generate_nonce, generate_salt, to_array, EncryptedKey, Key, Salt,
ENCRYPTED_KEY_LEN, FILE_KEY_CONTEXT, SALT_LEN,
},
protected::ProtectedVec,
Error, Protected, Result,
};
@@ -41,9 +42,9 @@ pub struct Keyslot {
pub version: KeyslotVersion,
pub algorithm: Algorithm, // encryption algorithm
pub hashing_algorithm: HashingAlgorithm, // password hashing algorithm
pub salt: [u8; SALT_LEN], // the salt used for deriving a KEK from a (key/content salt) hash
pub content_salt: [u8; SALT_LEN],
pub master_key: [u8; ENCRYPTED_KEY_LEN], // this is encrypted so we can store it
pub salt: Salt, // the salt used for deriving a KEK from a (key/content salt) hash
pub content_salt: Salt,
pub master_key: EncryptedKey, // this is encrypted so we can store it
pub nonce: Vec<u8>,
}
@@ -64,25 +65,28 @@ impl Keyslot {
///
/// You will need to provide the password, and a generated master key (this can't generate it, otherwise it can't be used elsewhere)
#[allow(clippy::needless_pass_by_value)]
pub fn new(
pub async fn new(
version: KeyslotVersion,
algorithm: Algorithm,
hashing_algorithm: HashingAlgorithm,
content_salt: [u8; SALT_LEN],
hashed_key: Protected<[u8; KEY_LEN]>,
master_key: Protected<[u8; KEY_LEN]>,
content_salt: Salt,
hashed_key: Protected<Key>,
master_key: Protected<Key>,
) -> Result<Self> {
let nonce = generate_nonce(algorithm);
let salt = generate_salt();
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(StreamEncryption::encrypt_bytes(
derive_key(hashed_key, salt, FILE_KEY_CONTEXT),
&nonce,
algorithm,
master_key.expose(),
&[],
)?)?;
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(
StreamEncryption::encrypt_bytes(
derive_key(hashed_key, salt, FILE_KEY_CONTEXT),
&nonce,
algorithm,
master_key.expose(),
&[],
)
.await?,
)?;
Ok(Self {
version,
@@ -101,7 +105,7 @@ impl Keyslot {
///
/// An error will be returned on failure.
#[allow(clippy::needless_pass_by_value)]
pub fn decrypt_master_key(&self, password: Protected<Vec<u8>>) -> Result<Protected<Vec<u8>>> {
pub async fn decrypt_master_key(&self, password: ProtectedVec<u8>) -> Result<ProtectedVec<u8>> {
let key = self
.hashing_algorithm
.hash(password, self.content_salt, None)
@@ -114,6 +118,7 @@ impl Keyslot {
&self.master_key,
&[],
)
.await
}
/// This function should not be used directly, use `header.decrypt_master_key()` instead
@@ -123,10 +128,10 @@ impl Keyslot {
/// No hashing is done internally.
///
/// An error will be returned on failure.
pub fn decrypt_master_key_from_prehashed(
pub async fn decrypt_master_key_from_prehashed(
&self,
key: Protected<[u8; KEY_LEN]>,
) -> Result<Protected<Vec<u8>>> {
key: Protected<Key>,
) -> Result<ProtectedVec<u8>> {
StreamDecryption::decrypt_bytes(
derive_key(key, self.salt, FILE_KEY_CONTEXT),
&self.nonce,
@@ -134,6 +139,7 @@ impl Keyslot {
&self.master_key,
&[],
)
.await
}
/// This function is used to serialize a keyslot into bytes
+52 -50
View File
@@ -27,15 +27,16 @@
//! )
//! .unwrap();
//! ```
use std::io::Read;
#[cfg(feature = "serde")]
use crate::{
crypto::stream::{StreamDecryption, StreamEncryption},
primitives::{generate_nonce, KEY_LEN},
Protected,
primitives::{generate_nonce, Key},
Protected, ProtectedVec,
};
use tokio::io::AsyncReadExt;
use crate::{crypto::stream::Algorithm, Error, Result};
use super::file::FileHeader;
@@ -68,15 +69,15 @@ impl FileHeader {
/// Metadata needs to be accessed switfly, so a key management system should handle the salt generation.
#[cfg(feature = "serde")]
#[allow(clippy::needless_pass_by_value)]
pub fn add_metadata<T>(
pub async fn add_metadata<T>(
&mut self,
version: MetadataVersion,
algorithm: Algorithm,
master_key: Protected<[u8; KEY_LEN]>,
master_key: Protected<Key>,
metadata: &T,
) -> Result<()>
where
T: ?Sized + serde::Serialize,
T: ?Sized + serde::Serialize + Sync + Send,
{
let metadata_nonce = generate_nonce(algorithm);
@@ -86,16 +87,15 @@ impl FileHeader {
algorithm,
&serde_json::to_vec(metadata).map_err(|_| Error::Serialization)?,
&[],
)?;
)
.await?;
let metadata = Metadata {
self.metadata = Some(Metadata {
version,
algorithm,
metadata_nonce,
metadata: encrypted_metadata,
};
self.metadata = Some(metadata);
});
Ok(())
}
@@ -106,29 +106,29 @@ impl FileHeader {
///
/// A deserialized data type will be returned from this function
#[cfg(feature = "serde")]
pub fn decrypt_metadata_from_prehashed<T>(
pub async fn decrypt_metadata_from_prehashed<T>(
&self,
hashed_keys: Vec<Protected<[u8; KEY_LEN]>>,
hashed_keys: Vec<Protected<Key>>,
) -> Result<T>
where
T: serde::de::DeserializeOwned,
{
let master_key = self.decrypt_master_key_from_prehashed(hashed_keys)?;
let master_key = self.decrypt_master_key_from_prehashed(hashed_keys).await?;
self.metadata.as_ref().map_or_else(
|| Err(Error::NoMetadata),
|metadata| {
let metadata = StreamDecryption::decrypt_bytes(
master_key,
&metadata.metadata_nonce,
metadata.algorithm,
&metadata.metadata,
&[],
)?;
if let Some(metadata) = self.metadata.as_ref() {
let metadata = StreamDecryption::decrypt_bytes(
master_key,
&metadata.metadata_nonce,
metadata.algorithm,
&metadata.metadata,
&[],
)
.await?;
serde_json::from_slice::<T>(&metadata).map_err(|_| Error::Serialization)
},
)
serde_json::from_slice::<T>(&metadata).map_err(|_| Error::Serialization)
} else {
Err(Error::NoMetadata)
}
}
/// This function should be used to retrieve the metadata for a file
@@ -137,26 +137,26 @@ impl FileHeader {
///
/// A deserialized data type will be returned from this function
#[cfg(feature = "serde")]
pub fn decrypt_metadata<T>(&self, password: Protected<Vec<u8>>) -> Result<T>
pub async fn decrypt_metadata<T>(&self, password: ProtectedVec<u8>) -> Result<T>
where
T: serde::de::DeserializeOwned,
{
let master_key = self.decrypt_master_key(password)?;
let master_key = self.decrypt_master_key(password).await?;
self.metadata.as_ref().map_or_else(
|| Err(Error::NoMetadata),
|metadata| {
let metadata = StreamDecryption::decrypt_bytes(
master_key,
&metadata.metadata_nonce,
metadata.algorithm,
&metadata.metadata,
&[],
)?;
if let Some(metadata) = self.metadata.as_ref() {
let metadata = StreamDecryption::decrypt_bytes(
master_key,
&metadata.metadata_nonce,
metadata.algorithm,
&metadata.metadata,
&[],
)
.await?;
serde_json::from_slice::<T>(&metadata).map_err(|_| Error::Serialization)
},
)
serde_json::from_slice::<T>(&metadata).map_err(|_| Error::Serialization)
} else {
Err(Error::NoMetadata)
}
}
}
@@ -192,33 +192,35 @@ impl Metadata {
/// The cursor will be left at the end of the metadata item on success
///
/// The cursor will not be rewound on error.
pub fn from_reader<R>(reader: &mut R) -> Result<Self>
pub async fn from_reader<R>(reader: &mut R) -> Result<Self>
where
R: Read,
R: AsyncReadExt + Unpin + Send,
{
let mut version = [0u8; 2];
reader.read_exact(&mut version)?;
reader.read_exact(&mut version).await?;
let version = MetadataVersion::from_bytes(version).map_err(|_| Error::NoMetadata)?;
match version {
MetadataVersion::V1 => {
let mut algorithm = [0u8; 2];
reader.read_exact(&mut algorithm)?;
reader.read_exact(&mut algorithm).await?;
let algorithm = Algorithm::from_bytes(algorithm)?;
let mut metadata_nonce = vec![0u8; algorithm.nonce_len()];
reader.read_exact(&mut metadata_nonce)?;
reader.read_exact(&mut metadata_nonce).await?;
reader.read_exact(&mut vec![0u8; 24 - metadata_nonce.len()])?;
reader
.read_exact(&mut vec![0u8; 24 - metadata_nonce.len()])
.await?;
let mut metadata_length = [0u8; 8];
reader.read_exact(&mut metadata_length)?;
reader.read_exact(&mut metadata_length).await?;
let metadata_length = u64::from_le_bytes(metadata_length);
#[allow(clippy::cast_possible_truncation)]
let mut metadata = vec![0u8; metadata_length as usize];
reader.read_exact(&mut metadata)?;
reader.read_exact(&mut metadata).await?;
let metadata = Self {
version,
+53 -52
View File
@@ -20,12 +20,12 @@
//! )
//! .unwrap();
//! ```
use std::io::Read;
use tokio::io::AsyncReadExt;
use crate::{
crypto::stream::{Algorithm, StreamDecryption, StreamEncryption},
primitives::{generate_nonce, KEY_LEN},
Error, Protected, Result,
primitives::{generate_nonce, Key},
Error, Protected, ProtectedVec, Result,
};
use super::file::FileHeader;
@@ -57,26 +57,25 @@ impl FileHeader {
///
/// Preview media needs to be accessed switfly, so a key management system should handle the salt generation.
#[allow(clippy::needless_pass_by_value)]
pub fn add_preview_media(
pub async fn add_preview_media(
&mut self,
version: PreviewMediaVersion,
algorithm: Algorithm,
master_key: Protected<[u8; KEY_LEN]>,
master_key: Protected<Key>,
media: &[u8],
) -> Result<()> {
let media_nonce = generate_nonce(algorithm);
let encrypted_media =
StreamEncryption::encrypt_bytes(master_key, &media_nonce, algorithm, media, &[])?;
StreamEncryption::encrypt_bytes(master_key, &media_nonce, algorithm, media, &[])
.await?;
let pvm = PreviewMedia {
self.preview_media = Some(PreviewMedia {
version,
algorithm,
media_nonce,
media: encrypted_media,
};
self.preview_media = Some(pvm);
});
Ok(())
}
@@ -86,26 +85,26 @@ impl FileHeader {
/// All it requires is pre-hashed keys returned from the key manager
///
/// Once provided, a `Vec<u8>` is returned that contains the preview media
pub fn decrypt_preview_media_from_prehashed(
pub async fn decrypt_preview_media_from_prehashed(
&self,
hashed_keys: Vec<Protected<[u8; KEY_LEN]>>,
) -> Result<Protected<Vec<u8>>> {
let master_key = self.decrypt_master_key_from_prehashed(hashed_keys)?;
hashed_keys: Vec<Protected<Key>>,
) -> Result<ProtectedVec<u8>> {
let master_key = self.decrypt_master_key_from_prehashed(hashed_keys).await?;
self.preview_media.as_ref().map_or_else(
|| Err(Error::NoPreviewMedia),
|pvm| {
let pvm = StreamDecryption::decrypt_bytes(
master_key,
&pvm.media_nonce,
pvm.algorithm,
&pvm.media,
&[],
)?;
if let Some(pvm) = self.preview_media.as_ref() {
let pvm = StreamDecryption::decrypt_bytes(
master_key,
&pvm.media_nonce,
pvm.algorithm,
&pvm.media,
&[],
)
.await?;
Ok(pvm)
},
)
Ok(pvm)
} else {
Err(Error::NoPreviewMedia)
}
}
/// This function is what you'll want to use to get the preview media for a file
@@ -113,26 +112,26 @@ impl FileHeader {
/// All it requires is the user's password. Hashing is handled for you.
///
/// Once provided, a `Vec<u8>` is returned that contains the preview media
pub fn decrypt_preview_media(
pub async fn decrypt_preview_media(
&self,
password: Protected<Vec<u8>>,
) -> Result<Protected<Vec<u8>>> {
let master_key = self.decrypt_master_key(password)?;
password: ProtectedVec<u8>,
) -> Result<ProtectedVec<u8>> {
let master_key = self.decrypt_master_key(password).await?;
self.preview_media.as_ref().map_or_else(
|| Err(Error::NoPreviewMedia),
|pvm| {
let pvm = StreamDecryption::decrypt_bytes(
master_key,
&pvm.media_nonce,
pvm.algorithm,
&pvm.media,
&[],
)?;
if let Some(pvm) = self.preview_media.as_ref() {
let pvm = StreamDecryption::decrypt_bytes(
master_key,
&pvm.media_nonce,
pvm.algorithm,
&pvm.media,
&[],
)
.await?;
Ok(pvm)
},
)
Ok(pvm)
} else {
Err(Error::NoPreviewMedia)
}
}
}
@@ -168,34 +167,36 @@ impl PreviewMedia {
/// The cursor will be left at the end of the preview media item on success
///
/// The cursor will not be rewound on error.
pub fn from_reader<R>(reader: &mut R) -> Result<Self>
pub async fn from_reader<R>(reader: &mut R) -> Result<Self>
where
R: Read,
R: AsyncReadExt + Unpin + Send,
{
let mut version = [0u8; 2];
reader.read_exact(&mut version)?;
reader.read_exact(&mut version).await?;
let version =
PreviewMediaVersion::from_bytes(version).map_err(|_| Error::NoPreviewMedia)?;
match version {
PreviewMediaVersion::V1 => {
let mut algorithm = [0u8; 2];
reader.read_exact(&mut algorithm)?;
reader.read_exact(&mut algorithm).await?;
let algorithm = Algorithm::from_bytes(algorithm)?;
let mut media_nonce = vec![0u8; algorithm.nonce_len()];
reader.read_exact(&mut media_nonce)?;
reader.read_exact(&mut media_nonce).await?;
reader.read_exact(&mut vec![0u8; 24 - media_nonce.len()])?;
reader
.read_exact(&mut vec![0u8; 24 - media_nonce.len()])
.await?;
let mut media_length = [0u8; 8];
reader.read_exact(&mut media_length)?;
reader.read_exact(&mut media_length).await?;
let media_length = u64::from_le_bytes(media_length);
#[allow(clippy::cast_possible_truncation)]
let mut media = vec![0u8; media_length as usize];
reader.read_exact(&mut media)?;
reader.read_exact(&mut media).await?;
let preview_media = Self {
version,
+16 -15
View File
@@ -11,9 +11,10 @@
//! let hashed_password = hashing_algorithm.hash(password, salt).unwrap();
//! ```
use crate::primitives::KEY_LEN;
use crate::Protected;
use crate::{primitives::SALT_LEN, Error, Result};
use crate::{
primitives::{Key, Salt, KEY_LEN},
Error, Protected, ProtectedVec, Result,
};
use argon2::Argon2;
use balloon_hash::Balloon;
@@ -52,10 +53,10 @@ impl HashingAlgorithm {
#[allow(clippy::needless_pass_by_value)]
pub fn hash(
&self,
password: Protected<Vec<u8>>,
salt: [u8; SALT_LEN],
secret: Option<Protected<Vec<u8>>>,
) -> Result<Protected<[u8; KEY_LEN]>> {
password: ProtectedVec<u8>,
salt: Salt,
secret: Option<ProtectedVec<u8>>,
) -> Result<Protected<Key>> {
match self {
Self::Argon2id(params) => PasswordHasher::argon2id(password, salt, secret, *params),
Self::BalloonBlake3(params) => {
@@ -106,11 +107,11 @@ struct PasswordHasher;
impl PasswordHasher {
#[allow(clippy::needless_pass_by_value)]
fn argon2id(
password: Protected<Vec<u8>>,
salt: [u8; SALT_LEN],
secret: Option<Protected<Vec<u8>>>,
password: ProtectedVec<u8>,
salt: Salt,
secret: Option<ProtectedVec<u8>>,
params: Params,
) -> Result<Protected<[u8; KEY_LEN]>> {
) -> Result<Protected<Key>> {
let secret = secret.map_or(Protected::new(vec![]), |k| k);
let mut key = [0u8; KEY_LEN];
@@ -129,11 +130,11 @@ impl PasswordHasher {
#[allow(clippy::needless_pass_by_value)]
fn balloon_blake3(
password: Protected<Vec<u8>>,
salt: [u8; SALT_LEN],
secret: Option<Protected<Vec<u8>>>,
password: ProtectedVec<u8>,
salt: Salt,
secret: Option<ProtectedVec<u8>>,
params: Params,
) -> Result<Protected<[u8; KEY_LEN]>> {
) -> Result<Protected<Key>> {
let secret = secret.map_or(Protected::new(vec![]), |k| k);
let mut key = [0u8; KEY_LEN];
+196 -158
View File
@@ -35,19 +35,21 @@
//! let keys = key_manager.enumerate_hashed_keys();
//! ```
use std::sync::Mutex;
use tokio::sync::Mutex;
use crate::crypto::stream::{StreamDecryption, StreamEncryption};
use crate::primitives::{
derive_key, generate_master_key, generate_nonce, generate_salt, to_array, OnboardingConfig,
KEY_LEN, LATEST_STORED_KEY, MASTER_PASSWORD_CONTEXT, ROOT_KEY_CONTEXT,
};
// use crate::primitives::{
// derive_key, generate_master_key, generate_nonce, generate_salt, to_array, EncryptedKey, Key,
// OnboardingConfig, Salt, KEY_LEN, LATEST_STORED_KEY, MASTER_PASSWORD_CONTEXT, ROOT_KEY_CONTEXT,
// };
use crate::{
crypto::stream::Algorithm,
primitives::{ENCRYPTED_KEY_LEN, SALT_LEN},
Protected,
crypto::stream::{Algorithm, StreamDecryption, StreamEncryption},
primitives::{
derive_key, generate_master_key, generate_nonce, generate_salt, to_array, EncryptedKey,
Key, OnboardingConfig, Salt, ENCRYPTED_KEY_LEN, KEY_LEN, LATEST_STORED_KEY,
MASTER_PASSWORD_CONTEXT, ROOT_KEY_CONTEXT,
},
Error, Protected, ProtectedVec, Result,
};
use crate::{Error, Result};
use dashmap::{DashMap, DashSet};
use uuid::Uuid;
@@ -66,13 +68,13 @@ pub struct StoredKey {
pub version: StoredKeyVersion,
pub algorithm: Algorithm, // encryption algorithm for encrypting the master key. can be changed (requires a re-encryption though)
pub hashing_algorithm: HashingAlgorithm, // hashing algorithm used for hashing the key with the content salt
pub content_salt: [u8; SALT_LEN],
pub content_salt: Salt,
#[cfg_attr(feature = "serde", serde(with = "BigArray"))] // salt used for file data
pub master_key: [u8; ENCRYPTED_KEY_LEN], // this is for encrypting the `key`
pub master_key: EncryptedKey, // this is for encrypting the `key`
pub master_key_nonce: Vec<u8>, // nonce for encrypting the master key
pub key_nonce: Vec<u8>, // nonce used for encrypting the main key
pub key: Vec<u8>, // encrypted. the key stored in spacedrive (e.g. generated 64 char key)
pub salt: [u8; SALT_LEN],
pub salt: Salt,
pub memory_only: bool,
pub automount: bool,
}
@@ -89,8 +91,8 @@ pub enum StoredKeyVersion {
/// This contains the plaintext key, and the same key hashed with the content salt.
#[derive(Clone)]
pub struct MountedKey {
pub uuid: Uuid, // used for identification. shared with stored keys
pub hashed_key: Protected<[u8; KEY_LEN]>, // this is hashed with the content salt, for instant access
pub uuid: Uuid, // used for identification. shared with stored keys
pub hashed_key: Protected<Key>, // this is hashed with the content salt, for instant access
}
/// This is the key manager itself.
@@ -99,7 +101,7 @@ pub struct MountedKey {
///
/// Use the associated functions to interact with it.
pub struct KeyManager {
root_key: Mutex<Option<Protected<[u8; KEY_LEN]>>>, // the root key for the vault
root_key: Mutex<Option<Protected<Key>>>, // the root key for the vault
verification_key: Mutex<Option<StoredKey>>,
keystore: DashMap<Uuid, StoredKey>,
keymount: DashMap<Uuid, MountedKey>,
@@ -110,7 +112,7 @@ pub struct KeyManager {
/// The `KeyManager` functions should be used for all key-related management.
impl KeyManager {
/// Initialize the Key Manager with `StoredKeys` retrieved from Prisma
pub fn new(stored_keys: Vec<StoredKey>) -> Result<Self> {
pub async fn new(stored_keys: Vec<StoredKey>) -> Result<Self> {
let keymanager = Self {
root_key: Mutex::new(None),
verification_key: Mutex::new(None),
@@ -120,7 +122,7 @@ impl KeyManager {
mounting_queue: DashSet::new(),
};
keymanager.populate_keystore(stored_keys)?;
keymanager.populate_keystore(stored_keys).await?;
Ok(keymanager)
}
@@ -131,7 +133,7 @@ impl KeyManager {
///
/// It will also generate a verification key, which should be written to the database.
#[allow(clippy::needless_pass_by_value)]
pub fn onboarding(config: OnboardingConfig) -> Result<StoredKey> {
pub async fn onboarding(config: OnboardingConfig) -> Result<StoredKey> {
let content_salt = generate_salt();
let secret_key = config.secret_key.map(Self::convert_secret_key_string);
@@ -156,13 +158,16 @@ impl KeyManager {
let root_key_nonce = generate_nonce(algorithm);
// Encrypt the master key with the hashed master password
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(StreamEncryption::encrypt_bytes(
derive_key(hashed_password, salt, MASTER_PASSWORD_CONTEXT),
&master_key_nonce,
algorithm,
master_key.expose(),
&[],
)?)?;
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(
StreamEncryption::encrypt_bytes(
derive_key(hashed_password, salt, MASTER_PASSWORD_CONTEXT),
&master_key_nonce,
algorithm,
master_key.expose(),
&[],
)
.await?,
)?;
let encrypted_root_key = StreamEncryption::encrypt_bytes(
master_key,
@@ -170,7 +175,8 @@ impl KeyManager {
algorithm,
root_key.expose(),
&[],
)?;
)
.await?;
let verification_key = StoredKey {
uuid,
@@ -195,14 +201,14 @@ impl KeyManager {
/// It's suitable for when you created the key manager without populating it.
///
/// This also detects the nil-UUID master passphrase verification key
pub fn populate_keystore(&self, stored_keys: Vec<StoredKey>) -> Result<()> {
pub async fn populate_keystore(&self, stored_keys: Vec<StoredKey>) -> Result<()> {
for key in stored_keys {
if self.keystore.contains_key(&key.uuid) {
continue;
}
if key.uuid.is_nil() {
*self.verification_key.lock()? = Some(key);
*self.verification_key.lock().await = Some(key);
} else {
self.keystore.insert(key.uuid, key);
}
@@ -212,11 +218,11 @@ impl KeyManager {
}
/// This function removes a key from the keystore, the keymount and it's unset as the default.
pub fn remove_key(&self, uuid: Uuid) -> Result<()> {
pub async fn remove_key(&self, uuid: Uuid) -> Result<()> {
if self.keystore.contains_key(&uuid) {
// if key is default, clear it
// do this manually to prevent deadlocks
let mut default = self.default.lock()?;
let mut default = self.default.lock().await;
if *default == Some(uuid) {
*default = None;
}
@@ -235,7 +241,7 @@ impl KeyManager {
}
#[allow(clippy::needless_pass_by_value)]
pub fn change_master_password(
pub async fn change_master_password(
&self,
master_password: Protected<String>,
algorithm: Algorithm,
@@ -257,19 +263,22 @@ impl KeyManager {
let master_key = generate_master_key();
let master_key_nonce = generate_nonce(algorithm);
let root_key = self.get_root_key()?;
let root_key = self.get_root_key().await?;
let root_key_nonce = generate_nonce(algorithm);
let salt = generate_salt();
// Encrypt the master key with the hashed master password
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(StreamEncryption::encrypt_bytes(
derive_key(hashed_password, salt, MASTER_PASSWORD_CONTEXT),
&master_key_nonce,
algorithm,
master_key.expose(),
&[],
)?)?;
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(
StreamEncryption::encrypt_bytes(
derive_key(hashed_password, salt, MASTER_PASSWORD_CONTEXT),
&master_key_nonce,
algorithm,
master_key.expose(),
&[],
)
.await?,
)?;
let encrypted_root_key = StreamEncryption::encrypt_bytes(
master_key,
@@ -277,7 +286,8 @@ impl KeyManager {
algorithm,
root_key.expose(),
&[],
)?;
)
.await?;
let verification_key = StoredKey {
uuid,
@@ -294,7 +304,7 @@ impl KeyManager {
automount: false,
};
*self.verification_key.lock()? = Some(verification_key.clone());
*self.verification_key.lock().await = Some(verification_key.clone());
Ok(verification_key)
}
@@ -303,7 +313,7 @@ impl KeyManager {
///
/// It returns a `Vec<StoredKey>` so they can be written to Prisma
#[allow(clippy::needless_pass_by_value)]
pub fn import_keystore_backup(
pub async fn import_keystore_backup(
&self,
master_password: Protected<String>, // at the time of the backup
secret_key: Option<Protected<String>>, // at the time of the backup
@@ -347,7 +357,8 @@ impl KeyManager {
old_verification_key.algorithm,
&old_verification_key.master_key,
&[],
)?;
)
.await?;
// get the root key from the backup
let old_root_key = StreamDecryption::decrypt_bytes(
@@ -356,7 +367,8 @@ impl KeyManager {
old_verification_key.algorithm,
&old_verification_key.key,
&[],
)?;
)
.await?;
Protected::new(to_array(old_root_key.into_inner())?)
}
@@ -379,6 +391,7 @@ impl KeyManager {
&key.master_key,
&[],
)
.await
.map_or(Err(Error::IncorrectPassword), |v| {
Ok(Protected::new(to_array::<KEY_LEN>(v.into_inner())?))
})?;
@@ -389,13 +402,16 @@ impl KeyManager {
let salt = generate_salt();
// encrypt the master key with the current root key
let encrypted_master_key = to_array(StreamEncryption::encrypt_bytes(
derive_key(self.get_root_key()?, salt, ROOT_KEY_CONTEXT),
&master_key_nonce,
key.algorithm,
master_key.expose(),
&[],
)?)?;
let encrypted_master_key = to_array(
StreamEncryption::encrypt_bytes(
derive_key(self.get_root_key().await?, salt, ROOT_KEY_CONTEXT),
&master_key_nonce,
key.algorithm,
master_key.expose(),
&[],
)
.await?,
)?;
let mut updated_key = key.clone();
updated_key.master_key_nonce = master_key_nonce;
@@ -411,7 +427,7 @@ impl KeyManager {
Ok(reencrypted_keys)
}
/// This requires both the master password and the secret key
/// This is used for unlocking the key manager, and requires both the master password and the secret key.
///
/// The master password and secret key are hashed together.
/// This minimises the risk of an attacker obtaining the master password, as both of these are required to unlock the vault (and both should be stored separately).
@@ -422,24 +438,24 @@ impl KeyManager {
///
/// Note: The invalidation function is ran after updating the queue both times, so it isn't required externally.
#[allow(clippy::needless_pass_by_value)]
pub fn set_master_password<F>(
pub async fn unlock<F>(
&self,
master_password: Protected<String>,
secret_key: Option<Protected<String>>,
invalidate: F,
) -> Result<()>
where
F: Fn(),
F: Fn() + Send,
{
let uuid = Uuid::nil();
if self.has_master_password()? {
if self.has_master_password().await? {
return Err(Error::KeyAlreadyMounted);
} else if self.is_queued(uuid) {
return Err(Error::KeyAlreadyQueued);
}
let verification_key = (*self.verification_key.lock()?)
let verification_key = (*self.verification_key.lock().await)
.as_ref()
.map_or(Err(Error::NoVerificationKey), |k| Ok(k.clone()))?;
@@ -473,12 +489,13 @@ impl KeyManager {
&verification_key.master_key,
&[],
)
.await
.map_err(|_| {
self.remove_from_queue(uuid).ok();
Error::IncorrectKeymanagerDetails
})?;
*self.root_key.lock()? = Some(Protected::new(
*self.root_key.lock().await = Some(Protected::new(
to_array(
StreamDecryption::decrypt_bytes(
Protected::new(to_array(master_key.into_inner())?),
@@ -486,7 +503,8 @@ impl KeyManager {
verification_key.algorithm,
&verification_key.key,
&[],
)?
)
.await?
.expose()
.clone(),
)
@@ -512,101 +530,113 @@ impl KeyManager {
/// This is to ensure that only functions which require access to the mounted key receive it.
///
/// We could add a log to this, so that the user can view mounts
pub fn mount(&self, uuid: Uuid) -> Result<()> {
pub async fn mount(&self, uuid: Uuid) -> Result<()> {
if self.keymount.get(&uuid).is_some() {
return Err(Error::KeyAlreadyMounted);
} else if self.is_queued(uuid) {
return Err(Error::KeyAlreadyQueued);
}
self.keystore
.get(&uuid)
.map_or(Err(Error::KeyNotFound), |stored_key| {
match stored_key.version {
StoredKeyVersion::V1 => {
self.mounting_queue.insert(uuid);
if let Some(stored_key) = self.keystore.get(&uuid) {
match stored_key.version {
StoredKeyVersion::V1 => {
self.mounting_queue.insert(uuid);
let master_key = StreamDecryption::decrypt_bytes(
derive_key(self.get_root_key()?, stored_key.salt, ROOT_KEY_CONTEXT),
&stored_key.master_key_nonce,
stored_key.algorithm,
&stored_key.master_key,
&[],
)
.map_or_else(
|_| {
self.remove_from_queue(uuid).ok();
Err(Error::IncorrectPassword)
},
|v| Ok(Protected::new(to_array(v.into_inner())?)),
)?;
// Decrypt the StoredKey using the decrypted master key
let key = StreamDecryption::decrypt_bytes(
master_key,
&stored_key.key_nonce,
stored_key.algorithm,
&stored_key.key,
&[],
)
let master_key = StreamDecryption::decrypt_bytes(
derive_key(
self.get_root_key().await?,
stored_key.salt,
ROOT_KEY_CONTEXT,
),
&stored_key.master_key_nonce,
stored_key.algorithm,
&stored_key.master_key,
&[],
)
.await
.map_or_else(
|_| {
self.remove_from_queue(uuid).ok();
Err(Error::IncorrectPassword)
},
|v| Ok(Protected::new(to_array(v.into_inner())?)),
)?;
// Decrypt the StoredKey using the decrypted master key
let key = StreamDecryption::decrypt_bytes(
master_key,
&stored_key.key_nonce,
stored_key.algorithm,
&stored_key.key,
&[],
)
.await
.map_err(|e| {
self.remove_from_queue(uuid).ok();
e
})?;
// Hash the key once with the parameters/algorithm the user selected during first mount
let hashed_key = stored_key
.hashing_algorithm
.hash(key, stored_key.content_salt, None)
.map_err(|e| {
self.remove_from_queue(uuid).ok();
e
})?;
// Hash the key once with the parameters/algorithm the user selected during first mount
let hashed_key = stored_key
.hashing_algorithm
.hash(key, stored_key.content_salt, None)
.map_err(|e| {
self.remove_from_queue(uuid).ok();
e
})?;
self.keymount.insert(
uuid,
MountedKey {
uuid: stored_key.uuid,
hashed_key,
},
);
self.keymount.insert(
uuid,
MountedKey {
uuid: stored_key.uuid,
hashed_key,
},
);
self.remove_from_queue(uuid)?;
}
self.remove_from_queue(uuid)?;
}
}
Ok(())
})
Ok(())
} else {
Err(Error::KeyNotFound)
}
}
/// This function is used for getting the key value itself, from a given UUID.
///
/// The master password/salt needs to be present, so we are able to decrypt the key itself from the stored key.
pub fn get_key(&self, uuid: Uuid) -> Result<Protected<Vec<u8>>> {
self.keystore
.get(&uuid)
.map_or(Err(Error::KeyNotFound), |stored_key| {
let master_key = StreamDecryption::decrypt_bytes(
derive_key(self.get_root_key()?, stored_key.salt, ROOT_KEY_CONTEXT),
&stored_key.master_key_nonce,
stored_key.algorithm,
&stored_key.master_key,
&[],
)
.map_or(Err(Error::IncorrectPassword), |k| {
Ok(Protected::new(to_array(k.into_inner())?))
})?;
pub async fn get_key(&self, uuid: Uuid) -> Result<ProtectedVec<u8>> {
if let Some(stored_key) = self.keystore.get(&uuid) {
let master_key = StreamDecryption::decrypt_bytes(
derive_key(
self.get_root_key().await?,
stored_key.salt,
ROOT_KEY_CONTEXT,
),
&stored_key.master_key_nonce,
stored_key.algorithm,
&stored_key.master_key,
&[],
)
.await
.map_or(Err(Error::IncorrectPassword), |k| {
Ok(Protected::new(to_array(k.into_inner())?))
})?;
// Decrypt the StoredKey using the decrypted master key
let key = StreamDecryption::decrypt_bytes(
master_key,
&stored_key.key_nonce,
stored_key.algorithm,
&stored_key.key,
&[],
)?;
// Decrypt the StoredKey using the decrypted master key
let key = StreamDecryption::decrypt_bytes(
master_key,
&stored_key.key_nonce,
stored_key.algorithm,
&stored_key.key,
&[],
)
.await?;
Ok(key)
})
Ok(key)
} else {
Err(Error::KeyNotFound)
}
}
/// This function is used to add a new key/password to the keystore.
@@ -621,14 +651,14 @@ impl KeyManager {
///
/// You may optionally provide a content salt, if not one will be generated (used primarily for password-based decryption)
#[allow(clippy::needless_pass_by_value)]
pub fn add_to_keystore(
pub async fn add_to_keystore(
&self,
key: Protected<Vec<u8>>,
key: ProtectedVec<u8>,
algorithm: Algorithm,
hashing_algorithm: HashingAlgorithm,
memory_only: bool,
automount: bool,
content_salt: Option<[u8; SALT_LEN]>,
content_salt: Option<Salt>,
) -> Result<Uuid> {
let uuid = uuid::Uuid::new_v4();
@@ -643,17 +673,20 @@ impl KeyManager {
let salt = generate_salt();
// Encrypt the master key with a derived key (derived from the root key)
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(StreamEncryption::encrypt_bytes(
derive_key(self.get_root_key()?, salt, ROOT_KEY_CONTEXT),
&master_key_nonce,
algorithm,
master_key.expose(),
&[],
)?)?;
let encrypted_master_key = to_array::<ENCRYPTED_KEY_LEN>(
StreamEncryption::encrypt_bytes(
derive_key(self.get_root_key().await?, salt, ROOT_KEY_CONTEXT),
&master_key_nonce,
algorithm,
master_key.expose(),
&[],
)
.await?,
)?;
// Encrypt the actual key (e.g. user-added/autogenerated, text-encodable)
let encrypted_key =
StreamEncryption::encrypt_bytes(master_key, &key_nonce, algorithm, &key, &[])?;
StreamEncryption::encrypt_bytes(master_key, &key_nonce, algorithm, &key, &[]).await?;
// Insert it into the Keystore
self.keystore.insert(
@@ -679,7 +712,7 @@ impl KeyManager {
}
#[allow(clippy::needless_pass_by_value)]
fn convert_secret_key_string(secret_key: Protected<String>) -> Protected<Vec<u8>> {
fn convert_secret_key_string(secret_key: Protected<String>) -> ProtectedVec<u8> {
Protected::new(secret_key.expose().as_bytes().to_vec())
}
@@ -700,9 +733,9 @@ impl KeyManager {
}
/// This allows you to set the default key
pub fn set_default(&self, uuid: Uuid) -> Result<()> {
pub async fn set_default(&self, uuid: Uuid) -> Result<()> {
if self.keystore.contains_key(&uuid) {
*self.default.lock()? = Some(uuid);
*self.default.lock().await = Some(uuid);
Ok(())
} else {
Err(Error::KeyNotFound)
@@ -710,18 +743,23 @@ impl KeyManager {
}
/// This allows you to get the default key's ID
pub fn get_default(&self) -> Result<Uuid> {
self.default.lock()?.ok_or(Error::NoDefaultKeySet)
pub async fn get_default(&self) -> Result<Uuid> {
self.default.lock().await.ok_or(Error::NoDefaultKeySet)
}
/// This should ONLY be used internally.
fn get_root_key(&self) -> Result<Protected<[u8; KEY_LEN]>> {
self.root_key.lock()?.clone().ok_or(Error::NoMasterPassword)
async fn get_root_key(&self) -> Result<Protected<Key>> {
self.root_key
.lock()
.await
.clone()
.ok_or(Error::NoMasterPassword)
}
pub fn get_verification_key(&self) -> Result<StoredKey> {
pub async fn get_verification_key(&self) -> Result<StoredKey> {
self.verification_key
.lock()?
.lock()
.await
.clone()
.ok_or(Error::NoVerificationKey)
}
@@ -753,11 +791,11 @@ impl KeyManager {
///
/// This means we don't need to keep super specific track of which key goes to which file, and we can just throw all of them at it.
#[must_use]
pub fn enumerate_hashed_keys(&self) -> Vec<Protected<[u8; KEY_LEN]>> {
pub fn enumerate_hashed_keys(&self) -> Vec<Protected<Key>> {
self.keymount
.iter()
.map(|mounted_key| mounted_key.hashed_key.clone())
.collect::<Vec<Protected<[u8; KEY_LEN]>>>()
.collect::<Vec<Protected<Key>>>()
}
/// This function is for converting a memory-only key to a saved key which syncs to the library.
@@ -784,8 +822,8 @@ impl KeyManager {
}
/// This function is for removing a previously-added master password
pub fn clear_root_key(&self) -> Result<()> {
*self.root_key.lock()? = None;
pub async fn clear_root_key(&self) -> Result<()> {
*self.root_key.lock().await = None;
Ok(())
}
@@ -793,8 +831,8 @@ impl KeyManager {
/// This function is used for seeing if the key manager has a master password.
///
/// Technically this checks for the root key, but it makes no difference to the front end.
pub fn has_master_password(&self) -> Result<bool> {
Ok(self.root_key.lock()?.is_some())
pub async fn has_master_password(&self) -> Result<bool> {
Ok(self.root_key.lock().await.is_some())
}
/// This function is used for unmounting all keys at once.
+1
View File
@@ -24,6 +24,7 @@ pub use aead::Payload;
// Make this easier to use (e.g. `sd_crypto::Protected`)
pub use protected::Protected;
pub use protected::ProtectedVec;
// Re-export zeroize so it can be used elsewhere
pub use zeroize::Zeroize;
+8 -8
View File
@@ -44,6 +44,10 @@ pub const MASTER_PASSWORD_CONTEXT: &str =
"spacedrive 2022-12-14 15:35:41 master password hash derivation"; // used for deriving keys from the master password hash
pub const FILE_KEY_CONTEXT: &str = "spacedrive 2022-12-14 12:54:12 file key derivation"; // used for deriving keys from user key/content salt hashes (for file encryption)
pub type Key = [u8; KEY_LEN];
pub type EncryptedKey = [u8; ENCRYPTED_KEY_LEN];
pub type Salt = [u8; SALT_LEN];
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[cfg_attr(feature = "rspc", derive(specta::Type))]
@@ -70,7 +74,7 @@ pub fn generate_nonce(algorithm: Algorithm) -> Vec<u8> {
///
/// This function uses `ChaCha20Rng` for generating cryptographically-secure random data
#[must_use]
pub fn generate_salt() -> [u8; SALT_LEN] {
pub fn generate_salt() -> Salt {
let mut salt = [0u8; SALT_LEN];
rand_chacha::ChaCha20Rng::from_entropy().fill_bytes(&mut salt);
salt
@@ -82,7 +86,7 @@ pub fn generate_salt() -> [u8; SALT_LEN] {
///
/// This function uses `ChaCha20Rng` for generating cryptographically-secure random data
#[must_use]
pub fn generate_master_key() -> Protected<[u8; KEY_LEN]> {
pub fn generate_master_key() -> Protected<Key> {
let mut master_key = [0u8; KEY_LEN];
rand_chacha::ChaCha20Rng::from_entropy().fill_bytes(&mut master_key);
Protected::new(master_key)
@@ -90,11 +94,7 @@ pub fn generate_master_key() -> Protected<[u8; KEY_LEN]> {
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn derive_key(
key: Protected<[u8; KEY_LEN]>,
salt: [u8; SALT_LEN],
context: &str,
) -> Protected<[u8; KEY_LEN]> {
pub fn derive_key(key: Protected<Key>, salt: Salt, context: &str) -> Protected<Key> {
let mut input = key.expose().to_vec();
input.extend_from_slice(&salt);
@@ -107,7 +107,7 @@ pub fn derive_key(
/// This is used for converting a `Vec<u8>` to an array of bytes
///
/// It's main usage is for converting an encrypted master key from a `Vec<u8>` to `[u8; ENCRYPTED_KEY_LEN]`
/// It's main usage is for converting an encrypted master key from a `Vec<u8>` to `EncryptedKey`
///
/// As the master key is encrypted at this point, it does not need to be `Protected<>`
///
+1
View File
@@ -31,6 +31,7 @@
use std::{fmt::Debug, mem::swap};
use zeroize::Zeroize;
pub type ProtectedVec<T> = Protected<Vec<T>>;
#[derive(Clone)]
pub struct Protected<T>
where
+2 -2
View File
@@ -15,8 +15,8 @@ resolver = "2"
ffmpeg-sys-next = "5.1.1"
thiserror = "1.0.37"
webp = "0.2.2"
tokio = { version = "1.21.2", features = ["fs", "rt"] }
tokio = { workspace = true, features = ["fs", "rt"] }
[dev-dependencies]
tempfile = "3.3.0"
tokio = { version = "1.21.2", features = ["fs", "rt", "macros"] }
tokio = { workspace = true, features = ["fs", "rt", "macros"] }
+8 -5
View File
@@ -2,16 +2,19 @@
name = "sd-file-ext"
version = "0.0.0"
edition = "2021"
authors = ["Brendan Allen <brendan@spacedrive.com>", "Jamie Pine <jamie@spacedrive.com>"]
authors = [
"Brendan Allen <brendan@spacedrive.com>",
"Jamie Pine <jamie@spacedrive.com>",
]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
int-enum = "0.5.0"
serde = {version = "1.0.145", features = ["derive"]}
serde = { version = "1.0.145", features = ["derive"] }
serde_json = "1.0.85"
strum = { version = "0.24", features = ["derive"]}
strum = { version = "0.24", features = ["derive"] }
strum_macros = "0.24"
tokio = { version = "1.21.2", features = ["fs", "rt", "io-util"] }
tokio = { workspace = true, features = ["fs", "rt", "io-util"] }
[dev-dependencies]
tokio = { version = "1.21.2", features = ["fs", "rt", "macros"] }
tokio = { workspace = true, features = ["fs", "rt", "macros"] }
+2 -2
View File
@@ -9,7 +9,7 @@ sd-tunnel-utils = { path = "./tunnel/utils" }
dashmap = "5.3.4"
rcgen = "0.9.2"
rustls = "0.20.6"
tokio = { version = "1.19.2", features = ["macros", "sync"] }
tokio = { workspace = true, features = ["macros", "sync"] }
if-watch = "1.1.1"
thiserror = "1.0.31"
mdns-sd = "0.5.5"
@@ -25,4 +25,4 @@ tracing = "0.1.35"
specta = "0.0.2"
[dev-dependencies]
tokio = { version = "1.19.2", features = ["rt-multi-thread"] }
tokio = { workspace = true, features = ["rt-multi-thread"] }
+4 -2
View File
@@ -14,11 +14,13 @@ quinn = "0.8.3"
rcgen = "0.9.2"
rustls = "0.20.6"
serde = { version = "1.0.137", features = ["derive"] }
tokio = { version = "1.19.2", features = ["rt-multi-thread", "macros"] }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
tracing = "0.1.35"
tracing-subscriber = { version = "0.3.11", features = ["env-filter"] }
metrics = "0.19.0"
metrics-exporter-prometheus = { version = "0.10.0", features = ["http-listener"] }
metrics-exporter-prometheus = { version = "0.10.0", features = [
"http-listener",
] }
thiserror = "1.0.31"
rmp-serde = "1.1.0"
bb8-redis = "0.11.0"
+1 -1
View File
@@ -10,7 +10,7 @@ serde_json = "1.0.85"
serde = { version = "1.0.145", features = ["derive"] }
axum = "0.5.16"
rspc = { workspace = true, features = ["axum"] }
tokio = { version = "1.21.2", features = ["full"] }
tokio = { workspace = true, features = ["full"] }
prisma-client-rust = { workspace = true }
dotenv = "0.15.0"
tower-http = { version = "0.3.4", features = ["cors"] }
+3 -3
View File
@@ -55,8 +55,8 @@ export type Procedures = {
{ key: "keys.mount", input: LibraryArgs<string>, result: null } |
{ key: "keys.restoreKeystore", input: LibraryArgs<RestoreBackupArgs>, result: number } |
{ key: "keys.setDefault", input: LibraryArgs<string>, result: null } |
{ key: "keys.setMasterPassword", input: LibraryArgs<SetMasterPasswordArgs>, result: null } |
{ key: "keys.syncKeyToLibrary", input: LibraryArgs<string>, result: null } |
{ key: "keys.unlockKeyManager", input: LibraryArgs<UnlockKeyManagerArgs>, result: null } |
{ key: "keys.unmount", input: LibraryArgs<string>, result: null } |
{ key: "keys.unmountAll", input: LibraryArgs<null>, result: null } |
{ key: "keys.updateAutomountStatus", input: LibraryArgs<AutomountUpdateArgs>, result: null } |
@@ -177,8 +177,6 @@ export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChil
export interface SetFavoriteArgs { id: number, favorite: boolean }
export interface SetMasterPasswordArgs { password: string, secret_key: string | null }
export interface SetNoteArgs { id: number, note: string | null }
export interface Statistics { id: number, date_captured: string, total_object_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string }
@@ -195,6 +193,8 @@ export interface TagCreateArgs { name: string, color: string }
export interface TagUpdateArgs { id: number, name: string | null, color: string | null }
export interface UnlockKeyManagerArgs { password: string, secret_key: string | null }
export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean }
export interface FilePathWithObject { id: number, is_dir: boolean, cas_id: string | null, integrity_checksum: string | null, 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',