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