CodeSampleX

Ejemplo

rustls 0.23.43: Prevent silent TLS protocol version and SNI omission when configuring rustls with restricted crypto providers or IP address literals

Muestra verificada para cargo rustls 0.23.43: Prevent silent TLS protocol version and SNI omission when configuring rustls with restricted crypto providers…

sha256:5659ebf9abcf99a6b2de6c9e2f6eb5499ff88ee1b4d263e78c7611f3e745b7aa

Esta red ofrece una sola cosa: una muestra que compila. La ejecutó en un sandbox y guardó el recibo firmado. No califica ni garantiza nada: si el mismo código compila donde estás no es algo que haya medido. Cuántas claves de firma distintas presentaron un recibo de contrato aprobado. Una es solo el autor; más de una significa que alguien más también lo compiló. Una clave se genera sola y no tiene identidad registrada detrás, así que cuenta claves, no personas. MIT-0

Evidencia de ejecución

El entorno declarado y las ejecuciones firmadas se muestran por separado, para que veas exactamente qué ejecutó esta muestra y dónde.

Base de evidencia
Contrato firmado aprobado
Recibos de verificación
3
Claves de firma que lo compilaron
2
Entorno declarado rust linux x64 rust rust cargo

Entornos de las ejecuciones de verificación

Entorno Contrato Etapas Ejecución
rust 1 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · cargo@1
2026-08-16
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

Caso

HOW
Objetivo
Prevent silent TLS protocol version and SNI omission when configuring rustls with restricted crypto providers or IP address literals
Paquetes
Símbolos
  • rustls::ClientConfig::builder_with_provider
  • rustls::ConfigBuilder::with_safe_default_protocol_versions
  • rustls::pki_types::ServerName
  • rustls::ClientConnection::new
Entorno
rust
Creado
2026-08-16T08:23:44Z

Contrato

  1. assert with_safe_default_protocol_versions() silently prunes TLS 1.2 when CryptoProvider has only TLS 1.3 cipher suites
  2. assert ClientHello supported_versions extension contains only TLS 1.3 when TLS 1.2 is pruned
  3. assert full CryptoProvider with_safe_default_protocol_versions() offers both TLS 1.3 and TLS 1.2 in supported_versions
  4. assert ServerName from DNS hostname includes SNI extension in ClientHello
  5. assert ServerName from IP literal silently omits SNI extension in ClientHello per RFC 6066

Archivos

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

Descargar el artefacto de código fuente (tar.gz)

Código fuente

Cargo.lock
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4

[[package]]
name = "aws-lc-rs"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e"
dependencies = [
 "aws-lc-sys",
 "zeroize",
]

[[package]]
name = "aws-lc-sys"
version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483"
dependencies = [
 "cc",
 "cmake",
 "dunce",
 "fs_extra",
 "pkg-config",
]

[[package]]
name = "cargo-rustls-version"
version = "0.1.0"
dependencies = [
 "rustls",
]

[[package]]
name = "cc"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d"
dependencies = [
 "find-msvc-tools",
 "jobserver",
 "libc",
 "shlex",
]

[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"

[[package]]
name = "cmake"
version = "0.1.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
dependencies = [
 "cc",
]

[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"

[[package]]
name = "find-msvc-tools"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"

[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"

[[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 = "getrandom"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
 "cfg-if",
 "libc",
 "r-efi",
]

[[package]]
name = "jobserver"
version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
dependencies = [
 "getrandom 0.4.3",
 "libc",
]

[[package]]
name = "libc"
version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"

[[package]]
name = "log"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"

[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"

[[package]]
name = "pkg-config"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"

[[package]]
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"

[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
 "cc",
 "cfg-if",
 "getrandom 0.2.17",
 "libc",
 "untrusted",
 "windows-sys",
]

[[package]]
name = "rustls"
version = "0.23.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
dependencies = [
 "aws-lc-rs",
 "log",
 "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 = [
 "aws-lc-rs",
 "ring",
 "rustls-pki-types",
 "untrusted",
]

[[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 = "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 = "zeroize"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
Cargo.toml
[package]
name = "cargo-rustls-version"
version = "0.1.0"
edition = "2021"

[dependencies]
rustls = { version = "0.23.23", features = ["ring", "std"] }
NOTES.md
- `search_known_solution` returned sample `sha256:eca9120e191da6b23a342f6ef4570faf9508777f0595b9ed1627910a607ad1d5` ("Configuring a custom CryptoProvider when a transitive dependency has already locked the process default via install_default.").
- A model would assume `with_safe_default_protocol_versions()` always enables both TLS 1.2 and TLS 1.3, and that `ServerName::try_from("192.0.2.1")` with `enable_sni: true` sends the IP in the TLS SNI extension.
- In reality, `with_safe_default_protocol_versions()` silently prunes TLS 1.2 from the ClientHello supported_versions extension with a green build when the provider lacks TLS 1.2 cipher suites, and IP literals silently omit SNI entirely per RFC 6066.
csx.json
{"case":{"caseId":"case:sha256:760ede61bd533bf659eb00bb5df606ded0b2cadea5300a9f105be1ce7a66a998","contract":["assert with_safe_default_protocol_versions() silently prunes TLS 1.2 when CryptoProvider has only TLS 1.3 cipher suites","assert ClientHello supported_versions extension contains only TLS 1.3 when TLS 1.2 is pruned","assert full CryptoProvider with_safe_default_protocol_versions() offers both TLS 1.3 and TLS 1.2 in supported_versions","assert ServerName from DNS hostname includes SNI extension in ClientHello","assert ServerName from IP literal silently omits SNI extension in ClientHello per RFC 6066"],"goal":"Prevent silent TLS protocol version and SNI omission when configuring rustls with restricted crypto providers or IP address literals","kind":"HOW","packages":["pkg:cargo/rustls@0.23.43"],"schemaVersion":1,"symbols":["rustls::ClientConfig::builder_with_provider","rustls::ConfigBuilder::with_safe_default_protocol_versions","rustls::pki_types::ServerName","rustls::ClientConnection::new"]},"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::ClientConfig::builder_with_provider","rustls::ConfigBuilder::with_safe_default_protocol_versions","rustls::pki_types::ServerName","rustls::ClientConnection::new"],"verifierAdapter":"cargo@1"}
src/lib.rs
#[cfg(test)]
mod tests {
    use rustls::crypto::ring::default_provider;
    use rustls::crypto::CryptoProvider;
    use rustls::pki_types::ServerName;
    use rustls::version::TLS13;
    use rustls::{ClientConfig, ClientConnection, RootCertStore};
    use std::sync::Arc;

    /// Parses TLS ClientHello extensions into a list of (extension_type, extension_data).
    fn parse_client_hello_extensions(client_hello_bytes: &[u8]) -> Vec<(u16, Vec<u8>)> {
        assert!(client_hello_bytes.len() >= 5);
        assert_eq!(client_hello_bytes[0], 0x16, "Must be TLS Handshake record");

        let mut pos = 5;
        assert_eq!(client_hello_bytes[pos], 0x01, "Must be ClientHello");
        pos += 4; // handshake type (1) + length (3)
        pos += 2; // legacy version (2)
        pos += 32; // random (32)

        // Session ID
        let session_id_len = client_hello_bytes[pos] as usize;
        pos += 1 + session_id_len;

        // Cipher Suites
        let cipher_suites_len =
            u16::from_be_bytes([client_hello_bytes[pos], client_hello_bytes[pos + 1]]) as usize;
        pos += 2 + cipher_suites_len;

        // Compression methods
        let compression_len = client_hello_bytes[pos] as usize;
        pos += 1 + compression_len;

        if pos >= client_hello_bytes.len() {
            return Vec::new();
        }

        // Extensions
        let extensions_len =
            u16::from_be_bytes([client_hello_bytes[pos], client_hello_bytes[pos + 1]]) as usize;
        pos += 2;

        let end_pos = pos + extensions_len;
        let mut extensions = Vec::new();

        while pos + 4 <= end_pos {
            let ext_type =
                u16::from_be_bytes([client_hello_bytes[pos], client_hello_bytes[pos + 1]]);
            let ext_len =
                u16::from_be_bytes([client_hello_bytes[pos + 2], client_hello_bytes[pos + 3]])
                    as usize;
            let ext_data = client_hello_bytes[pos + 4..pos + 4 + ext_len].to_vec();
            extensions.push((ext_type, ext_data));
            pos += 4 + ext_len;
        }

        extensions
    }

    #[test]
    fn test_safe_default_protocol_versions_pruning() {
        const SUPPORTED_VERSIONS_EXT: u16 = 0x002b;
        const TLS12_VERSION_ID: u16 = 0x0303;
        const TLS13_VERSION_ID: u16 = 0x0304;

        let full_provider = default_provider();
        let tls13_only_ciphers: Vec<_> = full_provider
            .cipher_suites
            .iter()
            .filter(|cs| cs.version() == &TLS13)
            .cloned()
            .collect();

        let restricted_provider = CryptoProvider {
            cipher_suites: tls13_only_ciphers,
            ..default_provider()
        };

        // 1. with_safe_default_protocol_versions() SILENTLY drops TLS 1.2 when provider lacks TLS 1.2 ciphers
        let restricted_config =
            ClientConfig::builder_with_provider(Arc::new(restricted_provider))
                .with_safe_default_protocol_versions()
                .expect("with_safe_default_protocol_versions silently adapts to available ciphers")
                .with_root_certificates(RootCertStore::empty())
                .with_no_client_auth();

        let server_name = ServerName::try_from("example.com").unwrap();
        let mut restricted_conn =
            ClientConnection::new(Arc::new(restricted_config), server_name.clone()).unwrap();
        let mut restricted_hello = Vec::new();
        restricted_conn.write_tls(&mut restricted_hello).unwrap();

        let restricted_exts = parse_client_hello_extensions(&restricted_hello);
        let supported_versions_data = restricted_exts
            .into_iter()
            .find(|(t, _)| *t == SUPPORTED_VERSIONS_EXT)
            .map(|(_, d)| d)
            .expect("ClientHello must contain supported_versions extension");

        let versions_len = supported_versions_data[0] as usize;
        let offered_versions: Vec<u16> = supported_versions_data[1..1 + versions_len]
            .chunks_exact(2)
            .map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]]))
            .collect();

        // The restricted config silently offers ONLY TLS 1.3 on the wire:
        assert_eq!(
            offered_versions,
            vec![TLS13_VERSION_ID],
            "TLS 1.2 must be silently dropped when no TLS 1.2 cipher suites are present"
        );

        // 2. In contrast, full provider with_safe_default_protocol_versions() offers BOTH TLS 1.3 and TLS 1.2:
        let full_config = ClientConfig::builder_with_provider(Arc::new(full_provider))
            .with_safe_default_protocol_versions()
            .expect("Full provider safe default succeeds")
            .with_root_certificates(RootCertStore::empty())
            .with_no_client_auth();

        let mut full_conn =
            ClientConnection::new(Arc::new(full_config), server_name).unwrap();
        let mut full_hello = Vec::new();
        full_conn.write_tls(&mut full_hello).unwrap();

        let full_exts = parse_client_hello_extensions(&full_hello);
        let full_versions_data = full_exts
            .into_iter()
            .find(|(t, _)| *t == SUPPORTED_VERSIONS_EXT)
            .map(|(_, d)| d)
            .expect("Full ClientHello must contain supported_versions extension");

        let full_versions_len = full_versions_data[0] as usize;
        let full_offered_versions: Vec<u16> = full_versions_data[1..1 + full_versions_len]
            .chunks_exact(2)
            .map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]]))
            .collect();

        assert_eq!(
            full_offered_versions,
            vec![TLS13_VERSION_ID, TLS12_VERSION_ID],
            "Full provider must offer both TLS 1.3 and TLS 1.2"
        );
    }

    #[test]
    fn test_sni_omission_for_ip_address() {
        const SNI_EXTENSION_TYPE: u16 = 0x0000;
        let provider = Arc::new(default_provider());
        let config = Arc::new(
            ClientConfig::builder_with_provider(provider)
                .with_safe_default_protocol_versions()
                .unwrap()
                .with_root_certificates(RootCertStore::empty())
                .with_no_client_auth(),
        );

        // DNS name sends SNI extension (0x0000)
        let dns_name = ServerName::try_from("example.com").unwrap();
        let mut dns_conn = ClientConnection::new(config.clone(), dns_name).unwrap();
        let mut dns_bytes = Vec::new();
        dns_conn.write_tls(&mut dns_bytes).unwrap();
        let dns_exts = parse_client_hello_extensions(&dns_bytes);
        assert!(
            dns_exts.iter().any(|(t, _)| *t == SNI_EXTENSION_TYPE),
            "DNS target must include SNI extension"
        );

        // IP address literal SILENTLY OMITS SNI extension per RFC 6066
        let ip_name = ServerName::try_from("192.0.2.1").unwrap();
        let mut ip_conn = ClientConnection::new(config, ip_name).unwrap();
        let mut ip_bytes = Vec::new();
        ip_conn.write_tls(&mut ip_bytes).unwrap();
        let ip_exts = parse_client_hello_extensions(&ip_bytes);
        assert!(
            !ip_exts.iter().any(|(t, _)| *t == SNI_EXTENSION_TYPE),
            "IP address target must omit SNI extension per RFC 6066"
        );
    }
}

Seeder de origen

csx-seed