[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
This commit is contained in:
jake 2023-01-11 13:15:08 +00:00 committed by GitHub
parent 9de6f00c1d
commit 3c0729e7aa
7 changed files with 205 additions and 142 deletions

View File

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

View File

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

View File

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

View File

@ -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<PrismaClient, MigrationErr
/// 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: &PrismaClient, key: &StoredKey) -> 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(),

View File

@ -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::<KEY_LEN>(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::<KEY_LEN>(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<Protected<Vec<u8>>> {
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,

View File

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

View File

@ -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<number>, master_key: Array<number>, master_key_nonce: Array<number>, key_nonce: Array<number>, key: Array<number>, salt: Array<number>, memory_only: boolean, automount: boolean }
export interface StoredKey { uuid: string, version: StoredKeyVersion, 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>, salt: Array<number>, memory_only: boolean, automount: boolean }
export type StoredKeyVersion = "V1"
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 }