use std::sync::Arc;
use base64::Engine as _;
use chronix_core::FieldEncryption;
use chronix_engine::segment::field_encryption::{
FieldEncryptionConfig, FieldEncryptionKey, FieldKeyProvider, StaticKeyProvider,
};
use crate::error::{DbError, Result};
#[derive(Clone)]
pub(crate) struct ResolvedFieldEncryption {
pub(crate) writer: FieldEncryptionConfig,
pub(crate) reader: Arc<dyn FieldKeyProvider>,
}
impl std::fmt::Debug for ResolvedFieldEncryption {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ResolvedFieldEncryption")
.field("columns", &"<redacted>")
.finish()
}
}
pub(crate) fn resolve(settings: &FieldEncryption) -> Result<Option<ResolvedFieldEncryption>> {
if settings.is_empty() && settings.keys.is_empty() {
return Ok(None);
}
let mut provider = StaticKeyProvider::new();
let mut keys: std::collections::BTreeMap<String, FieldEncryptionKey> =
std::collections::BTreeMap::new();
for (key_id, var) in &settings.keys {
let encoded = std::env::var(var).map_err(|_| {
DbError::InvalidRequest(format!(
"field encryption key '{key_id}' reads ${var}, which is not set — every \
declared key must resolve at startup, including one no column names yet, \
because segments written under it are unreadable without it"
))
})?;
let bytes = base64::engine::general_purpose::STANDARD
.decode(encoded.trim())
.map_err(|_| {
DbError::InvalidRequest(format!(
"field encryption key '{key_id}': ${var} is not valid base64"
))
})?;
let bytes: [u8; 32] = bytes.as_slice().try_into().map_err(|_| {
DbError::InvalidRequest(format!(
"field encryption key '{key_id}': ${var} decodes to {} bytes, and AES-256 \
needs exactly 32",
bytes.len()
))
})?;
provider.add_key(key_id.clone(), bytes);
keys.insert(
key_id.clone(),
FieldEncryptionKey::new(key_id.clone(), bytes),
);
}
let mut writer = FieldEncryptionConfig::new();
for (column, key_id) in &settings.columns {
let key = keys.get(key_id).ok_or_else(|| {
DbError::InvalidRequest(format!(
"field encryption: column '{column}' names key '{key_id}', which is not declared"
))
})?;
writer.encrypt_column(column.clone(), key.clone());
}
Ok(Some(ResolvedFieldEncryption {
writer,
reader: Arc::new(provider),
}))
}