use super::WriterCore;
use crate::coordinate::Coordinate;
use crate::event::{EventKind, PayloadEncryption};
use crate::id::EventId;
use crate::store::keyscope::{payload_aad, scope_for};
use crate::store::StoreError;
pub(super) struct SealedPayload {
pub(super) ciphertext: Vec<u8>,
pub(super) meta: PayloadEncryption,
pub(super) needs_fence: bool,
}
impl WriterCore {
pub(super) fn seal_event_payload(
&self,
coord: &Coordinate,
kind: EventKind,
event_id: EventId,
plaintext: &[u8],
) -> Result<Option<SealedPayload>, StoreError> {
let Some(key_store) = self.runtime.key_store.as_ref() else {
return Ok(None);
};
if kind.is_reserved() {
return Ok(None);
}
let mut nonce = [0u8; 24];
getrandom::fill(&mut nonce).map_err(|error| StoreError::PayloadSealFailed {
detail: format!("nonce CSPRNG failed: {error}"),
})?;
let mut guard = key_store.lock();
let scope = scope_for(guard.granularity(), coord, kind, event_id);
let minted = guard.get(&scope).is_none();
let aad = payload_aad(coord, kind, event_id);
let ciphertext = {
let key =
guard
.get_or_create(&scope)
.map_err(|error| StoreError::PayloadSealFailed {
detail: format!("mint key: {error}"),
})?;
key.seal(&nonce, &aad, plaintext)
.map_err(|error| StoreError::PayloadSealFailed {
detail: format!("seal: {error}"),
})?
};
if minted {
guard.mark_dirty();
}
let needs_fence = guard.is_dirty();
drop(guard);
Ok(Some(SealedPayload {
meta: PayloadEncryption {
keyscope_id: scope.as_bytes().to_vec(),
nonce,
},
ciphertext,
needs_fence,
}))
}
pub(super) fn encrypt_single_payload(
&self,
coord: &Coordinate,
kind: EventKind,
event: &mut crate::event::Event<Vec<u8>>,
) -> Result<(), StoreError> {
let Some(sealed) =
self.seal_event_payload(coord, kind, event.header.event_id, &event.payload)?
else {
return Ok(());
};
if sealed.needs_fence {
self.flush_keyset_durable()?;
}
event.header.payload_size = u32::try_from(sealed.ciphertext.len())
.map_err(|_| StoreError::ser_msg("ciphertext length exceeds u32::MAX"))?;
event.payload = sealed.ciphertext;
event.header.payload_encryption = Some(sealed.meta);
Ok(())
}
pub(super) fn flush_keyset_durable(&self) -> Result<(), StoreError> {
let Some(key_store) = self.runtime.key_store.as_ref() else {
return Ok(());
};
let mut guard = key_store.lock();
guard.flush_with_fs(&self.config.data_dir, self.config.fs().as_ref())
}
}