Fix (sealing): Simple sealing with random nonce
Some checks failed
Rust / build (pull_request) Failing after 40s

This commit is contained in:
Laurenz 2025-03-27 22:13:57 +01:00
parent 88ed714e22
commit 5de9e1d74e
Signed by: C0ffeeCode
SSH key fingerprint: SHA256:prvFOyBjButRypyXm7X8lbbCkly2Dq1PF7e/mrsPVjw
4 changed files with 46 additions and 29 deletions

View file

@ -3,5 +3,6 @@
CREATE TABLE root_key ( CREATE TABLE root_key (
version INTEGER PRIMARY KEY CHECK ( version = 1 ), version INTEGER PRIMARY KEY CHECK ( version = 1 ),
encrypted_key BLOB NOT NULL, encrypted_key BLOB NOT NULL,
nonce BLOB,
type TEXT NOT NULL CHECK ( type IN ('dev_only', 'simple') ) type TEXT NOT NULL CHECK ( type IN ('dev_only', 'simple') )
); );

View file

@ -1,8 +1,12 @@
use super::structs::KvV2WriteRequest; use super::structs::KvV2WriteRequest;
use crate::{ use crate::{
common::HttpError, engines::{ DbPool,
kv::structs::{KvSecretData, KvSecretRes, KvV2WriteResponse, Wrapper}, EnginePath common::HttpError,
}, storage::sealing::{encrypt, decrypt}, DbPool engines::{
EnginePath,
kv::structs::{KvSecretData, KvSecretRes, KvV2WriteResponse, Wrapper},
},
storage::sealing::{decrypt, encrypt},
}; };
use axum::{ use axum::{
Extension, Json, Extension, Json,
@ -52,9 +56,7 @@ pub async fn get_data(
Path(path): Path<String>, Path(path): Path<String>,
Extension(EnginePath(engine_path)): Extension<EnginePath>, Extension(EnginePath(engine_path)): Extension<EnginePath>,
) -> Result<Response, ()> { ) -> Result<Response, ()> {
debug!( debug!("Get request: Engine: {engine_path}, path: {path}",);
"Get request: Engine: {engine_path}, path: {path}",
);
let res = if params.version != 0 { let res = if params.version != 0 {
// With specific version // With specific version
@ -138,7 +140,8 @@ pub async fn post_data(
"src/engines/kv/post_secret.sql", "src/engines/kv/post_secret.sql",
engine_path, engine_path,
kv_path, kv_path,
nonce, enc, nonce,
enc,
ts, ts,
secret.version, secret.version,
) )
@ -230,8 +233,6 @@ pub async fn delete_data(
Ok(NoContent.into_response()) Ok(NoContent.into_response())
} }
// Not
pub async fn patch_data( pub async fn patch_data(
State(pool): State<DbPool>, State(pool): State<DbPool>,
Path(kv_path): Path<String>, Path(kv_path): Path<String>,
@ -241,4 +242,3 @@ pub async fn patch_data(
// TODO: implement only application/merge-patch+json // TODO: implement only application/merge-patch+json
todo!("Not implemented") todo!("Not implemented")
} }

View file

@ -13,8 +13,8 @@ use super::DbPool;
enum KeyEnum { enum KeyEnum {
/// Final key /// Final key
MainKey(Vec<u8>), MainKey(Vec<u8>),
/// Encrypted with single secret /// Encrypted with single secret (protected_rk, nonce)
Simple(Vec<u8>), Simple(Vec<u8>, Vec<u8>),
/// Unknown or not initialized /// Unknown or not initialized
Uninitialized, Uninitialized,
// ShamirPortion(Vec<String>), // ShamirPortion(Vec<String>),
@ -25,6 +25,7 @@ static ROOT_KEY_MAYBE: RwLock<KeyEnum> = RwLock::const_new(KeyEnum::Uninitialize
struct ProtectedRK { struct ProtectedRK {
pub protection_type: String, pub protection_type: String,
pub encrypted_key: Vec<u8>, pub encrypted_key: Vec<u8>,
pub nonce: Option<Vec<u8>>,
} }
/// Returns `true` if vault is initialized or unsealed. /// 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!( let rk = sqlx::query_as!(
ProtectedRK, 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) .fetch_optional(pool)
.await .await
@ -69,7 +70,10 @@ pub async fn prepare_unseal(pool: &DbPool) -> bool {
*lock = KeyEnum::MainKey(v.encrypted_key); *lock = KeyEnum::MainKey(v.encrypted_key);
} }
"simple" => { "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"), _ => 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. /// Must NOT be used in production.
/// Token is plainly stored in the database and will be unsealed directly by [prepare_unseal]! /// Token is plainly stored in the database and will be unsealed directly by [prepare_unseal]!
/// Danger! /// Danger!
#[allow(unused)]
pub async fn init_insecure_in_db(pool: &DbPool) { pub async fn init_insecure_in_db(pool: &DbPool) {
let root_key = Aes256GcmSiv::generate_key(&mut OsRng); let root_key = Aes256GcmSiv::generate_key(&mut OsRng);
let root_key = root_key.as_slice().to_owned(); let root_key = root_key.as_slice().to_owned();
@ -86,17 +91,23 @@ pub async fn init_insecure_in_db(pool: &DbPool) {
warn!( warn!(
"Danger: INSECURE! Generated root key is stored plainly in the database. Must ONLY be used for development!" "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<u8>, type_to_be: &str) { async fn write_new_root_key(
pool: &DbPool,
protected_key: Vec<u8>,
type_to_be: &str,
nonce: Option<&[u8]>,
) {
let _ = sqlx::query!( let _ = sqlx::query!(
" "
INSERT INTO root_key (encrypted_key, type, version) INSERT INTO root_key (encrypted_key, type, version, nonce)
VALUES ($1, $2, 1) VALUES ($1, $2, 1, $3)
", ",
protected_key, protected_key,
type_to_be type_to_be,
nonce
) )
.execute(pool) .execute(pool)
.await .await
@ -117,7 +128,7 @@ pub async fn sealing_status() {
let lock = ROOT_KEY_MAYBE.read().await; let lock = ROOT_KEY_MAYBE.read().await;
match &*lock { match &*lock {
KeyEnum::MainKey(_) => todo!(), KeyEnum::MainKey(_) => todo!(),
KeyEnum::Simple(_) => todo!(), KeyEnum::Simple(_, _) => todo!(),
KeyEnum::Uninitialized => 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"); info!("Providing keys is useless since vault is already unlocked");
return; return;
} }
KeyEnum::Simple(protected_rk) => { KeyEnum::Simple(protected_rk, nonce) => {
KeyEnum::MainKey(simple::unseal(protected_rk, key).await) KeyEnum::MainKey(simple::unseal(protected_rk, key, nonce).await)
} }
KeyEnum::Uninitialized => { KeyEnum::Uninitialized => {
error!("Cannot process provided key when the vault is uninitialized"); error!("Cannot process provided key when the vault is uninitialized");

View file

@ -1,31 +1,36 @@
use aes_gcm_siv::{aead::{Aead, OsRng}, Aes256GcmSiv, KeyInit}; use aes_gcm_siv::{
use base64::{prelude::BASE64_STANDARD, Engine}; AeadCore, Aes256GcmSiv, KeyInit,
aead::{Aead, OsRng, generic_array::GenericArray},
};
use base64::{Engine, prelude::BASE64_STANDARD};
use crate::DbPool; use crate::DbPool;
use super::write_new_root_key; use super::write_new_root_key;
pub async fn init_simple(pool: &DbPool) -> String { 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 root_key = Aes256GcmSiv::generate_key(&mut OsRng);
let nonce: GenericArray<u8, <Aes256GcmSiv as AeadCore>::NonceSize> =
Aes256GcmSiv::generate_nonce(&mut OsRng); // 96-bits; unique per message
let root_key = root_key.as_slice().to_owned(); let root_key = root_key.as_slice().to_owned();
let (user_key, protected_rk) = { let (user_key, protected_rk) = {
let key = Aes256GcmSiv::generate_key(&mut OsRng); let key = Aes256GcmSiv::generate_key(&mut OsRng);
let cipher = Aes256GcmSiv::new(&key); 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 nonce = aes_gcm_siv::aead::generic_array::GenericArray::from_slice(nonce);
let enc = cipher.encrypt(nonce, root_key.as_slice()).unwrap(); let enc = cipher.encrypt(nonce, root_key.as_slice()).unwrap();
(key, enc) (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) BASE64_STANDARD.encode(user_key)
} }
pub async fn unseal(protected_rk: &Vec<u8>, key: String) -> Vec<u8> { pub async fn unseal(protected_rk: &Vec<u8>, key: String, nonce: &[u8]) -> Vec<u8> {
let key = BASE64_STANDARD.decode(key).unwrap(); let key = BASE64_STANDARD.decode(key).unwrap();
let cipher = Aes256GcmSiv::new_from_slice(&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); let nonce = aes_gcm_siv::aead::generic_array::GenericArray::from_slice(nonce);
cipher.decrypt(nonce, protected_rk.as_ref()).unwrap() cipher.decrypt(nonce, protected_rk.as_ref()).unwrap()
} }