1use crate::CoapError;
25use openssl::pkey::PKey;
26use openssl::ssl::{SslContext, SslMethod, SslVerifyMode};
27use openssl::x509::X509;
28use std::collections::HashMap;
29use std::net::SocketAddr;
30use std::time::{Duration, Instant};
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37pub enum DtlsVersion {
38 V1_2,
40 V1_3,
42}
43
44impl DtlsVersion {
45 pub fn as_str(&self) -> &'static str {
47 match self {
48 Self::V1_2 => "DTLS 1.2",
49 Self::V1_3 => "DTLS 1.3",
50 }
51 }
52}
53
54#[derive(Debug, Clone)]
63pub struct DtlsSession {
64 session_id: Vec<u8>,
66 peer_addr: SocketAddr,
68 client_cert: Option<Vec<u8>>,
73 created_at: Instant,
75 protocol_version: DtlsVersion,
77}
78
79impl DtlsSession {
80 pub fn new(session_id: Vec<u8>, peer_addr: SocketAddr, protocol_version: DtlsVersion) -> Self {
82 Self {
83 session_id,
84 peer_addr,
85 client_cert: None,
86 created_at: Instant::now(),
87 protocol_version,
88 }
89 }
90
91 pub fn with_client_cert(
93 session_id: Vec<u8>,
94 peer_addr: SocketAddr,
95 protocol_version: DtlsVersion,
96 client_cert_der: Vec<u8>,
97 ) -> Self {
98 Self {
99 session_id,
100 peer_addr,
101 client_cert: Some(client_cert_der),
102 created_at: Instant::now(),
103 protocol_version,
104 }
105 }
106
107 pub fn session_id(&self) -> &[u8] {
109 &self.session_id
110 }
111
112 pub fn peer_addr(&self) -> SocketAddr {
114 self.peer_addr
115 }
116
117 pub fn client_cert(&self) -> Option<&[u8]> {
119 self.client_cert.as_deref()
120 }
121
122 pub fn created_at(&self) -> Instant {
124 self.created_at
125 }
126
127 pub fn protocol_version(&self) -> DtlsVersion {
129 self.protocol_version
130 }
131
132 pub fn is_expired(&self, ttl: Duration) -> bool {
134 self.created_at.elapsed() > ttl
135 }
136
137 pub fn client_cert_info(&self) -> Option<ClientCertInfo> {
145 let der = self.client_cert.as_ref()?;
146 ClientCertInfo::from_der(der)
147 }
148}
149
150#[derive(Debug, Clone, PartialEq, Eq)]
155pub struct ClientCertInfo {
156 pub subject_dn: String,
158 pub serial: Vec<u8>,
160 pub der_bytes: Vec<u8>,
162}
163
164impl ClientCertInfo {
165 pub fn from_der(der: &[u8]) -> Option<Self> {
172 let cert = synta_certificate::Certificate::from_der(der).ok()?;
173 let subject_dn = synta_certificate::format_dn(cert.tbs_certificate.subject.0);
174 let serial = cert.tbs_certificate.serial_number.as_bytes().to_vec();
175
176 Some(Self {
177 subject_dn,
178 serial,
179 der_bytes: der.to_vec(),
180 })
181 }
182}
183
184#[derive(Debug)]
195pub struct DtlsSessionCache {
196 sessions: HashMap<SocketAddr, DtlsSession>,
198 max_sessions: usize,
200 ttl: Duration,
202}
203
204impl DtlsSessionCache {
205 pub fn new(max_sessions: usize, ttl: Duration) -> Self {
213 Self {
214 sessions: HashMap::with_capacity(max_sessions),
215 max_sessions,
216 ttl,
217 }
218 }
219
220 pub fn insert(&mut self, session: DtlsSession) {
225 if self.sessions.len() >= self.max_sessions
226 && !self.sessions.contains_key(&session.peer_addr)
227 {
228 self.cleanup_expired();
229
230 if self.sessions.len() >= self.max_sessions
232 && let Some(oldest_addr) = self.oldest_session_addr()
233 {
234 self.sessions.remove(&oldest_addr);
235 }
236 }
237
238 self.sessions.insert(session.peer_addr, session);
239 }
240
241 pub fn get(&mut self, peer_addr: &SocketAddr) -> Option<&DtlsSession> {
246 if let Some(session) = self.sessions.get(peer_addr)
248 && session.is_expired(self.ttl)
249 {
250 self.sessions.remove(peer_addr);
251 return None;
252 }
253
254 self.sessions.get(peer_addr)
255 }
256
257 pub fn remove(&mut self, peer_addr: &SocketAddr) -> Option<DtlsSession> {
262 self.sessions.remove(peer_addr)
263 }
264
265 pub fn cleanup_expired(&mut self) -> usize {
269 let ttl = self.ttl;
270 let before = self.sessions.len();
271 self.sessions.retain(|_, session| !session.is_expired(ttl));
272 before - self.sessions.len()
273 }
274
275 pub fn len(&self) -> usize {
277 self.sessions.len()
278 }
279
280 pub fn is_empty(&self) -> bool {
282 self.sessions.is_empty()
283 }
284
285 pub fn max_sessions(&self) -> usize {
287 self.max_sessions
288 }
289
290 pub fn ttl(&self) -> Duration {
292 self.ttl
293 }
294
295 fn oldest_session_addr(&self) -> Option<SocketAddr> {
297 self.sessions
298 .iter()
299 .min_by_key(|(_, session)| session.created_at)
300 .map(|(addr, _)| *addr)
301 }
302}
303
304pub struct DtlsContext {
323 ctx: SslContext,
324}
325
326impl DtlsContext {
327 pub fn new(cert_pem: &[u8], key_pem: &[u8], ca_pem: &[u8]) -> Result<Self, CoapError> {
339 let mut ctx_builder = SslContext::builder(SslMethod::dtls())
340 .map_err(|e| CoapError::DtlsError(format!("Failed to create DTLS context: {e}")))?;
341
342 let cert = X509::from_pem(cert_pem).map_err(|e| {
343 CoapError::DtlsError(format!("Failed to parse server certificate PEM: {e}"))
344 })?;
345 ctx_builder
346 .set_certificate(&cert)
347 .map_err(|e| CoapError::DtlsError(format!("Failed to set server certificate: {e}")))?;
348
349 let key = PKey::private_key_from_pem(key_pem).map_err(|e| {
350 CoapError::DtlsError(format!("Failed to parse server private key PEM: {e}"))
351 })?;
352 ctx_builder
353 .set_private_key(&key)
354 .map_err(|e| CoapError::DtlsError(format!("Failed to set server private key: {e}")))?;
355
356 ctx_builder
357 .check_private_key()
358 .map_err(|e| CoapError::DtlsError(format!("Server certificate/key mismatch: {e}")))?;
359
360 ctx_builder.set_verify(SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT);
363 ctx_builder
364 .set_min_proto_version(Some(openssl::ssl::SslVersion::DTLS1_2))
365 .map_err(|e| CoapError::DtlsError(e.to_string()))?;
366
367 let ca = X509::from_pem(ca_pem).map_err(|e| {
369 CoapError::DtlsError(format!("Failed to parse CA certificate PEM: {e}"))
370 })?;
371 ctx_builder
372 .cert_store_mut()
373 .add_cert(ca)
374 .map_err(|e| CoapError::DtlsError(format!("Failed to add CA to trust store: {e}")))?;
375
376 Ok(Self {
377 ctx: ctx_builder.build(),
378 })
379 }
380
381 pub fn ssl_context(&self) -> &SslContext {
385 &self.ctx
386 }
387}
388
389impl std::fmt::Debug for DtlsContext {
390 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
391 f.debug_struct("DtlsContext")
392 .field("ctx", &"<SslContext>")
393 .finish()
394 }
395}
396
397#[derive(Debug, Default)]
414struct DatagramIo {
415 incoming: std::collections::VecDeque<Vec<u8>>,
416 outgoing: Vec<Vec<u8>>,
417}
418impl std::io::Read for DatagramIo {
419 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
420 let packet = self
421 .incoming
422 .pop_front()
423 .ok_or(std::io::ErrorKind::WouldBlock)?;
424 if packet.len() > buf.len() {
425 return Err(std::io::ErrorKind::InvalidData.into());
426 }
427 buf[..packet.len()].copy_from_slice(&packet);
428 Ok(packet.len())
429 }
430}
431impl std::io::Write for DatagramIo {
432 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
433 if self.outgoing.len() >= 64 {
434 return Err(std::io::ErrorKind::OutOfMemory.into());
435 }
436 self.outgoing.push(buf.to_vec());
437 Ok(buf.len())
438 }
439 fn flush(&mut self) -> std::io::Result<()> {
440 Ok(())
441 }
442}
443
444#[derive(Debug)]
445pub struct DtlsConnection {
446 stream: openssl::ssl::SslStream<DatagramIo>,
447 peer_addr: SocketAddr,
448 client_cert: Option<ClientCertInfo>,
449 handshake_complete: bool,
450 pub(crate) created_at: Instant,
451}
452impl DtlsConnection {
453 pub fn new(ctx: &DtlsContext, peer_addr: SocketAddr) -> Result<Self, CoapError> {
454 let mut ssl = openssl::ssl::Ssl::new(ctx.ssl_context()).map_err(dtls_error)?;
455 ssl.set_accept_state();
456 ssl.set_mtu(1200).map_err(dtls_error)?;
457 let stream =
458 openssl::ssl::SslStream::new(ssl, DatagramIo::default()).map_err(dtls_error)?;
459 Ok(Self {
460 stream,
461 peer_addr,
462 client_cert: None,
463 handshake_complete: false,
464 created_at: Instant::now(),
465 })
466 }
467 pub fn peer_addr(&self) -> SocketAddr {
468 self.peer_addr
469 }
470 pub fn client_cert(&self) -> Option<&ClientCertInfo> {
471 self.client_cert.as_ref()
472 }
473 pub fn is_handshake_complete(&self) -> bool {
474 self.handshake_complete
475 }
476 pub fn ssl(&self) -> &openssl::ssl::SslRef {
477 self.stream.ssl()
478 }
479 pub(crate) fn receive(&mut self, packet: &[u8]) -> Result<Vec<Vec<u8>>, CoapError> {
480 self.stream.get_mut().incoming.push_back(packet.to_vec());
481 if !self.handshake_complete {
482 match self.stream.accept() {
483 Ok(()) => {
484 let cert = self.stream.ssl().peer_certificate().ok_or_else(|| {
485 CoapError::Unauthorized("client certificate required".into())
486 })?;
487 self.client_cert =
488 ClientCertInfo::from_der(&cert.to_der().map_err(dtls_error)?);
489 if self.client_cert.is_none() {
490 return Err(CoapError::Unauthorized("invalid client certificate".into()));
491 }
492 self.handshake_complete = true;
493 }
494 Err(e) if retry_ssl(&e) => return Ok(Vec::new()),
495 Err(e) => return Err(dtls_error(e)),
496 }
497 }
498 let mut result = Vec::new();
499 let mut buf = vec![0; 65535];
500 loop {
501 match self.stream.ssl_read(&mut buf) {
502 Ok(0) => break,
503 Ok(n) => result.push(buf[..n].to_vec()),
504 Err(e) if retry_ssl(&e) => break,
505 Err(e) => return Err(dtls_error(e)),
506 }
507 }
508 Ok(result)
509 }
510 pub(crate) fn encrypt(&mut self, data: &[u8]) -> Result<(), CoapError> {
511 if !self.handshake_complete {
512 return Err(CoapError::Unauthorized("DTLS handshake incomplete".into()));
513 }
514 self.stream.ssl_write(data).map_err(dtls_error)?;
515 Ok(())
516 }
517 pub(crate) fn handle_timeout(&mut self) -> Result<(), CoapError> {
518 use foreign_types::ForeignTypeRef;
519 let result = unsafe {
522 openssl_sys::SSL_ctrl(self.stream.ssl().as_ptr(), 74, 0, std::ptr::null_mut())
523 };
524 if result < 0 {
525 return Err(CoapError::DtlsError("DTLS retransmission failed".into()));
526 }
527 Ok(())
528 }
529 pub(crate) fn outgoing(&mut self) -> Vec<Vec<u8>> {
530 std::mem::take(&mut self.stream.get_mut().outgoing)
531 }
532}
533fn retry_ssl(e: &openssl::ssl::Error) -> bool {
534 matches!(
535 e.code(),
536 openssl::ssl::ErrorCode::WANT_READ | openssl::ssl::ErrorCode::WANT_WRITE
537 )
538}
539fn dtls_error(e: impl std::fmt::Display) -> CoapError {
540 CoapError::DtlsError(e.to_string())
541}
542
543#[cfg(test)]
544mod tests {
545 use super::*;
546 use std::net::{IpAddr, Ipv4Addr};
547
548 fn test_addr(port: u16) -> SocketAddr {
549 SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, port as u8)), port)
550 }
551
552 #[test]
553 fn test_dtls_version_as_str() {
554 assert_eq!(DtlsVersion::V1_2.as_str(), "DTLS 1.2");
555 assert_eq!(DtlsVersion::V1_3.as_str(), "DTLS 1.3");
556 }
557
558 #[test]
559 fn test_session_creation() {
560 let addr = test_addr(5683);
561 let session = DtlsSession::new(vec![1, 2, 3], addr, DtlsVersion::V1_3);
562
563 assert_eq!(session.session_id(), &[1, 2, 3]);
564 assert_eq!(session.peer_addr(), addr);
565 assert!(session.client_cert().is_none());
566 assert_eq!(session.protocol_version(), DtlsVersion::V1_3);
567 }
568
569 #[test]
570 fn test_session_with_client_cert_raw_access() {
571 let addr = test_addr(5683);
572 let cert_der = vec![0x30, 0x82, 0x01, 0x00];
573 let session =
574 DtlsSession::with_client_cert(vec![1, 2, 3], addr, DtlsVersion::V1_2, cert_der.clone());
575
576 assert_eq!(session.client_cert(), Some(cert_der.as_slice()));
578 assert!(session.client_cert_info().is_none());
580 }
581
582 #[test]
583 fn test_session_with_real_client_cert() {
584 use openssl::asn1::Asn1Time;
586 use openssl::bn::BigNum;
587 use openssl::hash::MessageDigest;
588 use openssl::pkey::PKey;
589 use openssl::rsa::Rsa;
590 use openssl::x509::{X509Builder, X509NameBuilder};
591
592 let rsa = Rsa::generate(2048).unwrap();
593 let key = PKey::from_rsa(rsa).unwrap();
594
595 let mut name_builder = X509NameBuilder::new().unwrap();
596 name_builder
597 .append_entry_by_text("CN", "test-client")
598 .unwrap();
599 let name = name_builder.build();
600
601 let mut builder = X509Builder::new().unwrap();
602 builder.set_version(2).unwrap();
603 builder.set_subject_name(&name).unwrap();
604 builder.set_issuer_name(&name).unwrap();
605 builder.set_pubkey(&key).unwrap();
606
607 let serial = BigNum::from_u32(42).unwrap();
608 builder
609 .set_serial_number(&serial.to_asn1_integer().unwrap())
610 .unwrap();
611
612 let not_before = Asn1Time::days_from_now(0).unwrap();
613 let not_after = Asn1Time::days_from_now(365).unwrap();
614 builder.set_not_before(¬_before).unwrap();
615 builder.set_not_after(¬_after).unwrap();
616
617 builder.sign(&key, MessageDigest::sha256()).unwrap();
618 let cert = builder.build();
619 let cert_der = cert.to_der().unwrap();
620
621 let addr = test_addr(5683);
622 let session =
623 DtlsSession::with_client_cert(vec![1, 2, 3], addr, DtlsVersion::V1_2, cert_der.clone());
624
625 assert_eq!(session.client_cert(), Some(cert_der.as_slice()));
626 let info = session.client_cert_info().unwrap();
627 assert!(
628 info.subject_dn.contains("test-client"),
629 "Expected subject DN to contain 'test-client', got: {}",
630 info.subject_dn
631 );
632 assert!(!info.serial.is_empty());
633 assert_eq!(info.der_bytes, cert_der);
634 }
635
636 #[test]
637 fn test_client_cert_info_from_der_invalid() {
638 assert!(ClientCertInfo::from_der(&[0x00, 0x01]).is_none());
640 assert!(ClientCertInfo::from_der(&[]).is_none());
641 }
642
643 #[test]
644 fn test_session_expiry() {
645 let addr = test_addr(5683);
646 let session = DtlsSession::new(vec![1], addr, DtlsVersion::V1_3);
647
648 assert!(!session.is_expired(Duration::from_secs(3600)));
650
651 assert!(session.is_expired(Duration::ZERO));
653 }
654
655 #[test]
656 fn test_cache_insert_and_get() {
657 let mut cache = DtlsSessionCache::new(10, Duration::from_secs(3600));
658 let addr = test_addr(5683);
659 let session = DtlsSession::new(vec![1, 2, 3], addr, DtlsVersion::V1_3);
660
661 cache.insert(session);
662 assert_eq!(cache.len(), 1);
663 assert!(!cache.is_empty());
664
665 let retrieved = cache.get(&addr);
666 assert!(retrieved.is_some());
667 assert_eq!(retrieved.unwrap().session_id(), &[1, 2, 3]);
668 }
669
670 #[test]
671 fn test_cache_remove() {
672 let mut cache = DtlsSessionCache::new(10, Duration::from_secs(3600));
673 let addr = test_addr(5683);
674 let session = DtlsSession::new(vec![1], addr, DtlsVersion::V1_3);
675
676 cache.insert(session);
677 assert_eq!(cache.len(), 1);
678
679 let removed = cache.remove(&addr);
680 assert!(removed.is_some());
681 assert_eq!(cache.len(), 0);
682 }
683
684 #[test]
685 fn test_cache_eviction_on_capacity() {
686 let mut cache = DtlsSessionCache::new(2, Duration::from_secs(3600));
687
688 cache.insert(DtlsSession::new(vec![1], test_addr(1), DtlsVersion::V1_3));
689 cache.insert(DtlsSession::new(vec![2], test_addr(2), DtlsVersion::V1_3));
690 assert_eq!(cache.len(), 2);
691
692 cache.insert(DtlsSession::new(vec![3], test_addr(3), DtlsVersion::V1_3));
694 assert_eq!(cache.len(), 2);
695
696 assert!(cache.get(&test_addr(1)).is_none());
698 assert!(cache.get(&test_addr(3)).is_some());
699 }
700
701 #[test]
702 fn test_cache_expired_not_returned() {
703 let mut cache = DtlsSessionCache::new(10, Duration::ZERO);
704 let addr = test_addr(5683);
705 let session = DtlsSession::new(vec![1], addr, DtlsVersion::V1_3);
706
707 cache.insert(session);
708 assert!(cache.get(&addr).is_none());
710 assert_eq!(cache.len(), 0);
711 }
712
713 #[test]
714 fn test_cache_cleanup_expired() {
715 let mut cache = DtlsSessionCache::new(10, Duration::ZERO);
716 cache.insert(DtlsSession::new(vec![1], test_addr(1), DtlsVersion::V1_3));
717 cache.insert(DtlsSession::new(vec![2], test_addr(2), DtlsVersion::V1_3));
718
719 let removed = cache.cleanup_expired();
721 assert_eq!(removed, 2);
722 assert!(cache.is_empty());
723 }
724
725 #[test]
726 fn test_cache_update_existing() {
727 let mut cache = DtlsSessionCache::new(10, Duration::from_secs(3600));
728 let addr = test_addr(5683);
729
730 cache.insert(DtlsSession::new(vec![1], addr, DtlsVersion::V1_2));
731 cache.insert(DtlsSession::new(vec![2], addr, DtlsVersion::V1_3));
732
733 assert_eq!(cache.len(), 1);
734 let session = cache.get(&addr).unwrap();
735 assert_eq!(session.session_id(), &[2]);
736 assert_eq!(session.protocol_version(), DtlsVersion::V1_3);
737 }
738}