Skip to main content

purecrypto/tls/
connection.rs

1//! Unified [`Connection`] enum over the four TLS/DTLS connection engines.
2//!
3//! All eight per-(version, role) connection types live behind a single
4//! state-machine-pump API: [`Connection::handshake`], [`feed`](Connection::feed),
5//! [`pop`](Connection::pop), [`send`](Connection::send), and
6//! [`recv`](Connection::recv). The variants are `pub(crate)` so the public API
7//! is the methods only.
8
9use alloc::boxed::Box;
10use alloc::vec::Vec;
11use core::time::Duration;
12
13use crate::rng::OsRng;
14
15use super::config::Config;
16use super::error::Error;
17use super::version::ProtocolVersion;
18
19/// Handshake progress, as observed from the uniform API.
20#[derive(Copy, Clone, PartialEq, Eq, Debug)]
21pub enum HandshakeStatus {
22    /// The handshake is complete; application data may flow.
23    Complete,
24    /// The engine has nothing to emit; the caller should
25    /// [`feed`](Connection::feed) bytes from the peer.
26    WantRead,
27    /// The engine has wire bytes ready; the caller should drain them with
28    /// [`pop`](Connection::pop) and forward them to the peer.
29    WantWrite,
30}
31
32/// A unified TLS or DTLS connection (client or server, any supported
33/// version).
34///
35/// Construct via [`Connection::client`] or [`Connection::server`], passing a
36/// shared [`super::Config`]. The internal engine is picked from
37/// `config.max_version`.
38pub struct Connection {
39    inner: Engine,
40    /// Pending outbound DTLS datagrams; [`Connection::pop`] returns one per
41    /// call. Empty for TLS engines, which return their entire write buffer
42    /// in one call.
43    pending_dtls: alloc::collections::VecDeque<Vec<u8>>,
44}
45
46#[allow(clippy::large_enum_variant)]
47enum Engine {
48    /// TLS 1.3 client.
49    ClientTls13(Box<super::conn::ClientConnection>),
50    /// TLS 1.2 client.
51    ClientTls12(Box<super::conn::ClientConnection12>),
52    /// TLS 1.3 server.
53    ServerTls13(Box<super::conn::ServerConnection<OsRng>>),
54    /// TLS 1.2 server.
55    ServerTls12(Box<super::conn::ServerConnection12<OsRng>>),
56    /// DTLS 1.3 client.
57    ClientDtls13(Box<crate::dtls::DtlsClientConnection13>),
58    /// DTLS 1.2 client.
59    ClientDtls12(Box<crate::dtls::DtlsClientConnection12>),
60    /// DTLS 1.3 server.
61    ServerDtls13(Box<crate::dtls::DtlsServerConnection13<OsRng>>),
62    /// DTLS 1.2 server.
63    ServerDtls12(Box<crate::dtls::DtlsServerConnection12<OsRng>>),
64}
65
66impl Connection {
67    /// Build a client connection. Picks the engine from `config.max_version`.
68    pub fn client(config: &Config) -> Result<Self, Error> {
69        config.check_versions()?;
70        let inner = match config.max_version {
71            ProtocolVersion::TLSv1_3 => Engine::ClientTls13(Box::new(build_tls13_client(config)?)),
72            ProtocolVersion::TLSv1_2 => Engine::ClientTls12(Box::new(build_tls12_client(config)?)),
73            // The TLS 1.2 engine also drives the opt-in legacy path; a caller
74            // that tops out at TLS 1.0/1.1 still routes through it.
75            #[cfg(feature = "tls-legacy")]
76            ProtocolVersion::TLSv1_1 | ProtocolVersion::TLSv1_0 | ProtocolVersion::SSLv3 => {
77                Engine::ClientTls12(Box::new(build_tls12_client(config)?))
78            }
79            ProtocolVersion::DTLSv1_3 => {
80                Engine::ClientDtls13(Box::new(build_dtls13_client(config)?))
81            }
82            ProtocolVersion::DTLSv1_2 => {
83                Engine::ClientDtls12(Box::new(build_dtls12_client(config)?))
84            }
85            _ => return Err(Error::UnsupportedVersion),
86        };
87        Ok(Connection {
88            inner,
89            pending_dtls: alloc::collections::VecDeque::new(),
90        })
91    }
92
93    /// Build a server connection. Picks the engine from `config.max_version`.
94    /// Requires `config.identity.is_some()`.
95    pub fn server(config: &Config) -> Result<Self, Error> {
96        config.check_versions()?;
97        if config.identity.is_none() {
98            return Err(Error::InappropriateState);
99        }
100        let inner = match config.max_version {
101            ProtocolVersion::TLSv1_3 => Engine::ServerTls13(Box::new(build_tls13_server(config)?)),
102            ProtocolVersion::TLSv1_2 => Engine::ServerTls12(Box::new(build_tls12_server(config)?)),
103            #[cfg(feature = "tls-legacy")]
104            ProtocolVersion::TLSv1_1 | ProtocolVersion::TLSv1_0 | ProtocolVersion::SSLv3 => {
105                Engine::ServerTls12(Box::new(build_tls12_server(config)?))
106            }
107            ProtocolVersion::DTLSv1_3 => {
108                Engine::ServerDtls13(Box::new(build_dtls13_server(config)?))
109            }
110            ProtocolVersion::DTLSv1_2 => {
111                Engine::ServerDtls12(Box::new(build_dtls12_server(config)?))
112            }
113            _ => return Err(Error::UnsupportedVersion),
114        };
115        Ok(Connection {
116            inner,
117            pending_dtls: alloc::collections::VecDeque::new(),
118        })
119    }
120
121    /// Drive the handshake forward. Returns the next [`HandshakeStatus`].
122    pub fn handshake(&mut self) -> Result<HandshakeStatus, Error> {
123        if self.is_handshake_complete() {
124            return Ok(HandshakeStatus::Complete);
125        }
126        // Refill DTLS pending queue.
127        self.refill_dtls_pending();
128        if self.wants_write() {
129            Ok(HandshakeStatus::WantWrite)
130        } else {
131            Ok(HandshakeStatus::WantRead)
132        }
133    }
134
135    /// Wire bytes from the peer into the engine. Returns the number of
136    /// bytes consumed.
137    pub fn feed(&mut self, wire_in: &[u8]) -> Result<usize, Error> {
138        match &mut self.inner {
139            Engine::ClientTls13(c) => {
140                c.read_tls(wire_in);
141                c.process_new_packets()?;
142            }
143            Engine::ClientTls12(c) => {
144                c.read_tls(wire_in);
145                c.process_new_packets()?;
146            }
147            Engine::ServerTls13(c) => {
148                c.read_tls(wire_in);
149                c.process_new_packets()?;
150            }
151            Engine::ServerTls12(c) => {
152                c.read_tls(wire_in);
153                c.process_new_packets()?;
154            }
155            Engine::ClientDtls12(c) => c.feed_datagram(wire_in)?,
156            Engine::ClientDtls13(c) => c.feed_datagram(wire_in)?,
157            Engine::ServerDtls12(c) => c.feed_datagram(wire_in)?,
158            Engine::ServerDtls13(c) => c.feed_datagram(wire_in)?,
159        }
160        // Eagerly pull DTLS datagrams into the buffer.
161        self.refill_dtls_pending();
162        Ok(wire_in.len())
163    }
164
165    /// Wire bytes the engine wants to send to the peer. For TLS, this is a
166    /// contiguous stream slice; for DTLS, this is one datagram per call.
167    pub fn pop(&mut self) -> Result<Vec<u8>, Error> {
168        let bytes: Vec<u8> = match &mut self.inner {
169            Engine::ClientTls13(c) => c.write_tls(),
170            Engine::ClientTls12(c) => c.write_tls(),
171            Engine::ServerTls13(c) => c.write_tls(),
172            Engine::ServerTls12(c) => c.write_tls(),
173            _ => {
174                // Refill if buffer empty, then pop the next datagram.
175                if self.pending_dtls.is_empty() {
176                    let drained = match &mut self.inner {
177                        Engine::ClientDtls12(c) => c.pop_outbound_datagrams(),
178                        Engine::ClientDtls13(c) => c.pop_outbound_datagrams(),
179                        Engine::ServerDtls12(c) => c.pop_outbound_datagrams(),
180                        Engine::ServerDtls13(c) => c.pop_outbound_datagrams(),
181                        _ => Vec::new(),
182                    };
183                    for dg in drained {
184                        self.pending_dtls.push_back(dg);
185                    }
186                }
187                self.pending_dtls.pop_front().unwrap_or_default()
188            }
189        };
190        Ok(bytes)
191    }
192
193    /// App bytes into the engine (post-handshake).
194    pub fn send(&mut self, app: &[u8]) -> Result<(), Error> {
195        match &mut self.inner {
196            Engine::ClientTls13(c) => c.send_application_data(app),
197            Engine::ClientTls12(c) => c.send_application_data(app),
198            Engine::ServerTls13(c) => c.send_application_data(app),
199            Engine::ServerTls12(c) => c.send_application_data(app),
200            Engine::ClientDtls12(c) => c.send(app),
201            Engine::ClientDtls13(c) => c.send(app),
202            Engine::ServerDtls12(c) => c.send(app),
203            Engine::ServerDtls13(c) => c.send(app),
204        }
205    }
206
207    /// App bytes out (post-handshake).
208    pub fn recv(&mut self) -> Result<Vec<u8>, Error> {
209        Ok(match &mut self.inner {
210            Engine::ClientTls13(c) => c.take_received_plaintext(),
211            Engine::ClientTls12(c) => c.take_received_plaintext(),
212            Engine::ServerTls13(c) => c.take_received_plaintext(),
213            Engine::ServerTls12(c) => c.take_received_plaintext(),
214            Engine::ClientDtls12(c) => c.take_received(),
215            Engine::ClientDtls13(c) => c.take_received(),
216            Engine::ServerDtls12(c) => c.take_received(),
217            Engine::ServerDtls13(c) => c.take_received(),
218        })
219    }
220
221    /// Close the connection, emitting a close_notify alert if the engine
222    /// supports it.
223    pub fn close(&mut self) -> Result<(), Error> {
224        match &mut self.inner {
225            Engine::ClientTls13(c) => c.send_close_notify(),
226            Engine::ClientTls12(c) => c.send_close_notify(),
227            Engine::ServerTls13(c) => c.send_close_notify(),
228            Engine::ServerTls12(c) => c.send_close_notify(),
229            // DTLS in this library does not emit an explicit close_notify
230            // through its public API; the connection is closed when freed.
231            _ => {}
232        }
233        Ok(())
234    }
235
236    /// True once the handshake has completed.
237    pub fn is_handshake_complete(&self) -> bool {
238        match &self.inner {
239            Engine::ClientTls13(c) => !c.is_handshaking(),
240            Engine::ClientTls12(c) => !c.is_handshaking(),
241            Engine::ServerTls13(c) => !c.is_handshaking(),
242            Engine::ServerTls12(c) => !c.is_handshaking(),
243            Engine::ClientDtls12(c) => c.is_handshake_complete(),
244            Engine::ClientDtls13(c) => c.is_handshake_complete(),
245            Engine::ServerDtls12(c) => c.is_handshake_complete(),
246            Engine::ServerDtls13(c) => c.is_handshake_complete(),
247        }
248    }
249
250    /// The negotiated wire version, if the handshake has progressed enough
251    /// to determine it.
252    pub fn negotiated_version(&self) -> Option<ProtocolVersion> {
253        match &self.inner {
254            Engine::ClientTls13(_) | Engine::ServerTls13(_) => Some(ProtocolVersion::TLSv1_3),
255            // The TLS 1.2 engine also drives the opt-in legacy versions, so it
256            // reports its own negotiated version (TLS 1.0/1.1 when lowered).
257            Engine::ClientTls12(c) => c.negotiated_protocol_version(),
258            Engine::ServerTls12(c) => c.negotiated_protocol_version(),
259            Engine::ClientDtls12(_) | Engine::ServerDtls12(_) => Some(ProtocolVersion::DTLSv1_2),
260            Engine::ClientDtls13(_) | Engine::ServerDtls13(_) => Some(ProtocolVersion::DTLSv1_3),
261        }
262    }
263
264    /// IANA cipher-suite identifier of the negotiated suite. `None`
265    /// until the handshake has advanced far enough to fix the suite
266    /// (ServerHello processed on the client, ClientHello processed on
267    /// the server).
268    pub fn negotiated_cipher_suite(&self) -> Option<u16> {
269        match &self.inner {
270            Engine::ClientTls13(c) => c.negotiated_cipher_suite(),
271            Engine::ClientTls12(c) => c.negotiated_cipher_suite(),
272            Engine::ServerTls13(c) => {
273                // The TLS 1.3 server tracks its suite internally; the
274                // existing public surface is `negotiated_suite()`-shaped
275                // (Option<CipherSuite>). Defer to the same accessor.
276                c.negotiated_cipher_suite()
277            }
278            Engine::ServerTls12(c) => c.negotiated_cipher_suite(),
279            Engine::ClientDtls13(c) => c.negotiated_cipher_suite(),
280            Engine::ServerDtls13(c) => c.negotiated_cipher_suite(),
281            Engine::ClientDtls12(c) => c.negotiated_cipher_suite(),
282            Engine::ServerDtls12(c) => c.negotiated_cipher_suite(),
283        }
284    }
285
286    /// The IANA name of the negotiated cipher suite, or `None` until the
287    /// suite is fixed. Returns the well-known strings for the suites
288    /// purecrypto negotiates (TLS 1.3 trio + the TLS 1.2 ECDHE-AEAD
289    /// set); unknown codes resolve to `"UNKNOWN"`.
290    pub fn negotiated_cipher_suite_name(&self) -> Option<&'static str> {
291        self.negotiated_cipher_suite().map(cipher_suite_name)
292    }
293
294    /// The negotiated ALPN protocol, if any.
295    pub fn alpn_selected(&self) -> Option<&[u8]> {
296        match &self.inner {
297            Engine::ClientTls13(c) => c.alpn_protocol(),
298            Engine::ClientTls12(c) => c.alpn_protocol(),
299            Engine::ServerTls13(c) => c.alpn_protocol(),
300            Engine::ServerTls12(c) => c.alpn_protocol(),
301            Engine::ClientDtls13(c) => c.alpn_protocol(),
302            _ => None,
303        }
304    }
305
306    /// Server-side: the SNI host_name the client offered in the ClientHello
307    /// `server_name` extension (RFC 6066 §3). `None` for client engines,
308    /// for DTLS engines (no SNI plumbing yet), or when the peer omitted the
309    /// extension. Available once the ClientHello has been processed.
310    pub fn peer_server_name(&self) -> Option<&str> {
311        match &self.inner {
312            Engine::ServerTls13(c) => c.peer_server_name(),
313            Engine::ServerTls12(c) => c.peer_server_name(),
314            _ => None,
315        }
316    }
317
318    /// The peer's certificate chain (leaf first, DER).
319    pub fn peer_certificates(&self) -> &[Vec<u8>] {
320        match &self.inner {
321            Engine::ClientTls13(c) => c.peer_certificates(),
322            Engine::ClientTls12(c) => c.peer_certificates(),
323            Engine::ServerTls13(c) => c.peer_certificates(),
324            Engine::ServerTls12(c) => c.peer_certificates(),
325            Engine::ClientDtls13(c) => c.peer_certificates(),
326            _ => &[],
327        }
328    }
329
330    /// DTLS: next retransmit timeout. None on TLS variants.
331    pub fn next_timeout(&self) -> Option<Duration> {
332        match &self.inner {
333            Engine::ClientDtls12(c) => c.next_timeout(),
334            Engine::ClientDtls13(c) => c.next_timeout(),
335            Engine::ServerDtls12(c) => c.next_timeout(),
336            Engine::ServerDtls13(c) => c.next_timeout(),
337            _ => None,
338        }
339    }
340
341    /// DTLS: notify the engine that the retransmit deadline has elapsed.
342    /// No-op on TLS variants.
343    pub fn on_timeout(&mut self, now: Duration) {
344        match &mut self.inner {
345            Engine::ClientDtls12(c) => c.on_timeout(now),
346            Engine::ClientDtls13(c) => c.on_timeout(now),
347            Engine::ServerDtls12(c) => c.on_timeout(now),
348            Engine::ServerDtls13(c) => c.on_timeout(now),
349            _ => {}
350        }
351    }
352
353    fn wants_write(&self) -> bool {
354        match &self.inner {
355            Engine::ClientTls13(c) => c.wants_write(),
356            Engine::ClientTls12(c) => c.wants_write(),
357            Engine::ServerTls13(c) => c.wants_write(),
358            Engine::ServerTls12(c) => c.wants_write(),
359            // DTLS: any pending datagram counts as wanting-write.
360            _ => !self.pending_dtls.is_empty(),
361        }
362    }
363
364    /// Drain new outbound datagrams from the DTLS engine into the pending
365    /// buffer. No-op for TLS variants.
366    fn refill_dtls_pending(&mut self) {
367        let drained: Vec<Vec<u8>> = match &mut self.inner {
368            Engine::ClientDtls12(c) => c.pop_outbound_datagrams(),
369            Engine::ClientDtls13(c) => c.pop_outbound_datagrams(),
370            Engine::ServerDtls12(c) => c.pop_outbound_datagrams(),
371            Engine::ServerDtls13(c) => c.pop_outbound_datagrams(),
372            _ => return,
373        };
374        for dg in drained {
375            self.pending_dtls.push_back(dg);
376        }
377    }
378}
379
380/// Maps an IANA cipher-suite wire code to its registered name. Covers
381/// every suite this crate negotiates; unknown codes resolve to
382/// `"UNKNOWN"` so the function is total.
383fn cipher_suite_name(id: u16) -> &'static str {
384    match id {
385        0x1301 => "TLS_AES_128_GCM_SHA256",
386        0x1302 => "TLS_AES_256_GCM_SHA384",
387        0x1303 => "TLS_CHACHA20_POLY1305_SHA256",
388        0xC02B => "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
389        0xC02C => "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
390        0xC02F => "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
391        0xC030 => "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
392        0xCCA8 => "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
393        0xCCA9 => "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
394        // Opt-in legacy CBC suites (tls-legacy).
395        0x000A => "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
396        0x002F => "TLS_RSA_WITH_AES_128_CBC_SHA",
397        0x0035 => "TLS_RSA_WITH_AES_256_CBC_SHA",
398        0x003C => "TLS_RSA_WITH_AES_128_CBC_SHA256",
399        0x003D => "TLS_RSA_WITH_AES_256_CBC_SHA256",
400        0xC012 => "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
401        0xC013 => "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
402        0xC014 => "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
403        0xC027 => "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
404        0xC028 => "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA256",
405        _ => "UNKNOWN",
406    }
407}
408
409// ---- Engine builders --------------------------------------------------------
410
411/// The client's intended server name, used for SNI and (when enabled) hostname
412/// verification. A name is **required only when `verify_certificates` is on** —
413/// without it there is nothing to check the peer certificate against, so a
414/// missing name is a misconfiguration. With verification off (e.g. connecting to
415/// a device by IP), the name is optional; an empty string means "no SNI, no
416/// hostname check", which the engines honour by omitting the SNI extension.
417fn client_server_name(cfg: &Config) -> Result<&str, Error> {
418    match cfg.server_name.as_deref() {
419        Some(name) => Ok(name),
420        None if !cfg.verify_certificates => Ok(""),
421        None => Err(Error::MissingServerName),
422    }
423}
424
425fn build_tls13_client(cfg: &Config) -> Result<super::conn::ClientConnection, Error> {
426    let mut cc = super::conn::ClientConfig::new(cfg.roots.clone_store());
427    cc.verify_certificates = cfg.verify_certificates;
428    cc.cipher_suites = cfg.cipher_suites.clone();
429    if !cfg.alpn_protocols.is_empty() {
430        cc = cc.with_alpn(cfg.alpn_protocols.clone());
431    }
432    if !cfg.crls.is_empty() {
433        cc = cc.with_crls(cfg.crls.clone_store());
434    }
435    if let Some(t) = cfg.verification_time.clone() {
436        cc.verification_time = Some(t);
437    }
438    if let Some(rsl) = cfg.record_size_limit {
439        cc = cc.with_record_size_limit(rsl);
440    }
441    cc = cc.with_signature_policy(cfg.signature_policy.clone());
442    if let Some(id) = &cfg.identity {
443        let cc_cfg = client_cert_from_signing(id);
444        if let Some(c) = cc_cfg {
445            cc = cc.with_client_cert(c);
446        }
447    }
448    cc = cc.with_server_cert_type_preference(cfg.server_cert_type_preference.clone());
449    cc = cc.with_client_cert_type_preference(cfg.client_cert_type_preference.clone());
450    for spki in &cfg.expected_raw_public_keys {
451        cc = cc.add_expected_raw_public_key(spki.clone());
452    }
453    cc.key_log = cfg.key_log.clone();
454    #[cfg(feature = "ech")]
455    {
456        cc.ech = cfg.ech.clone();
457    }
458    #[cfg(feature = "cert-compression")]
459    {
460        cc = cc.with_cert_compression_algorithms(cfg.cert_compression_algorithms.clone());
461    }
462    let server_name = client_server_name(cfg)?;
463    Ok(super::conn::ClientConnection::new(
464        cc,
465        server_name,
466        &mut OsRng,
467    ))
468}
469
470fn build_tls12_client(cfg: &Config) -> Result<super::conn::ClientConnection12, Error> {
471    let mut cc = super::conn::ClientConfig12::new(cfg.roots.clone_store());
472    cc.verify_certificates = cfg.verify_certificates;
473    cc.cipher_suites = cfg.cipher_suites.clone();
474    if !cfg.alpn_protocols.is_empty() {
475        cc = cc.with_alpn(cfg.alpn_protocols.clone());
476    }
477    if !cfg.crls.is_empty() {
478        cc = cc.with_crls(cfg.crls.clone_store());
479    }
480    if let Some(t) = cfg.verification_time.clone() {
481        cc = cc.with_verification_time(t);
482    }
483    if let Some(rsl) = cfg.record_size_limit {
484        cc = cc.with_record_size_limit(rsl);
485    }
486    cc = cc.with_signature_policy(cfg.signature_policy.clone());
487    if let Some(id) = &cfg.identity {
488        let cc_cfg = client_cert_from_signing(id);
489        if let Some(c) = cc_cfg {
490            cc = cc.with_client_cert(c);
491        }
492    }
493    cc.key_log = cfg.key_log.clone();
494    #[cfg(feature = "tls-legacy")]
495    {
496        cc = cc.with_min_version(cfg.min_version);
497        // The 1.2 engine caps at TLS 1.2; only propagate a lower max so a
498        // legacy-only caller offers `legacy_version` ≤ 1.1 and no AEAD suites.
499        if cfg.max_version.as_u16() < ProtocolVersion::TLSv1_2.as_u16() {
500            cc = cc.with_max_version(cfg.max_version);
501        }
502    }
503    let server_name = client_server_name(cfg)?;
504    Ok(super::conn::ClientConnection12::new(
505        cc,
506        server_name,
507        &mut OsRng,
508    ))
509}
510
511fn build_tls13_server(cfg: &Config) -> Result<super::conn::ServerConnection<OsRng>, Error> {
512    let id = cfg.identity.as_ref().ok_or(Error::InappropriateState)?;
513    let chain = id.cert_chain.clone();
514    let mut sc = match &id.key {
515        super::config::SigningKey::Rsa(k) => super::conn::ServerConfig::with_rsa(chain, k.clone()),
516        super::config::SigningKey::Ecdsa(k) => {
517            super::conn::ServerConfig::with_ecdsa(chain, k.clone())
518        }
519        super::config::SigningKey::Ed25519(k) => {
520            super::conn::ServerConfig::with_ed25519(chain, k.clone())
521        }
522        super::config::SigningKey::Ed448(k) => {
523            super::conn::ServerConfig::with_ed448(chain, k.clone())
524        }
525        super::config::SigningKey::MlDsa44(k) => {
526            super::conn::ServerConfig::with_mldsa44(chain, k.clone())
527        }
528        super::config::SigningKey::MlDsa65(k) => {
529            super::conn::ServerConfig::with_mldsa65(chain, k.clone())
530        }
531        super::config::SigningKey::MlDsa87(k) => {
532            super::conn::ServerConfig::with_mldsa87(chain, k.clone())
533        }
534    };
535    if !cfg.alpn_protocols.is_empty() {
536        sc = sc.with_alpn(cfg.alpn_protocols.clone());
537    }
538    if !cfg.crls.is_empty() {
539        sc = sc.with_crls(cfg.crls.clone_store());
540    }
541    if let Some(rsl) = cfg.record_size_limit {
542        sc = sc.with_record_size_limit(rsl);
543    }
544    if let Some(ca) = &cfg.client_auth {
545        sc = sc.with_client_auth(ca.roots.clone_store(), ca.required);
546    }
547    if let Some(tk) = cfg.ticket_key {
548        sc = sc.with_ticket_key(tk);
549    }
550    if cfg.max_early_data_size > 0 {
551        sc = sc.with_max_early_data(cfg.max_early_data_size);
552    }
553    #[cfg(feature = "std")]
554    if let Some(rw) = cfg.replay_window.clone() {
555        sc = sc.with_replay_window(rw);
556    }
557    if let Some(crl) = cfg.stapled_crl.clone() {
558        sc = sc.with_stapled_crl(crl);
559    }
560    if let Some(ocsp) = cfg.stapled_ocsp_response.clone() {
561        sc = sc.with_stapled_ocsp_response(ocsp);
562    }
563    sc = sc.with_server_cert_type_preference(cfg.server_cert_type_preference.clone());
564    sc = sc.with_client_cert_type_preference(cfg.client_cert_type_preference.clone());
565    if let Some(spki) = cfg.raw_public_key_spki.clone() {
566        sc = sc.with_raw_public_key_spki(spki);
567    }
568    sc = sc.with_signature_policy(cfg.signature_policy.clone());
569    #[cfg(feature = "cert-compression")]
570    {
571        sc = sc.with_cert_compression_algorithms(cfg.cert_compression_algorithms.clone());
572    }
573    #[cfg(feature = "ech")]
574    if let Some(ech) = cfg.ech_server.clone() {
575        sc = sc.with_ech_server(ech);
576    }
577    if let Some(g) = cfg.preferred_key_exchange_group {
578        sc = sc.with_preferred_key_exchange_group(g);
579    }
580    if let Some(t) = cfg.verification_time.clone() {
581        sc = sc.with_verification_time(t);
582    }
583    sc.key_log = cfg.key_log.clone();
584    Ok(super::conn::ServerConnection::new(sc, OsRng))
585}
586
587fn build_tls12_server(cfg: &Config) -> Result<super::conn::ServerConnection12<OsRng>, Error> {
588    let id = cfg.identity.as_ref().ok_or(Error::InappropriateState)?;
589    let chain = id.cert_chain.clone();
590    let mut sc = id
591        .key
592        .try_into_server_config_12(chain)
593        .ok_or(Error::UnsupportedVersion)?;
594    if !cfg.alpn_protocols.is_empty() {
595        sc = sc.with_alpn(cfg.alpn_protocols.clone());
596    }
597    if !cfg.crls.is_empty() {
598        sc = sc.with_crls(cfg.crls.clone_store());
599    }
600    if let Some(rsl) = cfg.record_size_limit {
601        sc = sc.with_record_size_limit(rsl);
602    }
603    if let Some(ca) = &cfg.client_auth {
604        sc = sc.with_client_auth(ca.roots.clone_store(), ca.required);
605    }
606    if let Some(tk) = cfg.ticket_key {
607        sc = sc.with_ticket_key(tk);
608    }
609    if let Some(ocsp) = cfg.stapled_ocsp_response.clone() {
610        sc = sc.with_stapled_ocsp_response(ocsp);
611    }
612    sc = sc.with_signature_policy(cfg.signature_policy.clone());
613    if let Some(t) = cfg.verification_time.clone() {
614        sc = sc.with_verification_time(t);
615    }
616    sc.key_log = cfg.key_log.clone();
617    #[cfg(feature = "tls-legacy")]
618    {
619        sc = sc.with_min_version(cfg.min_version);
620    }
621    Ok(super::conn::ServerConnection12::new(sc, OsRng))
622}
623
624fn build_dtls12_client(cfg: &Config) -> Result<crate::dtls::DtlsClientConnection12, Error> {
625    let server_name = client_server_name(cfg)?;
626    let mut dc = crate::dtls::ClientConfig12Internal::new(cfg.roots.clone_store(), server_name);
627    if !cfg.verify_certificates {
628        dc = dc.without_certificate_verification();
629    }
630    if !cfg.crls.is_empty() {
631        dc = dc.with_crls(cfg.crls.clone_store());
632    }
633    if let Some(t) = cfg.verification_time.clone() {
634        dc = dc.with_verification_time(t);
635    }
636    dc = dc.with_signature_policy(cfg.signature_policy.clone());
637    dc.key_log = cfg.key_log.clone();
638    Ok(crate::dtls::DtlsClientConnection12::new(
639        dc,
640        Vec::new(),
641        &mut OsRng,
642    ))
643}
644
645fn build_dtls13_client(cfg: &Config) -> Result<crate::dtls::DtlsClientConnection13, Error> {
646    let server_name = client_server_name(cfg)?;
647    let mut dc = crate::dtls::ClientConfig13Internal::new(cfg.roots.clone_store(), server_name);
648    if !cfg.verify_certificates {
649        dc = dc.without_certificate_verification();
650    }
651    if !cfg.crls.is_empty() {
652        dc = dc.with_crls(cfg.crls.clone_store());
653    }
654    if let Some(t) = cfg.verification_time.clone() {
655        dc = dc.with_verification_time(t);
656    }
657    dc = dc.with_signature_policy(alloc::sync::Arc::new(cfg.signature_policy.clone()));
658    dc.max_record_size = cfg.max_record_size;
659    dc.key_log = cfg.key_log.clone();
660    Ok(crate::dtls::DtlsClientConnection13::new(
661        dc,
662        Vec::new(),
663        &mut OsRng,
664    ))
665}
666
667fn build_dtls12_server(cfg: &Config) -> Result<crate::dtls::DtlsServerConnection12<OsRng>, Error> {
668    let id = cfg.identity.as_ref().ok_or(Error::InappropriateState)?;
669    // RFC 6347 §4.2.1: the cookie exchange defeats blind amplification
670    // attacks. We refuse to construct a server that claims to require the
671    // exchange but cannot mint cookies — silently disabling cookies under a
672    // misconfiguration is the 50-100x DoS amplification vector. Fail-closed
673    // so the operator makes a deliberate choice.
674    if cfg.require_cookie && cfg.cookie_secret.is_none() {
675        return Err(Error::InappropriateState);
676    }
677    let chain = id.cert_chain.clone();
678    let mut sc = match &id.key {
679        super::config::SigningKey::Ecdsa(k) => {
680            crate::dtls::ServerConfig12Internal::with_ecdsa(chain, k.clone())
681        }
682        super::config::SigningKey::Rsa(k) => {
683            crate::dtls::ServerConfig12Internal::with_rsa(chain, k.clone())
684        }
685        // DTLS 1.2 mirrors TLS 1.2's scope: RSA + ECDSA only. Ed25519 and
686        // ML-DSA are not common in TLS 1.2 practice.
687        _ => return Err(Error::UnsupportedVersion),
688    };
689    if let Some(secret) = cfg.cookie_secret {
690        sc = sc.with_cookie_secret(secret);
691    }
692    if !cfg.require_cookie {
693        sc = sc.require_cookie_exchange(false);
694    }
695    sc.key_log = cfg.key_log.clone();
696    Ok(crate::dtls::DtlsServerConnection12::new(
697        alloc::sync::Arc::new(sc),
698        Vec::new(),
699        OsRng,
700    ))
701}
702
703fn build_dtls13_server(cfg: &Config) -> Result<crate::dtls::DtlsServerConnection13<OsRng>, Error> {
704    let id = cfg.identity.as_ref().ok_or(Error::InappropriateState)?;
705    // RFC 9147 §5.1: DTLS 1.3 retains the cookie-based stateless rejection
706    // for the same DoS-amplification reason. Mirror the fail-closed posture
707    // of `build_dtls12_server`.
708    if cfg.require_cookie && cfg.cookie_secret.is_none() {
709        return Err(Error::InappropriateState);
710    }
711    let chain = id.cert_chain.clone();
712    let server_key = id.key.to_server_key_13();
713    let mut sc = crate::dtls::ServerConfig13Internal::with_signing_key(chain, server_key);
714    if let Some(secret) = cfg.cookie_secret {
715        sc = sc.with_cookie_secret(secret);
716    }
717    if !cfg.require_cookie {
718        sc = sc.with_no_cookie();
719    }
720    sc.key_log = cfg.key_log.clone();
721    Ok(crate::dtls::DtlsServerConnection13::new(
722        alloc::sync::Arc::new(sc),
723        Vec::new(),
724        OsRng,
725    ))
726}
727
728fn client_cert_from_signing(id: &super::config::Identity) -> Option<super::conn::ClientCertConfig> {
729    Some(match &id.key {
730        super::config::SigningKey::Rsa(k) => {
731            super::conn::ClientCertConfig::with_rsa(id.cert_chain.clone(), k.clone())
732        }
733        super::config::SigningKey::Ecdsa(k) => {
734            super::conn::ClientCertConfig::with_ecdsa(id.cert_chain.clone(), k.clone())
735        }
736        super::config::SigningKey::Ed25519(k) => {
737            super::conn::ClientCertConfig::with_ed25519(id.cert_chain.clone(), k.clone())
738        }
739        super::config::SigningKey::Ed448(k) => {
740            super::conn::ClientCertConfig::with_ed448(id.cert_chain.clone(), k.clone())
741        }
742        super::config::SigningKey::MlDsa44(k) => {
743            super::conn::ClientCertConfig::with_mldsa44(id.cert_chain.clone(), k.clone())
744        }
745        super::config::SigningKey::MlDsa65(k) => {
746            super::conn::ClientCertConfig::with_mldsa65(id.cert_chain.clone(), k.clone())
747        }
748        super::config::SigningKey::MlDsa87(k) => {
749            super::conn::ClientCertConfig::with_mldsa87(id.cert_chain.clone(), k.clone())
750        }
751    })
752}
753
754#[cfg(test)]
755mod tests {
756    use super::*;
757    use crate::ec::{BoxedEcdsaPrivateKey, CurveId};
758    use crate::hash::Sha256;
759    use crate::rng::HmacDrbg;
760    use crate::x509::{CertSigner, Certificate, DistinguishedName, Time, Validity};
761
762    /// Build a minimal DTLS server [`Config`] (P-256 ECDSA leaf, self-signed)
763    /// with `require_cookie` defaulted to true and `cookie_secret = None`.
764    fn dtls_server_cfg_without_cookie_secret(max_version: ProtocolVersion) -> Config {
765        let mut rng = HmacDrbg::<Sha256>::new(b"h3-dtls-cookie", b"nonce", &[]);
766        let key = BoxedEcdsaPrivateKey::generate(CurveId::P256, &mut rng);
767        let name = DistinguishedName::common_name("dtls.example");
768        let validity = Validity::new(
769            Time::utc(2024, 1, 1, 0, 0, 0),
770            Time::utc(2034, 1, 1, 0, 0, 0),
771        );
772        let cert = Certificate::self_signed_general(
773            &CertSigner::Ecdsa(&key),
774            &name,
775            &validity,
776            1,
777            false,
778            &["dtls.example"],
779        )
780        .unwrap();
781        Config::builder()
782            .versions(max_version, max_version)
783            .identity(
784                alloc::vec![cert.to_der().to_vec()],
785                super::super::config::SigningKey::Ecdsa(key),
786            )
787            .build()
788    }
789
790    // RFC 6347 §4.2.1 / RFC 9147 §5.1: the cookie exchange is the DoS-
791    // amplification mitigation. A server that intends to require it but
792    // forgot to wire a cookie secret used to silently downgrade to "no
793    // cookies" — the AND-combine of `require_cookie && cookie_secret`.
794    // Fail-closed: refuse to construct the engine.
795    #[test]
796    fn dtls_server_refuses_construction_without_cookie_secret() {
797        // DTLS 1.2 path.
798        let cfg = dtls_server_cfg_without_cookie_secret(ProtocolVersion::DTLSv1_2);
799        assert!(cfg.require_cookie);
800        assert!(cfg.cookie_secret.is_none());
801        match Connection::server(&cfg) {
802            Err(Error::InappropriateState) => {}
803            Err(e) => panic!("expected InappropriateState, got {e:?}"),
804            Ok(_) => panic!("DTLS 1.2 server must refuse construction"),
805        }
806
807        // DTLS 1.3 path.
808        let cfg = dtls_server_cfg_without_cookie_secret(ProtocolVersion::DTLSv1_3);
809        match Connection::server(&cfg) {
810            Err(Error::InappropriateState) => {}
811            Err(e) => panic!("expected InappropriateState, got {e:?}"),
812            Ok(_) => panic!("DTLS 1.3 server must refuse construction"),
813        }
814
815        // Explicit secret -> allowed.
816        let mut cfg = dtls_server_cfg_without_cookie_secret(ProtocolVersion::DTLSv1_3);
817        cfg.cookie_secret = Some([0x42u8; 32]);
818        assert!(Connection::server(&cfg).is_ok());
819
820        // Explicit opt-out (require_cookie = false) -> allowed.
821        let mut cfg = dtls_server_cfg_without_cookie_secret(ProtocolVersion::DTLSv1_3);
822        cfg.require_cookie = false;
823        assert!(Connection::server(&cfg).is_ok());
824    }
825
826    /// `server_name` is required only when certificate verification is on.
827    ///
828    /// When verifying (audit F1), a missing name must be rejected at
829    /// construction rather than silently substituted — the old `"localhost"`
830    /// substitution was a footgun, since any local cert listing `localhost` as a
831    /// SAN would then satisfy verification for an unintended peer. But with
832    /// verification *off* there is nothing to verify against, so a name is
833    /// optional (e.g. connecting to a device by IP); the engines simply omit the
834    /// SNI extension. This holds across every TLS/DTLS engine path.
835    #[test]
836    fn client_server_name_required_only_when_verifying() {
837        for v in [
838            ProtocolVersion::TLSv1_3,
839            ProtocolVersion::TLSv1_2,
840            ProtocolVersion::DTLSv1_3,
841            ProtocolVersion::DTLSv1_2,
842        ] {
843            // verify on (default) + no server_name → rejected at construction.
844            let cfg = Config::builder().versions(v, v).build();
845            assert!(cfg.verify_certificates && cfg.server_name.is_none());
846            match Connection::client(&cfg) {
847                Err(Error::MissingServerName) => {}
848                Err(e) => panic!("{v:?}: expected MissingServerName, got {e:?}"),
849                Ok(_) => panic!("{v:?}: verifying client must require server_name"),
850            }
851
852            // verify off + no server_name → allowed (no SNI, no hostname check).
853            let cfg = Config::builder()
854                .versions(v, v)
855                .verify_certificates(false)
856                .build();
857            assert!(cfg.server_name.is_none());
858            assert!(
859                Connection::client(&cfg).is_ok(),
860                "{v:?}: verify-off client must not require server_name"
861            );
862
863            // With an explicit server_name, construction succeeds either way.
864            let cfg = Config::builder()
865                .versions(v, v)
866                .verify_certificates(false)
867                .server_name("example.test")
868                .build();
869            assert!(Connection::client(&cfg).is_ok(), "{v:?}: explicit SNI ok");
870        }
871    }
872
873    /// `cipher_suite_name` covers every suite the negotiator can pick,
874    /// plus the unknown-fallback case.
875    #[test]
876    fn cipher_suite_name_table() {
877        assert_eq!(cipher_suite_name(0x1301), "TLS_AES_128_GCM_SHA256");
878        assert_eq!(cipher_suite_name(0x1302), "TLS_AES_256_GCM_SHA384");
879        assert_eq!(cipher_suite_name(0x1303), "TLS_CHACHA20_POLY1305_SHA256");
880        assert_eq!(
881            cipher_suite_name(0xC02B),
882            "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
883        );
884        assert_eq!(
885            cipher_suite_name(0xC02C),
886            "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
887        );
888        assert_eq!(
889            cipher_suite_name(0xC02F),
890            "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
891        );
892        assert_eq!(
893            cipher_suite_name(0xC030),
894            "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
895        );
896        assert_eq!(
897            cipher_suite_name(0xCCA8),
898            "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"
899        );
900        assert_eq!(
901            cipher_suite_name(0xCCA9),
902            "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"
903        );
904        assert_eq!(cipher_suite_name(0xFFFF), "UNKNOWN");
905    }
906
907    /// Before any wire bytes are exchanged the suite is undetermined on
908    /// every engine variant. (Once the handshake progresses far enough
909    /// the existing per-engine loopback tests in `tls::conn::mod` /
910    /// `dtls::*` verify the positive case.)
911    #[test]
912    fn negotiated_cipher_suite_is_none_before_handshake() {
913        let mut rng = HmacDrbg::<Sha256>::new(b"suite-none", b"nonce", &[]);
914        let key = BoxedEcdsaPrivateKey::generate(CurveId::P256, &mut rng);
915        let validity = Validity::new(
916            Time::utc(2024, 1, 1, 0, 0, 0),
917            Time::utc(2034, 1, 1, 0, 0, 0),
918        );
919        let cert = Certificate::self_signed_general(
920            &CertSigner::Ecdsa(&key),
921            &DistinguishedName::common_name("suite.example"),
922            &validity,
923            1,
924            false,
925            &["suite.example"],
926        )
927        .unwrap();
928
929        // TLS 1.3 client (cipher selected from ServerHello — None
930        // before any bytes flow in).
931        let cfg = Config::builder()
932            .tls_only()
933            .server_name("suite.example")
934            .build();
935        let client = Connection::client(&cfg).unwrap();
936        assert!(client.negotiated_cipher_suite().is_none());
937        assert!(client.negotiated_cipher_suite_name().is_none());
938
939        // TLS 1.3 server (cipher selected during ClientHello dispatch).
940        let cfg = Config::builder()
941            .tls_only()
942            .identity(
943                alloc::vec![cert.to_der().to_vec()],
944                super::super::config::SigningKey::Ecdsa(key),
945            )
946            .build();
947        let server = Connection::server(&cfg).unwrap();
948        assert!(server.negotiated_cipher_suite().is_none());
949    }
950}