샘플
rustls 0.23.43: Prevent silent TLS protocol version and SNI omission when configuring rustls with restricted crypto providers or IP address literals
검증된 샘플 — cargo rustls 0.23.43: Prevent silent TLS protocol version and SNI omission when configuring rustls with restricted crypto providers or IP address…
sha256:5659ebf9abcf99a6b2de6c9e2f6eb5499ff88ee1b4d263e78c7611f3e745b7aa
이 네트워크가 제공하는 것은 하나입니다. 빌드되는 샘플. 샌드박스에서 돌리고 서명된 영수증을 보관합니다. 등급을 매기지 않고 무엇도 보증하지 않습니다 — 같은 코드가 당신 환경에서 빌드되는지는 측정한 적이 없습니다.
통과한 계약 영수증을 낸 서로 다른 서명 키의 수입니다. 하나면 작성자 혼자이고, 둘 이상이면 다른 사람도 빌드했다는 뜻입니다. 키는 스스로 만드는 것이고 뒤에 등록된 신원이 없으므로, 세는 것은 사람이 아니라 키입니다.
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-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 |
케이스
HOW- 목표
- Prevent silent TLS protocol version and SNI omission when configuring rustls with restricted crypto providers or IP address literals
- 패키지
- 심벌
-
- rustls::ClientConfig::builder_with_provider
- rustls::ConfigBuilder::with_safe_default_protocol_versions
- rustls::pki_types::ServerName
- rustls::ClientConnection::new
- 환경
- rust
- 생성일
- 2026-08-16T08:23:44Z
컨트랙트
- 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
파일
- Cargo.lock
- Cargo.toml
- NOTES.md
- csx.json
- src/lib.rs
소스
# 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"
[package]
name = "cargo-rustls-version"
version = "0.1.0"
edition = "2021"
[dependencies]
rustls = { version = "0.23.23", features = ["ring", "std"] }
- `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.
{"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"}
#[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"
);
}
}