Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/gunnar

crates/gunnar-server/src/layout_config.rs

//! **The layout config: gunnar is TOLD where its data lives, and obeys.**
//!
//! # The rule, and why it is the whole design
//!
//! gunnar has **no disk logic**. It does not survey `/sys/block`, it does not
//! read the mount table to work out what `/data` is, and it does not probe to
//! check what it was told. `korp-installer` chooses the layout, partitions it,
//! RAIDs it, lays the directories — and then installs gunnar **with parameters
//! saying where to fetch data**. That is not a new idea: `GUNNAR_DATA_ROOT=/data`
//! is a boot parameter the installer bakes in today, and this file is that idea
//! carried to completion.
//!
//! Inferring is how *"why is this disk unused?"* gets answered wrongly. A
//! current mount table can say what exists; it can never say **why** a disk was
//! rejected, which group won `/data`, or what the installer was aiming at. So
//! the reasoning is written down at install time by the thing that did the
//! reasoning, and read back verbatim.
//!
//! # Where it is, and why there
//!
//! `/boot/korp/layout.conf` — on the ESP, mounted read-only at `/boot`, beside
//! the trust records (`korp_installer::layout::CONFIG_ESP_PATH`). It is not on
//! `/data` for a plain reason the installer states: it is the thing that SAYS
//! where `/data` is, and a config that has to be found by first knowing what it
//! tells you is not a config.
//!
//! # One artifact, two readers
//!
//! The same file is read for **placement** (`crate::slow`) and rendered for the
//! **admin** (`Admin.StorageInfo`'s storage reality). Not two views of the same
//! facts that can drift apart: one parse, one set of key-value pairs, handed to
//! both.
//!
//! # The format
//!
//! `korp_installer::layout::LayoutConfig::body` + `seal` — version FIRST, then
//! `key=value`, one per line, keys unique, values escaped (`\n`, `\\`) so no
//! value can forge a line; lists are indexed keys with a `.count`:
//!
//! ```text
//! korp-layout-config 1
//! install.unix=1789…
//! boot.dev=/dev/vda
//! boot.why=largest-group-remainder
//! data.count=4
//! data.0.dev=/dev/vdb
//! unused.0.why=unequal, not used
//! dir.log.path=/data/log          ← the parameters: what the app is TOLD
//! dir.log.device=serialdat
//! dir.log.off_stripe_satisfied=yes
//! auth.domain=korp.layout-config.v1
//! auth.blake3=…
//! auth.signature=
//! ```
//!
//! Unknown KEYS are ignored — that is what lets version 1 gain keys — but an
//! unknown VERSION is refused, because a key whose meaning changed underneath
//! is worse than no config.
//!
//! # `dir.<name>.path` is obeyed, and there is no second placement mechanism
//!
//! The installer lays `crash`, `log`, `gc`, `quarantine` (and the two that are
//! not gunnar's) PHYSICALLY on `/serialdat` and makes `/data/<name>` a
//! **symlink** to each. So `dir.log.path` is `/data/log` and writing there
//! already lands on the other spindle. gunnar writes the path it is given and
//! adds nothing on top: two things deciding the same thing is how they start
//! disagreeing.
//!
//! # Absent is a first-class answer
//!
//! Every appliance installed before this config existed has none, and gunnar
//! must start on it. So the absent case is not an error and not a silence: it
//! is [`Load::Absent`] carrying **the sentence that says why**, which travels
//! all the way to the admin's screen.
//!
//! # Authenticity — and the third state
//!
//! [`verify`] is `korp_installer::layout::verify`, arm for arm:
//!
//! * a first line that is not this format and version, or a body whose blake3
//!   does not match what is written beside it ⇒ `Err`. A config that does not
//!   hold together is not read for its values, and the caller treats it as
//!   **absent rather than trusted** — this file says where the forge's data
//!   lives, so forging it redirects the forge's storage.
//! * a signature present AND an enrolled key present ⇒ it must verify, or
//!   `Err`. **A failed verify is never downgraded to integrity-only**; that
//!   downgrade is how an attacker turns "signed" into "either will do".
//! * `auth.signature=` EMPTY, or no enrolled key ⇒
//!   [`Authenticity::IntegrityOnly`].
//!
//! That third state is the one that matters today. **No `KORP_ROOT_SIGNING_KEY`
//! is enrolled on this estate**, so every config built today carries an empty
//! signature and says so in its own bytes (`auth.note`). An empty signature is
//! not a failed signature and not a passed one — it is *nobody signed this*.
//! The installer's console prints `INTEGRITY ONLY`; [`Authenticity::word`]
//! prints the same two words, and nothing anywhere calls it verified.

use std::collections::BTreeMap;
use std::path::{Path, PathBuf};

/// `korp_installer::layout::CONFIG_ESP_PATH`, as the full path on a running
/// appliance: the ESP is mounted read-only at `/boot`.
pub const DEFAULT_PATH: &str = "/boot/korp/layout.conf";

/// The boot parameter that names the config, when it is not at the default.
/// Rides the same channel `GUNNAR_DATA_ROOT` already rides (Linux turns an
/// unrecognised `key=value` boot parameter into an environment variable for
/// init).
pub const CONFIG_ENV: &str = "GUNNAR_LAYOUT_CONFIG";

/// The enrolled ed25519 root public key, 64 hex characters — the key an
/// `auth.signature` must verify against.
///
/// **Unset on this estate.** With no key enrolled, a signature that is present
/// cannot be checked and the answer is [`Authenticity::IntegrityOnly`], never
/// "verified".
pub const ROOT_KEY_ENV: &str = "GUNNAR_LAYOUT_ROOT_KEY";

/// The first token of the first line.
pub const HEADER: &str = "korp-layout-config";

/// The only version this build reads.
pub const VERSION: u32 = 1;

/// `korp_installer::layout::CONFIG_DOMAIN` — the domain tag the signature
/// covers, so a signature over this config can never be replayed as a
/// signature over anything else the constellation signs.
pub const DOMAIN: &[u8] = b"korp.layout-config.v1\0";

/// Where the authenticity block begins. The body is everything before it, and
/// the body is what the digest and the signature are over.
const AUTH_MARKER: &str = "auth.domain=";

/// What is known about the config's authenticity.
///
/// Three states, and the third is the point: an EMPTY `auth.signature` is not
/// a failed signature and not a passed one. A failure is not in this enum at
/// all — it is an `Err` out of [`verify`], which the caller turns into an
/// absent config.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Authenticity {
    /// An ed25519 signature over the domain-separated body digest verified
    /// against the enrolled root key.
    Verified,
    /// The body's blake3 matches what is written beside it, and **nothing else
    /// is claimed**. Integrity, not authenticity: anyone who can write the
    /// plaintext ESP can rewrite the body and its digest together. This is the
    /// state every appliance built today is in.
    IntegrityOnly,
}

impl Authenticity {
    /// The words the console prints — **the same words the installer's console
    /// prints**, so an admin comparing the two is not left wondering whether
    /// they mean the same thing.
    pub fn word(self) -> &'static str {
        match self {
            Authenticity::Verified => "verified",
            Authenticity::IntegrityOnly => "INTEGRITY ONLY",
        }
    }

    /// Only [`Authenticity::Verified`]. An unsigned config is never presented
    /// as verified.
    pub fn is_verified(self) -> bool {
        matches!(self, Authenticity::Verified)
    }
}

/// The parsed config: the version, where it came from, how authentic it is,
/// and every `key=value` in it, verbatim.
#[derive(Clone, Debug)]
pub struct LayoutConfig {
    path: PathBuf,
    version: u32,
    authenticity: Authenticity,
    /// The `auth.note` the installer wrote — the sentence saying, in the
    /// file's own bytes, what is NOT verified. Empty on a signed config.
    note: String,
    fields: BTreeMap<String, String>,
}

impl LayoutConfig {
    pub fn path(&self) -> &Path {
        &self.path
    }

    pub fn version(&self) -> u32 {
        self.version
    }

    pub fn authenticity(&self) -> Authenticity {
        self.authenticity
    }

    /// The installer's own sentence about what is not verified, or empty.
    pub fn note(&self) -> &str {
        &self.note
    }

    /// One value, exactly as written. `None` is "the config does not carry
    /// this key" and is never a zero, an empty string or a guess.
    pub fn get(&self, key: &str) -> Option<&str> {
        self.fields
            .get(key)
            .map(String::as_str)
            .filter(|v| !v.is_empty())
    }

    /// One value as a number, or `None` — including for a value that does not
    /// parse, because a number that could not be read must not be reported as
    /// one.
    pub fn u64(&self, key: &str) -> Option<u64> {
        self.get(key).and_then(|v| v.parse().ok())
    }

    /// A `yes`/`no` value. Anything else, or absent, is `None`.
    pub fn yes_no(&self, key: &str) -> Option<bool> {
        match self.get(key)? {
            "yes" => Some(true),
            "no" => Some(false),
            _ => None,
        }
    }

    /// `<prefix>.count`, or 0.
    pub fn count(&self, prefix: &str) -> usize {
        self.u64(&format!("{prefix}.count")).unwrap_or(0) as usize
    }

    /// Every field, in key order — what the admin view renders.
    pub fn fields(&self) -> impl Iterator<Item = (&str, &str)> {
        self.fields.iter().map(|(k, v)| (k.as_str(), v.as_str()))
    }

    /// **`dir.<name>.path` — the parameter gunnar obeys.**
    pub fn dir_path(&self, name: &str) -> Option<PathBuf> {
        self.get(&format!("dir.{name}.path")).map(PathBuf::from)
    }

    /// `dir.<name>.off_stripe_required`.
    pub fn dir_requires_off_stripe(&self, name: &str) -> Option<bool> {
        self.yes_no(&format!("dir.{name}.off_stripe_required"))
    }

    /// `dir.<name>.off_stripe_satisfied` — **the installer's verdict**, not
    /// gunnar's re-derivation of it.
    pub fn dir_off_stripe_satisfied(&self, name: &str) -> Option<bool> {
        self.yes_no(&format!("dir.{name}.off_stripe_satisfied"))
    }

    /// Parse a SEALED config's bytes. `Err` is the sentence that says why not.
    ///
    /// Parsing does not verify; [`load`] verifies first and parses only what
    /// held together.
    pub fn parse(path: PathBuf, text: &str, authenticity: Authenticity) -> Result<Self, String> {
        let mut lines = text.lines();
        let header = lines.next().unwrap_or_default().trim();
        let mut tokens = header.split_whitespace();
        if tokens.next() != Some(HEADER) {
            return Err(format!(
                "{} does not begin with `{HEADER}`; it is not a layout config",
                path.display()
            ));
        }
        let version: u32 = tokens
            .next()
            .and_then(|v| v.parse().ok())
            .ok_or_else(|| format!("{} carries no format version", path.display()))?;
        if version != VERSION {
            return Err(format!(
                "{} is format version {version}; this build reads version {VERSION} only",
                path.display()
            ));
        }
        let mut fields = BTreeMap::new();
        for line in lines {
            let line = line.trim_end_matches('\r');
            if line.trim().is_empty() || line.starts_with('#') {
                continue;
            }
            let Some((key, value)) = line.split_once('=') else {
                continue;
            };
            fields.insert(key.to_owned(), unescape(value));
        }
        let note = fields.get("auth.note").cloned().unwrap_or_default();
        Ok(LayoutConfig {
            path,
            version,
            authenticity,
            note,
            fields,
        })
    }
}

/// The writer escapes `\n` and `\\`; unescape in one pass so a trailing
/// backslash cannot swallow the next character.
fn unescape(value: &str) -> String {
    let mut out = String::with_capacity(value.len());
    let mut chars = value.chars();
    while let Some(c) = chars.next() {
        if c != '\\' {
            out.push(c);
            continue;
        }
        match chars.next() {
            Some('n') => out.push('\n'),
            Some('\\') => out.push('\\'),
            Some(other) => {
                out.push('\\');
                out.push(other);
            }
            None => out.push('\\'),
        }
    }
    out
}

/// The answer to "is there a layout config?", and never anything in between.
#[derive(Clone, Debug)]
pub enum Load {
    Found(Box<LayoutConfig>),
    /// **The sentence that says why there is none.** It reaches the admin's
    /// screen unchanged: "the config is missing" is an answer, and inferring
    /// the installer's decisions from the current mount table is not.
    Absent(String),
}

impl Load {
    pub fn config(&self) -> Option<&LayoutConfig> {
        match self {
            Load::Found(c) => Some(c),
            Load::Absent(_) => None,
        }
    }

    pub fn absent_reason(&self) -> Option<&str> {
        match self {
            Load::Absent(why) => Some(why),
            Load::Found(_) => None,
        }
    }
}

/// Where the config lives: [`CONFIG_ENV`], else [`DEFAULT_PATH`]. **One place,
/// not a search** — a config found by looking around is a config an attacker
/// can put somewhere.
pub fn config_path() -> PathBuf {
    std::env::var_os(CONFIG_ENV)
        .filter(|v| !v.is_empty())
        .map(PathBuf::from)
        .unwrap_or_else(|| PathBuf::from(DEFAULT_PATH))
}

/// Read, verify, and parse the layout config.
///
/// Never an error and never a panic: an old appliance has none and must still
/// start.
pub fn load() -> Load {
    load_from(&config_path(), enrolled_root_key().as_ref())
}

/// [`load`] with the path and the enrolled key named — the seam the tests
/// drive, so none of them touches a process-global.
pub fn load_from(path: &Path, key: Option<&[u8; 32]>) -> Load {
    let text = match std::fs::read_to_string(path) {
        Ok(text) => text,
        Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
            return Load::Absent(format!(
                "no layout config at {}. An appliance installed before the config existed has \
                 none, and nothing here infers the installer's decisions from the current \
                 machine",
                path.display()
            ))
        }
        Err(err) => return Load::Absent(format!("{} could not be read: {err}", path.display())),
    };
    // **Verify BEFORE reading a single value.** A config that does not hold
    // together is treated as absent rather than trusted: it says where the
    // forge's data lives.
    let authenticity = match verify(&text, key) {
        Ok(a) => a,
        Err(why) => {
            return Load::Absent(format!(
                "{} is present but does not verify ({why}); it is treated as absent rather \
                 than trusted",
                path.display()
            ))
        }
    };
    match LayoutConfig::parse(path.to_path_buf(), &text, authenticity) {
        Ok(config) => Load::Found(Box::new(config)),
        Err(why) => Load::Absent(why),
    }
}

/// [`ROOT_KEY_ENV`] as 32 raw bytes, or `None`.
///
/// A malformed value is `None` and not a refusal: the honest consequence is
/// that a present signature cannot be checked, which is
/// [`Authenticity::IntegrityOnly`] — never "verified".
pub fn enrolled_root_key() -> Option<[u8; 32]> {
    let spec = std::env::var(ROOT_KEY_ENV).ok()?;
    let spec = spec.trim();
    if spec.len() != 64 {
        return None;
    }
    let mut out = [0u8; 32];
    for (i, byte) in out.iter_mut().enumerate() {
        *byte = u8::from_str_radix(spec.get(i * 2..i * 2 + 2)?, 16).ok()?;
    }
    Some(out)
}

/// **Verify a sealed config** — `korp_installer::layout::verify`, arm for arm.
///
/// See this module's header for the three outcomes and why the middle one may
/// never be reached by downgrading the last.
pub fn verify(sealed: &str, pubkey: Option<&[u8; 32]>) -> Result<Authenticity, String> {
    let header = sealed.lines().next().unwrap_or_default();
    let want = format!("{HEADER} {VERSION}");
    if header != want {
        return Err(format!(
            "first line is {header:?}, not {want:?} — refusing to read a shape this version \
             does not know"
        ));
    }
    let (body, auth) = sealed
        .split_once(AUTH_MARKER)
        .ok_or_else(|| "no auth block — it was never sealed".to_string())?;
    let field =
        |k: &str| -> Option<&str> { auth.lines().find_map(|l| l.strip_prefix(&format!("{k}="))) };
    let want_hash = field("auth.blake3").ok_or_else(|| "no auth.blake3".to_string())?;
    let got = blake3::hash(body.as_bytes()).to_hex().to_string();
    if got != want_hash {
        return Err(format!(
            "body blake3 is {got}, the file claims {want_hash} — it has been altered since it \
             was written"
        ));
    }
    let sig_hex = field("auth.signature").unwrap_or_default();
    // **The third state.** Nobody signed it: not a failure, not a pass.
    if sig_hex.is_empty() {
        return Ok(Authenticity::IntegrityOnly);
    }
    // A signature nobody can check is not evidence of anything either. It is
    // still integrity-only, and it is never called verified.
    let Some(key) = pubkey else {
        return Ok(Authenticity::IntegrityOnly);
    };
    let sig_bytes: [u8; 64] = decode_hex(sig_hex)
        .and_then(|v| v.try_into().ok())
        .ok_or_else(|| "auth.signature is not 64 hex bytes".to_string())?;
    let vk = ed25519_dalek::VerifyingKey::from_bytes(key)
        .map_err(|err| format!("the enrolled pubkey is malformed: {err}"))?;
    // The same refusal the installer makes: a small-order key verifies a
    // forged signature over ANY config, so it is refused rather than trusted.
    if vk.is_weak() {
        return Err(
            "the enrolled pubkey is a small-order point — such a key verifies a forged \
             signature over ANY config, so it is refused rather than trusted"
                .to_string(),
        );
    }
    use ed25519_dalek::Verifier as _;
    vk.verify(
        &signing_message(body),
        &ed25519_dalek::Signature::from_bytes(&sig_bytes),
    )
    .map_err(|_| {
        "the signature does not verify against the enrolled root key — the config is NOT the \
         one the installer signed"
            .to_string()
    })?;
    Ok(Authenticity::Verified)
}

/// The bytes an ed25519 signature covers: the domain tag, then the body's
/// blake3. Fixed width, one interpretation, and the tag is what stops a
/// signature over this being replayed as a signature over something else.
pub fn signing_message(body: &str) -> Vec<u8> {
    let mut m = Vec::with_capacity(DOMAIN.len() + 32);
    m.extend_from_slice(DOMAIN);
    m.extend_from_slice(blake3::hash(body.as_bytes()).as_bytes());
    m
}

fn decode_hex(s: &str) -> Option<Vec<u8>> {
    if s.len() % 2 != 0 {
        return None;
    }
    (0..s.len() / 2)
        .map(|i| u8::from_str_radix(s.get(i * 2..i * 2 + 2)?, 16).ok())
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    const BODY: &str = concat!(
        "korp-layout-config 1\n",
        "install.unix=1789000000\n",
        "install.product=gunnar\n",
        "install.mode=fresh\n",
        "boot.dev=/dev/vda\n",
        "boot.why=largest-group-remainder\n",
        "boot.p3_dev=/dev/vda3\n",
        "data.mountpoint=/data\n",
        "data.shape=raid0\n",
        "data.striped=yes\n",
        "data.count=4\n",
        "data.0.dev=/dev/vdb\n",
        "data.3.dev=/dev/vde\n",
        "serialdat.mountpoint=/serialdat\n",
        "serialdat.class=p3\n",
        "serialdat.dev=/dev/vda3\n",
        "serialdat.off_stripe=yes\n",
        "unused.count=1\n",
        "unused.0.name=vdf\n",
        "unused.0.why=unequal, not used\n",
        "dir.users_dir=repos\n",
        "dir.count=8\n",
        "dir.repos.path=/data/repos\n",
        "dir.repos.device=data\n",
        "dir.repos.off_stripe_required=no\n",
        "dir.repos.off_stripe_satisfied=yes\n",
        "dir.meta.path=/serialdat/meta\n",
        "dir.meta.device=serialdat\n",
        "dir.meta.off_stripe_required=yes\n",
        "dir.meta.off_stripe_satisfied=yes\n",
        "dir.log.path=/data/log\n",
        "dir.log.device=serialdat\n",
        "dir.log.off_stripe_required=yes\n",
        "dir.log.off_stripe_satisfied=yes\n",
        "threshold.rootfs_measured_bytes=12163481\n",
    );

    /// Seal exactly as `korp_installer::layout::seal` does with no key: an
    /// empty signature and the note that says so.
    fn seal_unsigned(body: &str) -> String {
        format!(
            "{body}auth.domain=korp.layout-config.v1\nauth.blake3={}\nauth.signature=\n\
             auth.pubkey=\nauth.note=NOT SIGNED: no ed25519 root key was enrolled at build \
             time (KORP_ROOT_SIGNING_KEY unset)\n",
            blake3::hash(body.as_bytes()).to_hex()
        )
    }

    fn seal_signed(body: &str, seed: &[u8; 32]) -> (String, [u8; 32]) {
        use ed25519_dalek::Signer;
        let sk = ed25519_dalek::SigningKey::from_bytes(seed);
        let sig = sk.sign(&signing_message(body));
        let hex = |b: &[u8]| b.iter().map(|x| format!("{x:02x}")).collect::<String>();
        (
            format!(
                "{body}auth.domain=korp.layout-config.v1\nauth.blake3={}\nauth.signature={}\n\
                 auth.pubkey={}\nauth.note=\n",
                blake3::hash(body.as_bytes()).to_hex(),
                hex(&sig.to_bytes()),
                hex(sk.verifying_key().as_bytes())
            ),
            sk.verifying_key().to_bytes(),
        )
    }

    // ── the format ─────────────────────────────────────────────────────────

    #[test]
    fn the_config_parses_into_its_keys() {
        let c = LayoutConfig::parse(
            PathBuf::from(DEFAULT_PATH),
            &seal_unsigned(BODY),
            Authenticity::IntegrityOnly,
        )
        .expect("the sample parses");
        assert_eq!(c.version(), 1);
        assert_eq!(c.get("boot.why"), Some("largest-group-remainder"));
        assert_eq!(c.count("data"), 4);
        assert_eq!(c.get("data.3.dev"), Some("/dev/vde"));
        assert_eq!(c.get("unused.0.why"), Some("unequal, not used"));
        assert_eq!(c.u64("threshold.rootfs_measured_bytes"), Some(12_163_481));
    }

    /// The parameters gunnar obeys, read as parameters.
    #[test]
    fn the_dir_parameters_are_what_the_app_is_told() {
        let c = LayoutConfig::parse(
            PathBuf::from(DEFAULT_PATH),
            &seal_unsigned(BODY),
            Authenticity::IntegrityOnly,
        )
        .expect("parses");
        // The consumer's path stays under /data — it is a SYMLINK onto the
        // other spindle, which is exactly why nothing in gunnar has to build a
        // second placement mechanism.
        assert_eq!(c.dir_path("log"), Some(PathBuf::from("/data/log")));
        // The metadata mirror is a second copy and has no /data twin.
        assert_eq!(c.dir_path("meta"), Some(PathBuf::from("/serialdat/meta")));
        assert_eq!(c.dir_path("repos"), Some(PathBuf::from("/data/repos")));
        assert_eq!(c.dir_requires_off_stripe("log"), Some(true));
        assert_eq!(c.dir_requires_off_stripe("repos"), Some(false));
        assert_eq!(c.dir_off_stripe_satisfied("log"), Some(true));
        assert_eq!(c.dir_path("nothing-like-this"), None);
    }

    /// **Never report a number you did not read.**
    #[test]
    fn an_absent_key_is_none_and_never_a_zero() {
        let c = LayoutConfig::parse(
            PathBuf::from("/x"),
            &seal_unsigned(BODY),
            Authenticity::IntegrityOnly,
        )
        .expect("parses");
        assert_eq!(c.get("data.bytes"), None);
        assert_eq!(c.u64("data.bytes"), None);
        assert_eq!(
            c.u64("boot.why"),
            None,
            "a value that is not a number is not one"
        );
        assert_eq!(c.yes_no("boot.why"), None);
        assert_eq!(c.count("nothing"), 0);
    }

    /// Unknown KEYS are kept; an unknown VERSION is refused.
    #[test]
    fn version_1_may_gain_keys_but_never_change_version_silently() {
        let grown = format!("{BODY}future.key=from a later installer\n");
        let c = LayoutConfig::parse(
            PathBuf::from("/x"),
            &seal_unsigned(&grown),
            Authenticity::IntegrityOnly,
        )
        .expect("parses");
        assert_eq!(c.get("future.key"), Some("from a later installer"));

        let err = LayoutConfig::parse(
            PathBuf::from("/x"),
            "korp-layout-config 2\nboot.dev=/dev/vda\n",
            Authenticity::IntegrityOnly,
        )
        .expect_err("version 2 must be refused");
        assert!(err.contains("version 2"), "{err}");
    }

    #[test]
    fn escaped_values_come_back_whole() {
        assert_eq!(
            unescape("two\\nlines and a \\\\ backslash"),
            "two\nlines and a \\ backslash"
        );
    }

    // ── authenticity: three states, and a failure is not one of them ───────

    /// ★ **Today's reality on every appliance.** `KORP_ROOT_SIGNING_KEY` is not
    /// enrolled, so the signature is empty — *nobody signed this* — and the
    /// words are the installer's own: `INTEGRITY ONLY`.
    #[test]
    fn an_empty_signature_is_integrity_only_and_never_verified() {
        let a = verify(&seal_unsigned(BODY), None).expect("an unsigned config still holds");
        assert_eq!(a, Authenticity::IntegrityOnly);
        assert!(!a.is_verified());
        assert_eq!(a.word(), "INTEGRITY ONLY");
    }

    /// And it stays integrity-only even where a key IS enrolled: an empty
    /// signature is the absence of a claim, not a broken one.
    #[test]
    fn an_empty_signature_with_a_key_enrolled_is_still_integrity_only() {
        let (_, key) = seal_signed(BODY, &[7u8; 32]);
        assert_eq!(
            verify(&seal_unsigned(BODY), Some(&key)),
            Ok(Authenticity::IntegrityOnly)
        );
    }

    #[test]
    fn a_real_signature_against_the_enrolled_key_verifies() {
        let (sealed, key) = seal_signed(BODY, &[9u8; 32]);
        let a = verify(&sealed, Some(&key)).expect("it must verify");
        assert_eq!(a, Authenticity::Verified);
        assert_eq!(a.word(), "verified");
    }

    /// ★ **A failed verify is NEVER downgraded to integrity-only.** That
    /// downgrade is how an attacker turns "signed" into "either will do".
    #[test]
    fn a_signature_that_does_not_verify_is_an_error_not_a_downgrade() {
        let (sealed, _) = seal_signed(BODY, &[9u8; 32]);
        let (_, other_key) = seal_signed(BODY, &[11u8; 32]);
        let err = verify(&sealed, Some(&other_key)).expect_err("it must not pass");
        assert!(err.contains("does not verify"), "{err}");
    }

    /// A body edited after sealing does not hold together, and is not read for
    /// its values at all.
    #[test]
    fn an_altered_body_is_refused_by_name() {
        let sealed = seal_unsigned(BODY).replace("/dev/vdb", "/dev/sdz");
        let err = verify(&sealed, None).expect_err("the digest must catch it");
        assert!(err.contains("altered since it was written"), "{err}");
    }

    #[test]
    fn something_that_was_never_sealed_is_refused() {
        let err = verify(BODY, None).expect_err("no auth block");
        assert!(err.contains("never sealed"), "{err}");
    }

    // ── loading ────────────────────────────────────────────────────────────

    #[test]
    fn an_absent_config_names_where_it_looked_and_is_not_an_error() {
        let dir = tempfile::tempdir().expect("a temp dir");
        let load = load_from(&dir.path().join("layout.conf"), None);
        let why = load.absent_reason().expect("absent");
        assert!(why.contains("no layout config at"), "{why}");
        assert!(why.contains("layout.conf"), "{why}");
        assert!(load.config().is_none());
    }

    #[test]
    fn a_sealed_config_on_disk_loads_as_integrity_only() {
        let dir = tempfile::tempdir().expect("a temp dir");
        let path = dir.path().join("layout.conf");
        std::fs::write(&path, seal_unsigned(BODY)).expect("write");
        let load = load_from(&path, None);
        let c = load.config().expect("found");
        assert_eq!(c.authenticity(), Authenticity::IntegrityOnly);
        assert!(!c.authenticity().is_verified());
        assert!(c.note().contains("NOT SIGNED"), "{}", c.note());
        assert_eq!(c.dir_path("meta"), Some(PathBuf::from("/serialdat/meta")));
    }

    /// **Tampered is treated as ABSENT, not as trusted** — and the sentence
    /// says which, because "there is no config" and "the config on this box
    /// has been altered" send an admin to two different places.
    #[test]
    fn a_tampered_config_is_absent_rather_than_trusted() {
        let dir = tempfile::tempdir().expect("a temp dir");
        let path = dir.path().join("layout.conf");
        std::fs::write(&path, seal_unsigned(BODY).replace("/dev/vdb", "/dev/sdz")).expect("write");
        let load = load_from(&path, None);
        assert!(
            load.config().is_none(),
            "not one value may be read out of it"
        );
        let why = load.absent_reason().expect("absent");
        assert!(why.contains("does not verify"), "{why}");
        assert!(why.contains("altered"), "{why}");
        assert!(why.contains("rather than trusted"), "{why}");
    }
}