liminal-server 0.24.1

Standalone server for the liminal messaging bus
Documentation
//! No credential or credential-derived bytes in any `Debug` (PROTO-007).
//!
//! Each pin formats a value carrying a real-shaped credential with `{:?}` and
//! `{:#?}` and searches the output for the credential's bytes as a decimal
//! list, in lower- and upper-case hex and in base64, whole and as any four
//! consecutive bytes, so a partial print is caught too (R4). Types private to a
//! module are pinned beside it, with these helpers.

use core::fmt::Write as _;

/// A token as a real one is shaped: distinct non-zero bytes, then zeros to the
/// type's length. Bytes 151, 152, … are three digits each and never a run of
/// one hex digit, so no rendering of one can hide in a type name or a length.
pub fn token_shaped<const N: usize>(distinct: usize) -> [u8; N] {
    core::array::from_fn(|index| {
        if index < distinct {
            u8::try_from(151 + index).unwrap_or(u8::MAX)
        } else {
            0
        }
    })
}

const BASE64: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

fn base64(bytes: &[u8]) -> String {
    let mut out = String::new();
    for chunk in bytes.chunks(3) {
        let b = [
            chunk.first().copied().unwrap_or(0),
            chunk.get(1).copied().unwrap_or(0),
            chunk.get(2).copied().unwrap_or(0),
        ];
        let n = (u32::from(b[0]) << 16) | (u32::from(b[1]) << 8) | u32::from(b[2]);
        for (slot, shift) in [18_u32, 12, 6, 0].into_iter().enumerate() {
            if slot <= chunk.len() {
                let index = usize::try_from((n >> shift) & 63).unwrap_or(0);
                out.push(char::from(BASE64.get(index).copied().unwrap_or(b'=')));
            } else {
                out.push('=');
            }
        }
    }
    out
}

/// Every form a print of `bytes`, or of four consecutive bytes of it holding at
/// least one non-zero byte, could take, with whitespace removed.
fn forms(bytes: &[u8]) -> Vec<String> {
    let render = |run: &[u8]| -> Vec<String> {
        let decimal = run.iter().map(u8::to_string).collect::<Vec<_>>().join(",");
        let lower = run.iter().fold(String::new(), |mut out, b| {
            let _ = write!(out, "{b:02x}");
            out
        });
        let upper = run.iter().fold(String::new(), |mut out, b| {
            let _ = write!(out, "{b:02X}");
            out
        });
        vec![decimal, lower, upper, base64(run)]
    };
    let mut out = render(bytes);
    for run in bytes.windows(4).filter(|run| run.iter().any(|b| *b != 0)) {
        out.extend(render(run));
    }
    out
}

/// `value`'s `{:?}` and `{:#?}` show none of `bytes` in any form.
pub fn shows_none_of<T: core::fmt::Debug>(value: &T, bytes: &[u8]) {
    for rendered in [format!("{value:?}"), format!("{value:#?}")] {
        let squeezed: String = rendered.chars().filter(|c| !c.is_whitespace()).collect();
        for form in forms(bytes) {
            assert!(
                !squeezed.contains(&form),
                "Debug shows the credential as {form}: {rendered}"
            );
        }
    }
}

#[test]
fn the_search_finds_bytes_printed_as_a_derived_debug_prints_them() {
    // a 32-byte digest's shape, as the server's enrollment fingerprint lays a token out: token bytes, then zeros
    let digest = token_shaped::<32>(16);
    let printed = format!("{digest:?}");
    let caught = std::panic::catch_unwind(|| shows_none_of(&digest, &digest));
    assert!(caught.is_err(), "the search missed {printed}");
}