liminal-server 0.24.0

Standalone server for the liminal messaging bus
Documentation
//! ACCESS-004 R5: no credential bytes in any `Debug`. Each value is built
//! from distinct marker bytes, and its `Debug` must show none of them, in
//! decimal or in hex, while naming each credential field by its length.

use std::collections::BTreeSet;

use super::{PassPrincipal, WirePassV1};
use crate::config::{AuthConfig, PassConfig};

/// Marker bytes no ordinary field holds.
const PARTICIPANT: [u8; 6] = [200, 201, 202, 203, 204, 205];

/// The renderings a derived `Debug` or a hex dump would give `bytes`.
fn renderings(bytes: &[u8]) -> Vec<String> {
    let pair = &bytes[..2.min(bytes.len())];
    let decimal = pair
        .iter()
        .map(u8::to_string)
        .collect::<Vec<_>>()
        .join(", ");
    vec![decimal, hex::encode(pair)]
}

fn assert_hides(rendered: &str, what: &str, bytes: &[u8]) {
    for rendering in renderings(bytes) {
        assert!(
            !rendered.contains(&rendering),
            "the Debug of {what} shows its credential bytes as {rendering:?}: {rendered}"
        );
    }
}

fn principal() -> PassPrincipal {
    PassPrincipal {
        participant: PARTICIPANT.to_vec(),
        public_key: [210; 32],
        conversations: BTreeSet::from([7, 9]),
        live: "workspace/acme/".to_owned(),
        may_enroll: true,
    }
}

#[test]
fn a_pass_principal_debug_names_its_credential_fields_by_length_only() {
    let principal = principal();
    let rendered = format!("{principal:?}");
    assert_hides(
        &rendered,
        "PassPrincipal.participant",
        &principal.participant,
    );
    assert_hides(&rendered, "PassPrincipal.public_key", &principal.public_key);
    assert!(
        rendered.contains("participant: 6 bytes") && rendered.contains("public_key: 32 bytes"),
        "the Debug of PassPrincipal must name each credential field by its length: {rendered}"
    );
}

#[test]
fn a_wire_pass_debug_names_its_credential_fields_by_length_only() {
    let pass = WirePassV1 {
        participant: PARTICIPANT.to_vec(),
        public_key: [210; 32],
        conversations: vec![7, 9],
        live: "workspace/acme/".to_owned(),
        may_enroll: true,
        issued_at: 1,
        expires_at: 2,
        signature: [220; 64],
    };
    let rendered = format!("{pass:?}");
    assert_hides(&rendered, "WirePassV1.participant", &pass.participant);
    assert_hides(&rendered, "WirePassV1.public_key", &pass.public_key);
    assert_hides(&rendered, "WirePassV1.signature", &pass.signature);
    assert!(
        rendered.contains("signature: 64 bytes"),
        "the Debug of WirePassV1 must name its signature by length: {rendered}"
    );
}

#[test]
fn an_auth_config_debug_hides_its_token() {
    let token = "token-marker-7f3e";
    let auth = AuthConfig {
        token: token.to_owned(),
        pass: Some(PassConfig {
            registry_verifying_key: "00".repeat(32),
            maximum_clock_skew_seconds: 30,
        }),
        require_verified_worker: false,
        lys: None,
    };
    let rendered = format!("{auth:?}");
    assert!(
        !rendered.contains(token),
        "the Debug of AuthConfig shows its token: {rendered}"
    );
    assert!(
        rendered.contains(&format!("token: {} bytes", token.len())),
        "the Debug of AuthConfig must name its token by length: {rendered}"
    );
}

#[test]
fn a_verified_holder_debug_names_its_participant_by_length_only() {
    let holder = super::VerifiedHolder {
        participant: PARTICIPANT.to_vec(),
        live: "workspace/acme/".to_owned(),
        source: super::HolderSource::RegistryPass,
    };
    let rendered = format!("{holder:?}");
    assert_hides(&rendered, "VerifiedHolder.participant", &PARTICIPANT);
    assert!(
        rendered.contains("participant: 6 bytes"),
        "the Debug of VerifiedHolder must name its participant by length: {rendered}"
    );
}

/// ACCESS-004 R5: a pass's live prefix
/// commonly carries its holder's name (a watcher's is `watcher.`), so the
/// `Debug` of every type holding one names the prefix by length, never by
/// its text. Red at f5ceb8d6, which printed `live` verbatim.
#[test]
fn a_debug_never_names_the_holder_through_its_live_prefix() {
    let live = "watcher.";
    let principal = PassPrincipal {
        live: live.to_owned(),
        ..principal()
    };
    let pass = WirePassV1 {
        participant: PARTICIPANT.to_vec(),
        public_key: [210; 32],
        conversations: vec![7],
        live: live.to_owned(),
        may_enroll: false,
        issued_at: 1,
        expires_at: 2,
        signature: [220; 64],
    };
    let holder = super::VerifiedHolder {
        participant: PARTICIPANT.to_vec(),
        live: live.to_owned(),
        source: super::HolderSource::RegistryPass,
    };
    for (what, rendered) in [
        ("PassPrincipal", format!("{principal:?}")),
        ("WirePassV1", format!("{pass:?}")),
        ("VerifiedHolder", format!("{holder:?}")),
    ] {
        assert!(
            !rendered.contains("watcher"),
            "the Debug of {what} names the holder through its live prefix: {rendered}"
        );
        assert!(
            rendered.contains(&format!("live: {} bytes", live.len())),
            "the Debug of {what} must name its live prefix by length: {rendered}"
        );
    }
}

/// ACCESS-004 R1, the Lys producer: a Lys holder's `Debug` names its
/// participant by length too, and its live prefix, which it has none of.
#[test]
fn a_lys_holder_debug_names_its_participant_by_length_only() {
    let holder = super::VerifiedHolder {
        participant: PARTICIPANT.to_vec(),
        live: String::new(),
        source: super::HolderSource::Lys {
            kind: Some("Agent".to_owned()),
        },
    };
    let rendered = format!("{holder:?}");
    assert_hides(&rendered, "VerifiedHolder.participant", &PARTICIPANT);
    assert!(
        rendered.contains("participant: 6 bytes") && rendered.contains("live: 0 bytes"),
        "the Debug of a Lys VerifiedHolder must name its fields by length: {rendered}"
    );
}

/// A registry holder's grant is its live prefix, and an empty prefix grants
/// nothing; a Lys holder adds no rule to its session's decision.
#[test]
fn a_holders_channel_grant_follows_the_verification_that_proved_it() {
    let registry = super::VerifiedHolder {
        participant: PARTICIPANT.to_vec(),
        live: "watcher.".to_owned(),
        source: super::HolderSource::RegistryPass,
    };
    assert!(registry.allows_channel("watcher.findings"));
    assert!(!registry.allows_channel("archie.findings"));
    assert_eq!(registry.kind(), None);
    let empty = super::VerifiedHolder {
        live: String::new(),
        ..registry
    };
    assert!(!empty.allows_channel("watcher.findings"));
    let lys = super::VerifiedHolder {
        participant: PARTICIPANT.to_vec(),
        live: String::new(),
        source: super::HolderSource::Lys {
            kind: Some("Agent".to_owned()),
        },
    };
    assert!(lys.allows_channel("archie.findings"));
    assert_eq!(lys.kind(), Some("Agent"));
}