use std::collections::BTreeMap;
use std::sync::Arc;
use std::time::SystemTime;
use async_trait::async_trait;
use corium_crypt::{KeyId, Keyring, SecretKey};
use corium_log::{LogCipher, LogError};
use corium_store::{
BlobId, BlobIdStream, BlobStore, EncryptedBlobStore, KeyManifest, RootStore, StoreError,
};
use crate::backend::NodeStore;
pub enum DbStore {
Plain(Arc<NodeStore>),
Encrypted(EncryptedBlobStore<Arc<NodeStore>>),
}
impl DbStore {
#[must_use]
pub fn raw(&self) -> &Arc<NodeStore> {
match self {
Self::Plain(store) => store,
Self::Encrypted(store) => store.inner(),
}
}
#[must_use]
pub fn storage_epoch(&self) -> Option<u32> {
match self {
Self::Plain(_) => None,
Self::Encrypted(store) => Some(store.current_epoch()),
}
}
}
#[async_trait]
impl BlobStore for DbStore {
async fn put(&self, bytes: &[u8]) -> Result<BlobId, StoreError> {
match self {
Self::Plain(store) => store.put(bytes).await,
Self::Encrypted(store) => store.put(bytes).await,
}
}
async fn get(&self, id: &BlobId) -> Result<Option<Vec<u8>>, StoreError> {
match self {
Self::Plain(store) => store.get(id).await,
Self::Encrypted(store) => store.get(id).await,
}
}
async fn contains(&self, id: &BlobId) -> Result<bool, StoreError> {
match self {
Self::Plain(store) => store.contains(id).await,
Self::Encrypted(store) => store.contains(id).await,
}
}
async fn put_if_absent(&self, bytes: &[u8]) -> Result<BlobId, StoreError> {
match self {
Self::Plain(store) => store.put_if_absent(bytes).await,
Self::Encrypted(store) => store.put_if_absent(bytes).await,
}
}
async fn delete(&self, id: &BlobId) -> Result<(), StoreError> {
self.raw().delete(id).await
}
async fn list(&self) -> Result<BlobIdStream, StoreError> {
self.raw().list().await
}
async fn modified_at(&self, id: &BlobId) -> Result<Option<SystemTime>, StoreError> {
self.raw().modified_at(id).await
}
}
#[async_trait]
impl RootStore for DbStore {
async fn get_root(&self, name: &str) -> Result<Option<Vec<u8>>, StoreError> {
self.raw().get_root(name).await
}
async fn cas_root(
&self,
name: &str,
expected: Option<&[u8]>,
new: &[u8],
) -> Result<(), StoreError> {
self.raw().cas_root(name, expected, new).await
}
async fn delete_root(&self, name: &str) -> Result<(), StoreError> {
self.raw().delete_root(name).await
}
async fn list_roots(&self, prefix: &str) -> Result<Vec<String>, StoreError> {
self.raw().list_roots(prefix).await
}
}
pub struct DbCrypto {
pub store: Arc<DbStore>,
pub cipher: Option<Arc<LogCipher>>,
}
#[derive(Debug, thiserror::Error)]
pub enum KeyWiringError {
#[error(
"database {db:?} is encrypted under key {kek}; \
start this process with --storage-key naming it"
)]
NoKeyring {
db: String,
kek: KeyId,
},
#[error("database {0:?} has a key manifest with no active storage-key epoch")]
NoActiveEpoch(String),
#[error(transparent)]
Store(#[from] StoreError),
#[error(transparent)]
Log(#[from] LogError),
}
#[must_use]
pub fn log_lineage(db: &str) -> Vec<u8> {
db.as_bytes().to_vec()
}
pub async fn resolve_db_crypto(
db: &str,
store: &Arc<NodeStore>,
manifest: Option<&KeyManifest>,
keyring: Option<&Arc<dyn Keyring>>,
) -> Result<DbCrypto, KeyWiringError> {
let Some(manifest) = manifest.filter(|manifest| !manifest.storage_keys.is_empty()) else {
return Ok(DbCrypto {
store: Arc::new(DbStore::Plain(Arc::clone(store))),
cipher: None,
});
};
let Some(keyring) = keyring else {
return Err(KeyWiringError::NoKeyring {
db: db.to_owned(),
kek: manifest.kek.clone(),
});
};
let (epoch, keys) = unwrap_active(db, manifest, keyring.as_ref()).await?;
Ok(DbCrypto {
store: Arc::new(DbStore::Encrypted(EncryptedBlobStore::new(
Arc::clone(store),
epoch,
keys.clone(),
)?)),
cipher: Some(Arc::new(LogCipher::new(log_lineage(db), epoch, keys)?)),
})
}
pub async fn reload_db_crypto(
db: &str,
crypto: &DbCrypto,
manifest: &KeyManifest,
keyring: Option<&Arc<dyn Keyring>>,
) -> Result<Arc<DbStore>, KeyWiringError> {
let Some(keyring) = keyring else {
return Err(KeyWiringError::NoKeyring {
db: db.to_owned(),
kek: manifest.kek.clone(),
});
};
let (epoch, keys) = unwrap_active(db, manifest, keyring.as_ref()).await?;
let store = Arc::new(DbStore::Encrypted(EncryptedBlobStore::new(
Arc::clone(crypto.store.raw()),
epoch,
keys.clone(),
)?));
if let Some(cipher) = &crypto.cipher {
cipher.install(epoch, keys)?;
}
Ok(store)
}
async fn unwrap_active(
db: &str,
manifest: &KeyManifest,
keyring: &dyn Keyring,
) -> Result<(u32, BTreeMap<u32, SecretKey>), KeyWiringError> {
let epoch = manifest
.active_storage_epoch()
.ok_or_else(|| KeyWiringError::NoActiveEpoch(db.to_owned()))?;
Ok((epoch, manifest.unwrap_storage_keys(keyring).await?))
}