rustls CryptoProvider (rustls-native-ossl)
rustls-native-ossl is a complete
rustls CryptoProvider that routes all
cryptographic operations through OpenSSL via native-ossl. It replaces the
default ring-backed provider with one that uses the system OpenSSL library —
no ring dependency anywhere in the TLS stack.
Why use it
- System OpenSSL — reuses the library already present on the host, including FIPS-validated builds.
- No ring — eliminates the ring dependency and its assembly code entirely.
- Full algorithm coverage — TLS 1.3 and TLS 1.2, ECDSA, Ed25519, RSA-PSS, RSA-PKCS1, X25519, P-256, P-384.
- Key material zeroization — all secret keys and AEAD keys are stored in
SecretBuf, which callsOPENSSL_cleanseon drop.
Adding the dependency
[dependencies]
rustls-native-ossl = { path = "../rustls-native-ossl" }
rustls = { version = "0.23", default-features = false, features = ["std"] }
Feature flags
| Feature | Default | Description |
|---|---|---|
tls12 | yes | Enables TLS 1.2 cipher suites |
Disable tls12 for a TLS-1.3-only build:
rustls-native-ossl = { path = "../rustls-native-ossl", default-features = false }
Quick start
Process-wide default provider
fn main() {
rustls_native_ossl::default_provider()
.install_default()
.expect("failed to install OpenSSL provider");
// All subsequent rustls configs use OpenSSL automatically.
}
Per-config provider
#![allow(unused)]
fn main() {
use rustls::{ClientConfig, RootCertStore};
let roots = RootCertStore::empty(); // populate from system store or PEM
let config = ClientConfig::builder_with_provider(
rustls_native_ossl::default_provider().into(),
)
.with_safe_default_protocol_versions()
.unwrap()
.with_root_certificates(roots)
.with_no_client_auth();
}
Server config
#![allow(unused)]
fn main() {
use rustls::ServerConfig;
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
fn make_server_config(
cert_chain: Vec<CertificateDer<'static>>,
key: PrivateKeyDer<'static>,
) -> ServerConfig {
ServerConfig::builder_with_provider(
rustls_native_ossl::default_provider().into(),
)
.with_safe_default_protocol_versions()
.unwrap()
.with_no_client_auth()
.with_single_cert(cert_chain, key)
.unwrap()
}
}
Provided algorithms
Cipher suites
ALL_CIPHER_SUITES (and DEFAULT_CIPHER_SUITES) lists all suites in preference
order: TLS 1.3 first, then TLS 1.2.
TLS 1.3 (always available):
| Suite | AEAD | Hash |
|---|---|---|
TLS13_AES_256_GCM_SHA384 | AES-256-GCM | SHA-384 |
TLS13_AES_128_GCM_SHA256 | AES-128-GCM | SHA-256 |
TLS13_CHACHA20_POLY1305_SHA256 | ChaCha20-Poly1305 | SHA-256 |
TLS 1.2 (requires tls12 feature, enabled by default):
| Suite | Key exchange | AEAD | Hash |
|---|---|---|---|
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 | ECDHE | AES-256-GCM | SHA-384 |
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 | ECDHE | AES-128-GCM | SHA-256 |
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 | ECDHE | ChaCha20-Poly1305 | SHA-256 |
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 | ECDHE | AES-256-GCM | SHA-384 |
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 | ECDHE | AES-128-GCM | SHA-256 |
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 | ECDHE | ChaCha20-Poly1305 | SHA-256 |
Key exchange groups
ALL_KX_GROUPS (and DEFAULT_KX_GROUPS) in preference order:
| Group | Static | Algorithm |
|---|---|---|
| X25519 | kx_group::X25519 | RFC 7748 |
| secp256r1 (P-256) | kx_group::SECP256R1 | NIST P-256 |
| secp384r1 (P-384) | kx_group::SECP384R1 | NIST P-384 |
EC peer public keys are validated for correct length and uncompressed-point
format (0x04 prefix) before ECDH is attempted.
Signature verification
SUPPORTED_SIG_ALGS covers the following combinations:
| Algorithm | Curves / key types | Hash |
|---|---|---|
| ECDSA | P-256 | SHA-256 |
| ECDSA | P-256 | SHA-384 |
| ECDSA | P-384 | SHA-256 |
| ECDSA | P-384 | SHA-384 |
| Ed25519 | — | internal |
| RSA-PSS | ≥ 2048-bit | SHA-256 / 384 / 512 |
| RSA-PKCS1 | ≥ 2048-bit | SHA-256 / 384 / 512 |
RSA public keys smaller than 2048 bits are rejected before verification.
Signing (server / mTLS client keys)
KeyProvider::load_private_key (wired into default_provider()) accepts:
PrivateKeyDer::Pkcs8— RSA, ECDSA (P-256, P-384), Ed25519 in PKCS#8 DERPrivateKeyDer::Sec1— EC private keys in SEC 1 DERPrivateKeyDer::Rsa— RSA private keys in PKCS#1 DER
Supported signature schemes: ECDSA_NISTP256_SHA256, ECDSA_NISTP384_SHA384,
ED25519, RSA_PSS_SHA256/384/512, RSA_PKCS1_SHA256/384/512.
Building a custom provider
Individual cipher suites and KX groups are re-exported under dedicated modules so you can assemble a restricted provider:
#![allow(unused)]
fn main() {
use rustls::crypto::CryptoProvider;
use rustls_native_ossl::{cipher_suite, kx_group, ALL_KX_GROUPS, SUPPORTED_SIG_ALGS};
use rustls_native_ossl::default_provider;
// TLS 1.3 + AES-only (no ChaCha20, no TLS 1.2)
let restricted = CryptoProvider {
cipher_suites: vec![
cipher_suite::TLS13_AES_256_GCM_SHA384,
cipher_suite::TLS13_AES_128_GCM_SHA256,
],
kx_groups: vec![kx_group::X25519, kx_group::SECP256R1],
..default_provider()
};
}
Hybrid post-quantum example
The workspace ships examples/hybrid_pq.rs in the rustls-native-ossl crate.
It demonstrates layering an ML-KEM-768 application-layer key exchange on top of
a classical TLS 1.3 connection to achieve post-quantum forward secrecy.
cargo run --example hybrid_pq
Protocol outline
- Server generates an Ed25519 keypair and self-signed X.509 certificate.
- Server and client perform a standard TLS 1.3 handshake (X25519 + Ed25519).
- Client generates an ML-KEM-768 keypair; sends the SPKI DER over TLS.
- Server encapsulates:
EncapCtx::new(&pub_key).encapsulate()→ ciphertext + shared secret. - Server sends the ciphertext over TLS.
- Client decapsulates:
DecapCtx::new(&priv_key).decapsulate(&ciphertext)→ same shared secret. - Both sides run HKDF-SHA256 over the ML-KEM shared secret to produce a 32-byte hybrid session key.
The example verifies that both parties derive the same key and prints it.
Why application-layer, not in-TLS? The rustls SupportedKxGroup contract
requires the local public key to be available before complete() is called.
ML-KEM ciphertext (the server’s “public key” in the encapsulation step) can
only be created after seeing the client’s ML-KEM public key, which arrives
inside complete(). Application-layer hybrid is the correct design for
post-quantum augmentation of classical TLS.
Requires OpenSSL 3.3 or later with ML-KEM support.
Security properties
| Property | Status |
|---|---|
| TLS 1.3 forward secrecy | Provided by X25519 / P-256 / P-384 ECDHE |
| Key material zeroization | SecretBuf (OPENSSL_cleanse) on all private keys, AEAD keys, HMAC keys |
| RSA minimum key size | 2048 bits enforced in verify_signature |
| EC point validation | Uncompressed format + exact length checked before ECDH |
| FIPS readiness | Inherits OpenSSL FIPS provider when the system library is FIPS-validated; no FIPS flag is set in code |
The fips() method on all cipher suites and KX groups returns false — FIPS
compliance is a property of the OpenSSL installation, not this crate.