uselesskey 0.6.0

Deterministic cryptographic key and certificate fixtures for Rust tests. Seed-stable, cached, scanner-safe.
Documentation

uselesskey

Crates.io docs.rs CI License: MIT OR Apache-2.0

Deterministic cryptographic test fixtures for Rust — stop committing PEM/DER blobs into your repos.

uselesskey is a test-fixture factory that generates cryptographic key material and X.509 certificates at runtime. It is not a crypto library — it replaces committed secret-shaped blobs with a single dev-dependency.

Do not use for production keys. Deterministic keys are predictable by design. Even random-mode keys are intended for tests only.

Why?

Secret scanners have changed the game for test fixtures:

Approach Drawback
Check in PEM files Triggers GitGuardian / GitHub push protection
Generate keys ad-hoc No caching → slow RSA keygen, no determinism
Use raw crypto crates Boilerplate for PEM/DER encoding, no negative fixtures

This crate replaces "security policy + docs + exceptions" with one dev-dependency.

Pick The Lane First

Start with the cheapest lane that preserves the test's semantics.

I need Recommended lane
entropy / scanner-shape only features = ["entropy"] or uselesskey-entropy
JWT / bearer / API-token shapes only features = ["token"] or uselesskey-token
valid runtime crypto semantics leaf crates such as uselesskey-rsa, uselesskey-x509, uselesskey-ssh
build-time materialized fixtures uselesskey-cli materialize + verify

Feature Selection

The facade default feature set is empty. A bare dependency gives you core types like Factory, Mode, and Seed; enable only the fixture families you need.

Token-only consumers can stay lightweight:

Dependency snippets:

  • Quick start (RSA)

    [dev-dependencies]
    uselesskey = { version = "0.6.0", features = ["rsa"] }
    
  • Token-only

    [dev-dependencies]
    uselesskey = { version = "0.6.0", default-features = false, features = ["token"] }
    
  • JWT/JWK

    [dev-dependencies]
    uselesskey = { version = "0.6.0", features = ["rsa", "jwk"] }
    
  • X.509 + rustls

    [dev-dependencies]
    uselesskey = { version = "0.6.0", features = ["x509"] }
    uselesskey-rustls = { version = "0.6.0", features = ["tls-config", "rustls-ring"] }
    
  • jsonwebtoken adapter

    [dev-dependencies]
    uselesskey = { version = "0.6.0", features = ["rsa", "ecdsa", "ed25519", "hmac"] }
    uselesskey-jsonwebtoken = { version = "0.6.0" }
    
  • JOSE/OpenID adapter

    [dev-dependencies]
    uselesskey = { version = "0.6.0", features = ["rsa", "ecdsa", "ed25519", "hmac"] }
    uselesskey-jose-openid = { version = "0.6.0" }
    
  • pgp-native adapter

    [dev-dependencies]
    uselesskey = { version = "0.6.0", features = ["pgp"] }
    uselesskey-pgp-native = { version = "0.6.0" }
    
use uselesskey::{Factory, TokenFactoryExt, TokenSpec};

let fx = Factory::deterministic_from_str("api-key-fixtures");
let token = fx.token("svc-api", TokenSpec::api_key());

assert!(token.value().starts_with("uk_test_"));

Entropy-only consumers can stay lighter still:

[dev-dependencies]
uselesskey = { version = "0.6.0", default-features = false, features = ["entropy"] }
use uselesskey::{EntropyFactoryExt, Factory};

let fx = Factory::deterministic_from_str("entropy-fixtures");
let bytes = fx.entropy("scan-fixture").bytes(64);

assert_eq!(bytes.len(), 64);

If a repo wants static-like local fixtures instead of runtime generation, use uselesskey-cli materialize to generate deterministic outputs into target/ or OUT_DIR, then uselesskey-cli verify in CI to keep the manifest honest.

Quick Start

If you want RSA fixtures, enable rsa explicitly:

[dev-dependencies]
uselesskey = { version = "0.6.0", features = ["rsa"] }
use uselesskey::{Factory, RsaFactoryExt, RsaSpec};

let fx = Factory::random();
let rsa = fx.rsa("my-service", RsaSpec::rs256());

let private_pem = rsa.private_key_pkcs8_pem();
let public_der = rsa.public_key_spki_der();

Deterministic Mode

Same seed + same label + same spec = identical output every time, regardless of call order:

use uselesskey::{Factory, RsaFactoryExt, RsaSpec};

let fx = Factory::deterministic_from_str("my-test-seed");
let rsa = fx.rsa("issuer", RsaSpec::rs256());

Or read the seed from an environment variable in CI:

use uselesskey::Factory;

let fx = Factory::deterministic_from_env("USELESSKEY_SEED")
    .unwrap_or_else(|_| Factory::random());

Supported Key Types

Algorithm Feature Extension Trait Spec Constructor
RSA 2048+ rsa RsaFactoryExt RsaSpec::rs256()
ECDSA P-256 / P-384 ecdsa EcdsaFactoryExt EcdsaSpec::es256() / es384()
Ed25519 ed25519 Ed25519FactoryExt Ed25519Spec::new()
HMAC hmac HmacFactoryExt HmacSpec::hs256() / hs384() / hs512()
Entropy bytes entropy EntropyFactoryExt fx.entropy(label).bytes(len)
OpenPGP pgp PgpFactoryExt PgpSpec::rsa_2048() / ed25519()
Tokens token TokenFactoryExt TokenSpec::api_key() / bearer() / oauth_access_token()
X.509 Certs x509 X509FactoryExt X509Spec::self_signed(cn) / ChainSpec::new(cn)

Feature Flags

Feature Description
rsa RSA keypairs
ecdsa ECDSA P-256 / P-384 keypairs
ed25519 Ed25519 keypairs
hmac HMAC secrets
entropy Deterministic high-entropy byte fixtures
pgp OpenPGP armored + binary keyblocks
token API key, bearer token, OAuth access token fixtures
x509 X.509 self-signed certificates and certificate chains
jwk JWK / JWKS output for enabled key types
all-keys All key algorithms (rsa + ecdsa + ed25519 + hmac + pgp)
full Everything (all-keys + token + x509 + jwk)

The default feature set is empty.

Output Formats

Every key type provides:

  • PKCS#8 PEM / DER — private keys
  • SPKI PEM / DER — public keys
  • Tempfileswrite_* methods for libraries that need file paths
  • JWK / JWKS — with the jwk feature

X.509 certificates additionally provide PEM / DER cert output, identity PEM (cert + key), and chain PEM (leaf + intermediate).

Negative Fixtures

Test error-handling paths with intentionally broken material:

use uselesskey::{Factory, RsaFactoryExt, RsaSpec};
use uselesskey::negative::CorruptPem;

let fx = Factory::random();
let rsa = fx.rsa("test", RsaSpec::rs256());

let bad_pem   = rsa.private_key_pkcs8_pem_corrupt(CorruptPem::BadBase64);
let truncated = rsa.private_key_pkcs8_der_truncated(32);
let mismatch  = rsa.mismatched_public_key_spki_der();

X.509 chains support expired and not-yet-valid leaf/intermediate certs, hostname mismatch, unknown CA, intermediate CA/key-usage violations, and revoked leaf (with CRL):

use uselesskey::{Factory, X509FactoryExt, ChainSpec};

let fx = Factory::random();
let chain = fx.x509_chain("svc", ChainSpec::new("test.example.com"));

let expired    = chain.expired_leaf();
let future     = chain.not_yet_valid_leaf();
let wrong_host = chain.hostname_mismatch("wrong.example.com");
let unknown_ca = chain.unknown_ca();
let bad_ca     = chain.intermediate_not_ca();
let bad_usage  = chain.intermediate_wrong_key_usage();
let revoked    = chain.revoked_leaf();

Adapter Crates

Adapter crates bridge uselesskey fixtures to third-party library types. They are separate crates (not features) to avoid coupling versioning.

Crate Provides
uselesskey-jsonwebtoken jsonwebtoken::EncodingKey / DecodingKey
uselesskey-rustls rustls ServerConfig / ClientConfig builders
uselesskey-tonic tonic::transport TLS identity / config for gRPC
uselesskey-ring ring 0.17 native signing key types
uselesskey-rustcrypto RustCrypto native types (rsa::RsaPrivateKey, etc.)
uselesskey-aws-lc-rs aws-lc-rs native types

Microcrate Architecture

uselesskey is a facade crate that re-exports from a family of focused crates. If you only need one key type and want to minimize compile time, depend on the implementation crate directly:

Crate Purpose
uselesskey-core Factory, derivation, caching
uselesskey-rsa RSA keypairs
uselesskey-ecdsa ECDSA P-256 / P-384
uselesskey-ed25519 Ed25519
uselesskey-hmac HMAC secrets
uselesskey-x509 X.509 certificates

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.