Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 calls OPENSSL_cleanse on drop.

Adding the dependency

[dependencies]
rustls-native-ossl = { path = "../rustls-native-ossl" }
rustls = { version = "0.23", default-features = false, features = ["std"] }

Feature flags

FeatureDefaultDescription
tls12yesEnables 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):

SuiteAEADHash
TLS13_AES_256_GCM_SHA384AES-256-GCMSHA-384
TLS13_AES_128_GCM_SHA256AES-128-GCMSHA-256
TLS13_CHACHA20_POLY1305_SHA256ChaCha20-Poly1305SHA-256

TLS 1.2 (requires tls12 feature, enabled by default):

SuiteKey exchangeAEADHash
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384ECDHEAES-256-GCMSHA-384
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256ECDHEAES-128-GCMSHA-256
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256ECDHEChaCha20-Poly1305SHA-256
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384ECDHEAES-256-GCMSHA-384
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256ECDHEAES-128-GCMSHA-256
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256ECDHEChaCha20-Poly1305SHA-256

Key exchange groups

ALL_KX_GROUPS (and DEFAULT_KX_GROUPS) in preference order:

GroupStaticAlgorithm
X25519kx_group::X25519RFC 7748
secp256r1 (P-256)kx_group::SECP256R1NIST P-256
secp384r1 (P-384)kx_group::SECP384R1NIST 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:

AlgorithmCurves / key typesHash
ECDSAP-256SHA-256
ECDSAP-256SHA-384
ECDSAP-384SHA-256
ECDSAP-384SHA-384
Ed25519internal
RSA-PSS≥ 2048-bitSHA-256 / 384 / 512
RSA-PKCS1≥ 2048-bitSHA-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 DER
  • PrivateKeyDer::Sec1 — EC private keys in SEC 1 DER
  • PrivateKeyDer::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

  1. Server generates an Ed25519 keypair and self-signed X.509 certificate.
  2. Server and client perform a standard TLS 1.3 handshake (X25519 + Ed25519).
  3. Client generates an ML-KEM-768 keypair; sends the SPKI DER over TLS.
  4. Server encapsulates: EncapCtx::new(&pub_key).encapsulate() → ciphertext + shared secret.
  5. Server sends the ciphertext over TLS.
  6. Client decapsulates: DecapCtx::new(&priv_key).decapsulate(&ciphertext) → same shared secret.
  7. 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

PropertyStatus
TLS 1.3 forward secrecyProvided by X25519 / P-256 / P-384 ECDHE
Key material zeroizationSecretBuf (OPENSSL_cleanse) on all private keys, AEAD keys, HMAC keys
RSA minimum key size2048 bits enforced in verify_signature
EC point validationUncompressed format + exact length checked before ECDH
FIPS readinessInherits 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.