#![doc = include_str!("../README.md")]
#![forbid(unsafe_code)]
pub mod audit;
pub mod notify;
pub mod record;
pub mod store;
mod handlers;
mod outer;
use std::sync::Arc;
use cratefield_core::{
Config, ConfigError, DataKind, Disposition, Migrations, Module, ModuleContext, PersonalDataSet,
Port, SqlMigration,
};
use cratefield_kms::Kms;
pub use crate::notify::{
MailNotifier, NoopNotifier, Notice, NotifyError, UnlockNotifier,
conformance as notifier_conformance,
};
pub const MODULE_NAME: &str = "sealed";
pub const MAX_RECORD_BYTES: usize = 1024 * 1024;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum SealedError {
#[error("{0}")]
BadInput(String),
#[error("{0}")]
BadWrap(String),
#[error("{0}")]
WeakParams(String),
#[error("{0}")]
ChainBroken(String),
#[error("{0}")]
Tampered(String),
#[error("{0}")]
Internal(String),
}
impl From<cratefield_core::DbError> for SealedError {
fn from(err: cratefield_core::DbError) -> Self {
Self::Internal(format!("a database statement failed: {err}"))
}
}
pub struct Sealed {
pub(crate) kms: Arc<dyn Kms>,
pub(crate) notifier: Arc<dyn UnlockNotifier>,
}
impl Sealed {
#[must_use]
pub fn new(kms: Arc<dyn Kms>, notifier: Arc<dyn UnlockNotifier>) -> Self {
Self { kms, notifier }
}
}
const MIGRATION_INIT: SqlMigration = SqlMigration::new(
"0001",
"init",
include_str!("../migrations/sqlite/0001_init.sql"),
);
const MIGRATION_INIT_PG: SqlMigration = SqlMigration::new(
"0001",
"init",
include_str!("../migrations/postgres/0001_init.sql"),
);
const MIGRATION_REVISION_BIGINT: SqlMigration = SqlMigration::new(
"0002",
"revision_bigint",
include_str!("../migrations/sqlite/0002_revision_bigint.sql"),
);
const MIGRATION_REVISION_BIGINT_PG: SqlMigration = SqlMigration::new(
"0002",
"revision_bigint",
include_str!("../migrations/postgres/0002_revision_bigint.sql"),
);
impl Module for Sealed {
fn name(&self) -> &'static str {
MODULE_NAME
}
fn version(&self) -> &'static str {
env!("CARGO_PKG_VERSION")
}
fn requires(&self) -> &'static [Port] {
&[Port::Db, Port::Auth, Port::Blob]
}
fn optional(&self) -> &'static [Port] {
&[Port::RateLimiter, Port::Clock]
}
fn tables(&self) -> &'static [&'static str] {
&["sealed_blobs", "sealed_deks", "sealed_audit"]
}
fn personal_data(&self) -> &'static [PersonalDataSet] {
const SETS: &[PersonalDataSet] = &[
PersonalDataSet {
table: "sealed_blobs",
subject: "subject",
kind: DataKind::Content,
disposition: Disposition::Erase,
description: "Blobs you sealed in your browser: we hold the encrypted \
bytes and the wrapped keys for them — never the plaintext, and \
never the key that opens it. An export lists the metadata, the \
wrap set and our own encrypted copy of the body; the copy you \
can open exists only in your browser. Erasing a blob deletes it \
and the internal key that unlocks our encrypted copy, after \
which nothing on our side can open what you stored.",
redacted: &[],
subject_via: None,
},
PersonalDataSet {
table: "sealed_deks",
subject: "subject",
kind: DataKind::Identifier,
disposition: Disposition::Erase,
description: "The internal, wrapped key that unlocks our encrypted copy \
of each of your blobs, kept in its own table so erasure deletes it \
first — the step that makes every remaining copy unreadable. It is \
key material, so an export lists it without copying it.",
redacted: &["wrapped_dek"],
subject_via: None,
},
PersonalDataSet {
table: "sealed_audit",
subject: "subject",
kind: DataKind::Usage,
disposition: Disposition::Retain(
"The access chain is append-only and hash-linked: its value is that \
no one — including an operator — can remove one person's rows \
without breaking the proof that nobody altered anyone else's. A \
row holds your account id, the blob's id, the action and the time, \
and never any content, because the server never has any.",
),
description: "Which of your blobs were stored, opened, edited, rotated or \
erased, and when.",
redacted: &[],
subject_via: None,
},
];
SETS
}
fn migrations(&self) -> Migrations {
const MIGRATIONS: [SqlMigration; 2] = [MIGRATION_INIT, MIGRATION_REVISION_BIGINT];
const MIGRATIONS_PG: [SqlMigration; 2] = [MIGRATION_INIT_PG, MIGRATION_REVISION_BIGINT_PG];
const _: () = cratefield_core::assert_migration_set(&MIGRATIONS);
Migrations {
sqlite: &MIGRATIONS,
postgres: &MIGRATIONS_PG,
}
}
fn validate_config(&self, _cfg: &dyn Config) -> Result<(), ConfigError> {
Ok(())
}
fn max_body_bytes(&self, _cfg: &dyn Config) -> usize {
MAX_RECORD_BYTES
}
fn router(&self, ctx: ModuleContext) -> axum::Router {
handlers::router(self, ctx)
}
}