use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use cellos_core::types::{OrgAuthorityCeilingManifestV1, AUTHORITY_CEILING_V1_PAYLOAD_TYPE};
use cellos_core::{
cloud_event_v1_keyset_verification_failed, cloud_event_v1_keyset_verified,
load_trust_verify_keys_file, ports::EventSink, verify_signed_trust_keyset_chain,
verify_signed_trust_keyset_envelope, SignedTrustKeysetEnvelope, TrustAnchorPublicKey,
};
use serde_json::Value;
#[derive(Debug, Clone)]
pub enum KeysetLoadOutcome {
NotConfigured,
Verified(KeysetVerifiedDetails),
Failed(KeysetVerificationFailedDetails),
}
#[derive(Debug, Clone)]
pub struct KeysetVerifiedDetails {
pub keyset_id: String,
pub payload_digest: String,
pub verified_signer_kid: String,
}
#[derive(Debug, Clone)]
pub struct KeysetVerificationFailedDetails {
pub attempted_keyset_basename: String,
pub reason: String,
}
pub fn load_trust_verify_keys_from_env(
require_trust_verify_keys: bool,
) -> Result<Arc<HashMap<String, TrustAnchorPublicKey>>, anyhow::Error> {
match std::env::var_os("CELLOS_TRUST_VERIFY_KEYS_PATH") {
None => {
if require_trust_verify_keys {
return Err(anyhow::anyhow!(
"CELLOS_REQUIRE_TRUST_VERIFY_KEYS is set but CELLOS_TRUST_VERIFY_KEYS_PATH is unset"
));
}
tracing::debug!(
target: "cellos.supervisor.trust",
"CELLOS_TRUST_VERIFY_KEYS_PATH unset; supervisor runs with empty trust keyring"
);
Ok(Arc::new(HashMap::new()))
}
Some(path_os) => {
let path = PathBuf::from(&path_os);
let keys = load_trust_verify_keys_file(&path).map_err(|e| {
anyhow::anyhow!(
"CELLOS_TRUST_VERIFY_KEYS_PATH: cannot load '{}': {e}",
path.display()
)
})?;
tracing::info!(
target: "cellos.supervisor.trust",
path = %path.display(),
kid_count = keys.len(),
"trust verifying keys loaded"
);
Ok(Arc::new(keys))
}
}
}
pub fn load_and_verify_trust_keyset_from_env(
keys: &HashMap<String, TrustAnchorPublicKey>,
require_trust_verify_keys: bool,
now: std::time::SystemTime,
) -> Result<KeysetLoadOutcome, anyhow::Error> {
if let Some(chain_path_os) = std::env::var_os("CELLOS_TRUST_KEYSET_CHAIN_PATH") {
return load_and_verify_chain_from_env(chain_path_os, keys, require_trust_verify_keys, now);
}
let Some(path_os) = std::env::var_os("CELLOS_TRUST_KEYSET_PATH") else {
return Ok(KeysetLoadOutcome::NotConfigured);
};
let path = PathBuf::from(&path_os);
let basename = path
.file_name()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_else(|| "(unknown)".to_string());
match attempt_load_and_verify(&path, keys, now) {
Ok(details) => {
tracing::info!(
target: "cellos.supervisor.trust",
keyset_id = %details.keyset_id,
payload_digest = %details.payload_digest,
verified_signer_kid = %details.verified_signer_kid,
"signed trust keyset envelope verified at supervisor startup"
);
Ok(KeysetLoadOutcome::Verified(details))
}
Err(reason_string) => {
if require_trust_verify_keys {
return Err(anyhow::anyhow!(
"CELLOS_TRUST_KEYSET_PATH: verification failed under CELLOS_REQUIRE_TRUST_VERIFY_KEYS=1: {reason_string}"
));
}
tracing::warn!(
target: "cellos.supervisor.trust",
attempted_keyset_basename = %basename,
reason = %reason_string,
"signed trust keyset envelope verification failed; continuing in degraded mode"
);
Ok(KeysetLoadOutcome::Failed(KeysetVerificationFailedDetails {
attempted_keyset_basename: basename,
reason: reason_string,
}))
}
}
}
fn load_and_verify_chain_from_env(
chain_path_os: std::ffi::OsString,
keys: &HashMap<String, TrustAnchorPublicKey>,
require_trust_verify_keys: bool,
now: std::time::SystemTime,
) -> Result<KeysetLoadOutcome, anyhow::Error> {
let raw_str = chain_path_os.to_string_lossy().into_owned();
let paths: Vec<PathBuf> = raw_str
.split([',', '\n'])
.map(str::trim)
.filter(|s| !s.is_empty())
.map(PathBuf::from)
.collect();
if paths.is_empty() {
let reason = "CELLOS_TRUST_KEYSET_CHAIN_PATH set but parsed no envelope paths".to_string();
if require_trust_verify_keys {
return Err(anyhow::anyhow!(
"CELLOS_TRUST_KEYSET_CHAIN_PATH: verification failed under CELLOS_REQUIRE_TRUST_VERIFY_KEYS=1: {reason}"
));
}
tracing::warn!(
target: "cellos.supervisor.trust",
reason = %reason,
"signed trust keyset chain verification failed; continuing in degraded mode"
);
return Ok(KeysetLoadOutcome::Failed(KeysetVerificationFailedDetails {
attempted_keyset_basename: "(empty chain)".into(),
reason,
}));
}
let head_basename = paths
.last()
.and_then(|p| p.file_name())
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_else(|| "(unknown)".to_string());
match attempt_load_and_verify_chain(&paths, keys, now) {
Ok(details) => {
tracing::info!(
target: "cellos.supervisor.trust",
envelope_count = paths.len(),
keyset_id = %details.keyset_id,
payload_digest = %details.payload_digest,
verified_signer_kid = %details.verified_signer_kid,
"verified {}-envelope chain (head digest: {})",
paths.len(),
details.payload_digest
);
Ok(KeysetLoadOutcome::Verified(details))
}
Err(reason_string) => {
if require_trust_verify_keys {
return Err(anyhow::anyhow!(
"CELLOS_TRUST_KEYSET_CHAIN_PATH: verification failed under CELLOS_REQUIRE_TRUST_VERIFY_KEYS=1: {reason_string}"
));
}
tracing::warn!(
target: "cellos.supervisor.trust",
attempted_keyset_basename = %head_basename,
envelope_count = paths.len(),
reason = %reason_string,
"signed trust keyset chain verification failed; continuing in degraded mode"
);
Ok(KeysetLoadOutcome::Failed(KeysetVerificationFailedDetails {
attempted_keyset_basename: head_basename,
reason: reason_string,
}))
}
}
}
fn attempt_load_and_verify_chain(
paths: &[PathBuf],
keys: &HashMap<String, TrustAnchorPublicKey>,
now: std::time::SystemTime,
) -> Result<KeysetVerifiedDetails, String> {
let mut chain: Vec<SignedTrustKeysetEnvelope> = Vec::with_capacity(paths.len());
for path in paths {
let raw =
read_envelope_file(path).map_err(|e| format!("cannot read {}: {e}", path.display()))?;
let envelope: SignedTrustKeysetEnvelope = serde_json::from_str(&raw)
.map_err(|e| format!("JSON parse error in {}: {e}", path.display()))?;
chain.push(envelope);
}
let head_payload_bytes =
verify_signed_trust_keyset_chain(&chain, keys, now).map_err(|e| format!("{e}"))?;
let head_envelope = chain.last().expect("chain non-empty");
let verified_signer_kid =
pick_verified_signer_kid(head_envelope, &head_payload_bytes, keys, now)
.unwrap_or_else(|| "(unknown)".to_string());
let keyset_id =
decode_inner_keyset_id(&head_payload_bytes).unwrap_or_else(|| "(unknown)".into());
Ok(KeysetVerifiedDetails {
keyset_id,
payload_digest: head_envelope.payload_digest.clone(),
verified_signer_kid,
})
}
fn attempt_load_and_verify(
path: &Path,
keys: &HashMap<String, TrustAnchorPublicKey>,
now: std::time::SystemTime,
) -> Result<KeysetVerifiedDetails, String> {
let raw =
read_envelope_file(path).map_err(|e| format!("cannot read {}: {e}", path.display()))?;
let envelope: SignedTrustKeysetEnvelope = serde_json::from_str(&raw)
.map_err(|e| format!("JSON parse error in {}: {e}", path.display()))?;
let payload_bytes =
verify_signed_trust_keyset_envelope(&envelope, keys, now).map_err(|e| format!("{e}"))?;
let verified_signer_kid = pick_verified_signer_kid(&envelope, &payload_bytes, keys, now)
.unwrap_or_else(|| "(unknown)".to_string());
let keyset_id = decode_inner_keyset_id(&payload_bytes).unwrap_or_else(|| "(unknown)".into());
Ok(KeysetVerifiedDetails {
keyset_id,
payload_digest: envelope.payload_digest.clone(),
verified_signer_kid,
})
}
fn pick_verified_signer_kid(
envelope: &SignedTrustKeysetEnvelope,
payload_bytes: &[u8],
keys: &HashMap<String, TrustAnchorPublicKey>,
now: std::time::SystemTime,
) -> Option<String> {
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine as _;
use chrono::DateTime;
for sig in &envelope.signatures {
if sig.algorithm != "ed25519" {
continue;
}
let Some(verifying_key) = keys.get(&sig.signer_kid) else {
continue;
};
if !window_contains(now, sig.not_before.as_deref(), sig.not_after.as_deref()) {
continue;
}
let sig_b64 = sig.signature.trim_end_matches('=');
let Ok(sig_bytes) = URL_SAFE_NO_PAD.decode(sig_b64) else {
continue;
};
if sig_bytes.len() != 64 {
continue;
}
if cellos_core::provider()
.verify_ed25519(verifying_key.as_bytes(), payload_bytes, &sig_bytes)
.is_ok()
{
return Some(sig.signer_kid.clone());
}
}
let _ = DateTime::<chrono::Utc>::from_timestamp(0, 0);
None
}
fn window_contains(
now: std::time::SystemTime,
not_before: Option<&str>,
not_after: Option<&str>,
) -> bool {
use chrono::DateTime;
let now_chrono: DateTime<chrono::Utc> = now.into();
if let Some(nb) = not_before {
match DateTime::parse_from_rfc3339(nb) {
Ok(t) => {
if now_chrono < t.with_timezone(&chrono::Utc) {
return false;
}
}
Err(_) => return false,
}
}
if let Some(na) = not_after {
match DateTime::parse_from_rfc3339(na) {
Ok(t) => {
if now_chrono > t.with_timezone(&chrono::Utc) {
return false;
}
}
Err(_) => return false,
}
}
true
}
fn decode_inner_keyset_id(payload_bytes: &[u8]) -> Option<String> {
let v: Value = serde_json::from_slice(payload_bytes).ok()?;
v.as_object()?.get("keysetId")?.as_str().map(String::from)
}
fn read_envelope_file(path: &Path) -> Result<String, std::io::Error> {
#[cfg(unix)]
{
use std::io::Read;
use std::os::unix::fs::OpenOptionsExt;
let mut opts = std::fs::OpenOptions::new();
opts.read(true);
opts.custom_flags(libc::O_RDONLY | libc::O_NOFOLLOW);
let mut file = opts.open(path)?;
let mut buf = String::new();
file.read_to_string(&mut buf)?;
Ok(buf)
}
#[cfg(not(unix))]
{
#[cfg(windows)]
cellos_core::trust_keys::reject_reparse_point(path)
.map_err(|e| std::io::Error::other(e.to_string()))?;
std::fs::read_to_string(path)
}
}
pub async fn emit_keyset_outcome(
outcome: &KeysetLoadOutcome,
event_sink: &Arc<dyn EventSink>,
jsonl_sink: Option<&Arc<dyn EventSink>>,
now: chrono::DateTime<chrono::Utc>,
) -> Result<(), anyhow::Error> {
let timestamp = now.to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
match outcome {
KeysetLoadOutcome::NotConfigured => Ok(()),
KeysetLoadOutcome::Verified(details) => {
let envelope = cloud_event_v1_keyset_verified(
"cellos-supervisor",
×tamp,
&details.keyset_id,
&details.payload_digest,
&details.verified_signer_kid,
×tamp,
None,
)?;
event_sink
.emit(&envelope)
.await
.map_err(|e| anyhow::anyhow!("emit keyset_verified: {e}"))?;
if let Some(secondary) = jsonl_sink {
secondary
.emit(&envelope)
.await
.map_err(|e| anyhow::anyhow!("emit keyset_verified to jsonl sink: {e}"))?;
}
Ok(())
}
KeysetLoadOutcome::Failed(details) => {
let envelope = cloud_event_v1_keyset_verification_failed(
"cellos-supervisor",
×tamp,
&details.attempted_keyset_basename,
&details.reason,
×tamp,
None,
)?;
event_sink
.emit(&envelope)
.await
.map_err(|e| anyhow::anyhow!("emit keyset_verification_failed: {e}"))?;
if let Some(secondary) = jsonl_sink {
secondary.emit(&envelope).await.map_err(|e| {
anyhow::anyhow!("emit keyset_verification_failed to jsonl sink: {e}")
})?;
}
Ok(())
}
}
}
#[derive(Debug, Clone)]
pub enum CeilingLoadOutcome {
NotConfigured,
Verified(CeilingVerifiedDetails),
}
#[derive(Debug, Clone)]
pub struct CeilingVerifiedDetails {
pub manifest: OrgAuthorityCeilingManifestV1,
pub manifest_digest: String,
pub verified_signer_kid: String,
}
pub fn load_and_verify_ceiling_from_env(
keys: &HashMap<String, TrustAnchorPublicKey>,
require_authority_ceiling: bool,
now: std::time::SystemTime,
) -> Result<CeilingLoadOutcome, anyhow::Error> {
let Some(path_os) = std::env::var_os("CELLOS_AUTHORITY_CEILING_PATH") else {
if require_authority_ceiling {
return Err(anyhow::anyhow!(
"CELLOS_REQUIRE_AUTHORITY_CEILING is set (or hardened profile) but \
CELLOS_AUTHORITY_CEILING_PATH is unset"
));
}
tracing::warn!(
target: "cellos.supervisor.ceiling",
"CELLOS_AUTHORITY_CEILING_PATH unset and ceiling not required; admission proceeds \
WITHOUT an org-root authority ceiling (labeled fail-open path)"
);
return Ok(CeilingLoadOutcome::NotConfigured);
};
let path = PathBuf::from(&path_os);
let details = attempt_load_and_verify_ceiling(&path, keys, now)
.map_err(|reason| anyhow::anyhow!("CELLOS_AUTHORITY_CEILING_PATH: {reason}"))?;
tracing::info!(
target: "cellos.supervisor.ceiling",
ceiling_id = %details.manifest.ceiling_id,
ceiling_epoch = details.manifest.ceiling_epoch,
manifest_digest = %details.manifest_digest,
verified_signer_kid = %details.verified_signer_kid,
ceilings = details.manifest.ceilings.len(),
"org-root authority-ceiling manifest verified at supervisor startup"
);
Ok(CeilingLoadOutcome::Verified(details))
}
fn attempt_load_and_verify_ceiling(
path: &Path,
keys: &HashMap<String, TrustAnchorPublicKey>,
now: std::time::SystemTime,
) -> Result<CeilingVerifiedDetails, String> {
let raw =
read_envelope_file(path).map_err(|e| format!("cannot read {}: {e}", path.display()))?;
let envelope: SignedTrustKeysetEnvelope = serde_json::from_str(&raw)
.map_err(|e| format!("JSON parse error in {}: {e}", path.display()))?;
let payload_bytes =
verify_signed_trust_keyset_envelope(&envelope, keys, now).map_err(|e| format!("{e}"))?;
if envelope.payload_type != AUTHORITY_CEILING_V1_PAYLOAD_TYPE {
return Err(format!(
"verified envelope payloadType is '{}', expected ceiling sibling '{}' \
(a validly-signed trust-keyset is not a ceiling)",
envelope.payload_type, AUTHORITY_CEILING_V1_PAYLOAD_TYPE
));
}
let manifest: OrgAuthorityCeilingManifestV1 = serde_json::from_slice(&payload_bytes)
.map_err(|e| format!("inner ceiling manifest is unparseable: {e}"))?;
enforce_ceiling_epoch_freshness(manifest.ceiling_epoch)?;
let verified_signer_kid = pick_verified_signer_kid(&envelope, &payload_bytes, keys, now)
.unwrap_or_else(|| "(unknown)".to_string());
Ok(CeilingVerifiedDetails {
manifest,
manifest_digest: envelope.payload_digest.clone(),
verified_signer_kid,
})
}
fn enforce_ceiling_epoch_freshness(ceiling_epoch: u64) -> Result<(), String> {
let Some(anchor_os) = std::env::var_os("CELLOS_AUTHORITY_CEILING_ANCHOR_PATH") else {
tracing::warn!(
target: "cellos.supervisor.ceiling",
ceiling_epoch,
"CELLOS_AUTHORITY_CEILING_ANCHOR_PATH unset; ceilingEpoch replay floor NOT enforced \
across processes (residual replay risk — see ADR-0024 §Manifest replay)"
);
return Ok(());
};
let anchor_path = PathBuf::from(&anchor_os);
let recorded = read_ceiling_epoch_anchor(&anchor_path)?;
if let Some(anchor_epoch) = recorded {
if ceiling_epoch < anchor_epoch {
return Err(format!(
"ceiling_epoch_stale: ceilingEpoch {ceiling_epoch} is below the freshness anchor \
floor {anchor_epoch} at {}",
anchor_path.display()
));
}
}
std::fs::write(&anchor_path, ceiling_epoch.to_string()).map_err(|e| {
format!(
"cannot advance ceiling freshness anchor at {}: {e}",
anchor_path.display()
)
})?;
Ok(())
}
fn read_ceiling_epoch_anchor(anchor_path: &Path) -> Result<Option<u64>, String> {
let raw = match std::fs::read_to_string(anchor_path) {
Ok(s) => s,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(e) => {
return Err(format!(
"cannot read ceiling freshness anchor at {}: {e}",
anchor_path.display()
));
}
};
let trimmed = raw.trim();
if trimmed.is_empty() {
return Ok(None);
}
let epoch: u64 = trimmed.parse().map_err(|e| {
format!(
"ceiling freshness anchor at {} holds non-u64 content '{trimmed}': {e}",
anchor_path.display()
)
})?;
Ok(Some(epoch))
}
#[cfg(test)]
mod tests {
use super::{
load_and_verify_trust_keyset_from_env, load_trust_verify_keys_from_env, KeysetLoadOutcome,
};
use std::sync::{Mutex, MutexGuard};
static ENV_MUTEX: Mutex<()> = Mutex::new(());
fn lock_env() -> MutexGuard<'static, ()> {
ENV_MUTEX.lock().unwrap_or_else(|e| e.into_inner())
}
#[test]
fn unset_path_with_require_unset_yields_empty_map() {
let _guard = lock_env();
std::env::remove_var("CELLOS_TRUST_VERIFY_KEYS_PATH");
let keys = load_trust_verify_keys_from_env(false).expect("legacy posture");
assert!(keys.is_empty());
}
#[test]
fn unset_path_with_require_set_errors() {
let _guard = lock_env();
std::env::remove_var("CELLOS_TRUST_VERIFY_KEYS_PATH");
let err = load_trust_verify_keys_from_env(true).expect_err("require set + unset path");
assert!(format!("{err}").contains("CELLOS_TRUST_VERIFY_KEYS_PATH"));
}
#[test]
fn keyset_path_unset_returns_not_configured() {
let _guard = lock_env();
std::env::remove_var("CELLOS_TRUST_KEYSET_PATH");
let outcome = load_and_verify_trust_keyset_from_env(
&Default::default(),
false,
std::time::SystemTime::now(),
)
.expect("unset path + fail-open");
assert!(matches!(outcome, KeysetLoadOutcome::NotConfigured));
}
use super::{
load_and_verify_ceiling_from_env, CeilingLoadOutcome, AUTHORITY_CEILING_V1_PAYLOAD_TYPE,
};
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine as _;
use cellos_core::types::TRUST_KEYSET_V1_PAYLOAD_TYPE;
use cellos_core::{SignedTrustKeysetEnvelope, TrustAnchorPublicKey, TrustKeysetSignature};
use ed25519_dalek::{Signer as _, SigningKey};
use sha2::{Digest, Sha256};
use std::collections::HashMap;
const CEILING_KID: &str = "org-root";
fn org_root_signing_key() -> SigningKey {
SigningKey::from_bytes(&[0x24; 32])
}
fn org_root_keyring() -> HashMap<String, TrustAnchorPublicKey> {
let mut keys = HashMap::new();
keys.insert(
CEILING_KID.to_string(),
TrustAnchorPublicKey::from_bytes_unchecked(
org_root_signing_key().verifying_key().to_bytes(),
),
);
keys
}
fn sha256_prefixed(bytes: &[u8]) -> String {
let digest = Sha256::digest(bytes);
let mut s = String::from("sha256:");
for byte in digest {
use std::fmt::Write as _;
write!(s, "{byte:02x}").expect("hex write");
}
s
}
fn ceiling_manifest_json(epoch: u64) -> Vec<u8> {
serde_json::to_vec(&serde_json::json!({
"schemaVersion": "1.0.0",
"ceilingId": "ceiling-test",
"ceilingEpoch": epoch,
"ceilings": [],
}))
.expect("serialize ceiling manifest")
}
fn build_signed_envelope(
payload_bytes: &[u8],
payload_type: &str,
) -> SignedTrustKeysetEnvelope {
let key = org_root_signing_key();
let signature = key.sign(payload_bytes);
SignedTrustKeysetEnvelope {
schema_version: "1.0.0".into(),
payload_type: payload_type.into(),
payload: URL_SAFE_NO_PAD.encode(payload_bytes),
signatures: vec![TrustKeysetSignature {
signer_kid: CEILING_KID.into(),
algorithm: "ed25519".into(),
signature: URL_SAFE_NO_PAD.encode(signature.to_bytes()),
not_before: None,
not_after: None,
}],
payload_digest: sha256_prefixed(payload_bytes),
produced_at: "2026-06-01T00:00:00Z".into(),
replaces_envelope_digest: None,
required_signer_count: None,
}
}
fn write_envelope(
dir: &std::path::Path,
envelope: &SignedTrustKeysetEnvelope,
) -> std::path::PathBuf {
let path = dir.join("ceiling.json");
std::fs::write(
&path,
serde_json::to_vec(envelope).expect("serialize envelope"),
)
.expect("write envelope");
path
}
struct CeilingEnvGuard {
path: Option<std::ffi::OsString>,
anchor: Option<std::ffi::OsString>,
}
impl CeilingEnvGuard {
fn new() -> Self {
let g = Self {
path: std::env::var_os("CELLOS_AUTHORITY_CEILING_PATH"),
anchor: std::env::var_os("CELLOS_AUTHORITY_CEILING_ANCHOR_PATH"),
};
std::env::remove_var("CELLOS_AUTHORITY_CEILING_PATH");
std::env::remove_var("CELLOS_AUTHORITY_CEILING_ANCHOR_PATH");
g
}
}
impl Drop for CeilingEnvGuard {
fn drop(&mut self) {
match self.path.take() {
Some(v) => std::env::set_var("CELLOS_AUTHORITY_CEILING_PATH", v),
None => std::env::remove_var("CELLOS_AUTHORITY_CEILING_PATH"),
}
match self.anchor.take() {
Some(v) => std::env::set_var("CELLOS_AUTHORITY_CEILING_ANCHOR_PATH", v),
None => std::env::remove_var("CELLOS_AUTHORITY_CEILING_ANCHOR_PATH"),
}
}
}
#[test]
fn ceiling_unset_not_required_returns_not_configured() {
let _guard = lock_env();
let _env = CeilingEnvGuard::new();
let outcome =
load_and_verify_ceiling_from_env(&HashMap::new(), false, std::time::SystemTime::now())
.expect("unset + not required is the labeled fail-open path");
assert!(matches!(outcome, CeilingLoadOutcome::NotConfigured));
}
#[test]
fn ceiling_required_but_unset_errors() {
let _guard = lock_env();
let _env = CeilingEnvGuard::new();
let err =
load_and_verify_ceiling_from_env(&HashMap::new(), true, std::time::SystemTime::now())
.expect_err("required + unset must abort");
assert!(format!("{err}").contains("CELLOS_AUTHORITY_CEILING_PATH"));
}
#[test]
fn ceiling_required_missing_file_errors() {
let _guard = lock_env();
let _env = CeilingEnvGuard::new();
let dir = tempfile::tempdir().expect("tempdir");
std::env::set_var(
"CELLOS_AUTHORITY_CEILING_PATH",
dir.path().join("does-not-exist.json"),
);
let err = load_and_verify_ceiling_from_env(
&org_root_keyring(),
true,
std::time::SystemTime::now(),
)
.expect_err("required + missing file must abort");
assert!(format!("{err}").contains("cannot read"));
}
#[test]
fn ceiling_tampered_signature_errors() {
let _guard = lock_env();
let _env = CeilingEnvGuard::new();
let dir = tempfile::tempdir().expect("tempdir");
let payload = ceiling_manifest_json(1);
let mut envelope = build_signed_envelope(&payload, AUTHORITY_CEILING_V1_PAYLOAD_TYPE);
let mut sig_bytes = URL_SAFE_NO_PAD
.decode(envelope.signatures[0].signature.trim_end_matches('='))
.expect("decode sig");
sig_bytes[0] ^= 0xff;
envelope.signatures[0].signature = URL_SAFE_NO_PAD.encode(&sig_bytes);
let path = write_envelope(dir.path(), &envelope);
std::env::set_var("CELLOS_AUTHORITY_CEILING_PATH", &path);
let err = load_and_verify_ceiling_from_env(
&org_root_keyring(),
true,
std::time::SystemTime::now(),
)
.expect_err("tampered signature must abort");
assert!(format!("{err}").contains("CELLOS_AUTHORITY_CEILING_PATH"));
}
#[test]
fn ceiling_wrong_payload_type_rejected() {
let _guard = lock_env();
let _env = CeilingEnvGuard::new();
let dir = tempfile::tempdir().expect("tempdir");
let payload = ceiling_manifest_json(1);
let envelope = build_signed_envelope(&payload, TRUST_KEYSET_V1_PAYLOAD_TYPE);
let path = write_envelope(dir.path(), &envelope);
std::env::set_var("CELLOS_AUTHORITY_CEILING_PATH", &path);
let err = load_and_verify_ceiling_from_env(
&org_root_keyring(),
true,
std::time::SystemTime::now(),
)
.expect_err("trust-keyset envelope presented as ceiling must be rejected");
assert!(format!("{err}").contains("expected ceiling sibling"));
}
#[test]
fn ceiling_valid_envelope_verifies_and_parses() {
let _guard = lock_env();
let _env = CeilingEnvGuard::new();
let dir = tempfile::tempdir().expect("tempdir");
let payload = ceiling_manifest_json(7);
let envelope = build_signed_envelope(&payload, AUTHORITY_CEILING_V1_PAYLOAD_TYPE);
let path = write_envelope(dir.path(), &envelope);
std::env::set_var("CELLOS_AUTHORITY_CEILING_PATH", &path);
let outcome = load_and_verify_ceiling_from_env(
&org_root_keyring(),
true,
std::time::SystemTime::now(),
)
.expect("valid ceiling envelope must verify");
match outcome {
CeilingLoadOutcome::Verified(details) => {
assert_eq!(details.manifest.ceiling_id, "ceiling-test");
assert_eq!(details.manifest.ceiling_epoch, 7);
assert_eq!(details.verified_signer_kid, CEILING_KID);
}
other => panic!("expected Verified, got {other:?}"),
}
}
#[test]
fn ceiling_epoch_below_anchor_rejected() {
let _guard = lock_env();
let _env = CeilingEnvGuard::new();
let dir = tempfile::tempdir().expect("tempdir");
let anchor = dir.path().join("ceiling.anchor");
std::fs::write(&anchor, "10").expect("write anchor");
std::env::set_var("CELLOS_AUTHORITY_CEILING_ANCHOR_PATH", &anchor);
let payload = ceiling_manifest_json(3);
let envelope = build_signed_envelope(&payload, AUTHORITY_CEILING_V1_PAYLOAD_TYPE);
let path = write_envelope(dir.path(), &envelope);
std::env::set_var("CELLOS_AUTHORITY_CEILING_PATH", &path);
let err = load_and_verify_ceiling_from_env(
&org_root_keyring(),
true,
std::time::SystemTime::now(),
)
.expect_err("ceilingEpoch below anchor must abort");
let msg = format!("{err}");
assert!(msg.contains("ceiling_epoch_stale"), "got: {msg}");
assert_eq!(
std::fs::read_to_string(&anchor)
.expect("read anchor")
.trim(),
"10"
);
}
#[test]
fn ceiling_epoch_advances_anchor_monotonically() {
let _guard = lock_env();
let _env = CeilingEnvGuard::new();
let dir = tempfile::tempdir().expect("tempdir");
let anchor = dir.path().join("ceiling.anchor");
std::env::set_var("CELLOS_AUTHORITY_CEILING_ANCHOR_PATH", &anchor);
let payload = ceiling_manifest_json(5);
let envelope = build_signed_envelope(&payload, AUTHORITY_CEILING_V1_PAYLOAD_TYPE);
let path = write_envelope(dir.path(), &envelope);
std::env::set_var("CELLOS_AUTHORITY_CEILING_PATH", &path);
load_and_verify_ceiling_from_env(&org_root_keyring(), true, std::time::SystemTime::now())
.expect("first observation accepted");
assert_eq!(
std::fs::read_to_string(&anchor)
.expect("read anchor")
.trim(),
"5"
);
}
}