Beispiel
rustls 0.23.43: Negotiate in-memory TLS 1.3 sessions between rustls ServerConnection and ClientConnection validating ALPN server preference ordering, disjoint protocol rejection, and Application Data record framing
Verifiziertes Beispiel für cargo rustls 0.23.43: Negotiate in-memory TLS 1.3 sessions between rustls ServerConnection and ClientConnection validating ALPN…
sha256:e33932c47209d54ef12a2a9d82f1c48cbae5f0897576864e98bed8f53a42d264
Dieses Netzwerk bietet eine Sache: ein Sample, das baut. Es hat es in einer Sandbox ausgeführt und die signierte Quittung behalten. Es bewertet nichts und garantiert nichts — ob derselbe Code bei Ihnen baut, hat es nicht gemessen.
Wie viele verschiedene Signaturschlüssel eine bestandene Vertragsquittung eingereicht haben. Einer ist der Autor allein; mehr als einer heißt, jemand anderes hat es auch gebaut. Ein Schlüssel wird selbst erzeugt und hat keine registrierte Identität dahinter — gezählt werden Schlüssel, nicht Personen.
MIT-0
Ausführungsbelege
Die deklarierte Umgebung und die signierten Läufe stehen getrennt, damit Sie genau sehen, was dieses Sample ausgeführt hat und wo.
- Beleggrundlage
- Signierter Vertrag bestanden
- Verifizierungsbelege
- 3
- Signaturschlüssel, die es gebaut haben
- 2
Deklarierte Umgebung
rust linux x64 rust rust cargo
Umgebungen der Verifizierungsläufe
| Umgebung | Contract | Stufen | Lauf |
|---|---|---|---|
| rust 1 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · cargo@1 |
2026-08-17 |
| rust 1 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · cargo@1 |
2026-08-18 |
| rust 1 · linux alpine/x64 · docker ed25519:c1973797be207ac4 | FAIL | compile:SKIPPED · contract:FAIL · load:SKIPPED · resolve:PASS CONTAINER_RUN · cargo@1rust:1-alpine@sha256:a10e64dd139b… |
2026-09-08 |
Fall
HOW- Ziel
- Negotiate in-memory TLS 1.3 sessions between rustls ServerConnection and ClientConnection validating ALPN server preference ordering, disjoint protocol rejection, and Application Data record framing
- Pakete
- Symbole
-
- rustls::ServerConfig
- rustls::ServerConnection
- rustls::ClientConfig
- rustls::ClientConnection
- rustls::RootCertStore
- Umgebung
- rust
- Erstellt
- 2026-08-17T11:36:26Z
Contract
- rustls ServerConnection and ClientConnection resolve ALPN negotiation according to the server's preference order and negotiate in-memory bidirectional TLS 1.3 streams
- rustls selects the server's preferred matching ALPN protocol regardless of client offer order
- rustls fails handshake with NoApplicationProtocol when client and server configure disjoint non-empty ALPN lists
- rustls establishes an in-memory TLS 1.3 session transitioning is_handshaking from true to false
- rustls frames post-handshake application traffic in TLS record type 0x17 with legacy version header 0x0303
- rustls rejects server certificates not anchored in the client's configured RootCertStore
Dateien
- Cargo.lock
- Cargo.toml
- NOTES.md
- csx.json
- src/lib.rs
Quelltext
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "cargo-rustls-version-depth"
version = "0.1.0"
dependencies = [
"rcgen",
"rustls",
"rustls-pki-types",
]
[[package]]
name = "cc"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "deranged"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
[[package]]
name = "find-msvc-tools"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "libc"
version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "num-conv"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "pem"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
dependencies = [
"base64",
"serde_core",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rcgen"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2"
dependencies = [
"pem",
"ring",
"rustls-pki-types",
"time",
"yasna",
]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom",
"libc",
"untrusted",
"windows-sys",
]
[[package]]
name = "rustls"
version = "0.23.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
dependencies = [
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pki-types"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "serde_core"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "time"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
dependencies = [
"deranged",
"num-conv",
"powerfmt",
"serde_core",
"time-core",
]
[[package]]
name = "time-core"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "yasna"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
dependencies = [
"time",
]
[[package]]
name = "zeroize"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
[package]
name = "cargo-rustls-version-depth"
version = "0.1.0"
edition = "2021"
[dependencies]
rustls = { version = "0.23.23", default-features = false, features = ["ring", "std", "tls12"] }
rustls-pki-types = "1.10"
rcgen = "0.13"
# rustls 0.23 Server/Client Handshake, ALPN Negotiation, and Record Framing
## search_known_solution result
`search_known_solution` returned existing published samples:
- `sha256:fff60de89bafdec671d0293d2d43ea9be8322563a5ed7399227d304ac778ad31` (focusing on client-side provider initialization, `RootCertStore`, and `ServerName` string parsing).
- `sha256:5659ebf9abcf99a6b2de6c9e2f6eb5499ff88ee1b4d263e78c7611f3e745b7aa` (focusing on provider cipher suite pruning and SNI omission for IP literals).
This sample tests and proves distinct end-to-end server and client connection lifecycle behaviors in `rustls`.
## Pinned Release
- Pinned release: `rustls` `0.23.43` (with `rustls-pki-types` `1.15.1` and `rcgen` `0.13.2`).
## Observed Behaviour
1. **ALPN Negotiation Order & Preference**: When `ServerConfig` defines `alpn_protocols = [b"h2", b"http/1.1"]` and `ClientConfig` defines `alpn_protocols = [b"http/1.1", b"h2"]`, `rustls` resolves ALPN according to server preference order (RFC 7301), returning `Some(b"h2")` for both endpoints once the handshake completes.
2. **ALPN Disjoint Protocol Rejection**: When a client provides an empty `alpn_protocols` list against an ALPN-configured server, the handshake completes with `alpn_protocol() == None`. However, if the client provides a non-empty list disjoint from the server's supported list, the handshake terminates with `rustls::Error::NoApplicationProtocol`.
3. **In-Memory TLS 1.3 Handshake**: Stepping the state machine across in-memory buffers via `write_tls`, `read_tls`, and `process_new_packets` transitions both endpoints from `is_handshaking() == true` to `is_handshaking() == false`.
4. **TLS Application Data Record Framing**: Post-handshake plaintext written via `writer().write_all()` is encapsulated in TLS records where byte 0 is `0x17` (Application Data) with legacy record header version `[0x03, 0x03]` (TLS 1.2 encapsulation mandated by RFC 8446 Section 5.1). Decryption and reception via `reader().read_exact()` yields exact plaintext.
5. **Certificate Validation**: Server certificates not anchored in the client's `RootCertStore` are strictly rejected during `process_new_packets` with `rustls::Error::InvalidCertificate`.
{"case":{"caseId":"case:sha256:599a60e9457fd22a7a4f9c106d0cd2af997fbd4adf7ff57e052d06c050f6e7c1","contract":["rustls ServerConnection and ClientConnection resolve ALPN negotiation according to the server's preference order and negotiate in-memory bidirectional TLS 1.3 streams","rustls selects the server's preferred matching ALPN protocol regardless of client offer order","rustls fails handshake with NoApplicationProtocol when client and server configure disjoint non-empty ALPN lists","rustls establishes an in-memory TLS 1.3 session transitioning is_handshaking from true to false","rustls frames post-handshake application traffic in TLS record type 0x17 with legacy version header 0x0303","rustls rejects server certificates not anchored in the client's configured RootCertStore"],"goal":"Negotiate in-memory TLS 1.3 sessions between rustls ServerConnection and ClientConnection validating ALPN server preference ordering, disjoint protocol rejection, and Application Data record framing","kind":"HOW","packages":["pkg:cargo/rustls@0.23.43"],"schemaVersion":1,"symbols":["rustls::ServerConfig","rustls::ServerConnection","rustls::ClientConfig","rustls::ClientConnection","rustls::RootCertStore"]},"contractCommand":["cargo","test","--offline"],"environment":{"arch":"x64","ecosystem":"cargo","executionContext":"rust","language":"rust","os":"linux","packageManager":"cargo","runtime":"rust","schemaVersion":1},"license":"MIT-0","packages":["pkg:cargo/rustls@0.23.43"],"schemaVersion":1,"symbols":["rustls::ServerConfig","rustls::ServerConnection","rustls::ClientConfig","rustls::ClientConnection","rustls::RootCertStore"],"verifierAdapter":"cargo@1"}
#[cfg(test)]
mod tests {
use std::io::{Read, Write};
use std::sync::Arc;
use rcgen::generate_simple_self_signed;
use rustls::crypto::ring::default_provider;
use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName};
use rustls::{ClientConfig, ClientConnection, RootCertStore, ServerConfig, ServerConnection};
/// Helper function to generate a self-signed certificate and private key for a given SAN.
fn generate_test_cert(
san: &str,
) -> (CertificateDer<'static>, PrivateKeyDer<'static>) {
let subject_alt_names = vec![san.to_string()];
let cert_key = generate_simple_self_signed(subject_alt_names)
.expect("self-signed certificate generation must succeed");
let cert_der = CertificateDer::from(cert_key.cert.der().to_vec());
let key_der = PrivateKeyDer::Pkcs8(cert_key.key_pair.serialize_der().into());
(cert_der, key_der)
}
/// Pump TLS records between client and server until both are no longer handshaking
/// or no progress can be made.
fn pump_handshake(
client: &mut ClientConnection,
server: &mut ServerConnection,
) -> Result<(), rustls::Error> {
let mut client_to_server = Vec::new();
let mut server_to_client = Vec::new();
for _ in 0..10 {
if client.wants_write() {
client.write_tls(&mut client_to_server).unwrap();
}
if !client_to_server.is_empty() {
let mut slice = &client_to_server[..];
server.read_tls(&mut slice).unwrap();
server.process_new_packets()?;
client_to_server.clear();
}
if server.wants_write() {
server.write_tls(&mut server_to_client).unwrap();
}
if !server_to_client.is_empty() {
let mut slice = &server_to_client[..];
client.read_tls(&mut slice).unwrap();
client.process_new_packets()?;
server_to_client.clear();
}
if !client.is_handshaking() && !server.is_handshaking() {
return Ok(());
}
}
panic!("Handshake did not complete within expected iterations");
}
#[test]
fn test_server_client_handshake_and_alpn_server_preference() {
let (cert_der, key_der) = generate_test_cert("example.com");
// 1. Build ServerConfig with ALPN preferences: prefer "h2", then "http/1.1"
let provider = Arc::new(default_provider());
let mut server_config = ServerConfig::builder_with_provider(provider.clone())
.with_safe_default_protocol_versions()
.expect("server safe default protocol versions must succeed")
.with_no_client_auth()
.with_single_cert(vec![cert_der.clone()], key_der)
.expect("server certificate and private key must be valid");
server_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
// 2. Build ClientConfig trusting the generated self-signed root cert
let mut root_store = RootCertStore::empty();
root_store
.add(cert_der)
.expect("adding self-signed root certificate must succeed");
let mut client_config = ClientConfig::builder_with_provider(provider)
.with_safe_default_protocol_versions()
.expect("client safe default protocol versions must succeed")
.with_root_certificates(root_store)
.with_no_client_auth();
// Client offers ALPN in reverse order: "http/1.1" first, then "h2"
client_config.alpn_protocols = vec![b"http/1.1".to_vec(), b"h2".to_vec()];
let server_name = ServerName::try_from("example.com")
.expect("example.com must be a valid DNS ServerName");
let mut client = ClientConnection::new(Arc::new(client_config), server_name)
.expect("ClientConnection creation must succeed");
let mut server = ServerConnection::new(Arc::new(server_config))
.expect("ServerConnection creation must succeed");
// Before handshake completes, ALPN is None and is_handshaking is true
assert!(client.is_handshaking());
assert!(server.is_handshaking());
assert_eq!(client.alpn_protocol(), None);
assert_eq!(server.alpn_protocol(), None);
// Perform bidirectional in-memory handshake
pump_handshake(&mut client, &mut server).expect("handshake must succeed");
// Handshake is established
assert!(!client.is_handshaking());
assert!(!server.is_handshaking());
// ALPN resolution: Rustls follows server preference order (RFC 7301), selecting "h2"
assert_eq!(client.alpn_protocol(), Some(b"h2".as_slice()));
assert_eq!(server.alpn_protocol(), Some(b"h2".as_slice()));
}
#[test]
fn test_alpn_empty_client_offers_resolves_to_none_while_disjoint_lists_fail() {
let (cert_der, key_der) = generate_test_cert("example.com");
let provider = Arc::new(default_provider());
let mut server_config = ServerConfig::builder_with_provider(provider.clone())
.with_safe_default_protocol_versions()
.unwrap()
.with_no_client_auth()
.with_single_cert(vec![cert_der.clone()], key_der.clone_key())
.unwrap();
server_config.alpn_protocols = vec![b"h2".to_vec()];
let mut root_store = RootCertStore::empty();
root_store.add(cert_der.clone()).unwrap();
// 1. When client config does NOT configure ALPN protocols, handshake succeeds with ALPN = None
let client_config_no_alpn = ClientConfig::builder_with_provider(provider.clone())
.with_safe_default_protocol_versions()
.unwrap()
.with_root_certificates(root_store.clone())
.with_no_client_auth();
let server_name = ServerName::try_from("example.com").unwrap();
let mut client1 =
ClientConnection::new(Arc::new(client_config_no_alpn), server_name.clone()).unwrap();
let mut server1 = ServerConnection::new(Arc::new(server_config.clone())).unwrap();
pump_handshake(&mut client1, &mut server1).expect("unconfigured client ALPN handshake succeeds");
assert_eq!(client1.alpn_protocol(), None);
assert_eq!(server1.alpn_protocol(), None);
// 2. When client configures a disjoint non-empty ALPN list, server rejects with NoApplicationProtocol
let mut client_config_disjoint = ClientConfig::builder_with_provider(provider)
.with_safe_default_protocol_versions()
.unwrap()
.with_root_certificates(root_store)
.with_no_client_auth();
client_config_disjoint.alpn_protocols = vec![b"http/1.1".to_vec()];
let mut client2 =
ClientConnection::new(Arc::new(client_config_disjoint), server_name).unwrap();
let mut server2 = ServerConnection::new(Arc::new(server_config)).unwrap();
let handshake_res = pump_handshake(&mut client2, &mut server2);
assert!(
handshake_res.is_err(),
"Disjoint ALPN protocols must cause handshake failure"
);
match handshake_res.unwrap_err() {
rustls::Error::NoApplicationProtocol => {}
other => panic!("Expected NoApplicationProtocol error, got {:?}", other),
}
}
#[test]
fn test_application_data_record_framing_and_bidirectional_transfer() {
let (cert_der, key_der) = generate_test_cert("example.com");
let provider = Arc::new(default_provider());
let server_config = ServerConfig::builder_with_provider(provider.clone())
.with_safe_default_protocol_versions()
.unwrap()
.with_no_client_auth()
.with_single_cert(vec![cert_der.clone()], key_der)
.unwrap();
let mut root_store = RootCertStore::empty();
root_store.add(cert_der).unwrap();
let client_config = ClientConfig::builder_with_provider(provider)
.with_safe_default_protocol_versions()
.unwrap()
.with_root_certificates(root_store)
.with_no_client_auth();
let server_name = ServerName::try_from("example.com").unwrap();
let mut client = ClientConnection::new(Arc::new(client_config), server_name).unwrap();
let mut server = ServerConnection::new(Arc::new(server_config)).unwrap();
pump_handshake(&mut client, &mut server).unwrap();
// 1. Client writes application plaintext
let message = b"GET /status HTTP/1.1\r\nHost: example.com\r\n\r\n";
client.writer().write_all(message).unwrap();
// 2. Flush TLS encrypted wire record from client
let mut tls_wire_buffer = Vec::new();
let written_bytes = client.write_tls(&mut tls_wire_buffer).unwrap();
assert!(written_bytes > 0);
// Verify TLS record framing:
// Byte 0 = 0x17 (TLS Application Data record content type)
// Bytes 1..3 = [0x03, 0x03] (TLS 1.2 legacy version in record layer header per RFC 8446 Section 5.1)
assert_eq!(
tls_wire_buffer[0], 0x17,
"Emitted TLS record must have content type 0x17 (Application Data)"
);
assert_eq!(
&tls_wire_buffer[1..3],
&[0x03, 0x03],
"TLS record layer header legacy version must be 0x0303"
);
// 3. Server receives TLS wire record and processes it
let mut slice = &tls_wire_buffer[..];
server.read_tls(&mut slice).unwrap();
let state = server.process_new_packets().unwrap();
assert!(state.plaintext_bytes_to_read() > 0);
let mut received = vec![0u8; message.len()];
server.reader().read_exact(&mut received).unwrap();
assert_eq!(&received, message);
// 4. Server writes response back to client
let response = b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK";
server.writer().write_all(response).unwrap();
let mut response_wire_buffer = Vec::new();
server.write_tls(&mut response_wire_buffer).unwrap();
assert_eq!(response_wire_buffer[0], 0x17);
let mut resp_slice = &response_wire_buffer[..];
client.read_tls(&mut resp_slice).unwrap();
client.process_new_packets().unwrap();
let mut client_received = vec![0u8; response.len()];
client.reader().read_exact(&mut client_received).unwrap();
assert_eq!(&client_received, response);
}
#[test]
fn test_untrusted_root_certificate_rejection() {
let (server_cert, server_key) = generate_test_cert("example.com");
let (unrelated_root_cert, _) = generate_test_cert("other.example.org");
let provider = Arc::new(default_provider());
let server_config = ServerConfig::builder_with_provider(provider.clone())
.with_safe_default_protocol_versions()
.unwrap()
.with_no_client_auth()
.with_single_cert(vec![server_cert], server_key)
.unwrap();
// Client only trusts an unrelated certificate
let mut root_store = RootCertStore::empty();
root_store.add(unrelated_root_cert).unwrap();
let client_config = ClientConfig::builder_with_provider(provider)
.with_safe_default_protocol_versions()
.unwrap()
.with_root_certificates(root_store)
.with_no_client_auth();
let server_name = ServerName::try_from("example.com").unwrap();
let mut client = ClientConnection::new(Arc::new(client_config), server_name).unwrap();
let mut server = ServerConnection::new(Arc::new(server_config)).unwrap();
// Client writes ClientHello
let mut c2s = Vec::new();
client.write_tls(&mut c2s).unwrap();
// Server receives ClientHello and replies with ServerHello + Certificate
let mut slice = &c2s[..];
server.read_tls(&mut slice).unwrap();
server.process_new_packets().unwrap();
let mut s2c = Vec::new();
server.write_tls(&mut s2c).unwrap();
// Client reads ServerHello + Certificate
let mut resp_slice = &s2c[..];
client.read_tls(&mut resp_slice).unwrap();
// Client process_new_packets fails with InvalidCertificate
let process_result = client.process_new_packets();
assert!(
process_result.is_err(),
"Client must reject untrusted server certificate"
);
match process_result.unwrap_err() {
rustls::Error::InvalidCertificate(_) => {}
other => panic!("Expected InvalidCertificate error, got {:?}", other),
}
}
}