From 0d554e9d06dc7c7ec18df7d83397d3c10e7985e9 Mon Sep 17 00:00:00 2001 From: jake <77554505+brxken128@users.noreply.github.com> Date: Wed, 11 Jan 2023 13:15:08 +0000 Subject: [PATCH] [ENG-331] `StoredKey` overhaul (#513) * add wip storedkey versioning * storedkey versioning! (not pretty, but it never will be) * add version to `StoredKey` and re-gen migrations to handle serde * use `serde` for interacting with the DB + handle errors --- .../20230110131305_key_overhaul/migration.sql | 31 ++ core/prisma/schema.prisma | 5 +- core/src/library/library_manager.rs | 14 +- core/src/util/db.rs | 12 +- crates/crypto/src/keys/keymanager.rs | 278 ++++++++++-------- crates/crypto/src/primitives.rs | 3 +- packages/client/src/core.ts | 4 +- 7 files changed, 205 insertions(+), 142 deletions(-) create mode 100644 core/prisma/migrations/20230110131305_key_overhaul/migration.sql diff --git a/core/prisma/migrations/20230110131305_key_overhaul/migration.sql b/core/prisma/migrations/20230110131305_key_overhaul/migration.sql new file mode 100644 index 000000000..1c1447066 --- /dev/null +++ b/core/prisma/migrations/20230110131305_key_overhaul/migration.sql @@ -0,0 +1,31 @@ +/* + Warnings: + + - Added the required column `version` to the `key` table without a default value. This is not possible if the table is not empty. + +*/ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_key" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "uuid" TEXT NOT NULL, + "version" TEXT NOT NULL, + "name" TEXT, + "default" BOOLEAN NOT NULL DEFAULT false, + "date_created" DATETIME DEFAULT CURRENT_TIMESTAMP, + "algorithm" TEXT NOT NULL, + "hashing_algorithm" TEXT NOT NULL, + "content_salt" BLOB NOT NULL, + "master_key" BLOB NOT NULL, + "master_key_nonce" BLOB NOT NULL, + "key_nonce" BLOB NOT NULL, + "key" BLOB NOT NULL, + "salt" BLOB NOT NULL, + "automount" BOOLEAN NOT NULL DEFAULT false +); +INSERT INTO "new_key" ("algorithm", "automount", "content_salt", "date_created", "default", "hashing_algorithm", "id", "key", "key_nonce", "master_key", "master_key_nonce", "name", "salt", "uuid") SELECT "algorithm", "automount", "content_salt", "date_created", "default", "hashing_algorithm", "id", "key", "key_nonce", "master_key", "master_key_nonce", "name", "salt", "uuid" FROM "key"; +DROP TABLE "key"; +ALTER TABLE "new_key" RENAME TO "key"; +CREATE UNIQUE INDEX "key_uuid_key" ON "key"("uuid"); +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/core/prisma/schema.prisma b/core/prisma/schema.prisma index ef2998b21..560fc0354 100644 --- a/core/prisma/schema.prisma +++ b/core/prisma/schema.prisma @@ -224,6 +224,7 @@ model Key { id Int @id @default(autoincrement()) // uuid to identify the key uuid String @unique + version String // the name that the user sets name String? // is this key the default for encryption? @@ -233,9 +234,9 @@ model Key { // nullable if concealed for security date_created DateTime? @default(now()) // encryption algorithm used to encrypt the key - algorithm Bytes + algorithm String // hashing algorithm used for hashing the key with the content salt - hashing_algorithm Bytes + hashing_algorithm String // salt used for encrypting data with this key content_salt Bytes // the *encrypted* master key (48 bytes) diff --git a/core/src/library/library_manager.rs b/core/src/library/library_manager.rs index 15d9c9a05..1750a4bbc 100644 --- a/core/src/library/library_manager.rs +++ b/core/src/library/library_manager.rs @@ -11,11 +11,7 @@ use crate::{ }; use sd_crypto::{ - crypto::stream::Algorithm, - keys::{ - hashing::HashingAlgorithm, - keymanager::{KeyManager, StoredKey}, - }, + keys::keymanager::{KeyManager, StoredKey}, primitives::{to_array, OnboardingConfig}, }; use std::{ @@ -95,13 +91,17 @@ pub async fn seed_keymanager( Ok(StoredKey { uuid, - algorithm: Algorithm::from_bytes(to_array(key.algorithm)?)?, + version: serde_json::from_str(&key.version) + .map_err(|_| sd_crypto::Error::Serialization)?, + algorithm: serde_json::from_str(&key.algorithm) + .map_err(|_| sd_crypto::Error::Serialization)?, content_salt: to_array(key.content_salt)?, master_key: to_array(key.master_key)?, master_key_nonce: key.master_key_nonce, key_nonce: key.key_nonce, key: key.key, - hashing_algorithm: HashingAlgorithm::from_bytes(to_array(key.hashing_algorithm)?)?, + hashing_algorithm: serde_json::from_str(&key.hashing_algorithm) + .map_err(|_| sd_crypto::Error::Serialization)?, salt: to_array(key.salt)?, memory_only: false, automount: key.automount, diff --git a/core/src/util/db.rs b/core/src/util/db.rs index 2cf0a54cb..e1483b9a6 100644 --- a/core/src/util/db.rs +++ b/core/src/util/db.rs @@ -1,5 +1,5 @@ +use crate::library::LibraryManagerError; use crate::prisma::{self, PrismaClient}; -use prisma_client_rust::QueryError; use prisma_client_rust::{migrations::*, NewClientError}; use sd_crypto::keys::keymanager::StoredKey; use thiserror::Error; @@ -45,13 +45,17 @@ pub async fn load_and_migrate(db_url: &str) -> Result Result<(), QueryError> { +pub async fn write_storedkey_to_db( + db: &PrismaClient, + key: &StoredKey, +) -> Result<(), LibraryManagerError> { if !key.memory_only { db.key() .create( key.uuid.to_string(), - key.algorithm.to_bytes().to_vec(), - key.hashing_algorithm.to_bytes().to_vec(), + serde_json::to_string(&key.version)?, + serde_json::to_string(&key.algorithm)?, + serde_json::to_string(&key.hashing_algorithm)?, key.content_salt.to_vec(), key.master_key.to_vec(), key.master_key_nonce.to_vec(), diff --git a/crates/crypto/src/keys/keymanager.rs b/crates/crypto/src/keys/keymanager.rs index 21db02a63..6ca44f594 100644 --- a/crates/crypto/src/keys/keymanager.rs +++ b/crates/crypto/src/keys/keymanager.rs @@ -40,7 +40,7 @@ use std::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, MASTER_PASSWORD_CONTEXT, ROOT_KEY_CONTEXT, + KEY_LEN, LATEST_STORED_KEY, MASTER_PASSWORD_CONTEXT, ROOT_KEY_CONTEXT, }; use crate::{ crypto::stream::Algorithm, @@ -62,7 +62,8 @@ use super::hashing::HashingAlgorithm; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "rspc", derive(specta::Type))] pub struct StoredKey { - pub uuid: uuid::Uuid, // uuid for identification. shared with mounted keys + pub uuid: uuid::Uuid, // uuid for identification. shared with mounted keys + 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], @@ -76,6 +77,13 @@ pub struct StoredKey { pub automount: bool, } +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "rspc", derive(specta::Type))] +pub enum StoredKeyVersion { + V1, +} + /// This is a mounted key, and needs to be kept somewhat hidden. /// /// This contains the plaintext key, and the same key hashed with the content salt. @@ -168,6 +176,7 @@ impl KeyManager { let verification_key = StoredKey { uuid, + version: LATEST_STORED_KEY, algorithm, hashing_algorithm, content_salt, // salt used for hashing @@ -274,6 +283,7 @@ impl KeyManager { let verification_key = StoredKey { uuid, + version: LATEST_STORED_KEY, algorithm, hashing_algorithm, content_salt, @@ -320,35 +330,39 @@ impl KeyManager { let old_verification_key = old_verification_key.ok_or(Error::NoVerificationKey)?; - let hashed_password = old_verification_key.hashing_algorithm.hash( - Protected::new(master_password.expose().as_bytes().to_vec()), - old_verification_key.content_salt, - secret_key, - )?; + let old_root_key = match old_verification_key.version { + StoredKeyVersion::V1 => { + let hashed_password = old_verification_key.hashing_algorithm.hash( + Protected::new(master_password.expose().as_bytes().to_vec()), + old_verification_key.content_salt, + secret_key, + )?; - // decrypt the root key's KEK - let master_key = StreamDecryption::decrypt_bytes( - derive_key( - hashed_password, - old_verification_key.salt, - MASTER_PASSWORD_CONTEXT, - ), - &old_verification_key.master_key_nonce, - old_verification_key.algorithm, - &old_verification_key.master_key, - &[], - )?; + // decrypt the root key's KEK + let master_key = StreamDecryption::decrypt_bytes( + derive_key( + hashed_password, + old_verification_key.salt, + MASTER_PASSWORD_CONTEXT, + ), + &old_verification_key.master_key_nonce, + old_verification_key.algorithm, + &old_verification_key.master_key, + &[], + )?; - // get the root key from the backup - let old_root_key = StreamDecryption::decrypt_bytes( - Protected::new(to_array(master_key.into_inner())?), - &old_verification_key.key_nonce, - old_verification_key.algorithm, - &old_verification_key.key, - &[], - )?; + // get the root key from the backup + let old_root_key = StreamDecryption::decrypt_bytes( + Protected::new(to_array(master_key.into_inner())?), + &old_verification_key.key_nonce, + old_verification_key.algorithm, + &old_verification_key.key, + &[], + )?; - let old_root_key = Protected::new(to_array(old_root_key.into_inner())?); + Protected::new(to_array(old_root_key.into_inner())?) + } + }; let mut reencrypted_keys = Vec::new(); @@ -357,39 +371,43 @@ impl KeyManager { continue; } - // decrypt the key's master key - let master_key = StreamDecryption::decrypt_bytes( - derive_key(old_root_key.clone(), key.salt, ROOT_KEY_CONTEXT), - &key.master_key_nonce, - key.algorithm, - &key.master_key, - &[], - ) - .map_or(Err(Error::IncorrectPassword), |v| { - Ok(Protected::new(to_array::(v.into_inner())?)) - })?; + match key.version { + StoredKeyVersion::V1 => { + // decrypt the key's master key + let master_key = StreamDecryption::decrypt_bytes( + derive_key(old_root_key.clone(), key.salt, ROOT_KEY_CONTEXT), + &key.master_key_nonce, + key.algorithm, + &key.master_key, + &[], + ) + .map_or(Err(Error::IncorrectPassword), |v| { + Ok(Protected::new(to_array::(v.into_inner())?)) + })?; - // generate a new nonce - let master_key_nonce = generate_nonce(key.algorithm); + // generate a new nonce + let master_key_nonce = generate_nonce(key.algorithm); - let salt = generate_salt(); + 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(), - &[], - )?)?; + // 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 mut updated_key = key.clone(); - updated_key.master_key_nonce = master_key_nonce; - updated_key.master_key = encrypted_master_key; - updated_key.salt = salt; + let mut updated_key = key.clone(); + updated_key.master_key_nonce = master_key_nonce; + updated_key.master_key = encrypted_master_key; + updated_key.salt = salt; - reencrypted_keys.push(updated_key.clone()); - self.keystore.insert(updated_key.uuid, updated_key); + reencrypted_keys.push(updated_key.clone()); + self.keystore.insert(updated_key.uuid, updated_key); + } + } } Ok(reencrypted_keys) @@ -413,37 +431,40 @@ impl KeyManager { let secret_key = secret_key.map(Self::convert_secret_key_string); - let hashed_password = verification_key.hashing_algorithm.hash( - Protected::new(master_password.expose().as_bytes().to_vec()), - verification_key.content_salt, - secret_key, - )?; + match verification_key.version { + StoredKeyVersion::V1 => { + let hashed_password = verification_key.hashing_algorithm.hash( + Protected::new(master_password.expose().as_bytes().to_vec()), + verification_key.content_salt, + secret_key, + )?; - let master_key = StreamDecryption::decrypt_bytes( - derive_key( - hashed_password, - verification_key.salt, - MASTER_PASSWORD_CONTEXT, - ), - &verification_key.master_key_nonce, - verification_key.algorithm, - &verification_key.master_key, - &[], - ) - .map_err(|_| Error::IncorrectKeymanagerDetails)?; - - *self.root_key.lock()? = Some(Protected::new(to_array( - StreamDecryption::decrypt_bytes( - Protected::new(to_array(master_key.into_inner())?), - &verification_key.key_nonce, - verification_key.algorithm, - &verification_key.key, - &[], - )? - .expose() - .clone(), - )?)); + let master_key = StreamDecryption::decrypt_bytes( + derive_key( + hashed_password, + verification_key.salt, + MASTER_PASSWORD_CONTEXT, + ), + &verification_key.master_key_nonce, + verification_key.algorithm, + &verification_key.master_key, + &[], + ) + .map_err(|_| Error::IncorrectKeymanagerDetails)?; + *self.root_key.lock()? = Some(Protected::new(to_array( + StreamDecryption::decrypt_bytes( + Protected::new(to_array(master_key.into_inner())?), + &verification_key.key_nonce, + verification_key.algorithm, + &verification_key.key, + &[], + )? + .expose() + .clone(), + )?)); + } + } Ok(()) } @@ -459,55 +480,58 @@ impl KeyManager { return Err(Error::KeyAlreadyMounted); } - match self.keystore.get(&uuid) { - Some(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), |v| { - Ok(Protected::new(to_array(v.into_inner())?)) - })?; + self.keystore + .get(&uuid) + .map_or(Err(Error::KeyNotFound), |stored_key| { + match stored_key.version { + StoredKeyVersion::V1 => { + 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), |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, + &[], + )?; - // 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, - &[], - )?; + // 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, + )?; - // 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)?; + self.keymount.insert( + uuid, + MountedKey { + uuid: stored_key.uuid, + hashed_key, + }, + ); - self.keymount.insert( - uuid, - MountedKey { - uuid: stored_key.uuid, - hashed_key, - }, - ); - - Ok(()) - } - None => Err(Error::KeyNotFound), - } + Ok(()) + } + } + }) } /// 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>> { - self.keystore.get(&uuid).map_or_else( - || Err(Error::KeyNotFound), - |stored_key| { + 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, @@ -529,8 +553,7 @@ impl KeyManager { )?; Ok(key) - }, - ) + }) } /// This function is used to add a new key/password to the keystore. @@ -584,6 +607,7 @@ impl KeyManager { uuid, StoredKey { uuid, + version: LATEST_STORED_KEY, algorithm, hashing_algorithm, content_salt, diff --git a/crates/crypto/src/primitives.rs b/crates/crypto/src/primitives.rs index d2ccab918..982e7deb0 100644 --- a/crates/crypto/src/primitives.rs +++ b/crates/crypto/src/primitives.rs @@ -11,7 +11,7 @@ use crate::{ file::FileHeaderVersion, keyslot::KeyslotVersion, metadata::MetadataVersion, preview_media::PreviewMediaVersion, }, - keys::hashing::HashingAlgorithm, + keys::{hashing::HashingAlgorithm, keymanager::StoredKeyVersion}, Error, Protected, Result, }; @@ -37,6 +37,7 @@ pub const LATEST_FILE_HEADER: FileHeaderVersion = FileHeaderVersion::V1; pub const LATEST_KEYSLOT: KeyslotVersion = KeyslotVersion::V1; pub const LATEST_METADATA: MetadataVersion = MetadataVersion::V1; pub const LATEST_PREVIEW_MEDIA: PreviewMediaVersion = PreviewMediaVersion::V1; +pub const LATEST_STORED_KEY: StoredKeyVersion = StoredKeyVersion::V1; pub const ROOT_KEY_CONTEXT: &str = "spacedrive 2022-12-14 12:53:54 root key derivation"; // used for deriving keys from the root key pub const MASTER_PASSWORD_CONTEXT: &str = diff --git a/packages/client/src/core.ts b/packages/client/src/core.ts index 40978c925..ae3f6be71 100644 --- a/packages/client/src/core.ts +++ b/packages/client/src/core.ts @@ -169,7 +169,9 @@ 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, master_key: Array, master_key_nonce: Array, key_nonce: Array, key: Array, salt: Array, memory_only: boolean, automount: boolean } +export interface StoredKey { uuid: string, version: StoredKeyVersion, algorithm: Algorithm, hashing_algorithm: HashingAlgorithm, content_salt: Array, master_key: Array, master_key_nonce: Array, key_nonce: Array, key: Array, salt: Array, memory_only: boolean, automount: boolean } + +export type StoredKeyVersion = "V1" export interface Tag { id: number, pub_id: Array, name: string | null, color: string | null, total_objects: number | null, redundancy_goal: number | null, date_created: string, date_modified: string }