#![cfg(feature = "std")]
use uselesskey_core::{Factory, Mode, Seed};
fn det_factory(byte: u8) -> Factory {
Factory::deterministic(Seed::new([byte; 32]))
}
fn byte_hex(b: u8) -> String {
format!("{b:02x}")
}
#[test]
fn seed_debug_is_redacted_for_all_byte_patterns() {
for byte in [0x00, 0xFF, 0xAB, 0x42, 0xDE, 0xAD] {
let seed = Seed::new([byte; 32]);
let debug = format!("{seed:?}");
assert_eq!(
debug, "Seed(**redacted**)",
"Seed Debug must always be 'Seed(**redacted**)' for byte 0x{byte:02x}, got: {debug}"
);
}
}
#[test]
fn seed_debug_does_not_contain_any_seed_byte_hex() {
let seed_bytes = [
0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD,
0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
0x77, 0x88,
];
let seed = Seed::new(seed_bytes);
let debug = format!("{seed:?}");
assert!(
!debug.to_lowercase().contains("dead"),
"Seed Debug must not contain hex seed bytes: {debug}"
);
assert!(
!debug.to_lowercase().contains("beef"),
"Seed Debug must not contain hex seed bytes: {debug}"
);
assert!(
!debug.to_lowercase().contains("cafe"),
"Seed Debug must not contain hex seed bytes: {debug}"
);
}
#[test]
fn factory_debug_does_not_leak_seed_hex() {
let seed_bytes = [0xAB; 32];
let fx = Factory::deterministic(Seed::new(seed_bytes));
let debug = format!("{fx:?}");
assert!(
!debug.to_lowercase().contains(&byte_hex(0xAB)),
"Factory Debug must not contain hex seed bytes, got: {debug}"
);
assert!(
!debug.contains("171"),
"Factory Debug must not contain decimal seed byte value 171, got: {debug}"
);
}
#[test]
fn factory_debug_shows_structure_not_secrets() {
let fx = det_factory(0xFF);
let debug = format!("{fx:?}");
assert!(debug.contains("Factory"), "missing 'Factory' in: {debug}");
assert!(
debug.contains("cache_size"),
"missing 'cache_size' in: {debug}"
);
assert!(
debug.contains("redacted"),
"seed must appear as redacted in: {debug}"
);
}
#[test]
fn mode_debug_deterministic_redacts_seed() {
let seed_bytes = [0xCA; 32];
let mode = Mode::Deterministic {
master: Seed::new(seed_bytes),
};
let debug = format!("{mode:?}");
assert!(
debug.contains("redacted"),
"Mode::Deterministic Debug must contain 'redacted', got: {debug}"
);
assert!(
!debug.to_lowercase().contains("ca"),
"Mode::Deterministic Debug must not contain hex seed bytes, got: {debug}"
);
}
#[test]
fn error_invalid_seed_message_does_not_contain_raw_input() {
let secret_looking_input = "deadbeefcafebabedeadbeefcafebabedeadbeefcafebabedeadbeefcafebabe";
let result = Seed::from_env_value(secret_looking_input);
assert!(result.is_ok());
let invalid_input = "not_hex_but_looks_secretly_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
let result = Seed::from_env_value(invalid_input);
if let Err(msg) = result {
let err_str = msg.to_string();
assert!(
!err_str.contains(invalid_input),
"Error message should not contain the full raw input: {err_str}"
);
}
}
#[test]
fn type_mismatch_panic_does_not_leak_seed() {
use std::panic::{AssertUnwindSafe, catch_unwind};
let seed_bytes = [0xBE; 32];
let fx = Factory::deterministic(Seed::new(seed_bytes));
let _ = fx.get_or_init("domain:sec", "lbl", b"spec", "good", |_rng| 42u32);
let result = catch_unwind(AssertUnwindSafe(|| {
let _ = fx.get_or_init("domain:sec", "lbl", b"spec", "good", |_rng| {
String::from("wrong type")
});
}));
assert!(result.is_err(), "expected panic on type mismatch");
if let Err(payload) = result
&& let Some(msg) = payload.downcast_ref::<String>()
{
let hex_seed: String = seed_bytes.iter().map(|b| format!("{b:02x}")).collect();
assert!(
!msg.contains(&hex_seed),
"panic message must not contain full hex seed: {msg}"
);
assert!(
!msg.contains("[190"),
"panic message must not contain decimal seed array: {msg}"
);
}
}
#[test]
fn factory_debug_with_cached_entries_does_not_leak_values() {
let fx = det_factory(0x01);
let _ = fx.get_or_init("domain:sec", "secret-label", b"spec", "good", |_rng| {
String::from("super_secret_value_12345")
});
let debug = format!("{fx:?}");
assert!(
!debug.contains("super_secret"),
"Factory Debug must not contain cached values: {debug}"
);
assert!(
!debug.contains("12345"),
"Factory Debug must not contain cached values: {debug}"
);
}
#[test]
fn seed_bytes_accessible_but_debug_hidden() {
let raw = [0x42; 32];
let seed = Seed::new(raw);
assert_eq!(seed.bytes(), &raw);
let debug = format!("{seed:?}");
assert!(
!debug.contains("42"),
"Debug must not show raw bytes: {debug}"
);
assert!(
!debug.contains("66"),
"Debug must not show decimal value of 0x42: {debug}"
);
}
#[test]
fn random_factory_debug_is_safe() {
let fx = Factory::random();
let debug = format!("{fx:?}");
assert!(debug.contains("Random"), "should show Random mode: {debug}");
assert!(
debug.contains("Factory"),
"should show Factory struct: {debug}"
);
assert!(
debug.len() < 200,
"Random factory Debug should be concise, got {} chars: {debug}",
debug.len()
);
}