mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2025-12-11 20:15:30 +01:00
[ENG-309] Envelope encryption for the KM's root key (#482)
* use master key to encrypt the root key (breaking) * introduce breaking changes to the keymanager (this breaks master password changing, backup restoration, etc) * change MP -> RK * minor tweaks + comment out broken features temporarily * experimental new master password changing * add a `write_storedkey_to_db` helper function * general cleanup and fixes * potentially patch backup restoration * uncomment+clean up restoration in api * strip whitespaces from user inputted secret key, and fix MPC bug * clippy * add memory only flag to `StoredKey`
This commit is contained in:
parent
7d73163af9
commit
24d8153f86
@ -10,6 +10,7 @@ use sd_crypto::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specta::Type;
|
||||
|
||||
use crate::util::db::write_storedkey_to_db;
|
||||
use crate::{invalidate_query, prisma::key};
|
||||
|
||||
use super::{utils::LibraryRequest, RouterBuilder};
|
||||
@ -120,7 +121,8 @@ pub(crate) fn mount() -> RouterBuilder {
|
||||
})
|
||||
.library_mutation("clearMasterPassword", |t| {
|
||||
t(|_, _: (), library| async move {
|
||||
library.key_manager.clear_master_password()?;
|
||||
// This technically clears the root key, but it means the same thing to the frontend
|
||||
library.key_manager.clear_root_key()?;
|
||||
|
||||
invalidate_query!(library, "keys.hasMasterPassword");
|
||||
Ok(())
|
||||
@ -128,14 +130,16 @@ pub(crate) fn mount() -> RouterBuilder {
|
||||
})
|
||||
.library_mutation("deleteFromLibrary", |t| {
|
||||
t(|_, key_uuid: uuid::Uuid, library| async move {
|
||||
library.key_manager.remove_key(key_uuid)?;
|
||||
if !library.key_manager.is_memory_only(key_uuid)? {
|
||||
library
|
||||
.db
|
||||
.key()
|
||||
.delete(key::uuid::equals(key_uuid.to_string()))
|
||||
.exec()
|
||||
.await?;
|
||||
}
|
||||
|
||||
library
|
||||
.db
|
||||
.key()
|
||||
.delete(key::uuid::equals(key_uuid.to_string()))
|
||||
.exec()
|
||||
.await?;
|
||||
library.key_manager.remove_key(key_uuid)?;
|
||||
|
||||
// we also need to delete all in-memory decrypted data associated with this key
|
||||
invalidate_query!(library, "keys.list");
|
||||
@ -159,22 +163,7 @@ pub(crate) fn mount() -> RouterBuilder {
|
||||
.exec()
|
||||
.await?;
|
||||
|
||||
library
|
||||
.db
|
||||
.key()
|
||||
.create(
|
||||
verification_key.uuid.to_string(),
|
||||
verification_key.algorithm.serialize().to_vec(),
|
||||
verification_key.hashing_algorithm.serialize().to_vec(),
|
||||
verification_key.content_salt.to_vec(),
|
||||
verification_key.master_key.to_vec(),
|
||||
verification_key.master_key_nonce.to_vec(),
|
||||
verification_key.key_nonce.to_vec(),
|
||||
verification_key.key.to_vec(),
|
||||
vec![],
|
||||
)
|
||||
.exec()
|
||||
.await?;
|
||||
write_storedkey_to_db(library.db.clone(), &verification_key).await?;
|
||||
|
||||
let keys = OnboardingKeys {
|
||||
master_password: bundle.master_password.expose().clone(),
|
||||
@ -289,28 +278,12 @@ pub(crate) fn mount() -> RouterBuilder {
|
||||
Protected::new(args.key.as_bytes().to_vec()),
|
||||
args.algorithm,
|
||||
args.hashing_algorithm,
|
||||
!args.library_sync,
|
||||
)?;
|
||||
|
||||
let stored_key = library.key_manager.access_keystore(uuid)?;
|
||||
|
||||
if args.library_sync {
|
||||
library
|
||||
.db
|
||||
.key()
|
||||
.create(
|
||||
uuid.to_string(),
|
||||
args.algorithm.serialize().to_vec(),
|
||||
args.hashing_algorithm.serialize().to_vec(),
|
||||
stored_key.content_salt.to_vec(),
|
||||
stored_key.master_key.to_vec(),
|
||||
stored_key.master_key_nonce.to_vec(),
|
||||
stored_key.key_nonce.to_vec(),
|
||||
stored_key.key.to_vec(),
|
||||
vec![],
|
||||
)
|
||||
.exec()
|
||||
.await?;
|
||||
}
|
||||
write_storedkey_to_db(library.db.clone(), &stored_key).await?;
|
||||
|
||||
// mount the key
|
||||
library.key_manager.mount(uuid)?;
|
||||
@ -326,6 +299,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.retain(|k| !k.memory_only);
|
||||
|
||||
let mut output_file = std::fs::File::create(path).map_err(|_| {
|
||||
rspc::Error::new(
|
||||
@ -382,22 +356,7 @@ pub(crate) fn mount() -> RouterBuilder {
|
||||
)?;
|
||||
|
||||
for key in &updated_keys {
|
||||
library
|
||||
.db
|
||||
.key()
|
||||
.create(
|
||||
key.uuid.to_string(),
|
||||
key.algorithm.serialize().to_vec(),
|
||||
key.hashing_algorithm.serialize().to_vec(),
|
||||
key.content_salt.to_vec(),
|
||||
key.master_key.to_vec(),
|
||||
key.master_key_nonce.to_vec(),
|
||||
key.key_nonce.to_vec(),
|
||||
key.key.to_vec(),
|
||||
vec![],
|
||||
)
|
||||
.exec()
|
||||
.await?;
|
||||
write_storedkey_to_db(library.db.clone(), key).await?;
|
||||
}
|
||||
|
||||
invalidate_query!(library, "keys.list");
|
||||
@ -414,50 +373,16 @@ pub(crate) fn mount() -> RouterBuilder {
|
||||
args.hashing_algorithm,
|
||||
)?;
|
||||
|
||||
let verification_key = bundle.verification_key;
|
||||
|
||||
// remove old nil-id keys if they were set
|
||||
// they possibly won't be, but we CANNOT have multiple
|
||||
library.db.key().delete_many(vec![]).exec().await?;
|
||||
|
||||
library
|
||||
.db
|
||||
.key()
|
||||
.create(
|
||||
verification_key.uuid.to_string(),
|
||||
verification_key.algorithm.serialize().to_vec(),
|
||||
verification_key.hashing_algorithm.serialize().to_vec(),
|
||||
verification_key.content_salt.to_vec(),
|
||||
verification_key.master_key.to_vec(),
|
||||
verification_key.master_key_nonce.to_vec(),
|
||||
verification_key.key_nonce.to_vec(),
|
||||
verification_key.key.to_vec(),
|
||||
vec![],
|
||||
)
|
||||
.delete_many(vec![key::uuid::equals(uuid::Uuid::nil().to_string())])
|
||||
.exec()
|
||||
.await?;
|
||||
|
||||
// sync new changes with prisma
|
||||
// note, this will write keys that were potentially marked as "don't sync to db"
|
||||
// i think the way around this will be to include a marker in the `StoredKey` struct, as that means we can exclude them from `dump_keystore()` commands
|
||||
for key in bundle.updated_keystore {
|
||||
library
|
||||
.db
|
||||
.key()
|
||||
.create(
|
||||
key.uuid.to_string(),
|
||||
key.algorithm.serialize().to_vec(),
|
||||
key.hashing_algorithm.serialize().to_vec(),
|
||||
key.content_salt.to_vec(),
|
||||
key.master_key.to_vec(),
|
||||
key.master_key_nonce.to_vec(),
|
||||
key.key_nonce.to_vec(),
|
||||
key.key.to_vec(),
|
||||
vec![],
|
||||
)
|
||||
.exec()
|
||||
.await?;
|
||||
}
|
||||
// write the new verification key
|
||||
write_storedkey_to_db(library.db.clone(), &bundle.verification_key).await?;
|
||||
|
||||
Ok(bundle.secret_key.expose().clone())
|
||||
})
|
||||
|
||||
@ -73,7 +73,7 @@ impl From<LibraryManagerError> for rspc::Error {
|
||||
|
||||
pub async fn create_keymanager(client: &PrismaClient) -> Result<KeyManager, LibraryManagerError> {
|
||||
// retrieve all stored keys from the DB
|
||||
let key_manager = KeyManager::new(vec![]);
|
||||
let key_manager = KeyManager::new(vec![])?;
|
||||
|
||||
// BRXKEN128: REMOVE THIS ONCE ONBOARDING HAS BEEN DONE
|
||||
// this is so if there's no verification key set, we set one so users can use the key manager
|
||||
@ -144,6 +144,7 @@ pub async fn create_keymanager(client: &PrismaClient) -> Result<KeyManager, Libr
|
||||
to_array(key.hashing_algorithm).unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
memory_only: false,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
use crate::prisma::{self, PrismaClient};
|
||||
use prisma_client_rust::QueryError;
|
||||
use prisma_client_rust::{migrations::*, NewClientError};
|
||||
use sd_crypto::keys::keymanager::StoredKey;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
|
||||
/// MigrationError represents an error that occurring while opening a initialising and running migrations on the database.
|
||||
@ -40,3 +43,29 @@ pub async fn load_and_migrate(db_url: &str) -> Result<PrismaClient, MigrationErr
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
/// This writes a `StoredKey` to prisma
|
||||
/// If the key is marked as memory-only, it is skipped
|
||||
pub async fn write_storedkey_to_db(
|
||||
db: Arc<PrismaClient>,
|
||||
key: &StoredKey,
|
||||
) -> Result<(), QueryError> {
|
||||
if !key.memory_only {
|
||||
db.key()
|
||||
.create(
|
||||
key.uuid.to_string(),
|
||||
key.algorithm.serialize().to_vec(),
|
||||
key.hashing_algorithm.serialize().to_vec(),
|
||||
key.content_salt.to_vec(),
|
||||
key.master_key.to_vec(),
|
||||
key.master_key_nonce.to_vec(),
|
||||
key.key_nonce.to_vec(),
|
||||
key.key.to_vec(),
|
||||
vec![],
|
||||
)
|
||||
.exec()
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -77,6 +77,7 @@ pub struct StoredKey {
|
||||
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 memory_only: bool,
|
||||
}
|
||||
|
||||
/// This is a mounted key, and needs to be kept somewhat hidden.
|
||||
@ -94,7 +95,7 @@ pub struct MountedKey {
|
||||
///
|
||||
/// Use the associated functions to interact with it.
|
||||
pub struct KeyManager {
|
||||
master_password: Mutex<Option<Protected<[u8; 32]>>>, // the *hashed* master password+secret key combo
|
||||
root_key: Mutex<Option<Protected<[u8; 32]>>>, // the root key for the vault
|
||||
verification_key: Mutex<Option<StoredKey>>,
|
||||
keystore: DashMap<Uuid, StoredKey>,
|
||||
keymount: DashMap<Uuid, MountedKey>,
|
||||
@ -118,7 +119,7 @@ pub struct OnboardingBundle {
|
||||
pub struct MasterPasswordChangeBundle {
|
||||
pub verification_key: StoredKey, // nil UUID key that is only ever used for verifying the master password is correct
|
||||
pub secret_key: Protected<String>, // hex encoded string that is required along with the master password
|
||||
pub updated_keystore: Vec<StoredKey>,
|
||||
// pub updated_keystore: Vec<StoredKey>,
|
||||
}
|
||||
|
||||
/// The `KeyManager` functions should be used for all key-related management.
|
||||
@ -169,6 +170,9 @@ impl KeyManager {
|
||||
let master_key = generate_master_key();
|
||||
let master_key_nonce = generate_nonce(algorithm);
|
||||
|
||||
let root_key = generate_master_key();
|
||||
let root_key_nonce = generate_nonce(algorithm);
|
||||
|
||||
// Encrypt the master key with the hashed master password
|
||||
let encrypted_master_key: [u8; 48] = to_array(StreamEncryption::encrypt_bytes(
|
||||
hashed_password,
|
||||
@ -178,6 +182,14 @@ impl KeyManager {
|
||||
&[],
|
||||
)?)?;
|
||||
|
||||
let encrypted_root_key = StreamEncryption::encrypt_bytes(
|
||||
master_key,
|
||||
&root_key_nonce,
|
||||
algorithm,
|
||||
root_key.expose(),
|
||||
&[],
|
||||
)?;
|
||||
|
||||
let verification_key = StoredKey {
|
||||
uuid,
|
||||
algorithm,
|
||||
@ -185,8 +197,9 @@ impl KeyManager {
|
||||
content_salt: [0u8; 16],
|
||||
master_key: encrypted_master_key,
|
||||
master_key_nonce,
|
||||
key_nonce: Vec::new(),
|
||||
key: Vec::new(),
|
||||
key_nonce: root_key_nonce,
|
||||
key: encrypted_root_key,
|
||||
memory_only: false,
|
||||
};
|
||||
|
||||
let secret_key = Self::format_secret_key(&salt);
|
||||
@ -201,28 +214,22 @@ impl KeyManager {
|
||||
}
|
||||
|
||||
/// Initialize the Key Manager with `StoredKeys` retrieved from Prisma
|
||||
#[must_use]
|
||||
pub fn new(stored_keys: Vec<StoredKey>) -> Self {
|
||||
pub fn new(stored_keys: Vec<StoredKey>) -> Result<Self> {
|
||||
let keystore = DashMap::new();
|
||||
let mut verification_key = None;
|
||||
|
||||
for key in stored_keys {
|
||||
if key.uuid.is_nil() {
|
||||
verification_key = Some(key);
|
||||
} else {
|
||||
keystore.insert(key.uuid, key);
|
||||
}
|
||||
}
|
||||
|
||||
let keymount: DashMap<Uuid, MountedKey> = DashMap::new();
|
||||
|
||||
Self {
|
||||
master_password: Mutex::new(None),
|
||||
verification_key: Mutex::new(verification_key),
|
||||
let keymanager = Self {
|
||||
root_key: Mutex::new(None),
|
||||
verification_key: Mutex::new(None),
|
||||
keystore,
|
||||
keymount,
|
||||
default: Mutex::new(None),
|
||||
}
|
||||
};
|
||||
|
||||
keymanager.populate_keystore(stored_keys)?;
|
||||
|
||||
Ok(keymanager)
|
||||
}
|
||||
|
||||
/// This function should be used to populate the keystore with multiple stored keys at a time.
|
||||
@ -298,14 +305,13 @@ impl KeyManager {
|
||||
}
|
||||
|
||||
/// This should ONLY be used internally.
|
||||
fn get_master_password(&self) -> Result<Protected<[u8; 32]>> {
|
||||
match &*self.master_password.lock()? {
|
||||
fn get_root_key(&self) -> Result<Protected<[u8; 32]>> {
|
||||
match &*self.root_key.lock()? {
|
||||
Some(k) => Ok(k.clone()),
|
||||
None => Err(Error::NoMasterPassword),
|
||||
}
|
||||
}
|
||||
|
||||
/// This should ONLY be used internally.
|
||||
pub fn get_verification_key(&self) -> Result<StoredKey> {
|
||||
match &*self.verification_key.lock()? {
|
||||
Some(k) => Ok(k.clone()),
|
||||
@ -313,75 +319,37 @@ impl KeyManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// This is used to change a master password.
|
||||
///
|
||||
/// The entire keystore is re-encrypted with the new master password, and will require dumping and syncing with Prisma.
|
||||
pub fn is_memory_only(&self, uuid: Uuid) -> Result<bool> {
|
||||
if let Some(key) = self.keystore.get(&uuid) {
|
||||
Ok(key.memory_only)
|
||||
} else {
|
||||
Err(Error::KeyNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn change_master_password(
|
||||
&self,
|
||||
master_password: Protected<String>,
|
||||
algorithm: Algorithm,
|
||||
hashing_algorithm: HashingAlgorithm,
|
||||
) -> Result<MasterPasswordChangeBundle> {
|
||||
// Generate a new secret key
|
||||
let salt = generate_salt();
|
||||
|
||||
// Hash the master password
|
||||
let hashed_password = hashing_algorithm.hash(
|
||||
Protected::new(master_password.expose().as_bytes().to_vec()),
|
||||
salt,
|
||||
)?;
|
||||
|
||||
// Iterate over the keystore - decrypt each master key, re-encrypt it with the same algorithm, and collect them into a vec
|
||||
let updated_keystore: Result<Vec<StoredKey>> = self
|
||||
.dump_keystore()
|
||||
.iter()
|
||||
.map(|stored_key| {
|
||||
let mut stored_key = stored_key.clone();
|
||||
|
||||
let master_key = if let Ok(decrypted_master_key) = StreamDecryption::decrypt_bytes(
|
||||
self.get_master_password()?,
|
||||
&stored_key.master_key_nonce,
|
||||
stored_key.algorithm,
|
||||
&stored_key.master_key,
|
||||
&[],
|
||||
) {
|
||||
Ok(Protected::new(to_array::<32>(
|
||||
decrypted_master_key.expose().clone(),
|
||||
)?))
|
||||
} else {
|
||||
Err(Error::IncorrectPassword)
|
||||
}?;
|
||||
|
||||
let master_key_nonce = generate_nonce(stored_key.algorithm);
|
||||
|
||||
// Encrypt the master key with the user's hashed password
|
||||
let encrypted_master_key: [u8; 48] = to_array(StreamEncryption::encrypt_bytes(
|
||||
hashed_password.clone(),
|
||||
&master_key_nonce,
|
||||
stored_key.algorithm,
|
||||
master_key.expose(),
|
||||
&[],
|
||||
)?)?;
|
||||
|
||||
stored_key.master_key = encrypted_master_key;
|
||||
stored_key.master_key_nonce = master_key_nonce;
|
||||
|
||||
Ok(stored_key)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// should use ? above
|
||||
let updated_keystore = updated_keystore?;
|
||||
|
||||
// Clear the current keystore and update it with our re-encrypted keystore
|
||||
self.empty_keystore();
|
||||
self.populate_keystore(updated_keystore.clone())?;
|
||||
|
||||
// Create a new verification key for the master password/secret key combination
|
||||
let uuid = uuid::Uuid::nil();
|
||||
|
||||
// Generate items we'll need for encryption
|
||||
let master_key = generate_master_key();
|
||||
let master_key_nonce = generate_nonce(algorithm);
|
||||
|
||||
let root_key = self.get_root_key()?;
|
||||
let root_key_nonce = generate_nonce(algorithm);
|
||||
|
||||
// Encrypt the master key with the hashed master password
|
||||
let encrypted_master_key: [u8; 48] = to_array(StreamEncryption::encrypt_bytes(
|
||||
hashed_password,
|
||||
@ -391,6 +359,14 @@ impl KeyManager {
|
||||
&[],
|
||||
)?)?;
|
||||
|
||||
let encrypted_root_key = StreamEncryption::encrypt_bytes(
|
||||
master_key,
|
||||
&root_key_nonce,
|
||||
algorithm,
|
||||
root_key.expose(),
|
||||
&[],
|
||||
)?;
|
||||
|
||||
let verification_key = StoredKey {
|
||||
uuid,
|
||||
algorithm,
|
||||
@ -398,33 +374,128 @@ impl KeyManager {
|
||||
content_salt: [0u8; 16],
|
||||
master_key: encrypted_master_key,
|
||||
master_key_nonce,
|
||||
key_nonce: Vec::new(),
|
||||
key: Vec::new(),
|
||||
key_nonce: root_key_nonce,
|
||||
key: encrypted_root_key,
|
||||
memory_only: false,
|
||||
};
|
||||
|
||||
*self.verification_key.lock()? = Some(verification_key.clone());
|
||||
|
||||
let secret_key = Self::format_secret_key(&salt);
|
||||
|
||||
let mpc_bundle = MasterPasswordChangeBundle {
|
||||
let mp_change_bundle = MasterPasswordChangeBundle {
|
||||
verification_key,
|
||||
secret_key,
|
||||
updated_keystore,
|
||||
};
|
||||
|
||||
// Update the internal verification key, and then set the master password
|
||||
*self.verification_key.lock()? = Some(mpc_bundle.verification_key.clone());
|
||||
self.set_master_password(master_password, mpc_bundle.secret_key.clone())?;
|
||||
|
||||
// Return the verification key so it can be written to Prisma and return the secret key so it can be shown to the user
|
||||
Ok(mpc_bundle)
|
||||
Ok(mp_change_bundle)
|
||||
}
|
||||
|
||||
/// This is used to change a master password.
|
||||
///
|
||||
/// The entire keystore is re-encrypted with the new master password, and will require dumping and syncing with Prisma.
|
||||
// pub fn rotate_root_key(
|
||||
// &self,
|
||||
// master_password: Protected<String>,
|
||||
// algorithm: Algorithm,
|
||||
// hashing_algorithm: HashingAlgorithm,
|
||||
// ) -> Result<MasterPasswordChangeBundle> {
|
||||
// let new_root_key = generate_master_key();
|
||||
|
||||
// // Iterate over the keystore - decrypt each master key, re-encrypt it with the same algorithm, and collect them into a vec
|
||||
// let updated_keystore: Result<Vec<StoredKey>> = self
|
||||
// .dump_keystore()
|
||||
// .iter()
|
||||
// .map(|stored_key| {
|
||||
// let mut stored_key = stored_key.clone();
|
||||
|
||||
// let master_key = if let Ok(decrypted_master_key) = StreamDecryption::decrypt_bytes(
|
||||
// self.get_root_key()?,
|
||||
// &stored_key.master_key_nonce,
|
||||
// stored_key.algorithm,
|
||||
// &stored_key.master_key,
|
||||
// &[],
|
||||
// ) {
|
||||
// Ok(Protected::new(to_array::<32>(
|
||||
// decrypted_master_key.expose().clone(),
|
||||
// )?))
|
||||
// } else {
|
||||
// Err(Error::IncorrectPassword)
|
||||
// }?;
|
||||
|
||||
// let master_key_nonce = generate_nonce(stored_key.algorithm);
|
||||
|
||||
// // Encrypt the master key with the user's hashed password
|
||||
// let encrypted_master_key: [u8; 48] = to_array(StreamEncryption::encrypt_bytes(
|
||||
// new_root_key.clone(),
|
||||
// &master_key_nonce,
|
||||
// stored_key.algorithm,
|
||||
// master_key.expose(),
|
||||
// &[],
|
||||
// )?)?;
|
||||
|
||||
// stored_key.master_key = encrypted_master_key;
|
||||
// stored_key.master_key_nonce = master_key_nonce;
|
||||
|
||||
// Ok(stored_key)
|
||||
// })
|
||||
// .collect();
|
||||
|
||||
// // should use ? above
|
||||
// let updated_keystore = updated_keystore?;
|
||||
|
||||
// // Clear the current keystore and update it with our re-encrypted keystore
|
||||
// self.empty_keystore();
|
||||
// self.populate_keystore(updated_keystore.clone())?;
|
||||
|
||||
// // Create a new verification key
|
||||
// let uuid = uuid::Uuid::nil();
|
||||
// let master_key = generate_master_key();
|
||||
// let master_key_nonce = generate_nonce(algorithm);
|
||||
|
||||
// // Encrypt the master key with the hashed master password
|
||||
// let encrypted_master_key: [u8; 48] = to_array(StreamEncryption::encrypt_bytes(
|
||||
// hashed_password,
|
||||
// &master_key_nonce,
|
||||
// algorithm,
|
||||
// master_key.expose(),
|
||||
// &[],
|
||||
// )?)?;
|
||||
|
||||
// let verification_key = StoredKey {
|
||||
// uuid,
|
||||
// algorithm,
|
||||
// hashing_algorithm,
|
||||
// content_salt: [0u8; 16],
|
||||
// master_key: encrypted_master_key,
|
||||
// master_key_nonce,
|
||||
// key_nonce: Vec::new(),
|
||||
// key: Vec::new(),
|
||||
// };
|
||||
|
||||
// let secret_key = Self::format_secret_key(&salt);
|
||||
|
||||
// let mpc_bundle = MasterPasswordChangeBundle {
|
||||
// verification_key,
|
||||
// secret_key,
|
||||
// updated_keystore,
|
||||
// };
|
||||
|
||||
// // Update the internal verification key, and then set the master password
|
||||
// *self.verification_key.lock()? = Some(mpc_bundle.verification_key.clone());
|
||||
// self.set_master_password(master_password, mpc_bundle.secret_key.clone())?;
|
||||
|
||||
// // Return the verification key so it can be written to Prisma and return the secret key so it can be shown to the user
|
||||
// Ok(mpc_bundle)
|
||||
// }
|
||||
|
||||
/// Used internally to convert from a hex-encoded `Protected<String>` to a `Protected<[u8; SALT_LEN]>` in a secretive manner.
|
||||
///
|
||||
/// If the secret key is wrong (not base64 or not the correct length), a filler secret key will be inserted secretly.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn convert_secret_key_string(secret_key: Protected<String>) -> Protected<[u8; SALT_LEN]> {
|
||||
let mut secret_key_clean = secret_key.expose().clone();
|
||||
secret_key_clean.retain(|c| c != '-');
|
||||
secret_key_clean.retain(|c| c != '-' && !c.is_whitespace());
|
||||
|
||||
let secret_key = if let Ok(secret_key) = hex::decode(secret_key_clean) {
|
||||
secret_key
|
||||
@ -441,7 +512,6 @@ impl KeyManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Opting to leave ser/de to external functions - the key manager isn't the right place to handle this.
|
||||
/// This re-encrypts master keys so they can be imported from a key backup into the current key manager.
|
||||
///
|
||||
/// It returns a `Vec<StoredKey>` so they can be written to Prisma
|
||||
@ -470,57 +540,83 @@ impl KeyManager {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let hashed_master_password = if let Some(verification_key) = verification_key {
|
||||
let verification_key = if let Some(verification_key) = verification_key {
|
||||
verification_key
|
||||
.hashing_algorithm
|
||||
.hash(master_password, *secret_key.expose())
|
||||
} else {
|
||||
Err(Error::NoVerificationKey)
|
||||
}?;
|
||||
return Err(Error::NoVerificationKey);
|
||||
};
|
||||
|
||||
let hashed_master_password = verification_key
|
||||
.hashing_algorithm
|
||||
.hash(master_password, *secret_key.expose())?;
|
||||
|
||||
// decrypt the root key's KEK
|
||||
let master_key = StreamDecryption::decrypt_bytes(
|
||||
hashed_master_password,
|
||||
&verification_key.master_key_nonce,
|
||||
verification_key.algorithm,
|
||||
&verification_key.master_key,
|
||||
&[],
|
||||
)?;
|
||||
|
||||
// get the root key from the backup
|
||||
let root_key = StreamDecryption::decrypt_bytes(
|
||||
Protected::new(to_array(master_key.expose().clone())?),
|
||||
&verification_key.key_nonce,
|
||||
verification_key.algorithm,
|
||||
&verification_key.key,
|
||||
&[],
|
||||
)?;
|
||||
|
||||
let root_key = Protected::new(to_array(root_key.expose().clone())?);
|
||||
|
||||
let mut reencrypted_keys = Vec::new();
|
||||
|
||||
for key in keys {
|
||||
if !self.keystore.contains_key(&key.uuid) {
|
||||
// could check the key material itself? if they match, attach the content salt
|
||||
let master_key = if let Ok(decrypted_master_key) = StreamDecryption::decrypt_bytes(
|
||||
hashed_master_password.clone(),
|
||||
&key.master_key_nonce,
|
||||
key.algorithm,
|
||||
&key.master_key,
|
||||
&[],
|
||||
) {
|
||||
Ok(Protected::new(to_array::<32>(
|
||||
decrypted_master_key.expose().clone(),
|
||||
)?))
|
||||
} else {
|
||||
Err(Error::IncorrectPassword)
|
||||
}?;
|
||||
|
||||
let master_key_nonce = generate_nonce(key.algorithm);
|
||||
|
||||
// Encrypt the master key with the user's hashed password
|
||||
let encrypted_master_key: [u8; 48] = to_array(StreamEncryption::encrypt_bytes(
|
||||
self.get_master_password()?,
|
||||
&master_key_nonce,
|
||||
key.algorithm,
|
||||
master_key.expose(),
|
||||
&[],
|
||||
)?)?;
|
||||
|
||||
let mut updated_key = key.clone();
|
||||
updated_key.master_key_nonce = master_key_nonce;
|
||||
updated_key.master_key = encrypted_master_key;
|
||||
|
||||
reencrypted_keys.push(updated_key.clone());
|
||||
self.keystore.insert(updated_key.uuid, updated_key);
|
||||
if self.keystore.contains_key(&key.uuid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// could check the key material itself? if they match, attach the content salt
|
||||
|
||||
// decrypt the key's master key
|
||||
let master_key = if let Ok(decrypted_master_key) = StreamDecryption::decrypt_bytes(
|
||||
root_key.clone(),
|
||||
&key.master_key_nonce,
|
||||
key.algorithm,
|
||||
&key.master_key,
|
||||
&[],
|
||||
) {
|
||||
Ok(Protected::new(to_array::<32>(
|
||||
decrypted_master_key.expose().clone(),
|
||||
)?))
|
||||
} else {
|
||||
Err(Error::IncorrectPassword)
|
||||
}?;
|
||||
|
||||
// generate a new nonce
|
||||
let master_key_nonce = generate_nonce(key.algorithm);
|
||||
|
||||
// encrypt the master key with the current root key
|
||||
let encrypted_master_key: [u8; 48] = to_array(StreamEncryption::encrypt_bytes(
|
||||
self.get_root_key()?,
|
||||
&master_key_nonce,
|
||||
key.algorithm,
|
||||
master_key.expose(),
|
||||
&[],
|
||||
)?)?;
|
||||
|
||||
let mut updated_key = key.clone();
|
||||
updated_key.master_key_nonce = master_key_nonce;
|
||||
updated_key.master_key = encrypted_master_key;
|
||||
|
||||
reencrypted_keys.push(updated_key.clone());
|
||||
self.keystore.insert(updated_key.uuid, updated_key);
|
||||
}
|
||||
|
||||
Ok(reencrypted_keys)
|
||||
}
|
||||
|
||||
// requires master password and the secret key
|
||||
/// This requires both the master password and the secret key
|
||||
///
|
||||
/// The master password and secret key are hashed together.
|
||||
@ -545,16 +641,25 @@ impl KeyManager {
|
||||
.hash(master_password, *secret_key.expose())?;
|
||||
|
||||
// Decrypt the StoredKey's master key using the user's hashed password
|
||||
let decryption_result = StreamDecryption::decrypt_bytes(
|
||||
hashed_master_password.clone(),
|
||||
if let Ok(master_key) = StreamDecryption::decrypt_bytes(
|
||||
hashed_master_password,
|
||||
&verification_key.master_key_nonce,
|
||||
verification_key.algorithm,
|
||||
&verification_key.master_key,
|
||||
&[],
|
||||
);
|
||||
|
||||
if decryption_result.is_ok() {
|
||||
*self.master_password.lock()? = Some(hashed_master_password);
|
||||
) {
|
||||
// decrypt the root key and set that as the master password
|
||||
*self.root_key.lock()? = Some(Protected::new(to_array(
|
||||
StreamDecryption::decrypt_bytes(
|
||||
Protected::new(to_array(master_key.expose().clone())?),
|
||||
&verification_key.key_nonce,
|
||||
verification_key.algorithm,
|
||||
&verification_key.key,
|
||||
&[],
|
||||
)?
|
||||
.expose()
|
||||
.clone(),
|
||||
)?));
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::IncorrectKeymanagerDetails)
|
||||
@ -562,8 +667,8 @@ impl KeyManager {
|
||||
}
|
||||
|
||||
/// This function is for removing a previously-added master password
|
||||
pub fn clear_master_password(&self) -> Result<()> {
|
||||
*self.master_password.lock()? = None;
|
||||
pub fn clear_root_key(&self) -> Result<()> {
|
||||
*self.root_key.lock()? = None;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -577,8 +682,10 @@ 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.master_password.lock()?.is_some())
|
||||
Ok(self.root_key.lock()?.is_some())
|
||||
}
|
||||
|
||||
/// This function is used for emptying the entire keystore.
|
||||
@ -653,9 +760,9 @@ impl KeyManager {
|
||||
|
||||
match self.keystore.get(&uuid) {
|
||||
Some(stored_key) => {
|
||||
// Decrypt the StoredKey's master key using the user's hashed password
|
||||
// Decrypt the StoredKey's master key using the root key
|
||||
let master_key = if let Ok(decrypted_master_key) = StreamDecryption::decrypt_bytes(
|
||||
self.get_master_password()?,
|
||||
self.get_root_key()?,
|
||||
&stored_key.master_key_nonce,
|
||||
stored_key.algorithm,
|
||||
&stored_key.master_key,
|
||||
@ -702,9 +809,9 @@ impl KeyManager {
|
||||
pub fn get_key(&self, uuid: Uuid) -> Result<Protected<Vec<u8>>> {
|
||||
match self.keystore.get(&uuid) {
|
||||
Some(stored_key) => {
|
||||
// Decrypt the StoredKey's master key using the user's hashed password
|
||||
// Decrypt the StoredKey's master key using the root key
|
||||
let master_key = if let Ok(decrypted_master_key) = StreamDecryption::decrypt_bytes(
|
||||
self.get_master_password()?,
|
||||
self.get_root_key()?,
|
||||
&stored_key.master_key_nonce,
|
||||
stored_key.algorithm,
|
||||
&stored_key.master_key,
|
||||
@ -778,6 +885,7 @@ impl KeyManager {
|
||||
key: Protected<Vec<u8>>,
|
||||
algorithm: Algorithm,
|
||||
hashing_algorithm: HashingAlgorithm,
|
||||
memory_only: bool,
|
||||
) -> Result<Uuid> {
|
||||
let uuid = uuid::Uuid::new_v4();
|
||||
|
||||
@ -789,7 +897,7 @@ impl KeyManager {
|
||||
|
||||
// Encrypt the master key with the user's hashed password
|
||||
let encrypted_master_key: [u8; 48] = to_array(StreamEncryption::encrypt_bytes(
|
||||
self.get_master_password()?,
|
||||
self.get_root_key()?,
|
||||
&master_key_nonce,
|
||||
algorithm,
|
||||
master_key.expose(),
|
||||
@ -810,6 +918,7 @@ impl KeyManager {
|
||||
master_key_nonce,
|
||||
key_nonce,
|
||||
key: encrypted_key,
|
||||
memory_only,
|
||||
};
|
||||
|
||||
// Insert it into the Keystore
|
||||
|
||||
@ -164,7 +164,7 @@ 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 }
|
||||
|
||||
export interface StoredKey { uuid: string, algorithm: Algorithm, hashing_algorithm: HashingAlgorithm, content_salt: Array<number>, master_key: Array<number>, master_key_nonce: Array<number>, key_nonce: Array<number>, key: Array<number> }
|
||||
export interface StoredKey { uuid: string, algorithm: Algorithm, hashing_algorithm: HashingAlgorithm, content_salt: Array<number>, master_key: Array<number>, master_key_nonce: Array<number>, key_nonce: Array<number>, key: Array<number>, memory_only: boolean }
|
||||
|
||||
export interface Tag { id: number, pub_id: Array<number>, name: string | null, color: string | null, total_objects: number | null, redundancy_goal: number | null, date_created: string, date_modified: string }
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user