mod testutil;
use uselesskey::prelude::*;
fn fx() -> Factory {
testutil::fx()
}
fn assert_no_key_leakage(debug: &str, context: &str) {
assert!(
!debug.contains("-----BEGIN"),
"{context}: Debug leaks PEM header: {debug}"
);
assert!(
!debug.contains("-----END"),
"{context}: Debug leaks PEM footer: {debug}"
);
assert!(
!debug.contains("PRIVATE KEY"),
"{context}: Debug leaks 'PRIVATE KEY': {debug}"
);
assert!(
!debug.contains("PUBLIC KEY"),
"{context}: Debug leaks 'PUBLIC KEY': {debug}"
);
assert!(
!debug.contains("CERTIFICATE"),
"{context}: Debug leaks 'CERTIFICATE': {debug}"
);
assert!(
!debug.contains("PGP"),
"{context}: Debug leaks 'PGP': {debug}"
);
assert!(
debug.len() < 500,
"{context}: Debug output suspiciously long ({} chars, may contain key material): {}",
debug.len(),
&debug[..200.min(debug.len())]
);
}
#[test]
fn security_seed_debug_is_redacted() {
let seed = Seed::from_env_value("super-secret-seed-value").unwrap();
let debug = format!("{:?}", seed);
assert!(
debug.contains("redacted"),
"Seed Debug should contain 'redacted': {debug}"
);
assert!(
!debug.contains("super-secret"),
"Seed Debug must not contain the input value"
);
assert!(debug.len() < 50, "Seed Debug too long: {debug}");
}
#[test]
fn security_factory_debug_does_not_leak_seed() {
let seed = Seed::from_env_value("another-secret-seed").unwrap();
let fx = Factory::deterministic(seed);
let debug = format!("{:?}", fx);
assert!(
!debug.contains("another-secret"),
"Factory Debug must not contain seed input"
);
assert!(
debug.contains("Factory"),
"Factory Debug should identify the type"
);
assert!(
debug.contains("Deterministic"),
"Factory Debug should show mode"
);
assert!(
debug.contains("redacted"),
"Factory Debug should show redacted seed: {debug}"
);
}
#[test]
fn security_factory_random_debug_is_safe() {
let fx = Factory::random();
let debug = format!("{:?}", fx);
assert!(debug.contains("Random"), "Should show Random mode");
assert_no_key_leakage(&debug, "Factory(Random)");
}
#[test]
#[cfg(feature = "rsa")]
fn security_rsa_keypair_debug_no_leakage() {
let kp = fx().rsa("security-rsa", RsaSpec::rs256());
let debug = format!("{:?}", kp);
assert_no_key_leakage(&debug, "RsaKeyPair");
assert!(
debug.contains("RsaKeyPair"),
"Should identify the type: {debug}"
);
assert!(debug.contains("security-rsa"), "Should show label: {debug}");
}
#[test]
#[cfg(feature = "rsa")]
fn security_rsa_debug_does_not_contain_actual_key_bytes() {
let kp = fx().rsa("security-rsa-bytes", RsaSpec::rs256());
let debug = format!("{:?}", kp);
let pem = kp.private_key_pkcs8_pem();
let base64_lines: Vec<&str> = pem.lines().filter(|l| !l.starts_with("-----")).collect();
for line in &base64_lines {
if line.len() > 8 {
assert!(
!debug.contains(line),
"Debug contains base64 key material: {line}"
);
}
}
}
#[test]
#[cfg(feature = "ecdsa")]
fn security_ecdsa_keypair_debug_no_leakage() {
let kp = fx().ecdsa("security-ecdsa", EcdsaSpec::es256());
let debug = format!("{:?}", kp);
assert_no_key_leakage(&debug, "EcdsaKeyPair(ES256)");
assert!(debug.contains("EcdsaKeyPair"), "Should identify type");
}
#[test]
#[cfg(feature = "ecdsa")]
fn security_ecdsa_es384_debug_no_leakage() {
let kp = fx().ecdsa("security-ecdsa-384", EcdsaSpec::es384());
let debug = format!("{:?}", kp);
assert_no_key_leakage(&debug, "EcdsaKeyPair(ES384)");
}
#[test]
#[cfg(feature = "ecdsa")]
fn security_ecdsa_debug_does_not_contain_actual_key_bytes() {
let kp = fx().ecdsa("security-ecdsa-leak", EcdsaSpec::es256());
let debug = format!("{:?}", kp);
let pem = kp.private_key_pkcs8_pem();
let base64_lines: Vec<&str> = pem
.lines()
.filter(|l| !l.starts_with("-----") && l.len() > 8)
.collect();
for line in &base64_lines {
assert!(
!debug.contains(line),
"ECDSA Debug contains base64 key material: {line}"
);
}
}
#[test]
#[cfg(feature = "ed25519")]
fn security_ed25519_keypair_debug_no_leakage() {
let kp = fx().ed25519("security-ed25519", Ed25519Spec::new());
let debug = format!("{:?}", kp);
assert_no_key_leakage(&debug, "Ed25519KeyPair");
assert!(debug.contains("Ed25519KeyPair"), "Should identify type");
}
#[test]
#[cfg(feature = "ed25519")]
fn security_ed25519_debug_does_not_contain_actual_key_bytes() {
let kp = fx().ed25519("security-ed25519-leak", Ed25519Spec::new());
let debug = format!("{:?}", kp);
let pem = kp.private_key_pkcs8_pem();
let base64_lines: Vec<&str> = pem
.lines()
.filter(|l| !l.starts_with("-----") && l.len() > 8)
.collect();
for line in &base64_lines {
assert!(
!debug.contains(line),
"Ed25519 Debug contains base64 key material: {line}"
);
}
}
#[test]
#[cfg(feature = "hmac")]
fn security_hmac_secret_debug_no_leakage() {
let secret = fx().hmac("security-hmac", HmacSpec::hs256());
let debug = format!("{:?}", secret);
assert_no_key_leakage(&debug, "HmacSecret(HS256)");
assert!(debug.contains("HmacSecret"), "Should identify type");
}
#[test]
#[cfg(feature = "hmac")]
fn security_hmac_hs384_debug_no_leakage() {
let secret = fx().hmac("security-hmac-384", HmacSpec::hs384());
let debug = format!("{:?}", secret);
assert_no_key_leakage(&debug, "HmacSecret(HS384)");
}
#[test]
#[cfg(feature = "hmac")]
fn security_hmac_hs512_debug_no_leakage() {
let secret = fx().hmac("security-hmac-512", HmacSpec::hs512());
let debug = format!("{:?}", secret);
assert_no_key_leakage(&debug, "HmacSecret(HS512)");
}
#[test]
#[cfg(feature = "hmac")]
fn security_hmac_debug_does_not_contain_secret_bytes() {
let secret = fx().hmac("security-hmac-bytes", HmacSpec::hs256());
let debug = format!("{:?}", secret);
let bytes = secret.secret_bytes();
let hex_str = bytes.iter().map(|b| format!("{b:02x}")).collect::<String>();
if hex_str.len() >= 16 {
let fragment = &hex_str[..16];
assert!(
!debug.contains(fragment),
"HMAC Debug contains hex-encoded secret bytes"
);
}
}
#[test]
#[cfg(feature = "token")]
fn security_token_debug_no_leakage() {
let tok = fx().token("security-token", TokenSpec::api_key());
let debug = format!("{:?}", tok);
assert_no_key_leakage(&debug, "TokenFixture(ApiKey)");
assert!(debug.contains("TokenFixture"), "Should identify type");
let value = tok.value();
assert!(
!debug.contains(value),
"Token Debug contains the actual token value"
);
}
#[test]
#[cfg(feature = "token")]
fn security_token_bearer_debug_no_leakage() {
let tok = fx().token("security-bearer", TokenSpec::bearer());
let debug = format!("{:?}", tok);
assert_no_key_leakage(&debug, "TokenFixture(Bearer)");
let value = tok.value();
assert!(
!debug.contains(value),
"Bearer token Debug contains the actual token value"
);
}
#[test]
#[cfg(feature = "pgp")]
fn security_pgp_keypair_debug_no_leakage() {
use uselesskey::{PgpFactoryExt, PgpSpec};
let kp = fx().pgp("security-pgp", PgpSpec::ed25519());
let debug = format!("{:?}", kp);
assert_no_key_leakage(&debug, "PgpKeyPair");
assert!(debug.contains("PgpKeyPair"), "Should identify type");
}
#[test]
#[cfg(feature = "pgp")]
fn security_pgp_debug_does_not_contain_armored_key() {
use uselesskey::{PgpFactoryExt, PgpSpec};
let kp = fx().pgp("security-pgp-leak", PgpSpec::ed25519());
let debug = format!("{:?}", kp);
let armored = kp.private_key_armored();
for line in armored.lines() {
if line.len() > 16 && !line.starts_with("-----") && !line.starts_with("Comment:") {
assert!(
!debug.contains(line),
"PGP Debug contains armored key data: {line}"
);
}
}
}
#[test]
#[cfg(feature = "x509")]
fn security_x509_cert_debug_no_leakage() {
use uselesskey::{X509FactoryExt, X509Spec};
let spec = X509Spec::self_signed("security-cn");
let cert = fx().x509_self_signed("security-x509", spec);
let debug = format!("{:?}", cert);
assert_no_key_leakage(&debug, "X509Cert");
assert!(debug.contains("X509Cert"), "Should identify type");
}
#[test]
#[cfg(feature = "x509")]
fn security_x509_chain_debug_no_leakage() {
use uselesskey::{ChainSpec, X509FactoryExt};
let spec = ChainSpec::new("security-chain-cn");
let chain = fx().x509_chain("security-chain", spec);
let debug = format!("{:?}", chain);
assert_no_key_leakage(&debug, "X509Chain");
assert!(debug.contains("X509Chain"), "Should identify type");
}
#[test]
#[cfg(feature = "x509")]
fn security_x509_debug_does_not_contain_cert_pem() {
use uselesskey::{X509FactoryExt, X509Spec};
let spec = X509Spec::self_signed("security-cn-leak");
let cert = fx().x509_self_signed("security-x509-leak", spec);
let debug = format!("{:?}", cert);
let pem = cert.cert_pem();
let base64_lines: Vec<&str> = pem
.lines()
.filter(|l| !l.starts_with("-----") && l.len() > 8)
.collect();
for line in &base64_lines {
assert!(
!debug.contains(line),
"X509 Debug contains cert PEM data: {line}"
);
}
}
#[test]
#[cfg(feature = "rsa")]
fn security_corrupt_pem_enum_debug_no_leakage() {
let variants = [
CorruptPem::BadHeader,
CorruptPem::BadFooter,
CorruptPem::BadBase64,
CorruptPem::Truncate { bytes: 10 },
CorruptPem::ExtraBlankLine,
];
for v in &variants {
let debug = format!("{:?}", v);
assert!(
!debug.contains("-----BEGIN"),
"CorruptPem Debug leaks PEM: {debug}"
);
assert!(debug.len() < 100, "CorruptPem Debug too long: {debug}");
}
}
#[test]
#[cfg(feature = "rsa")]
fn security_rsa_alternate_debug_no_leakage() {
let kp = fx().rsa("security-rsa-alt", RsaSpec::rs256());
let debug = format!("{:#?}", kp);
assert_no_key_leakage(&debug, "RsaKeyPair(alt)");
}
#[test]
#[cfg(feature = "ecdsa")]
fn security_ecdsa_alternate_debug_no_leakage() {
let kp = fx().ecdsa("security-ecdsa-alt", EcdsaSpec::es256());
let debug = format!("{:#?}", kp);
assert_no_key_leakage(&debug, "EcdsaKeyPair(alt)");
}
#[test]
#[cfg(feature = "ed25519")]
fn security_ed25519_alternate_debug_no_leakage() {
let kp = fx().ed25519("security-ed25519-alt", Ed25519Spec::new());
let debug = format!("{:#?}", kp);
assert_no_key_leakage(&debug, "Ed25519KeyPair(alt)");
}
#[test]
#[cfg(feature = "hmac")]
fn security_hmac_alternate_debug_no_leakage() {
let secret = fx().hmac("security-hmac-alt", HmacSpec::hs256());
let debug = format!("{:#?}", secret);
assert_no_key_leakage(&debug, "HmacSecret(alt)");
}
#[test]
#[cfg(feature = "token")]
fn security_token_alternate_debug_no_leakage() {
let tok = fx().token("security-token-alt", TokenSpec::api_key());
let debug = format!("{:#?}", tok);
assert_no_key_leakage(&debug, "TokenFixture(alt)");
assert!(
!debug.contains(tok.value()),
"Token alt-Debug contains token value"
);
}
#[test]
#[cfg(feature = "pgp")]
fn security_pgp_alternate_debug_no_leakage() {
use uselesskey::{PgpFactoryExt, PgpSpec};
let kp = fx().pgp("security-pgp-alt", PgpSpec::ed25519());
let debug = format!("{:#?}", kp);
assert_no_key_leakage(&debug, "PgpKeyPair(alt)");
}
#[test]
#[cfg(feature = "x509")]
fn security_x509_alternate_debug_no_leakage() {
use uselesskey::{X509FactoryExt, X509Spec};
let spec = X509Spec::self_signed("alt-cn");
let cert = fx().x509_self_signed("security-x509-alt", spec);
let debug = format!("{:#?}", cert);
assert_no_key_leakage(&debug, "X509Cert(alt)");
}
#[test]
fn security_error_missing_env_var_no_leakage() {
let result = Factory::deterministic_from_env("USELESSKEY_NONEXISTENT_VAR_12345");
assert!(result.is_err());
let err = result.unwrap_err();
let debug = format!("{:?}", err);
let display = format!("{}", err);
assert_no_key_leakage(&debug, "Error(Debug)");
assert_no_key_leakage(&display, "Error(Display)");
}
#[test]
#[cfg(feature = "rsa")]
fn security_random_rsa_debug_no_leakage() {
let fx = Factory::random();
let kp = fx.rsa("security-random-rsa", RsaSpec::rs256());
let debug = format!("{:?}", kp);
assert_no_key_leakage(&debug, "RsaKeyPair(random)");
}
#[test]
#[cfg(feature = "ecdsa")]
fn security_random_ecdsa_debug_no_leakage() {
let fx = Factory::random();
let kp = fx.ecdsa("security-random-ecdsa", EcdsaSpec::es256());
let debug = format!("{:?}", kp);
assert_no_key_leakage(&debug, "EcdsaKeyPair(random)");
}
#[test]
#[cfg(feature = "ed25519")]
fn security_random_ed25519_debug_no_leakage() {
let fx = Factory::random();
let kp = fx.ed25519("security-random-ed25519", Ed25519Spec::new());
let debug = format!("{:?}", kp);
assert_no_key_leakage(&debug, "Ed25519KeyPair(random)");
}
#[test]
#[cfg(feature = "hmac")]
fn security_random_hmac_debug_no_leakage() {
let fx = Factory::random();
let secret = fx.hmac("security-random-hmac", HmacSpec::hs256());
let debug = format!("{:?}", secret);
assert_no_key_leakage(&debug, "HmacSecret(random)");
}
#[test]
#[cfg(feature = "full")]
fn security_full_feature_all_types_debug_no_leakage() {
use uselesskey::{
ChainSpec, PgpFactoryExt, PgpSpec, TokenFactoryExt, TokenSpec, X509FactoryExt, X509Spec,
};
let fx = fx();
let rsa_debug = format!("{:?}", fx.rsa("sec-full-rsa", RsaSpec::rs256()));
assert_no_key_leakage(&rsa_debug, "full:RSA");
let ecdsa_debug = format!("{:?}", fx.ecdsa("sec-full-ec256", EcdsaSpec::es256()));
assert_no_key_leakage(&ecdsa_debug, "full:ECDSA-ES256");
let ecdsa384_debug = format!("{:?}", fx.ecdsa("sec-full-ec384", EcdsaSpec::es384()));
assert_no_key_leakage(&ecdsa384_debug, "full:ECDSA-ES384");
let ed_debug = format!("{:?}", fx.ed25519("sec-full-ed", Ed25519Spec::new()));
assert_no_key_leakage(&ed_debug, "full:Ed25519");
let hmac256_debug = format!("{:?}", fx.hmac("sec-full-h256", HmacSpec::hs256()));
assert_no_key_leakage(&hmac256_debug, "full:HMAC-HS256");
let hmac384_debug = format!("{:?}", fx.hmac("sec-full-h384", HmacSpec::hs384()));
assert_no_key_leakage(&hmac384_debug, "full:HMAC-HS384");
let hmac512_debug = format!("{:?}", fx.hmac("sec-full-h512", HmacSpec::hs512()));
assert_no_key_leakage(&hmac512_debug, "full:HMAC-HS512");
let tok = fx.token("sec-full-tok", TokenSpec::api_key());
let tok_debug = format!("{:?}", tok);
assert_no_key_leakage(&tok_debug, "full:Token");
assert!(
!tok_debug.contains(tok.value()),
"full:Token Debug leaks value"
);
let pgp_debug = format!("{:?}", fx.pgp("sec-full-pgp", PgpSpec::ed25519()));
assert_no_key_leakage(&pgp_debug, "full:PGP");
let spec = X509Spec::self_signed("sec-full-cn");
let x509_debug = format!("{:?}", fx.x509_self_signed("sec-full-x509", spec));
assert_no_key_leakage(&x509_debug, "full:X509");
let chain_debug = format!(
"{:?}",
fx.x509_chain("sec-full-chain", ChainSpec::new("sec-full-chain-cn"))
);
assert_no_key_leakage(&chain_debug, "full:X509Chain");
let factory_debug = format!("{:?}", fx);
assert_no_key_leakage(&factory_debug, "full:Factory");
assert!(
factory_debug.contains("redacted"),
"Factory Debug should redact seed"
);
}