Skip to main content

leyline/tls/
builder.rs

1#[cfg(any(
2    feature = "compression-gzip",
3    feature = "compression-brotli",
4    feature = "compression-deflate",
5    feature = "compression-zstd"
6))]
7use leyline_bssl::ssl::{CertificateCompressionAlgorithm, CertificateCompressor};
8use leyline_bssl::ssl::{SslContextBuilder, SslVerifyMode};
9
10use crate::profile::{BrowserProfile, TlsProfile};
11
12use crate::tls::error::TlsError;
13use crate::tls::keylog::install_from_env;
14use crate::tls::trust::{TlsTrustConfig, TrustIdentity, wire_configured_trust};
15
16mod phase;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
19#[non_exhaustive]
20pub enum TlsMinVersion {
21    Tls10,
22    Tls12,
23    Tls13,
24}
25
26#[cfg(any(leyline_unstable_bssl, feature = "bench-internals"))]
27impl BrowserProfile {
28    pub(crate) fn build_ssl_context(
29        &self,
30        min_version: TlsMinVersion,
31    ) -> Result<SslContextBuilder, TlsError> {
32        let mut builder = SslContextBuilder::new(leyline_bssl::ssl::SslMethod::tls())
33            .map_err(TlsError::from_stack)?;
34        let identity =
35            apply_profile_with_trust(&mut builder, self, min_version, &TlsTrustConfig::default())?;
36        drop(identity);
37        Ok(builder)
38    }
39}
40
41pub(crate) fn apply_profile_with_trust(
42    builder: &mut SslContextBuilder,
43    profile: &BrowserProfile,
44    min_version: TlsMinVersion,
45    trust: &TlsTrustConfig,
46) -> Result<TrustIdentity, TlsError> {
47    apply_tls_with_trust(builder, &profile.tls, min_version, trust)
48}
49
50pub(crate) fn apply_tls_with_trust(
51    builder: &mut SslContextBuilder,
52    tls: &TlsProfile,
53    min_version: TlsMinVersion,
54    trust: &TlsTrustConfig,
55) -> Result<TrustIdentity, TlsError> {
56    phase::ciphers(builder, tls)?;
57    phase::curves(builder, tls)?;
58    phase::sigalgs(builder, tls)?;
59    phase::extensions(builder, tls)?;
60    phase::versions(builder, tls, min_version, trust.min_tls_version)?;
61
62    let identity = wire_configured_trust(builder, trust)?;
63    builder.set_verify(SslVerifyMode::PEER);
64
65    install_from_env(builder);
66
67    Ok(identity)
68}
69
70fn tls13_cipher_ids(ciphers: &[String]) -> Result<Vec<u16>, TlsError> {
71    ciphers
72        .iter()
73        .filter_map(|cipher| match crate::iana::cipher_id(cipher) {
74            Some(id) if crate::iana::is_tls13_cipher(id) => Some(Ok(id)),
75            _ if cipher.starts_with("TLS_AES_") || cipher.starts_with("TLS_CHACHA20_") => {
76                Some(Err(TlsError::Profile(format!(
77                    "unknown TLS 1.3 cipher: {cipher}"
78                ))))
79            }
80            _ => None,
81        })
82        .collect()
83}
84
85fn profile_min_version(declared: &Option<String>) -> Result<Option<TlsMinVersion>, TlsError> {
86    match declared.as_deref() {
87        None => Ok(None),
88        Some("1.0") => Ok(Some(TlsMinVersion::Tls10)),
89        Some("1.2") => Ok(Some(TlsMinVersion::Tls12)),
90        Some("1.3") => Ok(Some(TlsMinVersion::Tls13)),
91        Some(other) => Err(TlsError::Profile(format!(
92            "unsupported min_tls_version {other:?}; expected \"1.0\", \"1.2\", or \"1.3\""
93        ))),
94    }
95}
96
97#[cfg(not(all(
98    feature = "compression-brotli",
99    feature = "compression-zstd",
100    any(feature = "compression-gzip", feature = "compression-deflate")
101)))]
102fn missing(algo: &str, feature: &str) -> TlsError {
103    TlsError::Profile(format!(
104        "profile requests {algo} certificate decompression but the `{feature}` feature is not compiled in"
105    ))
106}
107
108#[cfg(any(
109    feature = "compression-gzip",
110    feature = "compression-brotli",
111    feature = "compression-deflate",
112    feature = "compression-zstd"
113))]
114const MAX_CERT_DECOMPRESSED_BYTES: usize = 1024 * 1024;
115
116#[cfg(any(
117    feature = "compression-gzip",
118    feature = "compression-brotli",
119    feature = "compression-deflate",
120    feature = "compression-zstd"
121))]
122fn read_limited_cert<R: std::io::Read>(mut decoder: R) -> std::io::Result<Vec<u8>> {
123    let mut out = Vec::new();
124    let mut buf = [0u8; 8192];
125    loop {
126        let n = decoder.read(&mut buf)?;
127        if n == 0 {
128            return Ok(out);
129        }
130        if out.len() + n > MAX_CERT_DECOMPRESSED_BYTES {
131            return Err(std::io::Error::new(
132                std::io::ErrorKind::InvalidData,
133                "decompressed certificate exceeds limit",
134            ));
135        }
136        out.extend_from_slice(&buf[..n]);
137    }
138}
139
140#[cfg(feature = "compression-brotli")]
141#[derive(Debug)]
142struct BrotliDecompressor;
143
144#[cfg(feature = "compression-brotli")]
145impl CertificateCompressor for BrotliDecompressor {
146    const ALGORITHM: CertificateCompressionAlgorithm = CertificateCompressionAlgorithm::BROTLI;
147    const CAN_COMPRESS: bool = false;
148    const CAN_DECOMPRESS: bool = true;
149
150    fn decompress<W>(&self, input: &[u8], output: &mut W) -> std::io::Result<()>
151    where
152        W: std::io::Write,
153    {
154        let decoded = read_limited_cert(brotli::Decompressor::new(input, 4096))?;
155        output.write_all(&decoded)
156    }
157}
158
159#[cfg(any(feature = "compression-gzip", feature = "compression-deflate"))]
160#[derive(Debug)]
161struct ZlibDecompressor;
162
163#[cfg(any(feature = "compression-gzip", feature = "compression-deflate"))]
164impl CertificateCompressor for ZlibDecompressor {
165    const ALGORITHM: CertificateCompressionAlgorithm = CertificateCompressionAlgorithm::ZLIB;
166    const CAN_COMPRESS: bool = false;
167    const CAN_DECOMPRESS: bool = true;
168
169    fn decompress<W>(&self, input: &[u8], output: &mut W) -> std::io::Result<()>
170    where
171        W: std::io::Write,
172    {
173        let decoded = read_limited_cert(flate2::read::ZlibDecoder::new(input))?;
174        output.write_all(&decoded)
175    }
176}
177
178#[cfg(feature = "compression-zstd")]
179#[derive(Debug)]
180struct ZstdDecompressor;
181
182#[cfg(feature = "compression-zstd")]
183impl CertificateCompressor for ZstdDecompressor {
184    const ALGORITHM: CertificateCompressionAlgorithm = CertificateCompressionAlgorithm::ZSTD;
185    const CAN_COMPRESS: bool = false;
186    const CAN_DECOMPRESS: bool = true;
187
188    fn decompress<W>(&self, input: &[u8], output: &mut W) -> std::io::Result<()>
189    where
190        W: std::io::Write,
191    {
192        let decoder = zstd::stream::read::Decoder::new(input)?;
193        let decoded = read_limited_cert(decoder)?;
194        output.write_all(&decoded)
195    }
196}
197
198#[cfg(test)]
199mod tests;