Sealing: Encryption of Secrets #1
8 changed files with 386 additions and 208 deletions
10
Cargo.toml
10
Cargo.toml
|
|
@ -3,6 +3,12 @@ name = "rvault-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["shamir"]
|
||||||
|
# default = ["insecure-dev-sealing"]
|
||||||
|
insecure-dev-sealing = []
|
||||||
|
shamir = ["vsss-rs", "p256"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = "0.4.27"
|
log = "0.4.27"
|
||||||
env_logger = "0.11.7"
|
env_logger = "0.11.7"
|
||||||
|
|
@ -28,8 +34,8 @@ sqlx = { version = "0.8.3", features = [
|
||||||
] }
|
] }
|
||||||
|
|
||||||
aes-gcm-siv = "0.11.1"
|
aes-gcm-siv = "0.11.1"
|
||||||
vsss-rs = { version = "5.1.0", default-features = false, features = ["zeroize", "std"] }
|
vsss-rs = { version = "5.1.0", optional = true, default-features = false, features = ["zeroize", "std"] }
|
||||||
p256 = { version = "0.13.2", default-features = false, features = ["std", "ecdsa"] }
|
p256 = { version = "0.13.2", optional = true, default-features = false, features = ["std", "ecdsa"] }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
|
||||||
|
|
@ -40,18 +40,18 @@ func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Requires in-code portions
|
// Requires in-code portions
|
||||||
func TestUnseal(t *testing.T) {
|
// func TestUnseal(t *testing.T) {
|
||||||
abc := []string{
|
// abc := []string{
|
||||||
"eyJpIjpbMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwxXSwidiI6WzE4OCw2NiwxMTksMTQ0LDE1OSw3MCw4NiwxMTUsMTIwLDI1MywxMjQsOTYsMTM5LDk0LDQ1LDE2NiwyMTMsMzYsMTE1LDU4LDg5LDE0OCw2MCwyOCwxNTAsMTE2LDU3LDg5LDIwMCw5NywxNDYsMjEzXX0=",
|
// "eyJpIjpbMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwxXSwidiI6WzE4OCw2NiwxMTksMTQ0LDE1OSw3MCw4NiwxMTUsMTIwLDI1MywxMjQsOTYsMTM5LDk0LDQ1LDE2NiwyMTMsMzYsMTE1LDU4LDg5LDE0OCw2MCwyOCwxNTAsMTE2LDU3LDg5LDIwMCw5NywxNDYsMjEzXX0=",
|
||||||
"eyJpIjpbMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwyXSwidiI6WzE1OCwyNDQsNzEsOTUsMTIyLDEzOCwyNDEsMjEzLDQ1LDE1NiwxMTgsNCwxNzYsNiwxNTcsMTkyLDE2MSwxNjEsNDMsMTc1LDE5NSw4NywxODAsMTAwLDE1NiwxNCwxNDgsMTUsMTc4LDkwLDY3LDExOF19",
|
// "eyJpIjpbMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwyXSwidiI6WzE1OCwyNDQsNzEsOTUsMTIyLDEzOCwyNDEsMjEzLDQ1LDE1NiwxMTgsNCwxNzYsNiwxNTcsMTkyLDE2MSwxNjEsNDMsMTc1LDE5NSw4NywxODAsMTAwLDE1NiwxNCwxNDgsMTUsMTc4LDkwLDY3LDExOF19",
|
||||||
}
|
// }
|
||||||
for i := range abc {
|
// for i := range abc {
|
||||||
if _, err := Client.Sys().Unseal(abc[i]); err != nil {
|
// if _, err := Client.Sys().Unseal(abc[i]); err != nil {
|
||||||
t.Fatal("Error unsealing", err)
|
// t.Fatal("Error unsealing", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func kv2Write(t *testing.T, mount string, path string) {
|
func kv2Write(t *testing.T, mount string, path string) {
|
||||||
data := map[string]any{
|
data := map[string]any{
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,8 @@
|
||||||
use super::structs::KvV2WriteRequest;
|
use super::structs::KvV2WriteRequest;
|
||||||
use crate::{
|
use crate::{
|
||||||
DbPool,
|
common::HttpError, engines::{
|
||||||
common::HttpError,
|
kv::structs::{KvSecretData, KvSecretRes, KvV2WriteResponse, Wrapper}, EnginePath
|
||||||
engines::{
|
}, storage::sealing::Secret, DbPool
|
||||||
EnginePath,
|
|
||||||
kv::structs::{KvSecretData, KvSecretRes, KvV2WriteResponse, Wrapper},
|
|
||||||
},
|
|
||||||
storage::sealing::{decrypt, encrypt},
|
|
||||||
};
|
};
|
||||||
use axum::{
|
use axum::{
|
||||||
Extension, Json,
|
Extension, Json,
|
||||||
|
|
@ -39,13 +35,13 @@ struct SecretDataInternal {
|
||||||
|
|
||||||
impl SecretDataInternal {
|
impl SecretDataInternal {
|
||||||
pub async fn into_external(self) -> KvSecretData {
|
pub async fn into_external(self) -> KvSecretData {
|
||||||
let secret = decrypt(self.nonce, &self.encrypted_data).await;
|
let secret = Secret::new(self.encrypted_data, self.nonce).decrypt().await;
|
||||||
KvSecretData {
|
KvSecretData {
|
||||||
created_time: self.created_time,
|
created_time: self.created_time,
|
||||||
deletion_time: self.deletion_time,
|
deletion_time: self.deletion_time,
|
||||||
version_number: self.version_number,
|
version_number: self.version_number,
|
||||||
secret_path: self.secret_path,
|
secret_path: self.secret_path,
|
||||||
secret_data: secret,
|
secret_data: secret.unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -125,12 +121,12 @@ pub async fn post_data(
|
||||||
|
|
||||||
let content = serde_json::to_string(&secret.data).unwrap();
|
let content = serde_json::to_string(&secret.data).unwrap();
|
||||||
|
|
||||||
let (nonce, enc) = encrypt(&content).await;
|
let Secret { nonce, protected_data } = Secret::encrypt(&content).await.unwrap();
|
||||||
let nonce = nonce.as_slice();
|
let nonce = nonce.as_slice();
|
||||||
|
|
||||||
let mut tx = pool.begin().await.unwrap();
|
let mut tx = pool.begin().await.unwrap();
|
||||||
|
|
||||||
let res_m = sqlx::query!("
|
let _ = sqlx::query!("
|
||||||
INSERT INTO kv2_metadata (engine_path, secret_path, cas_required, created_time, max_versions, updated_time)
|
INSERT INTO kv2_metadata (engine_path, secret_path, cas_required, created_time, max_versions, updated_time)
|
||||||
VALUES ($1, $2, 0, $3, 100, $3)
|
VALUES ($1, $2, 0, $3, 100, $3)
|
||||||
ON CONFLICT(engine_path, secret_path) DO NOTHING;
|
ON CONFLICT(engine_path, secret_path) DO NOTHING;
|
||||||
|
|
@ -141,7 +137,7 @@ pub async fn post_data(
|
||||||
engine_path,
|
engine_path,
|
||||||
kv_path,
|
kv_path,
|
||||||
nonce,
|
nonce,
|
||||||
enc,
|
protected_data,
|
||||||
ts,
|
ts,
|
||||||
secret.version,
|
secret.version,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
10
src/main.rs
10
src/main.rs
|
|
@ -1,12 +1,12 @@
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
|
Router,
|
||||||
extract::Request,
|
extract::Request,
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
middleware::{self, Next},
|
middleware::{self, Next},
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
routing::get,
|
routing::get,
|
||||||
Router,
|
|
||||||
};
|
};
|
||||||
use log::*;
|
use log::*;
|
||||||
use std::{env, net::SocketAddr, str::FromStr};
|
use std::{env, net::SocketAddr, str::FromStr};
|
||||||
|
|
@ -51,14 +51,10 @@ async fn main() {
|
||||||
.with_state(pool.clone());
|
.with_state(pool.clone());
|
||||||
|
|
||||||
if !storage::sealing::prepare_unseal(&pool).await {
|
if !storage::sealing::prepare_unseal(&pool).await {
|
||||||
// storage::sealing::init_insecure_in_db(&pool).await;
|
storage::sealing::init_default(&pool).await;
|
||||||
let user_key = storage::sealing::shamir::init_shamir(&pool, 2, 5).await;
|
|
||||||
let success = storage::sealing::prepare_unseal(&pool).await;
|
|
||||||
warn!("New sealing password generated: ");
|
|
||||||
assert!(success, "Vault ought to have been initialized just now but it is not.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
warn!("Listening on {}", listen_addr.to_string());
|
warn!("Listening on {listen_addr}");
|
||||||
// Start listening
|
// Start listening
|
||||||
let listener = TcpListener::bind(listen_addr).await.unwrap();
|
let listener = TcpListener::bind(listen_addr).await.unwrap();
|
||||||
axum::serve(listener, app)
|
axum::serve(listener, app)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ pub async fn create_pool(db_url: String) -> DbPool {
|
||||||
if db_url.starts_with("sqlite:") && db_url != ("sqlite::memory:") {
|
if db_url.starts_with("sqlite:") && db_url != ("sqlite::memory:") {
|
||||||
let path = db_url.replace("sqlite:", "");
|
let path = db_url.replace("sqlite:", "");
|
||||||
if !Path::new(&path).exists() {
|
if !Path::new(&path).exists() {
|
||||||
warn!("Sqlite database does not exist, creating file {}", path);
|
warn!("Sqlite database does not exist, creating file {path}");
|
||||||
File::create(&path).expect("Failed to create database file");
|
File::create(&path).expect("Failed to create database file");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
pub mod simple;
|
#[cfg(feature = "shamir")]
|
||||||
pub mod shamir;
|
pub mod shamir;
|
||||||
|
pub mod simple;
|
||||||
|
|
||||||
use aes_gcm_siv::{
|
use aes_gcm_siv::{
|
||||||
AeadCore, Aes256GcmSiv, KeyInit,
|
AeadCore, Aes256GcmSiv, KeyInit,
|
||||||
aead::{Aead, OsRng},
|
aead::{Aead, OsRng},
|
||||||
};
|
};
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
|
use simple::SimpleSealing;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
use super::DbPool;
|
use super::DbPool;
|
||||||
|
|
@ -15,14 +17,19 @@ enum KeyEnum {
|
||||||
/// Final key
|
/// Final key
|
||||||
MainKey(Vec<u8>),
|
MainKey(Vec<u8>),
|
||||||
/// Encrypted with single secret (protected_rk, nonce)
|
/// Encrypted with single secret (protected_rk, nonce)
|
||||||
Simple(Vec<u8>, Vec<u8>),
|
Simple(SimpleSealing),
|
||||||
|
#[cfg(feature = "shamir")]
|
||||||
// Shamir's Secret Sharing
|
// Shamir's Secret Sharing
|
||||||
Shamir(Vec<u8>, Vec<u8>),
|
Shamir(shamir::ShamirBucket),
|
||||||
/// Unknown or not initialized
|
/// Unknown or not initialized
|
||||||
Uninitialized,
|
Uninitialized,
|
||||||
}
|
}
|
||||||
|
|
||||||
static ROOT_KEY_MAYBE: RwLock<KeyEnum> = RwLock::const_new(KeyEnum::Uninitialized);
|
trait Sealing {
|
||||||
|
fn new(protected_rk: Vec<u8>, nonce: Vec<u8>) -> Self;
|
||||||
|
|
||||||
|
async fn unseal(&mut self, key: String) -> UnsealResult;
|
||||||
|
}
|
||||||
|
|
||||||
struct ProtectedRK {
|
struct ProtectedRK {
|
||||||
pub protection_type: String,
|
pub protection_type: String,
|
||||||
|
|
@ -30,6 +37,8 @@ struct ProtectedRK {
|
||||||
pub nonce: Option<Vec<u8>>,
|
pub nonce: Option<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ROOT_KEY_MAYBE: RwLock<KeyEnum> = RwLock::const_new(KeyEnum::Uninitialized);
|
||||||
|
|
||||||
/// Returns `true` if vault is initialized or unsealed.
|
/// Returns `true` if vault is initialized or unsealed.
|
||||||
/// Returns `false` if uninitialized (nothing in the database).
|
/// Returns `false` if uninitialized (nothing in the database).
|
||||||
pub async fn prepare_unseal(pool: &DbPool) -> bool {
|
pub async fn prepare_unseal(pool: &DbPool) -> bool {
|
||||||
|
|
@ -63,35 +72,35 @@ pub async fn prepare_unseal(pool: &DbPool) -> bool {
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut lock = lock.await;
|
let mut lock = lock.await;
|
||||||
match &*v.protection_type {
|
let nonce = v.nonce.expect("Simple encryption but the nonce is missing");
|
||||||
|
let res = match &*v.protection_type {
|
||||||
|
#[cfg(feature = "insecure-dev-sealing")]
|
||||||
"dev_only" => {
|
"dev_only" => {
|
||||||
warn!(
|
warn!(
|
||||||
"Root key is of type {}. This is INSECURE and must only be used for development purposes!",
|
"Root key is of type {}. This is INSECURE and must only be used for development purposes!",
|
||||||
v.protection_type
|
v.protection_type
|
||||||
);
|
);
|
||||||
*lock = KeyEnum::MainKey(v.encrypted_key);
|
KeyEnum::MainKey(v.encrypted_key)
|
||||||
}
|
|
||||||
"simple" => {
|
|
||||||
*lock = KeyEnum::Simple(
|
|
||||||
v.encrypted_key,
|
|
||||||
v.nonce.expect("Simple encryption but the nonce is missing"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
"shamir" => {
|
|
||||||
*lock = KeyEnum::Shamir(
|
|
||||||
v.encrypted_key,
|
|
||||||
v.nonce.expect("Simple encryption but the nonce is missing"),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
#[cfg(not(feature = "insecure-dev-sealing"))]
|
||||||
|
"dev_only" => panic!(
|
||||||
|
r#"Database is insecure but "insecure-dev-sealing" is not enabled for this build!"#
|
||||||
|
),
|
||||||
|
"simple" => KeyEnum::Simple(SimpleSealing::new(v.encrypted_key, nonce)),
|
||||||
|
#[cfg(feature = "shamir")]
|
||||||
|
"shamir" => KeyEnum::Shamir(shamir::ShamirBucket::new(v.encrypted_key, nonce)),
|
||||||
|
#[cfg(not(feature = "shamir"))]
|
||||||
|
"shamir" => panic!(r#"Feature "shamir" is not enabled for this build!"#),
|
||||||
_ => panic!("Unknown root key type in database"),
|
_ => panic!("Unknown root key type in database"),
|
||||||
}
|
};
|
||||||
|
*lock = res;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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)]
|
#[cfg(feature = "insecure-dev-sealing")]
|
||||||
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();
|
||||||
|
|
@ -99,7 +108,7 @@ 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", None).await;
|
write_new_root_key(pool, root_key, "dev_only", Some(b"")).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn write_new_root_key(
|
async fn write_new_root_key(
|
||||||
|
|
@ -132,105 +141,194 @@ pub async fn reseal(pool: &DbPool) {
|
||||||
prepare_unseal(pool).await;
|
prepare_unseal(pool).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn sealing_status() {
|
// 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!(),
|
||||||
KeyEnum::Shamir(_, _) => todo!(),
|
// KeyEnum::Shamir(_, _) => todo!(),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub async fn provide_key(key: String) -> UnsealResult {
|
||||||
|
// First, check if we need to write-lock at all
|
||||||
|
{
|
||||||
|
let read_lock = ROOT_KEY_MAYBE.read().await;
|
||||||
|
if matches!(*read_lock, KeyEnum::MainKey(_)) {
|
||||||
|
info!("Providing keys is useless since vault is already unlocked");
|
||||||
|
return UnsealResult::AlreadyDone;
|
||||||
|
} else if matches!(*read_lock, KeyEnum::Uninitialized) {
|
||||||
|
error!("Cannot process provided key when the vault is uninitialized");
|
||||||
|
return UnsealResult::Uninitialized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A write lock is necessary.
|
||||||
|
let mut write_lock = ROOT_KEY_MAYBE.write().await;
|
||||||
|
let rk = match &mut *write_lock {
|
||||||
|
KeyEnum::MainKey(_) | KeyEnum::Uninitialized => {
|
||||||
|
unreachable!("Should have been checked above")
|
||||||
|
}
|
||||||
|
KeyEnum::Simple(simple) => simple.unseal(key).await,
|
||||||
|
#[cfg(feature = "shamir")]
|
||||||
|
KeyEnum::Shamir(shamir) => shamir.unseal(key).await,
|
||||||
|
};
|
||||||
|
let rk = match rk {
|
||||||
|
UnsealResult::DoneConfidential(rk) => rk,
|
||||||
|
UnsealResult::Done => unreachable!(),
|
||||||
|
reject_action => return reject_action,
|
||||||
|
};
|
||||||
|
*write_lock = KeyEnum::MainKey(rk);
|
||||||
|
|
||||||
|
info!("Unsealing done; Vault ready");
|
||||||
|
UnsealResult::Done
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Secret {
|
||||||
|
pub nonce: [u8; 12],
|
||||||
|
pub protected_data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Secret {
|
||||||
|
pub fn new<D, N>(data: D, nonce: N) -> Self
|
||||||
|
where
|
||||||
|
D: Into<Vec<u8>>,
|
||||||
|
N: AsRef<[u8]>,
|
||||||
|
{
|
||||||
|
let nonce_slice = nonce.as_ref();
|
||||||
|
assert!(
|
||||||
|
nonce_slice.len() == 12,
|
||||||
|
"Nonce must be exactly 12 bytes long"
|
||||||
|
);
|
||||||
|
|
||||||
|
let nonce: &[u8; 12] = nonce_slice.try_into().expect("Nonce must be 12 bytes long");
|
||||||
|
|
||||||
|
Self {
|
||||||
|
nonce: *nonce,
|
||||||
|
protected_data: data.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encrypt a secret
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if .
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error if the vault is uninitialized or an unknown error occurs.
|
||||||
|
pub async fn encrypt(data: &String) -> Result<Self, ()> {
|
||||||
|
let cipher = if let KeyEnum::MainKey(key) = &*ROOT_KEY_MAYBE.read().await {
|
||||||
|
match Aes256GcmSiv::new_from_slice(key) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to create new AesGcmSiv cipher from variable size key: {e}");
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("Cannot encrypt secret since the vault is not unsealed");
|
||||||
|
return Err(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let nonce: aes_gcm_siv::aead::generic_array::GenericArray<
|
||||||
|
u8,
|
||||||
|
<Aes256GcmSiv as aes_gcm_siv::AeadCore>::NonceSize,
|
||||||
|
> = Aes256GcmSiv::generate_nonce(&mut OsRng); // 96-bits; unique per message
|
||||||
|
let enc = match cipher.encrypt(&nonce, data.as_bytes()) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to encrypt secret with cipher: {e}");
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
debug_assert!(nonce.len() == 12, "Nonce should be exactly 12 bytes");
|
||||||
|
let nonce = match nonce.as_slice().try_into() {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Nonce should be exactly 12 bytes: {e}");
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Self {
|
||||||
|
nonce,
|
||||||
|
protected_data: enc,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn decrypt_bytes(self) -> Result<Vec<u8>, ()> {
|
||||||
|
assert!(self.nonce.len() == 12);
|
||||||
|
let cipher = match &*ROOT_KEY_MAYBE.read().await {
|
||||||
|
KeyEnum::MainKey(key) => Aes256GcmSiv::new_from_slice(key),
|
||||||
|
_ => panic!("Cannot seal secret since the vault is not unsealed"),
|
||||||
|
}
|
||||||
|
.expect("Failed to create new AesGcmSiv cipher from variable size key");
|
||||||
|
|
||||||
|
let nonce = aes_gcm_siv::aead::generic_array::GenericArray::from_slice(&self.nonce);
|
||||||
|
let enc = match cipher.decrypt(nonce, self.protected_data.as_ref()) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to decrypt secret with given nonce and cipher: {e}");
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn decrypt(self) -> Result<String, ()> {
|
||||||
|
String::from_utf8(self.decrypt_bytes().await?).map_err(|e| {
|
||||||
|
error!("Failed to parse secret as UTF8: {e}");
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn provide_key(key: String) {
|
pub enum UnsealResult {
|
||||||
let progressed_something = {
|
/// Unsealing finished, with root key hidden
|
||||||
let read_lock = ROOT_KEY_MAYBE.read().await;
|
Done,
|
||||||
match &*read_lock {
|
/// Was already unsealed, no action taken
|
||||||
KeyEnum::MainKey(_) => {
|
AlreadyDone,
|
||||||
info!("Providing keys is useless since vault is already unlocked");
|
/// Could not unseal as the vault is uninitialized
|
||||||
return;
|
Uninitialized,
|
||||||
}
|
|
||||||
KeyEnum::Simple(protected_rk, nonce) => {
|
/// Unsealing finished, returns root key
|
||||||
KeyEnum::MainKey(simple::unseal(protected_rk, key, nonce).await)
|
DoneConfidential(Vec<u8>),
|
||||||
}
|
/// Unsealing attempt has been recorded but is not sufficient
|
||||||
KeyEnum::Uninitialized => {
|
Unfinished,
|
||||||
error!("Cannot process provided key when the vault is uninitialized");
|
/// The provided or the set of previously provided portions are invalid.
|
||||||
return;
|
/// Unsealing has been reset.
|
||||||
}
|
InvalidReset,
|
||||||
KeyEnum::Shamir(protected_rk, nonce) => {
|
/// Duplicate share
|
||||||
let rk = shamir::unseal(protected_rk, key, nonce).await;
|
Duplicate,
|
||||||
match rk {
|
/// Error processing share, invalid
|
||||||
Ok(rk) => KeyEnum::MainKey(rk),
|
InvalidRejected,
|
||||||
Err(e) => {
|
}
|
||||||
match e {
|
|
||||||
vsss_rs::Error::SharingMinThreshold => {
|
pub async fn init_default(pool: &DbPool) {
|
||||||
info!("Shamir portion provided. Sharing threshold not reached.")
|
#[cfg(feature = "insecure-dev-sealing")]
|
||||||
},
|
let user_key = {
|
||||||
vsss_rs::Error::SharingLimitLessThanThreshold => unreachable!(),
|
storage::sealing::init_insecure_in_db(&pool).await;
|
||||||
vsss_rs::Error::InvalidSizeRequest => todo!(),
|
"INSECURE automatic unlock - TESTING ONLY"
|
||||||
vsss_rs::Error::SharingInvalidIdentifier => todo!(),
|
};
|
||||||
vsss_rs::Error::SharingDuplicateIdentifier => {
|
|
||||||
warn!("The supplied Shamir portion is already known. Duplication ignored.")
|
#[cfg(not(feature = "insecure-dev-sealing"))]
|
||||||
},
|
let user_key = {
|
||||||
vsss_rs::Error::SharingMaxRequest => todo!(),
|
#[cfg(not(feature = "shamir"))]
|
||||||
vsss_rs::Error::InvalidShare => todo!(),
|
{
|
||||||
vsss_rs::Error::InvalidGenerator(_) => todo!(),
|
simple::init_simple(&pool).await
|
||||||
vsss_rs::Error::InvalidSecret => todo!(),
|
}
|
||||||
vsss_rs::Error::InvalidShareConversion => todo!(),
|
|
||||||
vsss_rs::Error::NotImplemented => todo!(),
|
#[cfg(feature = "shamir")]
|
||||||
vsss_rs::Error::InvalidShareElement => todo!(),
|
{
|
||||||
vsss_rs::Error::NotEnoughShareIdentifiers => todo!(),
|
shamir::init_shamir(&pool, 2, 5).await
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// todo!()
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
info!(
|
let success = prepare_unseal(&pool).await;
|
||||||
"Progress on unsealing: {}",
|
warn!("New sealing password generated: {user_key:?}");
|
||||||
match progressed_something {
|
assert!(
|
||||||
KeyEnum::MainKey(_) => "done and ready",
|
success,
|
||||||
_ => "not yet ready",
|
"Vault ought to have been initialized just now but it is not."
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut write_lock = ROOT_KEY_MAYBE.write().await;
|
|
||||||
*write_lock = progressed_something;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn encrypt(data: &String) -> ([u8; 12], Vec<u8>) {
|
|
||||||
let cipher = if let KeyEnum::MainKey(key) = &*ROOT_KEY_MAYBE.read().await {
|
|
||||||
Aes256GcmSiv::new_from_slice(key)
|
|
||||||
} else {
|
|
||||||
panic!("Cannot seal secret since the vault is not unsealed")
|
|
||||||
}
|
|
||||||
.expect("Failed to create new AesGcmSiv cipher from variable size key");
|
|
||||||
|
|
||||||
let nonce: aes_gcm_siv::aead::generic_array::GenericArray<
|
|
||||||
u8,
|
|
||||||
<Aes256GcmSiv as aes_gcm_siv::AeadCore>::NonceSize,
|
|
||||||
> = Aes256GcmSiv::generate_nonce(&mut OsRng); // 96-bits; unique per message
|
|
||||||
let enc = cipher.encrypt(&nonce, data.as_bytes()).unwrap();
|
|
||||||
debug_assert!(nonce.len() == 12);
|
|
||||||
let nonce = nonce
|
|
||||||
.as_slice()
|
|
||||||
.try_into()
|
|
||||||
.expect("Nonce should be exactly 12 bytes");
|
|
||||||
(nonce, enc)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn decrypt(nonce: Vec<u8>, data: impl AsRef<[u8]>) -> String {
|
|
||||||
assert!(nonce.len() == 12);
|
|
||||||
let cipher = match &*ROOT_KEY_MAYBE.read().await {
|
|
||||||
KeyEnum::MainKey(key) => Aes256GcmSiv::new_from_slice(key),
|
|
||||||
_ => panic!("Cannot seal secret since the vault is not unsealed"),
|
|
||||||
}
|
|
||||||
.expect("Failed to create new AesGcmSiv cipher from variable size key");
|
|
||||||
|
|
||||||
let nonce = aes_gcm_siv::aead::generic_array::GenericArray::from_slice(&nonce);
|
|
||||||
let enc = cipher.decrypt(nonce, data.as_ref()).unwrap();
|
|
||||||
String::from_utf8(enc).expect("Failed to parse as utf8")
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,39 +3,117 @@ use aes_gcm_siv::{
|
||||||
aead::{Aead, OsRng, generic_array::GenericArray},
|
aead::{Aead, OsRng, generic_array::GenericArray},
|
||||||
};
|
};
|
||||||
use base64::{Engine, prelude::BASE64_STANDARD};
|
use base64::{Engine, prelude::BASE64_STANDARD};
|
||||||
use log::warn;
|
use log::{error, info, warn};
|
||||||
use p256::{NonZeroScalar, Scalar, SecretKey};
|
use p256::{NonZeroScalar, Scalar, SecretKey};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tokio::sync::RwLock;
|
|
||||||
use vsss_rs::{
|
use vsss_rs::{
|
||||||
DefaultShare, Error as VsssErr, IdentifierPrimeField, ReadableShareSet, ShareElement, ValuePrimeField
|
DefaultShare, Error as VsssErr, IdentifierPrimeField, ReadableShareSet, ShareElement,
|
||||||
|
ValuePrimeField,
|
||||||
};
|
};
|
||||||
use zeroize::ZeroizeOnDrop;
|
use zeroize::ZeroizeOnDrop;
|
||||||
|
|
||||||
use crate::DbPool;
|
use crate::DbPool;
|
||||||
|
|
||||||
use super::write_new_root_key;
|
use super::{write_new_root_key, Sealing, UnsealResult};
|
||||||
|
|
||||||
type P256Share = DefaultShare<IdentifierPrimeField<Scalar>, IdentifierPrimeField<Scalar>>;
|
type P256Share = DefaultShare<IdentifierPrimeField<Scalar>, IdentifierPrimeField<Scalar>>;
|
||||||
|
|
||||||
static PORTIONS: RwLock<Vec<ShamirPortion>> = RwLock::const_new(Vec::new());
|
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, ZeroizeOnDrop)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, ZeroizeOnDrop)]
|
||||||
/// Differs from [P256Share] by containing Strings
|
/// Differs from [P256Share] by containing Strings
|
||||||
pub struct ShamirPortion {
|
struct ShamirPortion {
|
||||||
#[serde(rename = "i")]
|
#[serde(rename = "i")]
|
||||||
pub identifier: Vec<u8>,
|
pub identifier: Vec<u8>,
|
||||||
#[serde(rename = "v")]
|
#[serde(rename = "v")]
|
||||||
pub value: Vec<u8>,
|
pub value: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
pub struct ShamirBucket {
|
||||||
|
portions: Vec<ShamirPortion>,
|
||||||
|
protected_rk: Vec<u8>,
|
||||||
|
nonce: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sealing for ShamirBucket {
|
||||||
|
fn new(protected_rk: Vec<u8>, nonce: Vec<u8>) -> Self {
|
||||||
|
Self {
|
||||||
|
portions: Vec::with_capacity(2),
|
||||||
|
protected_rk,
|
||||||
|
nonce,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn unseal(&mut self, key: String) -> UnsealResult {
|
||||||
|
let key = match BASE64_STANDARD.decode(key) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Portion could not be decoded: {e}");
|
||||||
|
return UnsealResult::InvalidRejected;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let key_portion: ShamirPortion = match serde_json::from_slice(&key) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
info!("Portion could not be parsed: {e}");
|
||||||
|
return UnsealResult::InvalidRejected;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.portions.contains(&key_portion) {
|
||||||
|
warn!("The supplied Shamir portion is already known. Duplication ignored.");
|
||||||
|
return UnsealResult::Duplicate;
|
||||||
|
}
|
||||||
|
self.portions.push(key_portion);
|
||||||
|
|
||||||
|
let abc = match join_keys(&self.portions) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
return match e {
|
||||||
|
VsssErr::SharingMinThreshold => {
|
||||||
|
info!("Shamir portion provided. Sharing threshold not reached.");
|
||||||
|
UnsealResult::Unfinished
|
||||||
|
},
|
||||||
|
VsssErr::SharingDuplicateIdentifier => unreachable!("Addition of duplicate keys should have been prevented by not recording them"),
|
||||||
|
e => {
|
||||||
|
error!("Unknown error occurred upon joining keys {e:?}");
|
||||||
|
unreachable!()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.to_bytes();
|
||||||
|
|
||||||
|
let cipher = match Aes256GcmSiv::new_from_slice(&abc) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
info!("Cipher could not be created from slice: {e}");
|
||||||
|
return UnsealResult::InvalidRejected;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
debug_assert_eq!(self.nonce.len(), 12);
|
||||||
|
let nonce = aes_gcm_siv::aead::generic_array::GenericArray::from_slice(&self.nonce);
|
||||||
|
let root_key = cipher.decrypt(nonce, self.protected_rk.as_ref());
|
||||||
|
match root_key {
|
||||||
|
Ok(v) => UnsealResult::DoneConfidential(v),
|
||||||
|
Err(_) => {
|
||||||
|
// Err is opaque on purpose
|
||||||
|
self.portions.clear();
|
||||||
|
warn!(
|
||||||
|
"Enough shares have been provided but the set of shares is invalid. The set of shares has been reset."
|
||||||
|
);
|
||||||
|
UnsealResult::InvalidReset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Shamir Secret Sharing does not verify a portion for validity,
|
/// Shamir Secret Sharing does not verify a portion for validity,
|
||||||
/// unlike Feldman Verified Secret Sharing, which is built on Shamir.
|
/// unlike Feldman Verified Secret Sharing, which is built on Shamir.
|
||||||
/// "Validation" happens by attempting to decrypt the root key.
|
/// "Validation" happens by attempting to decrypt the root key.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// List of key portions
|
/// List of encoded key portions
|
||||||
pub async fn init_shamir(pool: &DbPool, threshold: usize, limit: usize) -> Vec<String> {
|
pub async fn init_shamir(pool: &DbPool, threshold: usize, limit: usize) -> Vec<String> {
|
||||||
let root_key = Aes256GcmSiv::generate_key(&mut OsRng);
|
let root_key = Aes256GcmSiv::generate_key(&mut OsRng);
|
||||||
let nonce: GenericArray<u8, <Aes256GcmSiv as AeadCore>::NonceSize> =
|
let nonce: GenericArray<u8, <Aes256GcmSiv as AeadCore>::NonceSize> =
|
||||||
|
|
@ -60,35 +138,6 @@ pub async fn init_shamir(pool: &DbPool, threshold: usize, limit: usize) -> Vec<S
|
||||||
portions
|
portions
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn unseal(protected_rk: &Vec<u8>, key: String, nonce: &[u8]) -> Result<Vec<u8>, VsssErr> {
|
|
||||||
let key = BASE64_STANDARD.decode(key).unwrap();
|
|
||||||
let key_portion: ShamirPortion = serde_json::from_slice(&key).unwrap();
|
|
||||||
|
|
||||||
let mut portions = PORTIONS.write().await;
|
|
||||||
if portions.contains(&key_portion) {
|
|
||||||
return Err(VsssErr::SharingDuplicateIdentifier);
|
|
||||||
}
|
|
||||||
portions.push(key_portion);
|
|
||||||
|
|
||||||
let abc = match join_keys(&*portions) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
}
|
|
||||||
.to_bytes();
|
|
||||||
|
|
||||||
let cipher = Aes256GcmSiv::new_from_slice(&abc).unwrap();
|
|
||||||
debug_assert_eq!(nonce.len(), 12);
|
|
||||||
let nonce = aes_gcm_siv::aead::generic_array::GenericArray::from_slice(nonce);
|
|
||||||
let root_key = cipher.decrypt(nonce, protected_rk.as_ref());
|
|
||||||
match root_key {
|
|
||||||
Ok(v) => return Ok(v),
|
|
||||||
Err(_) => {
|
|
||||||
// Err is opaque on purpose
|
|
||||||
todo!("Error: While the threshold is reached, at least one portion is invalid. TODO: reset advised")
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a Vec of Base64 encoded JSON-wrapped identifier-value pairs
|
/// Returns a Vec of Base64 encoded JSON-wrapped identifier-value pairs
|
||||||
fn share_keys(
|
fn share_keys(
|
||||||
mut osrng: &mut OsRng,
|
mut osrng: &mut OsRng,
|
||||||
|
|
@ -122,18 +171,31 @@ fn share_keys(
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn join_keys(shares: &Vec<ShamirPortion>) -> Result<SecretKey, vsss_rs::Error> {
|
fn join_keys(shares: &[ShamirPortion]) -> Result<SecretKey, vsss_rs::Error> {
|
||||||
let shares: Vec<P256Share> = shares
|
let shares: Vec<P256Share> = shares
|
||||||
.iter()
|
.iter()
|
||||||
.map(|portion| P256Share {
|
.map(|portion| {
|
||||||
identifier: IdentifierPrimeField::<Scalar>::from_slice(&portion.identifier).unwrap(),
|
let identifier = IdentifierPrimeField::<Scalar>::from_slice(&portion.identifier)
|
||||||
value: ValuePrimeField::<Scalar>::from_slice(&portion.value).unwrap(),
|
.map_err(|e| {
|
||||||
|
info!("Portion could not be converted to IdentifierPrimeField: {e}");
|
||||||
|
VsssErr::InvalidShare
|
||||||
|
})?;
|
||||||
|
let value = ValuePrimeField::<Scalar>::from_slice(&portion.value).map_err(|e| {
|
||||||
|
info!("Portion could not be converted to ValuePrimeField: {e}");
|
||||||
|
VsssErr::InvalidShare
|
||||||
|
})?;
|
||||||
|
Ok(P256Share { identifier, value })
|
||||||
})
|
})
|
||||||
.collect();
|
.collect::<Result<_, VsssErr>>()?;
|
||||||
|
|
||||||
let scalar = shares.combine()?;
|
let scalar = shares.combine()?;
|
||||||
let nzs_dup = NonZeroScalar::from_repr(scalar.0.into()).unwrap();
|
// A little suboptimal thanks to CtOption
|
||||||
let sk_dup = SecretKey::from(nzs_dup);
|
let nzs = match NonZeroScalar::from_repr(scalar.0.into()).into_option() {
|
||||||
Ok(sk_dup)
|
Some(v) => v,
|
||||||
|
None => return Err(VsssErr::InvalidShare),
|
||||||
|
};
|
||||||
|
let sk = SecretKey::from(nzs);
|
||||||
|
Ok(sk)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -142,11 +204,20 @@ fn split_and_join() {
|
||||||
let root_key = root_key.as_slice().to_owned();
|
let root_key = root_key.as_slice().to_owned();
|
||||||
let kps = share_keys(&mut OsRng, 2, 5, &root_key);
|
let kps = share_keys(&mut OsRng, 2, 5, &root_key);
|
||||||
|
|
||||||
let kps: Vec<_> = kps.iter().map(|f| {
|
let kps: Vec<_> = kps
|
||||||
let b = BASE64_STANDARD.decode(f).unwrap();
|
.iter()
|
||||||
serde_json::from_slice(&b).unwrap()
|
.map(|f| {
|
||||||
}).collect();
|
let b = BASE64_STANDARD
|
||||||
let k = join_keys(&kps).unwrap();
|
.decode(f)
|
||||||
|
.expect("A portion could not be decoded from BASE64");
|
||||||
|
serde_json::from_slice(&b).expect("A portion could not be parsed as a key pair")
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let k = join_keys(&kps).expect("Error on joining key pairs");
|
||||||
|
|
||||||
assert_eq!(root_key, k.to_bytes().as_slice());
|
assert_eq!(
|
||||||
|
root_key,
|
||||||
|
k.to_bytes().as_slice(),
|
||||||
|
"Original key and re-combined key from shares are not equal"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,27 @@ use base64::{Engine, prelude::BASE64_STANDARD};
|
||||||
|
|
||||||
use crate::DbPool;
|
use crate::DbPool;
|
||||||
|
|
||||||
use super::write_new_root_key;
|
use super::{write_new_root_key, Sealing, UnsealResult};
|
||||||
|
|
||||||
|
/// Pair of protected root key and nonce
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
pub struct SimpleSealing(Vec<u8>, Vec<u8>);
|
||||||
|
|
||||||
|
impl Sealing for SimpleSealing {
|
||||||
|
fn new(protected_rk: Vec<u8>, nonce: Vec<u8>) -> Self {
|
||||||
|
Self(protected_rk, nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn unseal(&mut self, key: String) -> UnsealResult {
|
||||||
|
let key = BASE64_STANDARD.decode(key).unwrap();
|
||||||
|
let cipher = Aes256GcmSiv::new_from_slice(&key).unwrap();
|
||||||
|
debug_assert_eq!(self.1.len(), 12);
|
||||||
|
let nonce = aes_gcm_siv::aead::generic_array::GenericArray::from_slice(self.1.as_slice());
|
||||||
|
UnsealResult::DoneConfidential(cipher.decrypt(nonce, self.0.as_ref()).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
pub async fn init_simple(pool: &DbPool) -> String {
|
pub async fn init_simple(pool: &DbPool) -> String {
|
||||||
let root_key = Aes256GcmSiv::generate_key(&mut OsRng);
|
let root_key = Aes256GcmSiv::generate_key(&mut OsRng);
|
||||||
let nonce: GenericArray<u8, <Aes256GcmSiv as AeadCore>::NonceSize> =
|
let nonce: GenericArray<u8, <Aes256GcmSiv as AeadCore>::NonceSize> =
|
||||||
|
|
@ -26,11 +45,3 @@ pub async fn init_simple(pool: &DbPool) -> String {
|
||||||
write_new_root_key(pool, protected_rk, "simple", Some(nonce.as_slice())).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, nonce: &[u8]) -> Vec<u8> {
|
|
||||||
let key = BASE64_STANDARD.decode(key).unwrap();
|
|
||||||
let cipher = Aes256GcmSiv::new_from_slice(&key).unwrap();
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue