← Week 3: Hybrid Schemes and TLS

Day 20: rustls with aws-lc-rs CryptoProvider

Phase 3 · July 27, 2026

← Week 3: Hybrid Schemes and TLS

Agenda (2–3 hours)

  • Read (30 min): rustls-aws-lc-rs README; rustls CryptoProvider docs
  • Build (120 min): TLS client and server using aws-lc-rs provider with X25519MLKEM768
  • Verify (30 min): Confirm hybrid group is negotiated in the handshake
← Week 3: Hybrid Schemes and TLS

rustls CryptoProvider Architecture

rustls 0.23+ has a pluggable crypto backend:

// Default (ring):
rustls::crypto::ring::default_provider().install_default()?;

// AWS-LC with PQC support:
rustls_aws_lc_rs::default_provider().install_default()?;

Once rustls_aws_lc_rs is installed as the default provider:

  • X25519MLKEM768 is available as a KeyExchangeGroup
  • It is included in the default ClientConfig key_shares
  • Servers that support it will negotiate it automatically
← Week 3: Hybrid Schemes and TLS

Installing aws-lc-rs as the rustls Provider

[dependencies]
rustls = "0.23"
rustls-aws-lc-rs = "0.2"
tokio-rustls = "0.26"
fn main() -> anyhow::Result<()> {
    // Must be called before any rustls config is built
    rustls_aws_lc_rs::default_provider()
        .install_default()
        .expect("failed to install aws-lc-rs provider");

    // Now build configs as normal — PQC is automatic
    let config = ClientConfig::builder()
        .with_root_certificates(root_store)
        .with_no_client_auth();

    // ClientHello will include X25519MLKEM768 in key_share
    Ok(())
}
← Week 3: Hybrid Schemes and TLS

Confirming PQC Negotiation

use tokio_rustls::TlsConnector;

let stream = connector.connect(server_name, tcp_stream).await?;
let (_, conn) = stream.get_ref();

// Print the negotiated key exchange group
println!("TLS version: {:?}", conn.protocol_version());
println!("Cipher suite: {:?}", conn.negotiated_cipher_suite());
// Note: rustls doesn't expose key exchange group directly in 0.23
// Use Wireshark/tshark to confirm X25519MLKEM768 in the ServerHello

For server-side confirmation:

# Connect to your server and check the key_share
openssl s_client -connect localhost:4433 -groups X25519MLKEM768
# or use a chrome browser which also supports it
← Week 3: Hybrid Schemes and TLS

Custom Provider with Specific Groups

You can configure which key exchange groups to offer:

use rustls::crypto::CryptoProvider;
use rustls_aws_lc_rs::AWS_LC_RS;

let provider = CryptoProvider {
    kx_groups: vec![
        // Prefer hybrid PQC, fall back to classical
        aws_lc_rs::kx_group::X25519_MLKEM768,
        aws_lc_rs::kx_group::X25519,
    ],
    ..AWS_LC_RS.clone()
};
provider.install_default()?;

For mandatory hybrid (provisioning service that controls both endpoints):

kx_groups: vec![aws_lc_rs::kx_group::X25519_MLKEM768], // only hybrid
← Week 3: Hybrid Schemes and TLS

Challenge Assignment

Build pqc-demo/src/tls.rs:

pub async fn run_hybrid_tls_demo() -> anyhow::Result<()>

This function should:

  1. Install aws-lc-rs as the rustls provider
  2. Start an in-memory TLS server with a test cert (from toy-pki or rcgen)
  3. Connect from a client with X25519MLKEM768 as the only key exchange group
  4. Exchange a "hello world" message
  5. Verify the connection succeeded
  6. Run tshark or openssl s_client against the server and confirm the
    negotiated group is hybrid (from the Wireshark/openssl output)
← Week 3: Hybrid Schemes and TLS

Resources

  • rustls-aws-lc-rs: docs.rs/rustls-aws-lc-rs
  • rustls CryptoProvider: docs.rs/rustls — §CryptoProvider
  • aws-lc-rs kx_groups: docs.rs/aws-lc-rs — look for X25519_MLKEM768
  • AWS blog: "Introducing s2n-tls post-quantum support"