server/lib/tests/the_time_floor_never_moves_back.rs
//! ★ **The time floor never moves back** (lane HOL-NCSC, NCSC P3): an airgap box judges
//! every expiry and validity window at `max(clock, floor)`, where the floor
//! (`<data_root>/holger-time/floor`) is the highest TRUSTED time it has seen: a verified
//! estate clock, the signed `created_at` of every bundle it accepted, a signed time token. A
//! clock found behind the floor (rolled back from the console or the BIOS) is judged at the
//! floor, said by name and audited (`holger-time-1`); an unreadable floor refuses
//! (`holger-time-3`), it never reads as zero.
use std::sync::Arc;
use chrono::{DateTime, TimeZone, Utc};
use holger_server_lib::audit::MemoryAuditLog;
use holger_server_lib::time_floor::{FlooredTime, TimeFloor, DIR, FILE};
use update_bundle::time::{TimeError, TrustedTime};
const DAY_MS: i64 = 86_400_000;
const T: i64 = 1_900_000_000_000;
struct Fixed(i64, bool);
impl TrustedTime for Fixed {
fn source_name(&self) -> &'static str {
"fixed"
}
fn is_trusted(&self) -> bool {
self.1
}
fn now(&self) -> Result<DateTime<Utc>, TimeError> {
Ok(Utc.timestamp_millis_opt(self.0).unwrap())
}
}
#[test]
fn the_floor_only_rises_and_survives_a_restart() {
let dir = tempfile::tempdir().unwrap();
let f = TimeFloor::open(dir.path()).unwrap();
assert_eq!(f.floor_ms(), 0, "a fresh box has no floor");
assert!(
f.raise(T, "a bundle").unwrap(),
"the first trusted time sets it"
);
assert!(
!f.raise(T - DAY_MS, "an older bundle").unwrap(),
"an older time never lowers it"
);
assert_eq!(f.floor_ms(), T);
assert!(f.raise(T + DAY_MS, "a newer bundle").unwrap());
drop(f);
let again = TimeFloor::open(dir.path()).unwrap();
assert_eq!(again.floor_ms(), T + DAY_MS, "kept on the data disk");
assert!(
again.path().ends_with(format!("{DIR}/{FILE}")),
"{}",
again.path().display()
);
}
#[test]
fn a_clock_behind_the_floor_is_judged_at_the_floor_and_audited() {
let dir = tempfile::tempdir().unwrap();
let f = TimeFloor::open(dir.path()).unwrap();
f.raise(T, "a bundle").unwrap();
let log = Arc::new(MemoryAuditLog::new());
let rolled_back = Fixed(T - 30 * DAY_MS, false);
let t = FlooredTime::new(&f, &rolled_back, Some(log.clone()));
assert_eq!(
t.now().unwrap().timestamp_millis(),
T,
"judged at the floor, not at the rolled-back clock"
);
let rows = log.events();
assert!(
rows.iter().any(|r| r.detail.contains("[holger-time-1]")),
"the rollback is audited: {rows:?}"
);
// A clock ahead of the floor is the time, and nothing is said.
let log = Arc::new(MemoryAuditLog::new());
let ahead = Fixed(T + DAY_MS, false);
assert_eq!(
FlooredTime::new(&f, &ahead, Some(log.clone()))
.now()
.unwrap()
.timestamp_millis(),
T + DAY_MS
);
assert!(log.events().is_empty(), "{:?}", log.events());
}
#[test]
fn only_a_trusted_clock_raises_the_floor() {
let dir = tempfile::tempdir().unwrap();
let f = TimeFloor::open(dir.path()).unwrap();
f.raise(T, "a bundle").unwrap();
// An untrusted RTC set into the future would raise a floor that never comes down:
// only a verified time moves it.
FlooredTime::new(&f, &Fixed(T + 365 * DAY_MS, false), None)
.now()
.unwrap();
assert_eq!(
f.floor_ms(),
T,
"an untrusted clock does not move the floor"
);
FlooredTime::new(&f, &Fixed(T + DAY_MS, true), None)
.now()
.unwrap();
assert_eq!(f.floor_ms(), T + DAY_MS, "a verified clock does");
}
#[test]
fn an_unreadable_floor_refuses() {
let dir = tempfile::tempdir().unwrap();
std::fs::create_dir_all(dir.path().join(DIR)).unwrap();
std::fs::write(dir.path().join(DIR).join(FILE), b"not a floor").unwrap();
let e = TimeFloor::open(dir.path())
.err()
.expect("a floor that cannot be read is no floor of zero");
assert!(e.contains("[holger-time-3]"), "{e}");
}