async crypto!

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

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

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?;

View File

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

View File

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

View File

@@ -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,

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,

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,

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>>,