Skip to main content

miku_ktls/utils/
compatible_ciphers.rs

1//! kTLS compatible ciphers checker.
2
3use std::{future::Future, io, net::SocketAddr, os::unix::io::AsRawFd};
4
5use rustls::SupportedCipherSuite;
6use smallvec::SmallVec;
7use tokio::net::{TcpListener, TcpStream};
8
9use crate::{
10    cipher::{cipher_suite, KtlsCipherSuite, KtlsCipherType},
11    version::KtlsVersion,
12    CryptoInfo, Error,
13};
14
15#[derive(Debug, Default, Clone, Copy)]
16/// kTLS compatible ciphers.
17pub struct CompatibleCiphers {
18    /// TLS 1.2 compatible ciphers.
19    pub tls12: CompatibleCiphersForVersion,
20
21    /// TLS 1.3 compatible ciphers.
22    pub tls13: CompatibleCiphersForVersion,
23}
24
25impl CompatibleCiphers {
26    /// List compatible ciphers. This listens on a TCP socket and blocks for a
27    /// little while. Do once at the very start of a program.
28    pub async fn new() -> io::Result<Self> {
29        let mut ciphers = CompatibleCiphers::default();
30
31        let ln = TcpListener::bind("0.0.0.0:0").await?;
32        let local_addr = ln.local_addr()?;
33
34        // Accepted conns of ln
35        let mut accepted_conns: SmallVec<[TcpStream; 12]> = SmallVec::new();
36
37        let accept_conns_fut = async {
38            loop {
39                if let Ok((conn, _addr)) = ln.accept().await {
40                    accepted_conns.push(conn);
41                }
42            }
43        };
44
45        ciphers.test_ciphers(local_addr, accept_conns_fut).await?;
46
47        Ok(ciphers)
48    }
49
50    async fn test_ciphers(
51        &mut self,
52        local_addr: SocketAddr,
53        accept_conns_fut: impl Future<Output = ()>,
54    ) -> io::Result<()> {
55        let ciphers: Vec<(SupportedCipherSuite, &mut bool)> = vec![
56            (
57                cipher_suite::TLS13_AES_128_GCM_SHA256,
58                &mut self.tls13.aes_gcm_128,
59            ),
60            (
61                cipher_suite::TLS13_AES_256_GCM_SHA384,
62                &mut self.tls13.aes_gcm_256,
63            ),
64            (
65                cipher_suite::TLS13_CHACHA20_POLY1305_SHA256,
66                &mut self.tls13.chacha20_poly1305,
67            ),
68            (
69                cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
70                &mut self.tls12.aes_gcm_128,
71            ),
72            (
73                cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
74                &mut self.tls12.aes_gcm_256,
75            ),
76            (
77                cipher_suite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
78                &mut self.tls12.chacha20_poly1305,
79            ),
80        ];
81
82        let create_connections_fut = futures_util::future::try_join_all(
83            (0..ciphers.len()).map(|_| TcpStream::connect(local_addr)),
84        );
85
86        let socks = tokio::select! {
87            // Use biased here to optimize performance.
88            //
89            // With biased, tokio::select! would first poll create_connections_fut,
90            // which would poll all `TcpStream::connect` futures and requests
91            // new connections to `ln` then returns `Poll::Pending`.
92            //
93            // Then accept_conns_fut would be polled, which accepts all pending
94            // connections, wake up create_connections_fut then returns
95            // `Poll::Pending`.
96            //
97            // Finally, create_connections_fut wakes up and all connections
98            // are ready, the result is collected into a Vec and ends
99            // the tokio::select!.
100            biased;
101
102            res = create_connections_fut => res?,
103            _ = accept_conns_fut => unreachable!(),
104        };
105
106        assert_eq!(ciphers.len(), socks.len());
107
108        ciphers
109            .into_iter()
110            .zip(socks)
111            .for_each(|((cipher_suite, field), sock)| {
112                *field = sample_cipher_setup(&sock, cipher_suite).is_ok();
113            });
114
115        Ok(())
116    }
117
118    /// Returns true if we're reasonably confident that functions like
119    /// [config_ktls_client] and [config_ktls_server] will succeed.
120    pub fn is_compatible(&self, suite: SupportedCipherSuite) -> bool {
121        let kcs = match KtlsCipherSuite::try_from(suite) {
122            Ok(kcs) => kcs,
123            Err(_) => return false,
124        };
125
126        let fields = match kcs.version {
127            KtlsVersion::TLS12 => &self.tls12,
128            KtlsVersion::TLS13 => &self.tls13,
129        };
130
131        match kcs.typ {
132            KtlsCipherType::AesGcm128 => fields.aes_gcm_128,
133            KtlsCipherType::AesGcm256 => fields.aes_gcm_256,
134            KtlsCipherType::Chacha20Poly1305 => fields.chacha20_poly1305,
135        }
136    }
137}
138
139#[derive(Debug, Default, Clone, Copy)]
140pub struct CompatibleCiphersForVersion {
141    pub aes_gcm_128: bool,
142    pub aes_gcm_256: bool,
143    pub chacha20_poly1305: bool,
144}
145
146fn sample_cipher_setup(sock: &TcpStream, cipher_suite: SupportedCipherSuite) -> Result<(), Error> {
147    let kcs = match KtlsCipherSuite::try_from(cipher_suite) {
148        Ok(kcs) => kcs,
149        Err(_) => panic!("unsupported cipher suite"),
150    };
151
152    let ffi_version = match kcs.version {
153        KtlsVersion::TLS12 => crate::ffi::TLS_1_2_VERSION_NUMBER,
154        KtlsVersion::TLS13 => crate::ffi::TLS_1_3_VERSION_NUMBER,
155    };
156
157    let crypto_info = match kcs.typ {
158        KtlsCipherType::AesGcm128 => {
159            CryptoInfo::AesGcm128(crate::ffi::bindings::tls12_crypto_info_aes_gcm_128 {
160                info: crate::ffi::bindings::tls_crypto_info {
161                    version: ffi_version,
162                    cipher_type: crate::ffi::bindings::TLS_CIPHER_AES_GCM_128 as _,
163                },
164                iv: Default::default(),
165                key: Default::default(),
166                salt: Default::default(),
167                rec_seq: Default::default(),
168            })
169        }
170        KtlsCipherType::AesGcm256 => {
171            CryptoInfo::AesGcm256(crate::ffi::bindings::tls12_crypto_info_aes_gcm_256 {
172                info: crate::ffi::bindings::tls_crypto_info {
173                    version: ffi_version,
174                    cipher_type: crate::ffi::bindings::TLS_CIPHER_AES_GCM_256 as _,
175                },
176                iv: Default::default(),
177                key: Default::default(),
178                salt: Default::default(),
179                rec_seq: Default::default(),
180            })
181        }
182        KtlsCipherType::Chacha20Poly1305 => CryptoInfo::Chacha20Poly1305(
183            crate::ffi::bindings::tls12_crypto_info_chacha20_poly1305 {
184                info: crate::ffi::bindings::tls_crypto_info {
185                    version: ffi_version,
186                    cipher_type: crate::ffi::bindings::TLS_CIPHER_CHACHA20_POLY1305 as _,
187                },
188                iv: Default::default(),
189                key: Default::default(),
190                salt: Default::default(),
191                rec_seq: Default::default(),
192            },
193        ),
194    };
195    let fd = sock.as_raw_fd();
196
197    crate::ffi::setup_ulp(fd).map_err(Error::UlpError)?;
198
199    crate::ffi::setup_tls_info(fd, crate::ffi::Direction::Tx, crypto_info)?;
200
201    Ok(())
202}