CodeSampleX

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

  1. rustls ServerConnection and ClientConnection resolve ALPN negotiation according to the server's preference order and negotiate in-memory bidirectional TLS 1.3 streams
  2. rustls selects the server's preferred matching ALPN protocol regardless of client offer order
  3. rustls fails handshake with NoApplicationProtocol when client and server configure disjoint non-empty ALPN lists
  4. rustls establishes an in-memory TLS 1.3 session transitioning is_handshaking from true to false
  5. rustls frames post-handshake application traffic in TLS record type 0x17 with legacy version header 0x0303
  6. rustls rejects server certificates not anchored in the client's configured RootCertStore

Dateien

  • Cargo.lock
  • Cargo.toml
  • NOTES.md
  • csx.json
  • src/lib.rs

Quellartefakt herunterladen (tar.gz)

Quelltext

Cargo.lock
# 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"
Cargo.toml
[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"
NOTES.md
# 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`.
csx.json
{"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"}
src/lib.rs
#[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),
        }
    }
}

Ursprungs-Seeder

csx-seed