Deterministic cryptographic test fixtures for Rust.
uselesskey is a test-fixture factory, not a crypto library. It generates key material, certificates, token-shaped fixtures, and negative artifacts at runtime so tests do not need committed PEM/DER/JWK blobs.
uselesskey is a test-fixture layer, not a runtime crypto service.
Use it when you need realistic cryptographic fixtures without committing PEM/DER/JWK files.
It exists to remove this test friction:
- scanners inspect every commit in a PR, not just the final diff
- fake-looking keys still trigger policy, push protection, and review friction
uselesskey replaces security exceptions + path ignores + fixture directories with one dev-dependency and runtime generation.
Do not use this crate for production key generation or certificate management. Deterministic mode is intentionally predictable by design. Random mode is for tests only.
Without this layer, teams commonly end up with one of these:
| Approach | Problem |
|---|---|
| Commit PEM/DER files | Triggers scanners and push protection |
| Generate keys ad hoc in tests | Repeated boilerplate, slow RSA, no shared determinism |
| Use raw crypto crates directly | You still have to assemble PEM/DER/JWK/X.509 shapes yourself |
Use rcgen or other runtime crates directly |
Useful, but not centered on fixture ergonomics, determinism, or negative cases |
uselesskey is built specifically for test artifacts.
- RSA (2048, 3072, 4096)
- ECDSA (P-256, P-384)
- Ed25519
- HMAC (HS256, HS384, HS512)
- OpenPGP (RSA 2048/3072, Ed25519)
- Token fixtures (API key, bearer, OAuth access-token / JWT shape)
- X.509 self-signed certificates and certificate chains
- PKCS#8 PEM/DER
- SPKI PEM/DER
- OpenPGP armored and binary keyblocks
- JWK / JWKS
- tempfiles for path-based APIs
- X.509 leaves and chains, and negative variants
- corrupt PEM
- truncated DER
- mismatched keypairs
- expired / revoked / hostname-mismatch / unknown-CA certificates
Start with the cheapest lane that preserves the test's semantics.
| I need | Recommended lane | Why |
|---|---|---|
| entropy / scanner-shape only | uselesskey-entropy or facade features = ["entropy"] |
deterministic bytes without key-generation baggage |
| JWT / bearer / API-token shapes only | uselesskey-token or facade features = ["token"] |
token-shaped fixtures without RSA/X.509 pull-in |
| valid runtime crypto semantics | leaf crates such as uselesskey-rsa, uselesskey-x509, uselesskey-ssh |
real PKCS#8/JWK/X.509/SSH fixture behavior |
| build-time materialized fixtures | uselesskey-cli materialize + verify |
clean shape-only OUT_DIR / include_bytes! workflow, with RSA materialization as an explicit opt-in |
Start with docs/how-to/choose-features.md for feature selection. Use docs/how-to/choose-lane.md when deciding between entropy, token, semantic, and materialized fixture workflows. See docs/reference/dependency-economics.md and docs/reference/audit-surface.md for the current local-cost and advisory receipts.
The uselesskey facade has an empty default feature set. Enable only the fixture families you need.
Common starting points:
# Entropy-only fixtures
[dev-dependencies]
uselesskey = { version = "0.6.0", default-features = false, features = ["entropy"] }# RSA fixtures
[dev-dependencies]
uselesskey = { version = "0.6.0", features = ["rsa"] }# Token-only fixtures, no RSA/X.509 pull-in
[dev-dependencies]
uselesskey = { version = "0.6.0", default-features = false, features = ["token"] }# RSA + JWK/JWKS
[dev-dependencies]
uselesskey = { version = "0.6.0", features = ["rsa", "jwk"] }# X.509 fixtures
[dev-dependencies]
uselesskey = { version = "0.6.0", features = ["x509"] }# Build-time materialization, shape-only common lane
cargo run -p uselesskey-cli -- materialize --manifest crates/materialize-shape-buildrs-example/uselesskey-fixtures.toml --out-dir target/tmp-fixtures
cargo run -p uselesskey-cli -- verify --manifest crates/materialize-shape-buildrs-example/uselesskey-fixtures.toml --out-dir target/tmp-fixturesFor build.rs consumers:
# Common shape-only build-time path
[build-dependencies]
uselesskey-cli = { version = "0.6.0", default-features = false }# Specialized RSA PKCS#8 build-time path
[build-dependencies]
uselesskey-cli = { version = "0.6.0", default-features = false, features = ["rsa-materialize"] }Use the facade for convenience. Depend on leaf crates only when compile-time minimization matters enough to justify the sharper API.
If you are unsure which flags to start with, start from docs/how-to/choose-features.md. For downstream bot/reviewer policy, use docs/how-to/downstream-fixture-policy.md. For a crate-by-crate support contract (stable/incubating/experimental, audience, and publish status), see docs/reference/support-matrix.md.
use uselesskey::{Factory, RsaFactoryExt, RsaSpec};
// Random mode: different keys every run
let fx = Factory::random();
// Deterministic mode: stable output for a seed string
let fx = Factory::deterministic_from_str("my-test-seed");
// Or use env-var seed with random fallback
let fx = Factory::deterministic_from_env("USELESSKEY_SEED")
.unwrap_or_else(|_| Factory::random());
let rsa = fx.rsa("issuer", RsaSpec::rs256());
let pkcs8_pem = rsa.private_key_pkcs8_pem();
let spki_der = rsa.public_key_spki_der();The core shape is always:
(mode, domain, label, spec, variant) -> artifact
That keeps fixtures stable in deterministic mode and cacheable in both modes.
rsafor PEM/DER, tempfiles, and negative-key examplesrsa+jwkforpublic_jwk()/public_jwks()x509for certificate, rustls, and tonic examplestokenfor token-shaped fixtures onlypgpfor armored/binary OpenPGP fixtures
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" }
Requires features = ["rsa", "jwk"].
use uselesskey::{Factory, RsaSpec, RsaFactoryExt};
let fx = Factory::random();
let rsa = fx.rsa("issuer", RsaSpec::rs256());
let jwk = rsa.public_jwk();
let jwks = rsa.public_jwks();use uselesskey::{Factory, RsaSpec, RsaFactoryExt};
let fx = Factory::random();
let rsa = fx.rsa("server", RsaSpec::rs256());
let keyfile = rsa.write_private_key_pkcs8_pem().unwrap();
assert!(keyfile.path().exists());Requires features = ["x509"].
Self-signed certificates for simple TLS tests:
use uselesskey::{Factory, X509FactoryExt, X509Spec};
let fx = Factory::random();
let cert = fx.x509_self_signed("my-service", X509Spec::self_signed("test.example.com"));
let cert_pem = cert.cert_pem();
let key_pem = cert.private_key_pkcs8_pem();Three-level chains (root intermediate leaf):
use uselesskey::{Factory, X509FactoryExt, ChainSpec};
let fx = Factory::random();
let chain = fx.x509_chain("my-service", ChainSpec::new("test.example.com"));
// Standard TLS server chain: leaf + intermediate, no root
let chain_pem = chain.chain_pem();
// Individual artifacts for custom setups
let root_pem = chain.root_cert_pem();
let leaf_key = chain.leaf_private_key_pkcs8_pem();These are for error-path tests, not validation logic.
use uselesskey::{Factory, X509FactoryExt, ChainSpec};
let fx = Factory::random();
let chain = fx.x509_chain("my-service", ChainSpec::new("test.example.com"));
// Expired leaf certificate
let expired = chain.expired_leaf();
// Hostname mismatch (SAN doesn't match expected hostname)
let wrong_host = chain.hostname_mismatch("wrong.example.com");
// Signed by an unknown CA (not in your trust store)
let unknown = chain.unknown_ca();
// Revoked leaf with CRL signed by the intermediate CA
let revoked = chain.revoked_leaf();
let crl_pem = revoked.crl_pem().expect("CRL present for revoked variant");use uselesskey::{Factory, RsaSpec, RsaFactoryExt};
use uselesskey::negative::CorruptPem;
let fx = Factory::random();
let rsa = fx.rsa("issuer", RsaSpec::rs256());
let bad_pem = rsa.private_key_pkcs8_pem_corrupt(CorruptPem::BadBase64);
let truncated = rsa.private_key_pkcs8_der_truncated(32);
let mismatched_pub = rsa.mismatched_public_key_spki_der();Token fixtures are artifact shapes, not an auth framework. They exist so tests can use realistic-looking token values without committing blobs.
use uselesskey::{Factory, TokenFactoryExt, TokenSpec};
let fx = Factory::random();
let api_key = fx.token("billing", TokenSpec::api_key());
let bearer = fx.token("gateway", TokenSpec::bearer());
let oauth = fx.token("issuer", TokenSpec::oauth_access_token());
assert!(api_key.value().starts_with("uk_test_"));
assert!(bearer.authorization_header().starts_with("Bearer "));
assert_eq!(oauth.value().split('.').count(), 3);Adapter crates are separate packages, not facade features. That keeps integration versioning explicit and avoids coupling the facade to every downstream ecosystem type.
Use them when you want native third-party library types returned directly from fixture artifacts.
With the tls-config feature, build rustls configs in one step:
[dev-dependencies]
uselesskey = { version = "0.6.0", features = ["x509"] }
uselesskey-rustls = { version = "0.6.0", features = ["tls-config", "rustls-ring"] }use uselesskey::{ChainSpec, Factory, X509FactoryExt};
use uselesskey_rustls::{RustlsServerConfigExt, RustlsClientConfigExt};
let fx = Factory::random();
let chain = fx.x509_chain("my-service", ChainSpec::new("test.example.com"));
let server_config = chain.server_config_rustls();
let client_config = chain.client_config_rustls();[dev-dependencies]
uselesskey = { version = "0.6.0", features = ["rsa"] }
uselesskey-ring = { version = "0.6.0", features = ["all"] }use uselesskey::{Factory, RsaFactoryExt, RsaSpec};
use uselesskey_ring::RingRsaKeyPairExt;
let fx = Factory::random();
let rsa = fx.rsa("signer", RsaSpec::rs256());
let ring_kp = rsa.rsa_key_pair_ring();[dev-dependencies]
uselesskey = { version = "0.6.0", features = ["rsa"] }
uselesskey-rustcrypto = { version = "0.6.0", features = ["all"] }use uselesskey::{Factory, RsaFactoryExt, RsaSpec};
use uselesskey_rustcrypto::RustCryptoRsaExt;
let fx = Factory::random();
let rsa = fx.rsa("signer", RsaSpec::rs256());
let rsa_pk = rsa.rsa_private_key();[dev-dependencies]
uselesskey = { version = "0.6.0", features = ["rsa"] }
uselesskey-aws-lc-rs = { version = "0.6.0", features = ["native", "all"] }use uselesskey::{Factory, RsaFactoryExt, RsaSpec};
use uselesskey_aws_lc_rs::AwsLcRsRsaKeyPairExt;
let fx = Factory::random();
let rsa = fx.rsa("signer", RsaSpec::rs256());
let lc_kp = rsa.rsa_key_pair_aws_lc_rs();[dev-dependencies]
uselesskey = { version = "0.6.0", features = ["x509"] }
uselesskey-tonic = "0.6.0"use uselesskey::{ChainSpec, Factory, X509FactoryExt};
use uselesskey_tonic::{TonicClientTlsExt, TonicServerTlsExt};
let fx = Factory::random();
let chain = fx.x509_chain("grpc", ChainSpec::new("test.example.com"));
let server_tls = chain.server_tls_config_tonic();
let client_tls = chain.client_tls_config_tonic("test.example.com");The crates/uselesskey/examples/ directory contains standalone programs. Because the facade default feature set is empty, run them with cargo run -p uselesskey --example <name> --features "<flags>" using one working feature set below:
| Example | Feature(s) | Description |
|---|---|---|
| adapter_jsonwebtoken | rsa,ecdsa,ed25519,hmac |
Sign and verify JWTs using jsonwebtoken crate integration |
| adapter_rustls | x509 |
Convert X.509 fixtures into rustls ServerConfig / ClientConfig |
| basic_ecdsa | ecdsa,jwk |
Generate ECDSA keypairs for P-256 and P-384 in PEM, DER, JWK |
| basic_ed25519 | ed25519,jwk |
Generate Ed25519 keypairs in PEM, DER, and JWK formats |
| basic_hmac | hmac,jwk |
Generate HMAC secrets for HS256, HS384, and HS512 |
| basic_rsa | rsa,jwk |
Generate RSA keypairs in PEM, DER, and JWK formats |
| basic_token | token |
Generate API key, bearer token, and OAuth access-token fixtures |
| basic_usage | ecdsa,ed25519,rsa,jwk |
All-in-one: RSA, ECDSA, and Ed25519 fixture generation |
| deterministic | rsa |
Reproducible fixtures from seeds - same seed always yields the same key |
| deterministic_mode | rsa,ecdsa,ed25519 |
Order-independent deterministic derivation guarantees |
| jwk_generation | ecdsa,ed25519,hmac,rsa,jwk |
Build JWKs and JWKS with JwksBuilder across key types |
| jwk_jwks | ecdsa,ed25519,hmac,rsa,jwk |
JWK sets from multiple key types with metadata inspection |
| jwks | rsa,ecdsa,jwk |
Build a JWKS from RSA and ECDSA public keys |
| jwks_server_mock | rsa,ecdsa,ed25519,jwk |
Generate a JWKS response body for a mock /.well-known/jwks.json endpoint |
| jwt_rs256_jwks | rsa,jwk |
RSA keypairs with JWK/JWKS extraction for JWT verification flows |
| jwt_signing | rsa,jwk |
JWT signing with deterministic RSA, ECDSA, and HMAC keys (ECDSA/HMAC optional) |
| negative_fixtures | x509 |
Intentionally invalid certificates and keys for error-path testing |
| tempfile_paths | rsa,ed25519 |
Write key fixtures to temporary files for path-based APIs |
| tempfiles | x509 |
Write X.509 cert, key, and identity PEM to temp files |
| tls_server | x509 |
Certificate chain generation for TLS server testing |
| token_generation | token |
Realistic API keys, bearer tokens, and OAuth tokens for tests |
| x509_certificates | x509 |
Self-signed certs, cert chains, and negative X.509 fixtures |
uselesskey is a facade crate that re-exports from focused implementation crates.
Depend on the facade for convenience, or on individual crates to minimize compile time.
| Crate | Description |
|---|---|
uselesskey |
Public facade — re-exports all key types and traits behind feature flags |
uselesskey-core |
Factory, deterministic derivation, caching, and negative-fixture helpers |
uselesskey-entropy |
Deterministic high-entropy byte fixtures for scanner-safe and placeholder tests |
uselesskey-rsa |
RSA 2048/3072/4096 keypairs (PKCS#8, SPKI, PEM, DER) |
uselesskey-ecdsa |
ECDSA P-256 / P-384 keypairs |
uselesskey-ed25519 |
Ed25519 keypairs |
uselesskey-hmac |
HMAC HS256/HS384/HS512 secrets |
uselesskey-ssh |
Deterministic OpenSSH key and certificate fixtures |
uselesskey-pgp |
OpenPGP key fixtures (armored + binary keyblocks) |
uselesskey-token |
API key, bearer token, and OAuth access-token fixtures |
uselesskey-webhook |
Deterministic webhook fixtures for GitHub, Stripe, and Slack signature tests |
uselesskey-jwk |
Typed JWK/JWKS models and builders |
uselesskey-x509 |
X.509 self-signed certificates and certificate chains |
uselesskey-cli |
Command-line fixture generation, bundling, and export helpers |
uselesskey-test-server |
Deterministic OIDC discovery and JWKS HTTP test server fixtures |
| Crate | Description |
|---|---|
uselesskey-axum |
axum auth-test helpers with deterministic JWKS/OIDC routes |
uselesskey-jsonwebtoken |
jsonwebtoken EncodingKey / DecodingKey |
uselesskey-jose-openid |
JOSE/OpenID-oriented native jsonwebtoken key conversions |
uselesskey-pgp-native |
Native pgp SignedSecretKey / SignedPublicKey adapters |
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 |
The uselesskey facade defaults to no features.
Extension traits by feature:
rsa:RsaFactoryExtecdsa:EcdsaFactoryExted25519:Ed25519FactoryExthmac:HmacFactoryExtpgp:PgpFactoryExttoken:TokenFactoryExtx509:X509FactoryExt
For output-family coverage and dependency implications, use the matrix below.
| Feature | Extension Trait | Algorithms / Outputs | Implies |
|---|---|---|---|
rsa |
RsaFactoryExt |
RSA 2048/3072/4096 — PKCS#8, SPKI, PEM, DER | — |
ecdsa |
EcdsaFactoryExt |
P-256 (ES256), P-384 (ES384) — PKCS#8, SPKI | — |
ed25519 |
Ed25519FactoryExt |
Ed25519 — PKCS#8, SPKI | — |
hmac |
HmacFactoryExt |
HS256, HS384, HS512 | — |
pgp |
PgpFactoryExt |
OpenPGP RSA 2048/3072, Ed25519 — armored, binary | — |
token |
TokenFactoryExt |
API key, bearer access token, and OAuth access token | — |
x509 |
X509FactoryExt |
Self-signed certs, cert chains, negative certs | rsa |
jwk |
— | JWK/JWKS output for all enabled key types | — |
all-keys |
— | (bundle) | rsa ecdsa ed25519 hmac pgp |
full |
— | (everything) | all-keys token x509 jwk |
Each adapter crate has per-algorithm feature flags (rsa, ecdsa, ed25519, hmac) and an all convenience flag.
| Adapter | RSA | ECDSA | Ed25519 | HMAC | X.509 / TLS | Extra features |
|---|---|---|---|---|---|---|
uselesskey-jsonwebtoken |
✓ | ✓ | ✓ | ✓ | — | — |
uselesskey-jose-openid |
✓ | ✓ | ✓ | ✓ | — | — |
uselesskey-pgp-native |
— | — | — | — | — | — |
uselesskey-ring |
✓ | ✓ | ✓ | — | — | — |
uselesskey-rustcrypto |
✓ | ✓ | ✓ | ✓ | — | — |
uselesskey-aws-lc-rs |
✓ | ✓ | ✓ | — | — | native (enables aws-lc-rs dep) |
uselesskey-rustls |
✓ | ✓ | ✓ | — | ✓ | tls-config, rustls-ring, rustls-aws-lc-rs |
uselesskey-tonic |
— | — | — | — | ✓ | — |
Fixtures derive from stable identity components:
seed + (domain, label, spec, variant) -> derived seed -> artifact
Adding new fixtures doesn't perturb existing ones. Test order doesn't matter.
RSA keygen is expensive. Per-factory caching by (domain, label, spec, variant) makes runtime generation cheap enough to replace committed fixtures.
Ask for shapes first: PKCS#8, SPKI, PEM, DER, JWK, JWKS, or tempfiles. Consumers ask for artifact shapes; low-level crypto primitives are intentionally not the default output.
Corrupt PEM, truncated DER, mismatched keys, expired certs, revoked leaves with CRLs: these are exactly the artifacts teams otherwise handcraft and commit.
uselesskey makes them deterministic, cheap, and disposable.
- production key generation
- runtime certificate authority behavior
- certificate validation logic
- HSM / TPM / hardware-backed keys
- signing or verification APIs as the primary abstraction
For runtime certificate generation, reach for rcgen directly. For validation, use rustls, x509-parser, or the library actually responsible for verification.
Use uselesskey when you need realistic test fixtures that should not live in git history.
Reach for:
rcgenwhen you need runtime certificate generation outside a fixture-centric workflowrustlswhen you need TLS runtime integration and validationx509-parserwhen you need parsing/inspection/validation work
- CHANGELOG — release history
- CONTRIBUTING — how to build, test, and add new key types
- SECURITY — security policy (this is a test-only crate)
- CODE_OF_CONDUCT — Contributor Covenant
- SUPPORT — how to get help
Derivation stability
Artifacts for a given (seed, domain, label, spec, variant) tuple are stable within the same DerivationVersion.
If derivation logic changes, a new derivation version is introduced instead of mutating the old one.
Semver
Breaking API changes bump the minor version until 1.0, then the major version.
MSRV The minimum supported Rust version is 1.92 (edition 2024).
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.