From 5de9e1d74ec69f51efbe363455f34ea901f4d284 Mon Sep 17 00:00:00 2001 From: C0ffeeCode Date: Thu, 27 Mar 2025 22:13:57 +0100 Subject: [PATCH] Fix (sealing): Simple sealing with random nonce --- migrations/20250326160659_sealing.sql | 1 + src/engines/kv/data.rs | 20 +++++++-------- src/storage/sealing.rs | 35 ++++++++++++++++++--------- src/storage/sealing/simple.rs | 19 +++++++++------ 4 files changed, 46 insertions(+), 29 deletions(-) diff --git a/migrations/20250326160659_sealing.sql b/migrations/20250326160659_sealing.sql index 63f703b..08d6aa0 100644 --- a/migrations/20250326160659_sealing.sql +++ b/migrations/20250326160659_sealing.sql @@ -3,5 +3,6 @@ CREATE TABLE root_key ( version INTEGER PRIMARY KEY CHECK ( version = 1 ), encrypted_key BLOB NOT NULL, + nonce BLOB, type TEXT NOT NULL CHECK ( type IN ('dev_only', 'simple') ) ); diff --git a/src/engines/kv/data.rs b/src/engines/kv/data.rs index 0810f94..c81d977 100644 --- a/src/engines/kv/data.rs +++ b/src/engines/kv/data.rs @@ -1,8 +1,12 @@ use super::structs::KvV2WriteRequest; use crate::{ - common::HttpError, engines::{ - kv::structs::{KvSecretData, KvSecretRes, KvV2WriteResponse, Wrapper}, EnginePath - }, storage::sealing::{encrypt, decrypt}, DbPool + DbPool, + common::HttpError, + engines::{ + EnginePath, + kv::structs::{KvSecretData, KvSecretRes, KvV2WriteResponse, Wrapper}, + }, + storage::sealing::{decrypt, encrypt}, }; use axum::{ Extension, Json, @@ -52,9 +56,7 @@ pub async fn get_data( Path(path): Path, Extension(EnginePath(engine_path)): Extension, ) -> Result { - debug!( - "Get request: Engine: {engine_path}, path: {path}", - ); + debug!("Get request: Engine: {engine_path}, path: {path}",); let res = if params.version != 0 { // With specific version @@ -138,7 +140,8 @@ pub async fn post_data( "src/engines/kv/post_secret.sql", engine_path, kv_path, - nonce, enc, + nonce, + enc, ts, secret.version, ) @@ -230,8 +233,6 @@ pub async fn delete_data( Ok(NoContent.into_response()) } - -// Not pub async fn patch_data( State(pool): State, Path(kv_path): Path, @@ -241,4 +242,3 @@ pub async fn patch_data( // TODO: implement only application/merge-patch+json todo!("Not implemented") } - diff --git a/src/storage/sealing.rs b/src/storage/sealing.rs index 5e1991a..8e0fa60 100644 --- a/src/storage/sealing.rs +++ b/src/storage/sealing.rs @@ -13,8 +13,8 @@ use super::DbPool; enum KeyEnum { /// Final key MainKey(Vec), - /// Encrypted with single secret - Simple(Vec), + /// Encrypted with single secret (protected_rk, nonce) + Simple(Vec, Vec), /// Unknown or not initialized Uninitialized, // ShamirPortion(Vec), @@ -25,6 +25,7 @@ static ROOT_KEY_MAYBE: RwLock = RwLock::const_new(KeyEnum::Uninitialize struct ProtectedRK { pub protection_type: String, pub encrypted_key: Vec, + pub nonce: Option>, } /// Returns `true` if vault is initialized or unsealed. @@ -41,7 +42,7 @@ pub async fn prepare_unseal(pool: &DbPool) -> bool { let rk = sqlx::query_as!( ProtectedRK, - "SELECT encrypted_key, type as protection_type FROM root_key ORDER BY version LIMIT 1" + "SELECT encrypted_key, type as protection_type, nonce FROM root_key ORDER BY version LIMIT 1" ) .fetch_optional(pool) .await @@ -69,7 +70,10 @@ pub async fn prepare_unseal(pool: &DbPool) -> bool { *lock = KeyEnum::MainKey(v.encrypted_key); } "simple" => { - *lock = KeyEnum::Simple(v.encrypted_key); + *lock = KeyEnum::Simple( + v.encrypted_key, + v.nonce.expect("Simple encryption but the nonce is missing"), + ); } _ => panic!("Unknown root key type in database"), } @@ -79,6 +83,7 @@ pub async fn prepare_unseal(pool: &DbPool) -> bool { /// Must NOT be used in production. /// Token is plainly stored in the database and will be unsealed directly by [prepare_unseal]! /// Danger! +#[allow(unused)] pub async fn init_insecure_in_db(pool: &DbPool) { let root_key = Aes256GcmSiv::generate_key(&mut OsRng); let root_key = root_key.as_slice().to_owned(); @@ -86,17 +91,23 @@ pub async fn init_insecure_in_db(pool: &DbPool) { warn!( "Danger: INSECURE! Generated root key is stored plainly in the database. Must ONLY be used for development!" ); - write_new_root_key(pool, root_key, "dev_only").await; + write_new_root_key(pool, root_key, "dev_only", None).await; } -async fn write_new_root_key(pool: &DbPool, protected_key: Vec, type_to_be: &str) { +async fn write_new_root_key( + pool: &DbPool, + protected_key: Vec, + type_to_be: &str, + nonce: Option<&[u8]>, +) { let _ = sqlx::query!( " - INSERT INTO root_key (encrypted_key, type, version) - VALUES ($1, $2, 1) + INSERT INTO root_key (encrypted_key, type, version, nonce) + VALUES ($1, $2, 1, $3) ", protected_key, - type_to_be + type_to_be, + nonce ) .execute(pool) .await @@ -117,7 +128,7 @@ pub async fn sealing_status() { let lock = ROOT_KEY_MAYBE.read().await; match &*lock { KeyEnum::MainKey(_) => todo!(), - KeyEnum::Simple(_) => todo!(), + KeyEnum::Simple(_, _) => todo!(), KeyEnum::Uninitialized => todo!(), } } @@ -130,8 +141,8 @@ pub async fn provide_key(key: String) { info!("Providing keys is useless since vault is already unlocked"); return; } - KeyEnum::Simple(protected_rk) => { - KeyEnum::MainKey(simple::unseal(protected_rk, key).await) + KeyEnum::Simple(protected_rk, nonce) => { + KeyEnum::MainKey(simple::unseal(protected_rk, key, nonce).await) } KeyEnum::Uninitialized => { error!("Cannot process provided key when the vault is uninitialized"); diff --git a/src/storage/sealing/simple.rs b/src/storage/sealing/simple.rs index 9de5b12..6e6c74e 100644 --- a/src/storage/sealing/simple.rs +++ b/src/storage/sealing/simple.rs @@ -1,31 +1,36 @@ -use aes_gcm_siv::{aead::{Aead, OsRng}, Aes256GcmSiv, KeyInit}; -use base64::{prelude::BASE64_STANDARD, Engine}; +use aes_gcm_siv::{ + AeadCore, Aes256GcmSiv, KeyInit, + aead::{Aead, OsRng, generic_array::GenericArray}, +}; +use base64::{Engine, prelude::BASE64_STANDARD}; use crate::DbPool; use super::write_new_root_key; pub async fn init_simple(pool: &DbPool) -> String { - // let root_key = write_new_root_key(pool, p_type).await; let root_key = Aes256GcmSiv::generate_key(&mut OsRng); + let nonce: GenericArray::NonceSize> = + Aes256GcmSiv::generate_nonce(&mut OsRng); // 96-bits; unique per message let root_key = root_key.as_slice().to_owned(); let (user_key, protected_rk) = { let key = Aes256GcmSiv::generate_key(&mut OsRng); let cipher = Aes256GcmSiv::new(&key); - let nonce: &[u8; 12] = b"hello world!"; // TODO + let nonce: &[u8] = nonce.as_slice(); + debug_assert_eq!(nonce.len(), 12); let nonce = aes_gcm_siv::aead::generic_array::GenericArray::from_slice(nonce); let enc = cipher.encrypt(nonce, root_key.as_slice()).unwrap(); (key, enc) }; - write_new_root_key(pool, protected_rk, "simple").await; + write_new_root_key(pool, protected_rk, "simple", Some(nonce.as_slice())).await; BASE64_STANDARD.encode(user_key) } -pub async fn unseal(protected_rk: &Vec, key: String) -> Vec { +pub async fn unseal(protected_rk: &Vec, key: String, nonce: &[u8]) -> Vec { let key = BASE64_STANDARD.decode(key).unwrap(); let cipher = Aes256GcmSiv::new_from_slice(&key).unwrap(); - let nonce: &[u8; 12] = b"hello world!"; // TODO + debug_assert_eq!(nonce.len(), 12); let nonce = aes_gcm_siv::aead::generic_array::GenericArray::from_slice(nonce); cipher.decrypt(nonce, protected_rk.as_ref()).unwrap() }