async crypto!

This commit is contained in:
brxken128 committed 2023-01-24 14:19:27 +00:00
1 parent 7f5396249a
commit 73dd4df26b
8 files changed
+161 -141

No files matched your search

+40 -31
View File
@@ -23,7 +23,7 @@ pub struct KeyAddArgs {
}
#[derive(Type, Deserialize)]
pub struct SetMasterPasswordArgs {
pub struct UnlockKeyManagerArgs {
password: String,
secret_key: Option<String>,
}
@@ -64,14 +64,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(())
@@ -148,14 +148,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 +172,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");
}
@@ -222,14 +226,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 +255,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");
@@ -290,11 +297,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 +316,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
+1 -1
View File
@@ -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?;
+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.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)
+38 -36
View File
@@ -1,6 +1,6 @@
use std::{collections::VecDeque, fs::File, io::Read, path::PathBuf};
use std::{collections::VecDeque, path::PathBuf};
use tokio::task;
use tokio::{fs::File, io::AsyncReadExt};
use chrono::FixedOffset;
use sd_crypto::{
@@ -141,22 +141,25 @@ impl StatefulJob for FileEncryptorJob {
)
.await?;
let mut reader = task::block_in_place(|| File::open(&info.obj_path))?;
let mut writer = task::block_in_place(|| File::create(output_path))?;
let mut reader = File::open(&info.obj_path).await?;
let mut writer = File::create(output_path).await?;
let master_key = generate_master_key();
let mut header = FileHeader::new(
LATEST_FILE_HEADER,
state.init.algorithm,
vec![Keyslot::new(
LATEST_KEYSLOT,
state.init.algorithm,
user_key_details.hashing_algorithm,
user_key_details.content_salt,
user_key,
master_key.clone(),
)?],
vec![
Keyslot::new(
LATEST_KEYSLOT,
state.init.algorithm,
user_key_details.hashing_algorithm,
user_key_details.content_salt,
user_key,
master_key.clone(),
)
.await?,
],
);
if state.init.metadata || state.init.preview_media {
@@ -187,12 +190,14 @@ impl StatefulJob for FileEncryptorJob {
date_modified: object.date_modified,
};
header.add_metadata(
LATEST_METADATA,
state.init.algorithm,
master_key.clone(),
&metadata,
)?;
header
.add_metadata(
LATEST_METADATA,
state.init.algorithm,
master_key.clone(),
&metadata,
)
.await?;
}
// if state.init.preview_media
@@ -209,18 +214,17 @@ impl StatefulJob for FileEncryptorJob {
if tokio::fs::metadata(&pvm_path).await.is_ok() {
let mut pvm_bytes = Vec::new();
task::block_in_place(|| {
let mut pvm_file = File::open(pvm_path)?;
pvm_file.read_to_end(&mut pvm_bytes)?;
Ok::<_, JobError>(())
})?;
let mut pvm_file = File::open(pvm_path).await?;
pvm_file.read_to_end(&mut pvm_bytes).await?;
header.add_preview_media(
LATEST_PREVIEW_MEDIA,
state.init.algorithm,
master_key.clone(),
&pvm_bytes,
)?;
header
.add_preview_media(
LATEST_PREVIEW_MEDIA,
state.init.algorithm,
master_key.clone(),
&pvm_bytes,
)
.await?;
}
} else {
// should use container encryption if it's a directory
@@ -230,15 +234,13 @@ impl StatefulJob for FileEncryptorJob {
}
}
task::block_in_place(|| {
header.write(&mut writer)?;
header.write(&mut writer).await?;
let encryptor =
StreamEncryption::new(master_key, &header.nonce, header.algorithm)?;
let encryptor = StreamEncryption::new(master_key, &header.nonce, header.algorithm)?;
encryptor.encrypt_streams(&mut reader, &mut writer, &header.generate_aad())?;
Ok::<_, JobError>(())
})?;
encryptor
.encrypt_streams(&mut reader, &mut writer, &header.generate_aad())
.await?;
}
_ => warn!(
"encryption is skipping {} as it isn't a file",
+35 -30
View File
@@ -29,7 +29,9 @@
//! // 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,
@@ -121,11 +123,11 @@ impl FileHeader {
}
/// 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(())
}
@@ -254,12 +256,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);
@@ -267,36 +269,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())
@@ -304,18 +308,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| {
@@ -324,12 +331,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,
+11 -9
View File
@@ -27,7 +27,7 @@
//! )
//! .unwrap();
//! ```
use std::io::Read;
use tokio::io::AsyncReadExt;
#[cfg(feature = "serde")]
use crate::{
@@ -195,33 +195,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,
+11 -9
View File
@@ -20,7 +20,7 @@
//! )
//! .unwrap();
//! ```
use std::io::Read;
use tokio::io::AsyncReadExt;
use crate::{
crypto::stream::{Algorithm, StreamDecryption, StreamEncryption},
@@ -171,34 +171,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,
+2 -2
View File
@@ -425,7 +425,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).
@@ -436,7 +436,7 @@ 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 async fn set_master_password<F>(
pub async fn unlock<F>(
&self,
master_password: Protected<String>,
secret_key: Option<Protected<String>>,