Claude
Skills
Sign in
Back

rustls

Included with Lifetime
$97 forever

rustls — modern, safe TLS implementation in pure Rust. Drop-in replacement for OpenSSL/native-tls in Rust apps. No C dependencies — perfect for mobile cross- compile and embedded targets. Covers ClientConfig + ServerConfig, certificate verification with webpki-roots, mTLS, custom verifier (cert pinning), ALPN negotiation (HTTP/2, HTTP/3), session resumption, integration with hyper + reqwest + tokio. USE WHEN: user mentions "rustls", "ClientConfig", "ServerConfig", "webpki-roots", "rustls-pemfile", "rustls cert pinning", "rustls mTLS", "rustls Tokio", "rustls hyper" DO NOT USE FOR: OpenSSL specifics - use OpenSSL skill (or platform TLS) DO NOT USE FOR: Apple/Windows native TLS - use platform-specific skills DO NOT USE FOR: Tor anonymous transport - use `network/arti` DO NOT USE FOR: TLS protocol theory - use OWASP / RFC docs

Backend & APIs

What this skill does

# rustls — Pure-Rust TLS

> **Deep Knowledge**: Use `mcp__documentation__fetch_docs` with technology: `rustls`.

## Why rustls

| Feature | rustls | openssl-rs | native-tls |
|---|---|---|---|
| Implementation | Pure Rust | Bindings to OpenSSL C | Bindings to OS TLS (Sec/SChannel/OpenSSL) |
| Memory safety | ✅ | ❌ Periodic CVEs | Inherits OS TLS bugs |
| Cross-compile to mobile | ✅ Trivial | Requires OpenSSL build | Platform-dependent |
| TLS 1.3 | ✅ Default | ✅ | Depends on OS |
| TLS 1.2 | ✅ | ✅ | ✅ |
| TLS 1.0/1.1 | ❌ Removed | Configurable | Depends on OS |
| Audit | ✅ NCC + ISRG | C codebase | Platform-dependent |
| Adoption | Growing fast (Cloudflare, Deno, Tokio stack) | Legacy | Legacy |

For Rust libs targeting mobile (Android via cargo-ndk, iOS native Cargo): **rustls is the only sane choice** — OpenSSL cross-compile is painful, native-tls has different APIs per platform.

## Setup

```toml
[dependencies]
rustls = "0.23"
rustls-pemfile = "2"
webpki-roots = "0.26"                     # Mozilla CA bundle
tokio-rustls = "0.26"                      # async wrapper
rustls-pki-types = "1"

# Convenience: enable rustls in HTTP clients
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "rustls-tls-webpki-roots"] }
hyper-rustls = "0.27"
```

`reqwest` with `default-features = false` is critical — otherwise it pulls in `native-tls` (= OpenSSL).

## Client — Connect Over TLS

```rust
use rustls::{ClientConfig, RootCertStore};
use rustls_pki_types::ServerName;
use std::sync::Arc;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio_rustls::TlsConnector;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Use Mozilla's trust roots
    let mut root_store = RootCertStore::empty();
    root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());

    let config = ClientConfig::builder()
        .with_root_certificates(root_store)
        .with_no_client_auth();

    let connector = TlsConnector::from(Arc::new(config));

    let tcp = TcpStream::connect("api.example.com:443").await?;
    let domain = ServerName::try_from("api.example.com")?;
    let mut tls = connector.connect(domain, tcp).await?;

    tls.write_all(b"GET / HTTP/1.0\r\nHost: api.example.com\r\n\r\n").await?;

    let mut response = String::new();
    tls.read_to_string(&mut response).await?;
    println!("{}", response);

    Ok(())
}
```

## Reqwest with rustls (Recommended for Most Apps)

```toml
[dependencies]
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json", "rustls-tls-webpki-roots"] }
```

```rust
let client = reqwest::Client::builder()
    .use_rustls_tls()
    .https_only(true)                              // refuse plain HTTP
    .build()?;

let resp: serde_json::Value = client
    .get("https://api.example.com/health")
    .send()
    .await?
    .json()
    .await?;
```

## Server — Listen with TLS

```rust
use rustls::ServerConfig;
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use std::fs::File;
use std::io::BufReader;
use tokio::net::TcpListener;
use tokio_rustls::TlsAcceptor;

fn load_certs(path: &str) -> Result<Vec<CertificateDer<'static>>, Box<dyn std::error::Error>> {
    let mut reader = BufReader::new(File::open(path)?);
    let certs: Vec<_> = rustls_pemfile::certs(&mut reader)
        .filter_map(Result::ok)
        .collect();
    Ok(certs)
}

fn load_key(path: &str) -> Result<PrivateKeyDer<'static>, Box<dyn std::error::Error>> {
    let mut reader = BufReader::new(File::open(path)?);
    let key = rustls_pemfile::private_key(&mut reader)?
        .ok_or("no private key found")?;
    Ok(key)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let certs = load_certs("server.crt")?;
    let key = load_key("server.key")?;

    let config = ServerConfig::builder()
        .with_no_client_auth()
        .with_single_cert(certs, key)?;

    let acceptor = TlsAcceptor::from(Arc::new(config));
    let listener = TcpListener::bind("0.0.0.0:8443").await?;

    loop {
        let (tcp, _addr) = listener.accept().await?;
        let acceptor = acceptor.clone();
        tokio::spawn(async move {
            if let Ok(mut tls) = acceptor.accept(tcp).await {
                // handle TLS-wrapped stream
            }
        });
    }
}
```

## Mutual TLS (mTLS — Client Cert Auth)

```rust
// Server requires client cert
use rustls::server::WebPkiClientVerifier;

let mut client_root_store = RootCertStore::empty();
client_root_store.add_parsable_certificates(load_certs("ca.crt")?);

let verifier = WebPkiClientVerifier::builder(client_root_store.into()).build()?;

let config = ServerConfig::builder()
    .with_client_cert_verifier(verifier)
    .with_single_cert(server_certs, server_key)?;
```

```rust
// Client presents cert
let client_certs = load_certs("client.crt")?;
let client_key = load_key("client.key")?;

let config = ClientConfig::builder()
    .with_root_certificates(root_store)
    .with_client_auth_cert(client_certs, client_key)?;
```

## Certificate Pinning

For BHODL-style wallet apps, pin server cert hash to defend against MITM (rogue CA, compromised TLS termination).

```rust
use rustls::client::danger::{ServerCertVerified, ServerCertVerifier, HandshakeSignatureValid};
use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
use rustls::DigitallySignedStruct;

#[derive(Debug)]
struct PinnedCertVerifier {
    expected_pins: Vec<[u8; 32]>,             // SHA-256 of leaf cert SPKI
}

impl ServerCertVerifier for PinnedCertVerifier {
    fn verify_server_cert(
        &self,
        end_entity: &CertificateDer,
        _intermediates: &[CertificateDer],
        _server_name: &ServerName,
        _ocsp: &[u8],
        _now: UnixTime,
    ) -> Result<ServerCertVerified, rustls::Error> {
        use sha2::{Sha256, Digest};
        let leaf_hash: [u8; 32] = Sha256::digest(end_entity.as_ref()).into();

        if self.expected_pins.iter().any(|pin| pin == &leaf_hash) {
            Ok(ServerCertVerified::assertion())
        } else {
            Err(rustls::Error::General("cert not pinned".into()))
        }
    }

    fn verify_tls12_signature(
        &self,
        _message: &[u8],
        _cert: &CertificateDer,
        _dss: &DigitallySignedStruct,
    ) -> Result<HandshakeSignatureValid, rustls::Error> {
        Ok(HandshakeSignatureValid::assertion())
    }

    fn verify_tls13_signature(
        &self,
        _message: &[u8],
        _cert: &CertificateDer,
        _dss: &DigitallySignedStruct,
    ) -> Result<HandshakeSignatureValid, rustls::Error> {
        Ok(HandshakeSignatureValid::assertion())
    }

    fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
        rustls::crypto::ring::default_provider()
            .signature_verification_algorithms
            .supported_schemes()
    }
}

let verifier = PinnedCertVerifier {
    expected_pins: vec![
        [0xab, 0xcd, /* ... 30 more bytes */],
        [0xff, 0xee, /* backup pin */],
    ],
};

let config = ClientConfig::builder()
    .dangerous()
    .with_custom_certificate_verifier(Arc::new(verifier))
    .with_no_client_auth();
```

**Critical for wallets**: pin SPKI hash, not full cert (allows cert rotation without app update if SPKI stays same). Always have backup pins.

## ALPN (HTTP/2, HTTP/3 Negotiation)

```rust
let mut config = ClientConfig::builder()
    .with_root_certificates(root_store)
    .with_no_client_auth();
config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
```

For h3 (QUIC): use `quinn` crate (separate, not raw TLS).

## Session Resumption

```rust
use rustls::client::Resumption;

let config = ClientConfig::builder()
    .with_root_certificates(root_store)
    .with_no_client_auth();
// session resumption is on by default
```

To customize:

```rust
config.resumption = Resumption::store(Arc::new(MyTicketStorage));
```

Speeds up reconnections (1-RTT vs 2-RTT handshake).

## Cu

Related in Backend & APIs