use super::canonical::validate_envelope;
use super::error::ReplicationError;
use super::types::{ArtifactKind, MemoryMutationEnvelopeV1, SignerRole};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AllowedArtifacts {
Single(ArtifactKind),
RecoveryBootstrapAndPromotion,
}
impl AllowedArtifacts {
pub const fn allows(self, artifact: ArtifactKind) -> bool {
match self {
Self::Single(allowed) => allowed as u8 == artifact as u8,
Self::RecoveryBootstrapAndPromotion => {
matches!(artifact, ArtifactKind::Bootstrap | ArtifactKind::Promotion)
}
}
}
}
#[derive(Debug, Clone)]
pub struct TrustedKeyRecord {
pub signer_principal_id: String,
pub signer_key_version: u64,
pub public_key: [u8; 32],
pub signer_role: SignerRole,
pub allowed_artifacts: AllowedArtifacts,
pub activated_at: u64,
pub cutoff_at: u64,
pub revoked: bool,
pub home_device_id: String,
pub store_id: String,
pub namespace: String,
}
#[derive(Debug, Clone)]
pub struct TrustedKeyRegistry {
keys: Vec<TrustedKeyRecord>,
}
impl TrustedKeyRegistry {
pub fn new() -> Self {
Self { keys: Vec::new() }
}
pub fn try_admit(&mut self, record: TrustedKeyRecord) -> Result<(), ReplicationError> {
if self
.lookup(&record.signer_principal_id, record.signer_key_version)
.is_some()
{
return Err(ReplicationError::DuplicateTrustedKey {
principal_id: record.signer_principal_id,
key_version: record.signer_key_version,
});
}
self.keys.push(record);
Ok(())
}
pub fn admit(&mut self, record: TrustedKeyRecord) -> Result<(), ReplicationError> {
self.try_admit(record)
}
pub fn lookup(&self, principal_id: &str, key_version: u64) -> Option<&TrustedKeyRecord> {
self.keys
.iter()
.find(|k| k.signer_principal_id == principal_id && k.signer_key_version == key_version)
}
}
impl Default for TrustedKeyRegistry {
fn default() -> Self {
Self::new()
}
}
pub fn validate_trusted_key(
envelope: &MemoryMutationEnvelopeV1,
registry: &TrustedKeyRegistry,
) -> Result<(), ReplicationError> {
let record = registry
.lookup(&envelope.signer_principal_id, envelope.signer_key_version)
.ok_or_else(|| ReplicationError::SignerNotAdmitted {
principal_id: envelope.signer_principal_id.clone(),
key_version: envelope.signer_key_version,
})?;
if record.public_key != envelope.signer_public_key {
return Err(ReplicationError::KeyMismatch {
principal_id: envelope.signer_principal_id.clone(),
key_version: envelope.signer_key_version,
});
}
if record.signer_role != envelope.signer_role {
return Err(ReplicationError::RoleMismatch {
admitted_role: record.signer_role,
envelope_role: envelope.signer_role,
});
}
if !record.allowed_artifacts.allows(envelope.artifact_kind) {
return Err(ReplicationError::ArtifactPermissionMismatch {
admitted_artifact: envelope.artifact_kind,
envelope_artifact: envelope.artifact_kind,
});
}
if record.revoked {
return Err(ReplicationError::KeyRevoked {
principal_id: envelope.signer_principal_id.clone(),
key_version: envelope.signer_key_version,
});
}
if envelope.observed_at < record.activated_at || envelope.observed_at > record.cutoff_at {
return Err(ReplicationError::KeyLifecycleOutsideWindow {
principal_id: envelope.signer_principal_id.clone(),
key_version: envelope.signer_key_version,
observed_at: envelope.observed_at,
activated_at: record.activated_at,
cutoff_at: record.cutoff_at,
});
}
if envelope.home_device_id != record.home_device_id {
return Err(ReplicationError::KeyScopeMismatch {
principal_id: envelope.signer_principal_id.clone(),
key_version: envelope.signer_key_version,
field: "home_device_id",
envelope_value: envelope.home_device_id.clone(),
key_scope: record.home_device_id.clone(),
});
}
if envelope.store_id != record.store_id {
return Err(ReplicationError::KeyScopeMismatch {
principal_id: envelope.signer_principal_id.clone(),
key_version: envelope.signer_key_version,
field: "store_id",
envelope_value: envelope.store_id.clone(),
key_scope: record.store_id.clone(),
});
}
if envelope.namespace != record.namespace {
return Err(ReplicationError::KeyScopeMismatch {
principal_id: envelope.signer_principal_id.clone(),
key_version: envelope.signer_key_version,
field: "namespace",
envelope_value: envelope.namespace.clone(),
key_scope: record.namespace.clone(),
});
}
Ok(())
}
pub fn validate_admitted_envelope(
envelope: &MemoryMutationEnvelopeV1,
registry: &TrustedKeyRegistry,
) -> Result<(), ReplicationError> {
validate_envelope(envelope)?;
validate_trusted_key(envelope, registry)
}