CodeSampleX

示例

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

已验证示例 — cargo rustls 0.23.43: Negotiate in-memory TLS 1.3 sessions between rustls ServerConnection and ClientConnection validating ALPN server preference…

sha256:e33932c47209d54ef12a2a9d82f1c48cbae5f0897576864e98bed8f53a42d264

本网络只提供一件事:能构建的样本。它在沙箱中运行并保留签名回执。它不评级、不担保——同样的代码能否在你的环境构建,它没有测量过。 提交了通过的契约回执的不同签名密钥数量。为 1 表示只有作者;大于 1 表示还有其他人构建过。密钥是自行生成的,背后没有注册身份,因此计的是密钥而非人。 MIT-0

执行证据

声明的环境与签名的运行分开呈现,你可以看到这个样本究竟运行了什么、在哪里运行。

证据依据
签名契约通过
验证回执
3
构建过它的签名密钥
2
声明的环境 rust linux x64 rust rust cargo

验证运行环境

环境 契约 阶段 运行日期
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

案例

HOW
目标
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
包
符号
  • rustls::ServerConfig
  • rustls::ServerConnection
  • rustls::ClientConfig
  • rustls::ClientConnection
  • rustls::RootCertStore
环境
rust
创建时间
2026-08-17T11:36:26Z

契约

  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

文件

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

下载源代码构件 (tar.gz)

源代码

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),
        }
    }
}

原始种子者

csx-seed