diff --git a/Cargo.toml b/Cargo.toml index c133dfae..f8b5e0bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ desktop = [ "dep:urlencoding", "dep:rust-embed", "dep:mime_guess", - "dep:rustls", + "dep:openssl", "dep:rcgen", "dep:axum-server", "dep:clap", @@ -110,8 +110,7 @@ base64 = { version = "0.22", optional = true } tempfile = { version = "3", optional = true } # HTTP client (for URL downloads) -# Use rustls by default, but allow native-tls for systems with older GLIBC -reqwest = { version = "0.13", features = ["stream", "rustls-no-provider", "json"], default-features = false, optional = true } +reqwest = { version = "0.13", features = ["stream", "native-tls-vendored", "json"], default-features = false, optional = true } urlencoding = { version = "2", optional = true } # Static file embedding @@ -119,9 +118,9 @@ rust-embed = { version = "8", features = ["compression", "debug-embed"], optiona mime_guess = { version = "2", optional = true } # TLS/HTTPS -rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"], optional = true } +openssl = { version = "0.10", features = ["vendored"], optional = true } rcgen = { version = "0.14", optional = true } -axum-server = { version = "0.8", features = ["tls-rustls-no-provider"], optional = true } +axum-server = { version = "0.8", features = ["tls-openssl"], optional = true } # CLI argument parsing clap = { version = "4", features = ["derive"], optional = true } @@ -148,7 +147,7 @@ parking_lot = { version = "0.12", optional = true } arc-swap = { version = "1.8", optional = true } # WebRTC -webrtc = { version = "0.17", optional = true } +webrtc = { version = "0.17", features = ["vendored-openssl"], optional = true } rtp = { version = "0.17", optional = true } rtsp-types = { version = "0.1", optional = true } sdp-types = { version = "0.1", optional = true } @@ -206,6 +205,9 @@ windows-sys = { version = "0.61", features = [ [build-dependencies] protobuf-codegen = "3.7" +[patch.crates-io] +dtls = { path = "vendor/dtls" } + [profile.release] opt-level = 3 lto = true diff --git a/src/computer_use/openai.rs b/src/computer_use/openai.rs index 4f4ec0c1..a6866c82 100644 --- a/src/computer_use/openai.rs +++ b/src/computer_use/openai.rs @@ -995,7 +995,6 @@ mod tests { use super::*; fn test_provider(endpoint: &str) -> OpenAiComputerProvider { - let _ = rustls::crypto::ring::default_provider().install_default(); OpenAiComputerProvider::new( "secret".to_string(), endpoint.to_string(), diff --git a/src/main.rs b/src/main.rs index 897b9f3d..42193d73 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,10 +4,9 @@ use std::io::Write; use std::net::{IpAddr, SocketAddr}; use std::path::PathBuf; -use axum_server::tls_rustls::RustlsConfig; +use axum_server::tls_openssl::OpenSSLAcceptor; use clap::{Args, Parser, Subcommand, ValueEnum}; use futures::{stream::FuturesUnordered, StreamExt}; -use rustls::crypto::{ring, CryptoProvider}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use one_kvm::auth::{SessionStore, TwoFactorService, UserStore}; @@ -17,6 +16,7 @@ use one_kvm::platform::PlatformCapabilities; use one_kvm::runtime::{RuntimeBuilder, WebConfigOverrides}; use one_kvm::state::ShutdownAction; use one_kvm::utils::bind_tcp_listener; +use one_kvm::utils::tls::server_config_from_pem_file; #[derive(Debug, Clone, Copy, Default, ValueEnum)] enum LogLevel { @@ -100,9 +100,6 @@ async fn main() -> anyhow::Result<()> { init_logging(args.log_level); - CryptoProvider::install_default(ring::default_provider()) - .expect("Failed to install rustls crypto provider"); - tracing::info!("Starting One-KVM v{}", env!("CARGO_PKG_VERSION")); let platform = PlatformCapabilities::current(); tracing::info!( @@ -187,7 +184,7 @@ async fn main() -> anyhow::Result<()> { let tls_config = if let (Some(cert_path), Some(key_path)) = (&config.web.ssl_cert_path, &config.web.ssl_key_path) { - RustlsConfig::from_pem_file(cert_path, key_path).await? + server_config_from_pem_file(cert_path, key_path).await? } else { let cert_dir = data_dir.join("certs"); let cert_path = cert_dir.join("server.crt"); @@ -203,7 +200,7 @@ async fn main() -> anyhow::Result<()> { tracing::info!("Using existing TLS certificate from {}", cert_dir.display()); } - RustlsConfig::from_pem_file(&cert_path, &key_path).await? + server_config_from_pem_file(&cert_path, &key_path).await? }; let servers = FuturesUnordered::new(); @@ -211,7 +208,8 @@ async fn main() -> anyhow::Result<()> { let local_addr = listener.local_addr()?; tracing::info!("Starting HTTPS server on {}", local_addr); - let server = axum_server::from_tcp_rustls(listener, tls_config.clone())? + let server = axum_server::from_tcp(listener)? + .acceptor(OpenSSLAcceptor::new(tls_config.clone())) .serve(app.clone().into_make_service()); servers.push(server); } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index dbd54519..1cdbb1c3 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -9,6 +9,7 @@ pub mod net; pub mod net; pub mod serial; pub mod throttle; +pub mod tls; pub use fs::{list_dir_names, read_trimmed}; pub use host::{hostname_from_etc, hostname_uname}; diff --git a/src/utils/tls.rs b/src/utils/tls.rs new file mode 100644 index 00000000..cdbd9636 --- /dev/null +++ b/src/utils/tls.rs @@ -0,0 +1,119 @@ +use axum_server::tls_openssl::OpenSSLConfig; +use openssl::pkey::PKey; +use openssl::ssl::{SslAcceptor, SslMethod, SslVersion}; +use openssl::x509::X509; +use std::io; +use std::path::Path; + +pub fn server_config_from_pem(cert_pem: &[u8], key_pem: &[u8]) -> io::Result { + let mut certificates = X509::stack_from_pem(cert_pem) + .map_err(io::Error::other)? + .into_iter(); + let certificate = certificates.next().ok_or_else(|| { + io::Error::new(io::ErrorKind::InvalidData, "TLS certificate chain is empty") + })?; + let private_key = PKey::private_key_from_pem(key_pem).map_err(io::Error::other)?; + let mut acceptor = + SslAcceptor::mozilla_intermediate_v5(SslMethod::tls_server()).map_err(io::Error::other)?; + acceptor + .set_min_proto_version(Some(SslVersion::TLS1_2)) + .map_err(io::Error::other)?; + acceptor + .set_certificate(&certificate) + .map_err(io::Error::other)?; + acceptor + .set_private_key(&private_key) + .map_err(io::Error::other)?; + for certificate in certificates { + acceptor + .add_extra_chain_cert(certificate) + .map_err(io::Error::other)?; + } + OpenSSLConfig::try_from(acceptor).map_err(io::Error::other) +} + +pub async fn server_config_from_pem_file( + cert_path: impl AsRef, + key_path: impl AsRef, +) -> io::Result { + let cert_pem = tokio::fs::read(cert_path).await?; + let key_pem = tokio::fs::read(key_path).await?; + server_config_from_pem(&cert_pem, &key_pem) +} + +#[cfg(test)] +mod tests { + use super::*; + use axum_server::tls_openssl::OpenSSLAcceptor; + + #[test] + fn preserves_certificate_chain() { + let leaf = rcgen::generate_simple_self_signed(vec!["localhost".into()]).unwrap(); + let intermediate = rcgen::generate_simple_self_signed(vec!["issuer".into()]).unwrap(); + let chain = format!("{}{}", leaf.cert.pem(), intermediate.cert.pem()); + let config = server_config_from_pem( + chain.as_bytes(), + leaf.signing_key.serialize_pem().as_bytes(), + ) + .unwrap(); + assert_eq!(config.get_inner().context().extra_chain_certs().len(), 1); + } + + #[test] + fn rejects_empty_invalid_and_mismatched_credentials() { + let first = rcgen::generate_simple_self_signed(vec!["localhost".into()]).unwrap(); + let second = rcgen::generate_simple_self_signed(vec!["localhost".into()]).unwrap(); + let cert = first.cert.pem(); + let key = first.signing_key.serialize_pem(); + assert!(server_config_from_pem(b"", key.as_bytes()).is_err()); + assert!(server_config_from_pem(b"invalid", key.as_bytes()).is_err()); + assert!(server_config_from_pem(cert.as_bytes(), b"invalid").is_err()); + assert!(server_config_from_pem( + cert.as_bytes(), + second.signing_key.serialize_pem().as_bytes(), + ) + .is_err()); + } + + #[tokio::test] + async fn https_client_verifies_certificates_and_supports_tls12() { + let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()]).unwrap(); + let cert_pem = cert.cert.pem(); + let config = server_config_from_pem( + cert_pem.as_bytes(), + cert.signing_key.serialize_pem().as_bytes(), + ) + .unwrap(); + let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); + listener.set_nonblocking(true).unwrap(); + let address = listener.local_addr().unwrap(); + let handle = axum_server::Handle::new(); + let app = axum::Router::new().route("/", axum::routing::get(|| async { "tls-ok" })); + let server = axum_server::from_tcp(listener) + .unwrap() + .acceptor(OpenSSLAcceptor::new(config)) + .handle(handle.clone()) + .serve(app.into_make_service()); + let task = tokio::spawn(server); + handle.listening().await.unwrap(); + let url = format!("https://localhost:{}/", address.port()); + let root = reqwest::Certificate::from_pem(cert_pem.as_bytes()).unwrap(); + for tls12_only in [false, true] { + let mut client = reqwest::Client::builder() + .add_root_certificate(root.clone()) + .timeout(std::time::Duration::from_secs(5)); + if tls12_only { + client = client.max_tls_version(reqwest::tls::Version::TLS_1_2); + } + let response = client.build().unwrap().get(&url).send().await.unwrap(); + assert_eq!(response.text().await.unwrap(), "tls-ok"); + } + let untrusted = reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(5)) + .build() + .unwrap(); + assert!(untrusted.get(&url).send().await.is_err()); + handle.shutdown(); + task.await.unwrap().unwrap(); + } +} diff --git a/src/video/codec/h264_bitstream.rs b/src/video/codec/h264_bitstream.rs index d47357e5..a6dddab5 100644 --- a/src/video/codec/h264_bitstream.rs +++ b/src/video/codec/h264_bitstream.rs @@ -69,63 +69,54 @@ pub fn strip_aud_nal_units(data: &[u8]) -> Vec { result } -pub fn extract_sps_pps(data: &[u8]) -> (Option>, Option>) { - let mut sps: Option> = None; - let mut pps: Option> = None; - let mut i = 0; +#[derive(Debug, Default)] +pub struct H264AccessUnit<'data> { + pub sps: Option<&'data [u8]>, + pub pps: Option<&'data [u8]>, + pub is_idr: bool, +} - while i < data.len() { - let start_code_len = if i + 4 <= data.len() - && data[i] == 0 - && data[i + 1] == 0 - && data[i + 2] == 0 - && data[i + 3] == 1 - { - 4 - } else if i + 3 <= data.len() && data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 { - 3 - } else { - i += 1; - continue; - }; - - let nal_start = i + start_code_len; - if nal_start >= data.len() { - break; - } - - let nal_type = data[nal_start] & 0x1F; - - let mut nal_end = data.len(); - let mut j = nal_start + 1; - while j + 3 <= data.len() { - if (data[j] == 0 && data[j + 1] == 0 && data[j + 2] == 1) - || (j + 4 <= data.len() - && data[j] == 0 - && data[j + 1] == 0 - && data[j + 2] == 0 - && data[j + 3] == 1) - { - nal_end = j; - break; +fn next_start_code(data: &[u8], mut offset: usize) -> Option<(usize, usize)> { + while offset + 3 <= data.len() { + if data[offset] == 0 && data[offset + 1] == 0 { + if data[offset + 2] == 1 { + return Some((offset, 3)); } - j += 1; - } - - match nal_type { - 7 => { - sps = Some(data[nal_start..nal_end].to_vec()); + if offset + 4 <= data.len() && data[offset + 2] == 0 && data[offset + 3] == 1 { + return Some((offset, 4)); } - 8 => { - pps = Some(data[nal_start..nal_end].to_vec()); - } - _ => {} } - - i = nal_end; + offset += 1; } + None +} - (sps, pps) +pub fn inspect_annex_b(data: &[u8]) -> H264AccessUnit<'_> { + let mut inspection = H264AccessUnit::default(); + let mut start_code = next_start_code(data, 0); + while let Some((offset, prefix_length)) = start_code { + let nal_start = offset + prefix_length; + let next = next_start_code(data, nal_start); + let nal_end = next.map_or(data.len(), |(next_offset, _)| next_offset); + if nal_start < nal_end { + match data[nal_start] & 0x1f { + 5 => inspection.is_idr = true, + 7 => inspection.sps = Some(&data[nal_start..nal_end]), + 8 => inspection.pps = Some(&data[nal_start..nal_end]), + _ => {} + } + } + start_code = next; + } + inspection +} + +pub fn extract_sps_pps(data: &[u8]) -> (Option>, Option>) { + let inspection = inspect_annex_b(data); + ( + inspection.sps.map(<[u8]>::to_vec), + inspection.pps.map(<[u8]>::to_vec), + ) } pub fn has_sps_pps(data: &[u8]) -> bool { @@ -215,8 +206,9 @@ pub fn parse_profile_level_id_from_sps(sps: &[u8]) -> Option { } pub fn extract_profile_level_id(data: &[u8]) -> Option { - let (sps, _) = extract_sps_pps(data); - sps.and_then(|sps_data| parse_profile_level_id_from_sps(&sps_data)) + inspect_annex_b(data) + .sps + .and_then(parse_profile_level_id_from_sps) } pub fn is_annex_b(data: &[u8]) -> bool { @@ -292,6 +284,58 @@ pub fn normalize_for_webrtc(data: &[u8]) -> Vec { mod tests { use super::*; + #[test] + fn inspects_parameter_sets_and_idr_in_one_pass() { + let data = [ + 0, 0, 0, 1, 0x67, 0x42, 0xe0, 0x1f, 0, 0, 1, 0x68, 0xce, 0, 0, 0, 1, 0x65, 0x88, + ]; + let inspection = inspect_annex_b(&data); + assert_eq!(inspection.sps, Some(&data[4..8])); + assert_eq!(inspection.pps, Some(&data[11..13])); + assert!(inspection.is_idr); + assert!(std::ptr::eq( + inspection.sps.unwrap().as_ptr(), + data[4..].as_ptr() + )); + assert_eq!(extract_profile_level_id(&data).as_deref(), Some("42e01f")); + } + + #[test] + fn predicted_packet_has_no_parameter_sets_or_idr() { + let data = [0, 0, 0, 1, 0x41, 0xc0]; + let inspection = inspect_annex_b(&data); + assert!(inspection.sps.is_none()); + assert!(inspection.pps.is_none()); + assert!(!inspection.is_idr); + } + + #[test] + fn empty_and_truncated_nal_units_are_ignored() { + for data in [ + &[][..], + &[0][..], + &[0, 0][..], + &[0, 0, 1][..], + &[0, 0, 0, 1][..], + &[0, 0, 1, 0, 0, 0, 1][..], + ] { + let inspection = inspect_annex_b(data); + assert!(inspection.sps.is_none()); + assert!(inspection.pps.is_none()); + assert!(!inspection.is_idr); + } + } + + #[test] + fn inspection_keeps_last_parameter_sets() { + let data = [ + 0, 0, 1, 0x67, 0x42, 0xc0, 0x1e, 0, 0, 0, 1, 0x67, 0x42, 0xe0, 0x1f, + ]; + let inspection = inspect_annex_b(&data); + assert_eq!(inspection.sps, Some(&data[11..15])); + assert_eq!(extract_profile_level_id(&data).as_deref(), Some("42e01f")); + } + #[test] fn detects_h264_keyframes() { let idr_frame = vec![0x00, 0x00, 0x00, 0x01, 0x65]; diff --git a/src/video/pipeline/delivery.rs b/src/video/pipeline/delivery.rs new file mode 100644 index 00000000..7296a8a6 --- /dev/null +++ b/src/video/pipeline/delivery.rs @@ -0,0 +1,100 @@ +use std::collections::VecDeque; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::sync::Arc; +use std::time::Duration; + +use parking_lot::Mutex; +use tokio::sync::mpsc; + +use super::frame_mailbox::FrameMailbox; +use super::EncodedVideoFrame; +use crate::video::frame::VideoFrame; + +pub(super) type EncodedBatch = Arc<[Arc]>; +pub(super) type FrameMailboxHandle = Arc>>>>>; + +#[derive(Default)] +pub(super) struct DeliveryStats { + pub sent_frames: AtomicU64, + pub send_time_ns: AtomicU64, + pub send_errors: AtomicU64, +} + +pub(super) struct VideoSubscriber { + pub sender: mpsc::Sender, + pub needs_keyframe: AtomicBool, + pub recovery_requested: AtomicBool, +} + +pub(super) struct VideoFrameReservation { + pub subscriber: Arc, + pub permit: mpsc::OwnedPermit, +} + +pub struct EncodedVideoFrameReceiver { + receiver: mpsc::Receiver, + pending: VecDeque>, + mailbox: FrameMailboxHandle, + stats: Arc, +} + +impl EncodedVideoFrameReceiver { + pub(super) fn new( + receiver: mpsc::Receiver, + mailbox: FrameMailboxHandle, + stats: Arc, + ) -> Self { + Self { + receiver, + pending: VecDeque::new(), + mailbox, + stats, + } + } + + fn notify_capacity(&self) { + if let Some(mailbox) = self.mailbox.lock().as_ref() { + mailbox.notify(); + } + } + + pub async fn recv(&mut self) -> Option> { + loop { + if let Some(frame) = self.pending.pop_front() { + return Some(frame); + } + let batch = self.receiver.recv().await?; + self.pending.extend(batch.iter().cloned()); + self.notify_capacity(); + } + } + + pub fn try_recv(&mut self) -> Result, mpsc::error::TryRecvError> { + loop { + if let Some(frame) = self.pending.pop_front() { + return Ok(frame); + } + let batch = self.receiver.try_recv()?; + self.pending.extend(batch.iter().cloned()); + self.notify_capacity(); + } + } + + pub(crate) fn record_send(&self, elapsed: Duration, succeeded: bool) { + self.stats + .send_time_ns + .fetch_add(elapsed.as_nanos() as u64, Ordering::Relaxed); + if succeeded { + self.stats.sent_frames.fetch_add(1, Ordering::Relaxed); + } else { + self.stats.send_errors.fetch_add(1, Ordering::Relaxed); + } + } +} + +impl Drop for EncodedVideoFrameReceiver { + fn drop(&mut self) { + self.receiver.close(); + self.notify_capacity(); + } +} diff --git a/src/video/pipeline/dmabuf.rs b/src/video/pipeline/dmabuf.rs index 0fc9fba7..7fda10ae 100644 --- a/src/video/pipeline/dmabuf.rs +++ b/src/video/pipeline/dmabuf.rs @@ -256,60 +256,80 @@ fn run( } let capture = active.as_mut().expect("opened capture"); - let result = match capture.encoder.as_mut().expect("active encoder") { - CaptureEncoder::Dma(encoder) => { - let pts = pipeline.pts_ms(); - capture + let reservations = pipeline.reserve_outputs(); + let result = if reservations.as_ref().is_none_or(Vec::is_empty) { + match capture.encoder.as_mut().expect("active encoder") { + CaptureEncoder::Dma(_) => capture .stream - .with_next_dmabuf(|index, bytes_used, fresh_fd| { - let keyframe = pipeline.keyframe_requested.swap(false, Ordering::AcqRel); - // The callback holds the dequeue lease until native encode - // completes (or destroys MPP on error), before QBUF. - unsafe { encoder.encode(index, bytes_used, fresh_fd, pts, keyframe) } - }) - .map(|(_, packet)| { - packet - .map(|packet| { - let data = Bytes::from(packet); - let is_keyframe = match config.output_codec { - VideoEncoderType::H264 => h264_bitstream::is_keyframe(&data), - VideoEncoderType::H265 => h265_bitstream::is_keyframe(&data), - _ => false, - }; - let (data, is_keyframe) = pipeline.inspect_and_parameterize_packet( - config.output_codec, - data, - is_keyframe, - ); - if config.output_codec == VideoEncoderType::H264 { - pipeline.update_h264_profile_level_id(&data); - } - vec![EncodedVideoFrame { - data, - pts_ms: pts, - is_keyframe, - sequence: pipeline.sequence.fetch_add(1, Ordering::Relaxed) + 1, - duration: Duration::from_micros( - 1_000_000 / config.fps.max(1) as u64, - ), - codec: config.output_codec, - }] - }) - .map_err(AppError::VideoError) - }) + .with_next_dmabuf(|_, _, _| ()) + .map(|_| Ok(Vec::new())), + CaptureEncoder::Copy(_) => { + let mut raw = buffer_pool.take(0); + let result = capture.stream.next_into(&mut raw).map(|_| Ok(Vec::new())); + buffer_pool.put(raw); + result + } } - CaptureEncoder::Copy(encoder) => { - let mut raw = buffer_pool.take(0); - capture.stream.next_into(&mut raw).map(|meta| { - let frame = VideoFrame::from_pooled( - Arc::new(FrameBuffer::new(raw, Some(buffer_pool.clone()))), - config.resolution, - config.input_format, - capture.stream.stride(), - meta.sequence, - ); - pipeline.encode_frame_sync(encoder, &frame) - }) + } else { + match capture.encoder.as_mut().expect("active encoder") { + CaptureEncoder::Dma(encoder) => { + let pts = pipeline.pts_ms(); + capture + .stream + .with_next_dmabuf(|index, bytes_used, fresh_fd| { + let keyframe = + pipeline.keyframe_requested.swap(false, Ordering::AcqRel); + // The callback holds the dequeue lease until native encode + // completes (or destroys MPP on error), before QBUF. + unsafe { encoder.encode(index, bytes_used, fresh_fd, pts, keyframe) } + }) + .map(|(_, packet)| { + packet + .map(|packet| { + let data = Bytes::from(packet); + let is_keyframe = match config.output_codec { + VideoEncoderType::H264 => { + h264_bitstream::is_keyframe(&data) + } + VideoEncoderType::H265 => { + h265_bitstream::is_keyframe(&data) + } + _ => false, + }; + let (data, is_keyframe) = pipeline + .inspect_and_parameterize_packet( + config.output_codec, + data, + is_keyframe, + ); + vec![EncodedVideoFrame { + data, + pts_ms: pts, + is_keyframe, + sequence: pipeline.sequence.fetch_add(1, Ordering::Relaxed) + + 1, + duration: Duration::from_micros( + 1_000_000 / config.fps.max(1) as u64, + ), + codec: config.output_codec, + }] + }) + .map_err(AppError::VideoError) + }) + } + CaptureEncoder::Copy(encoder) => { + let mut raw = buffer_pool.take(0); + capture.stream.next_into(&mut raw).map(|meta| { + let frame = VideoFrame::from_pooled( + Arc::new(FrameBuffer::new(raw, Some(buffer_pool.clone()))), + config.resolution, + config.input_format, + capture.stream.stride(), + meta.sequence, + ); + pipeline.encode_frame_sync(encoder, &frame) + }) + } } }; @@ -321,10 +341,11 @@ fn run( config.input_format, config.fps, )); - for frame in frames { - pipeline.broadcast_encoded(Arc::new(frame)); - fps_frames += 1; - } + fps_frames += frames.len() as u32; + pipeline.broadcast_encoded_batch( + frames.into_iter().map(Arc::new).collect::>().into(), + reservations.unwrap_or_default(), + ); } Ok(Err(error)) => { if matches!(capture.encoder, Some(CaptureEncoder::Dma(_))) { @@ -380,7 +401,7 @@ fn run( } } if fps_start.elapsed() >= Duration::from_secs(1) { - pipeline.stats.blocking_lock().current_fps = + pipeline.stats.lock().current_fps = fps_frames as f32 / fps_start.elapsed().as_secs_f32(); fps_frames = 0; fps_start = Instant::now(); diff --git a/src/video/pipeline/encoder_state.rs b/src/video/pipeline/encoder_state.rs index 08b1cc3c..346365b8 100644 --- a/src/video/pipeline/encoder_state.rs +++ b/src/video/pipeline/encoder_state.rs @@ -41,6 +41,11 @@ pub(super) trait VideoEncoderTrait: Send { pub(super) struct EncodedFrame { pub(super) data: Bytes, pub(super) key: i32, + /// Packet pts in milliseconds, stamped by the encoder when the input + /// frame was accepted. With a pipelined encoder a packet can be handed + /// back one call later than its input frame, so consumers must use this + /// instead of the wall clock time of the current call. + pub(super) pts: i64, } struct H264EncoderWrapper(H264Encoder); @@ -53,6 +58,7 @@ impl VideoEncoderTrait for H264EncoderWrapper { .map(|f| EncodedFrame { data: f.data, key: f.key, + pts: f.pts, }) .collect()) } @@ -99,6 +105,7 @@ impl VideoEncoderTrait for H265EncoderWrapper { .map(|f| EncodedFrame { data: f.data, key: f.key, + pts: f.pts, }) .collect()) } @@ -126,6 +133,7 @@ impl VideoEncoderTrait for VP8EncoderWrapper { .map(|f| EncodedFrame { data: f.data.into(), key: f.key, + pts: f.pts, }) .collect()) } @@ -151,6 +159,7 @@ impl VideoEncoderTrait for VP9EncoderWrapper { .map(|f| EncodedFrame { data: f.data.into(), key: f.key, + pts: f.pts, }) .collect()) } diff --git a/src/video/pipeline/frame_mailbox.rs b/src/video/pipeline/frame_mailbox.rs new file mode 100644 index 00000000..2e0fa2c8 --- /dev/null +++ b/src/video/pipeline/frame_mailbox.rs @@ -0,0 +1,178 @@ +use std::sync::{Condvar, Mutex}; + +#[derive(Debug, PartialEq, Eq)] +pub(super) enum PublishResult { + Published, + Replaced, + Rejected, +} + +struct FrameState { + pending: Option, + last_sequence: Option, + closed: bool, +} + +pub(super) struct FrameMailbox { + state: Mutex>, + available: Condvar, +} + +impl FrameMailbox { + pub(super) fn new() -> Self { + Self { + state: Mutex::new(FrameState { + pending: None, + last_sequence: None, + closed: false, + }), + available: Condvar::new(), + } + } + + pub(super) fn publish(&self, sequence: u64, frame: Frame) -> PublishResult { + let mut state = self.state.lock().expect("frame mailbox lock poisoned"); + if state.closed || state.last_sequence.is_some_and(|last| sequence <= last) { + return PublishResult::Rejected; + } + state.last_sequence = Some(sequence); + let outcome = if state.pending.replace(frame).is_some() { + PublishResult::Replaced + } else { + PublishResult::Published + }; + self.available.notify_one(); + outcome + } + + #[cfg(test)] + pub(super) fn receive(&self) -> Option { + self.receive_when(|| Some(())).map(|(frame, ())| frame) + } + + pub(super) fn receive_when( + &self, + mut ready: impl FnMut() -> Option, + ) -> Option<(Frame, Ready)> { + let mut state = self.state.lock().expect("frame mailbox lock poisoned"); + loop { + if state.closed { + return None; + } + if state.pending.is_some() { + if let Some(ready) = ready() { + return state.pending.take().map(|frame| (frame, ready)); + } + } + state = self + .available + .wait(state) + .expect("frame mailbox lock poisoned"); + } + } + + pub(super) fn notify(&self) { + let _state = self.state.lock().expect("frame mailbox lock poisoned"); + self.available.notify_one(); + } + + pub(super) fn has_pending(&self) -> bool { + self.state + .lock() + .expect("frame mailbox lock poisoned") + .pending + .is_some() + } + + pub(super) fn close(&self) { + let mut state = self.state.lock().expect("frame mailbox lock poisoned"); + state.closed = true; + state.pending.take(); + self.available.notify_all(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::{mpsc, Arc}; + use std::time::Duration; + + #[test] + fn pending_frame_is_replaced_by_latest() { + let mailbox = FrameMailbox::new(); + assert_eq!(mailbox.publish(1, "first"), PublishResult::Published); + assert!(mailbox.has_pending()); + assert_eq!(mailbox.publish(2, "latest"), PublishResult::Replaced); + assert_eq!(mailbox.receive(), Some("latest")); + assert!(!mailbox.has_pending()); + } + + #[test] + fn consumed_sequence_still_rejects_old_and_duplicate_frames() { + let mailbox = FrameMailbox::new(); + assert_eq!(mailbox.publish(10, "current"), PublishResult::Published); + assert_eq!(mailbox.receive(), Some("current")); + assert_eq!(mailbox.publish(9, "old"), PublishResult::Rejected); + assert_eq!(mailbox.publish(10, "duplicate"), PublishResult::Rejected); + assert!(!mailbox.has_pending()); + assert_eq!(mailbox.publish(11, "next"), PublishResult::Published); + assert_eq!(mailbox.receive(), Some("next")); + } + + #[test] + fn close_discards_pending_and_rejects_future_frames() { + let mailbox = FrameMailbox::new(); + mailbox.publish(1, "pending"); + mailbox.close(); + assert!(!mailbox.has_pending()); + assert_eq!(mailbox.receive(), None); + assert_eq!(mailbox.publish(2, "late"), PublishResult::Rejected); + } + + #[test] + fn receiver_is_woken_by_publish() { + for sequence in 0..64 { + let mailbox = Arc::new(FrameMailbox::new()); + let receiver = mailbox.clone(); + let (ready_sender, ready_receiver) = mpsc::channel(); + let (result_sender, result_receiver) = mpsc::channel(); + let worker = std::thread::spawn(move || { + ready_sender.send(()).unwrap(); + result_sender.send(receiver.receive()).unwrap(); + }); + ready_receiver.recv().unwrap(); + mailbox.publish(sequence, sequence); + assert_eq!( + result_receiver + .recv_timeout(Duration::from_secs(2)) + .unwrap(), + Some(sequence) + ); + worker.join().unwrap(); + } + } + + #[test] + fn close_wakes_idle_receiver() { + for _ in 0..64 { + let mailbox = Arc::new(FrameMailbox::::new()); + let receiver = mailbox.clone(); + let (ready_sender, ready_receiver) = mpsc::channel(); + let (result_sender, result_receiver) = mpsc::channel(); + let worker = std::thread::spawn(move || { + ready_sender.send(()).unwrap(); + result_sender.send(receiver.receive()).unwrap(); + }); + ready_receiver.recv().unwrap(); + mailbox.close(); + assert_eq!( + result_receiver + .recv_timeout(Duration::from_secs(2)) + .unwrap(), + None + ); + worker.join().unwrap(); + } + } +} diff --git a/src/video/pipeline/mod.rs b/src/video/pipeline/mod.rs index e12e6e51..cbd0f99d 100644 --- a/src/video/pipeline/mod.rs +++ b/src/video/pipeline/mod.rs @@ -1,8 +1,11 @@ //! Video processing pipelines. +mod delivery; mod encoder_state; +mod frame_mailbox; mod shared; +pub use delivery::EncodedVideoFrameReceiver; pub use shared::{ EncodedVideoFrame, PipelineAppliedConfig, PipelineLifecycle, PipelineStateNotification, SharedVideoPipeline, SharedVideoPipelineConfig, SharedVideoPipelineStats, diff --git a/src/video/pipeline/shared.rs b/src/video/pipeline/shared.rs index 1269b43d..75b5f96e 100644 --- a/src/video/pipeline/shared.rs +++ b/src/video/pipeline/shared.rs @@ -24,10 +24,15 @@ use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; use std::sync::mpsc::{sync_channel, SyncSender, TrySendError}; use std::sync::Arc; use std::time::{Duration, Instant}; -use tokio::sync::{mpsc, watch, Mutex, RwLock}; +use tokio::sync::{mpsc, watch, RwLock}; use tracing::{debug, error, info, trace, warn}; +use super::delivery::{ + DeliveryStats, EncodedBatch, EncodedVideoFrameReceiver, FrameMailboxHandle, + VideoFrameReservation, VideoSubscriber, +}; use super::encoder_state::{build_encoder_state, should_parallel_decode_mjpeg, EncoderThreadState}; +use super::frame_mailbox::{FrameMailbox, PublishResult}; #[cfg(all(target_os = "linux", any(target_arch = "aarch64", target_arch = "arm")))] #[path = "dmabuf.rs"] @@ -151,6 +156,17 @@ use crate::video::recovery::{wait_for_source_change, CaptureRecoveryPolicy}; use crate::video::signal::SignalStatus; const MIN_CAPTURE_FRAME_SIZE: usize = 128; +type VideoFrameMailbox = FrameMailbox>; + +fn spawn_video_worker( + name: &'static str, + worker: impl FnOnce() + Send + 'static, +) -> std::io::Result> { + std::thread::Builder::new() + .name(name.to_string()) + .spawn(worker) +} + struct MjpegDecodeJob { data: Vec, sequence: u64, @@ -162,8 +178,7 @@ fn mjpeg_decode_worker_count(available_parallelism: usize) -> usize { fn spawn_mjpeg_decode_workers( pipeline: &Arc, - latest_frame: &Arc>>>, - frame_seq_tx: &watch::Sender, + frame_mailbox: &Arc, buffer_pool: &Arc, resolution: Resolution, ) -> Vec> { @@ -179,8 +194,7 @@ fn spawn_mjpeg_decode_workers( // latency behind stale frames. let (tx, rx) = sync_channel::(0); let worker_pipeline = pipeline.clone(); - let worker_latest_frame = latest_frame.clone(); - let worker_frame_seq_tx = frame_seq_tx.clone(); + let worker_frame_mailbox = frame_mailbox.clone(); let worker_buffer_pool = buffer_pool.clone(); let thread_name = format!("mjpeg-decoder-{worker_id}"); let spawn_result = std::thread::Builder::new() @@ -188,8 +202,20 @@ fn spawn_mjpeg_decode_workers( .spawn(move || { let mut decoder = MjpegToNv12Decoder::new(resolution); while let Ok(job) = rx.recv() { + if !worker_pipeline.running_flag.load(Ordering::Acquire) { + worker_buffer_pool.put(job.data); + break; + } + if worker_frame_mailbox.has_pending() { + worker_pipeline + .mjpeg_dropped_before_decode + .fetch_add(1, Ordering::Relaxed); + worker_buffer_pool.put(job.data); + continue; + } let nv12_size = resolution.width as usize * resolution.height as usize * 3 / 2; let mut nv12 = worker_buffer_pool.take(nv12_size); + let decode_started = Instant::now(); let decode_result = decoder.decode_into(&job.data, &mut nv12); worker_buffer_pool.put(job.data); @@ -198,7 +224,17 @@ fn spawn_mjpeg_decode_workers( warn!("Dropping undecodable MJPEG frame: {}", error); continue; } + worker_pipeline.mjpeg_decode_time_ns.fetch_add( + decode_started.elapsed().as_nanos() as u64, + Ordering::Relaxed, + ); + worker_pipeline + .mjpeg_decoded_frames + .fetch_add(1, Ordering::Relaxed); if !worker_pipeline.running_flag.load(Ordering::Acquire) { + worker_pipeline + .decoded_frames_not_encoded + .fetch_add(1, Ordering::Relaxed); worker_buffer_pool.put(nv12); break; } @@ -210,20 +246,11 @@ fn spawn_mjpeg_decode_workers( resolution.width, job.sequence, )); - let published = { - let mut latest = worker_latest_frame.write(); - if latest - .as_ref() - .is_some_and(|current| current.sequence >= job.sequence) - { - false - } else { - *latest = Some(frame); - true - } - }; - if published { - let _ = worker_frame_seq_tx.send(job.sequence.wrapping_add(1)); + if worker_frame_mailbox.publish(job.sequence, frame) != PublishResult::Published + { + worker_pipeline + .decoded_frames_not_encoded + .fetch_add(1, Ordering::Relaxed); } } }); @@ -461,10 +488,59 @@ fn log_encoding_error( } } +fn log_pipeline_performance( + stats: &SharedVideoPipelineStats, + previous: &SharedVideoPipelineStats, + elapsed: f64, +) { + let delta = |current: u64, previous: u64| current.saturating_sub(previous); + let decoded = delta(stats.mjpeg_decoded_frames, previous.mjpeg_decoded_frames); + let encoded_inputs = delta(stats.encoded_input_frames, previous.encoded_input_frames); + let encoded = delta(stats.encoded_frames, previous.encoded_frames); + let sent = delta(stats.sent_frames, previous.sent_frames); + let send_errors = delta(stats.send_errors, previous.send_errors); + let average_ms = |time_ns: u64, count: u64| { + if count == 0 { + 0.0 + } else { + time_ns as f64 / count as f64 / 1_000_000.0 + } + }; + info!( + "[VideoPerf] window_s={:.2} decoded_fps={:.2} encoded_fps={:.2} sent_fps={:.2} decode_ms={:.2} encode_ms={:.2} send_ms={:.2} encoded_mbps={:.2} dropped_before_decode={} decoded_unused={} backpressure_events={} slow_subscriber_skips={} send_errors={}", + elapsed, + decoded as f64 / elapsed, + encoded as f64 / elapsed, + sent as f64 / elapsed, + average_ms(delta(stats.mjpeg_decode_time_ns, previous.mjpeg_decode_time_ns), decoded), + average_ms(delta(stats.encode_time_ns, previous.encode_time_ns), encoded_inputs), + average_ms(delta(stats.send_time_ns, previous.send_time_ns), sent + send_errors), + delta(stats.encoded_bytes, previous.encoded_bytes) as f64 * 8.0 / elapsed / 1_000_000.0, + delta(stats.mjpeg_dropped_before_decode, previous.mjpeg_dropped_before_decode), + delta(stats.decoded_frames_not_encoded, previous.decoded_frames_not_encoded), + delta(stats.output_backpressure_events, previous.output_backpressure_events), + delta(stats.encoded_frames_skipped_for_slow_subscribers, previous.encoded_frames_skipped_for_slow_subscribers), + send_errors, + ); +} + /// Pipeline statistics #[derive(Debug, Clone, Default)] pub struct SharedVideoPipelineStats { pub current_fps: f32, + pub mjpeg_decoded_frames: u64, + pub mjpeg_dropped_before_decode: u64, + pub decoded_frames_not_encoded: u64, + pub mjpeg_decode_time_ns: u64, + pub encoded_input_frames: u64, + pub encoded_frames: u64, + pub encoded_bytes: u64, + pub encode_time_ns: u64, + pub output_backpressure_events: u64, + pub encoded_frames_skipped_for_slow_subscribers: u64, + pub sent_frames: u64, + pub send_time_ns: u64, + pub send_errors: u64, } #[derive(Default)] @@ -479,8 +555,21 @@ struct CachedH26xParameterSets { /// Universal shared video pipeline pub struct SharedVideoPipeline { config: RwLock, - subscribers: ParkingRwLock>>>, - stats: Mutex, + subscribers: ParkingRwLock>>, + stats: ParkingMutex, + frame_mailbox: FrameMailboxHandle, + mjpeg_decoded_frames: AtomicU64, + mjpeg_dropped_before_decode: AtomicU64, + decoded_frames_not_encoded: AtomicU64, + mjpeg_decode_time_ns: AtomicU64, + encoded_input_frames: AtomicU64, + encoded_frames: AtomicU64, + encoded_bytes: AtomicU64, + encode_time_ns: AtomicU64, + output_backpressure_events: AtomicU64, + output_backpressured: AtomicBool, + encoded_frames_skipped_for_slow_subscribers: AtomicU64, + delivery_stats: Arc, running: watch::Sender, running_rx: watch::Receiver, /// Becomes true only after the synchronous encoder worker has exited and @@ -527,7 +616,20 @@ impl SharedVideoPipeline { let pipeline = Arc::new(Self { config: RwLock::new(config), subscribers: ParkingRwLock::new(Vec::new()), - stats: Mutex::new(SharedVideoPipelineStats::default()), + stats: ParkingMutex::new(SharedVideoPipelineStats::default()), + frame_mailbox: Arc::new(ParkingMutex::new(None)), + mjpeg_decoded_frames: AtomicU64::new(0), + mjpeg_dropped_before_decode: AtomicU64::new(0), + decoded_frames_not_encoded: AtomicU64::new(0), + mjpeg_decode_time_ns: AtomicU64::new(0), + encoded_input_frames: AtomicU64::new(0), + encoded_frames: AtomicU64::new(0), + encoded_bytes: AtomicU64::new(0), + encode_time_ns: AtomicU64::new(0), + output_backpressure_events: AtomicU64::new(0), + output_backpressured: AtomicBool::new(false), + encoded_frames_skipped_for_slow_subscribers: AtomicU64::new(0), + delivery_stats: Arc::new(DeliveryStats::default()), running: running_tx, running_rx, encoder_done: encoder_done_tx, @@ -593,16 +695,21 @@ impl SharedVideoPipeline { } /// Subscribe to encoded frames - pub fn subscribe(&self) -> mpsc::Receiver> { - // A queued video frame is already stale when the next frame is ready. - // Keep at most one pending frame so a slow WebRTC writer cannot make - // the encoder wait or accumulate seconds of latency. + pub fn subscribe(&self) -> EncodedVideoFrameReceiver { let (tx, rx) = mpsc::channel(1); if let Some(frame) = self.bootstrap_frame.read().clone() { - let _ = tx.try_send(frame); + let batch: EncodedBatch = vec![frame].into(); + let _ = tx.try_send(batch); } - self.subscribers.write().push(tx); - rx + self.subscribers.write().push(Arc::new(VideoSubscriber { + sender: tx, + needs_keyframe: AtomicBool::new(true), + recovery_requested: AtomicBool::new(false), + })); + if let Some(mailbox) = self.frame_mailbox.lock().as_ref() { + mailbox.notify(); + } + EncodedVideoFrameReceiver::new(rx, self.frame_mailbox.clone(), self.delivery_stats.clone()) } /// Get subscriber count @@ -610,7 +717,7 @@ impl SharedVideoPipeline { self.subscribers .read() .iter() - .filter(|tx| !tx.is_closed()) + .filter(|subscriber| !subscriber.sender.is_closed()) .count() } @@ -671,7 +778,28 @@ impl SharedVideoPipeline { /// Get current stats pub async fn stats(&self) -> SharedVideoPipelineStats { - self.stats.lock().await.clone() + self.stats_snapshot() + } + + fn stats_snapshot(&self) -> SharedVideoPipelineStats { + let mut stats = self.stats.lock().clone(); + stats.mjpeg_decoded_frames = self.mjpeg_decoded_frames.load(Ordering::Relaxed); + stats.mjpeg_dropped_before_decode = + self.mjpeg_dropped_before_decode.load(Ordering::Relaxed); + stats.decoded_frames_not_encoded = self.decoded_frames_not_encoded.load(Ordering::Relaxed); + stats.mjpeg_decode_time_ns = self.mjpeg_decode_time_ns.load(Ordering::Relaxed); + stats.encoded_input_frames = self.encoded_input_frames.load(Ordering::Relaxed); + stats.encoded_frames = self.encoded_frames.load(Ordering::Relaxed); + stats.encoded_bytes = self.encoded_bytes.load(Ordering::Relaxed); + stats.encode_time_ns = self.encode_time_ns.load(Ordering::Relaxed); + stats.output_backpressure_events = self.output_backpressure_events.load(Ordering::Relaxed); + stats.encoded_frames_skipped_for_slow_subscribers = self + .encoded_frames_skipped_for_slow_subscribers + .load(Ordering::Relaxed); + stats.sent_frames = self.delivery_stats.sent_frames.load(Ordering::Relaxed); + stats.send_time_ns = self.delivery_stats.send_time_ns.load(Ordering::Relaxed); + stats.send_errors = self.delivery_stats.send_errors.load(Ordering::Relaxed); + stats } /// Check if running @@ -704,8 +832,8 @@ impl SharedVideoPipeline { self.h264_profile_level_id_rx.clone() } - fn update_h264_profile_level_id(&self, data: &[u8]) { - let Some(profile_level_id) = h264_bitstream::extract_profile_level_id(data) else { + fn update_h264_profile_level_id(&self, sps: &[u8]) { + let Some(profile_level_id) = h264_bitstream::parse_profile_level_id_from_sps(sps) else { return; }; if self.h264_profile_level_id.borrow().as_deref() == Some(profile_level_id.as_str()) { @@ -727,16 +855,19 @@ impl SharedVideoPipeline { if !was_annex_b && h264_bitstream::is_annex_b(data.as_ref()) { debug!("[Pipeline] Converted length-prefixed H264 packet to Annex-B"); } - let (sps, pps) = h264_bitstream::extract_sps_pps(data.as_ref()); + let inspection = h264_bitstream::inspect_annex_b(data.as_ref()); + let sps = inspection.sps; + let pps = inspection.pps; // Require metadata and payload to agree before advertising a // decoder bootstrap frame. - let is_idr = ffmpeg_keyframe && h264_bitstream::is_keyframe(data.as_ref()); + let is_idr = ffmpeg_keyframe && inspection.is_idr; let mut cache = self.parameter_sets.lock(); - if let Some(sps) = sps.as_ref() { - cache.h264_sps = Some(sps.clone()); + if let Some(sps) = sps { + self.update_h264_profile_level_id(sps); + cache.h264_sps = Some(sps.to_vec()); } - if let Some(pps) = pps.as_ref() { - cache.h264_pps = Some(pps.clone()); + if let Some(pps) = pps { + cache.h264_pps = Some(pps.to_vec()); } if !is_idr { @@ -808,30 +939,103 @@ impl SharedVideoPipeline { } } - fn broadcast_encoded(&self, frame: Arc) { - if frame.is_keyframe { + fn reserve_outputs(&self) -> Option> { + let subscribers = self.subscribers.read(); + let mut active_subscribers = 0; + let mut reservations = Vec::with_capacity(subscribers.len()); + for subscriber in subscribers.iter() { + if subscriber.sender.is_closed() { + continue; + } + active_subscribers += 1; + if let Ok(permit) = subscriber.sender.clone().try_reserve_owned() { + if subscriber.needs_keyframe.load(Ordering::Acquire) + && !subscriber.recovery_requested.swap(true, Ordering::AcqRel) + { + self.keyframe_requested.store(true, Ordering::Release); + } + reservations.push(VideoFrameReservation { + subscriber: subscriber.clone(), + permit, + }); + } + } + if active_subscribers > 0 && reservations.is_empty() { + if !self.output_backpressured.swap(true, Ordering::AcqRel) { + self.output_backpressure_events + .fetch_add(1, Ordering::Relaxed); + } + None + } else { + self.output_backpressured.store(false, Ordering::Release); + Some(reservations) + } + } + + fn broadcast_encoded_batch( + &self, + frames: EncodedBatch, + reservations: Vec, + ) { + if frames.is_empty() { + return; + } + self.encoded_frames + .fetch_add(frames.len() as u64, Ordering::Relaxed); + self.encoded_bytes.fetch_add( + frames.iter().map(|frame| frame.data.len() as u64).sum(), + Ordering::Relaxed, + ); + if let Some(frame) = frames.iter().rev().find(|frame| frame.is_keyframe) { *self.bootstrap_frame.write() = Some(frame.clone()); } - - let subscribers = { - let guard = self.subscribers.read(); - if guard.is_empty() { - return; + { + let mut subscribers = self.subscribers.write(); + subscribers.retain(|subscriber| !subscriber.sender.is_closed()); + for subscriber in subscribers.iter() { + if !reservations + .iter() + .any(|reservation| Arc::ptr_eq(&reservation.subscriber, subscriber)) + { + subscriber.needs_keyframe.store(true, Ordering::Release); + subscriber + .recovery_requested + .store(false, Ordering::Release); + self.encoded_frames_skipped_for_slow_subscribers + .fetch_add(frames.len() as u64, Ordering::Relaxed); + } } - guard.iter().cloned().collect::>() - }; - - for tx in &subscribers { - // Never await a consumer. A full one-slot queue means the - // consumer is behind; dropping this frame preserves bounded - // latency and the receiver's sequence-gap logic requests a fresh - // keyframe when necessary. - let _ = tx.try_send(frame.clone()); } - - if subscribers.iter().any(|tx| tx.is_closed()) { - let mut guard = self.subscribers.write(); - guard.retain(|tx| !tx.is_closed()); + for reservation in reservations { + let batch = if reservation + .subscriber + .needs_keyframe + .load(Ordering::Acquire) + { + let Some(first_keyframe) = frames.iter().position(|frame| frame.is_keyframe) else { + self.encoded_frames_skipped_for_slow_subscribers + .fetch_add(frames.len() as u64, Ordering::Relaxed); + continue; + }; + reservation + .subscriber + .needs_keyframe + .store(false, Ordering::Release); + reservation + .subscriber + .recovery_requested + .store(false, Ordering::Release); + self.encoded_frames_skipped_for_slow_subscribers + .fetch_add(first_keyframe as u64, Ordering::Relaxed); + if first_keyframe == 0 { + frames.clone() + } else { + frames[first_keyframe..].to_vec().into() + } + } else { + frames.clone() + }; + reservation.permit.send(batch); } } @@ -951,14 +1155,34 @@ impl SharedVideoPipeline { info!("Using parallel libyuv MJPEG decode with hardware encoding"); } let mut encoder_state = build_encoder_state(&encoder_config)?; + let frame_mailbox = Arc::new(VideoFrameMailbox::new()); + *self.frame_mailbox.lock() = Some(frame_mailbox.clone()); + self.mjpeg_decoded_frames.store(0, Ordering::Relaxed); + self.mjpeg_dropped_before_decode.store(0, Ordering::Relaxed); + self.decoded_frames_not_encoded.store(0, Ordering::Relaxed); + self.mjpeg_decode_time_ns.store(0, Ordering::Relaxed); + self.encoded_input_frames.store(0, Ordering::Relaxed); + self.encoded_frames.store(0, Ordering::Relaxed); + self.encoded_bytes.store(0, Ordering::Relaxed); + self.encode_time_ns.store(0, Ordering::Relaxed); + self.output_backpressure_events.store(0, Ordering::Relaxed); + self.output_backpressured.store(false, Ordering::Release); + self.encoded_frames_skipped_for_slow_subscribers + .store(0, Ordering::Relaxed); + self.delivery_stats.sent_frames.store(0, Ordering::Relaxed); + self.delivery_stats.send_time_ns.store(0, Ordering::Relaxed); + self.delivery_stats.send_errors.store(0, Ordering::Relaxed); + for subscriber in self.subscribers.read().iter() { + subscriber.needs_keyframe.store(true, Ordering::Release); + subscriber + .recovery_requested + .store(false, Ordering::Release); + } let _ = self.running.send(true); let _ = self.encoder_done.send(false); self.running_flag.store(true, Ordering::Release); let pipeline = self.clone(); - let latest_frame: Arc>>> = - Arc::new(ParkingRwLock::new(None)); - let (frame_seq_tx, mut frame_seq_rx) = watch::channel(0u64); let buffer_pool = Arc::new(FrameBufferPool::new(buffer_count.max(4) as usize)); let (cmd_tx, mut cmd_rx) = tokio::sync::mpsc::unbounded_channel(); { @@ -969,32 +1193,34 @@ impl SharedVideoPipeline { // Encoder loop uses a dedicated OS thread because FFmpeg work is synchronous. { let pipeline = pipeline.clone(); - let latest_frame = latest_frame.clone(); - let handle = tokio::runtime::Handle::current(); - std::thread::spawn(move || { + let frame_mailbox = frame_mailbox.clone(); + let encoder_worker = spawn_video_worker("video-encoder", move || { let mut input_frame_count: u64 = 0; let mut encoded_frame_count: u64 = 0; let mut last_fps_time = Instant::now(); let mut fps_frame_count: u64 = 0; - let mut last_seq = *frame_seq_rx.borrow(); + let perf_logging = std::env::var("ONE_KVM_VIDEO_STATS").as_deref() == Ok("1"); + let mut last_perf_time = Instant::now(); + let mut last_perf_stats = pipeline.stats_snapshot(); let encode_error_throttler = LogThrottler::with_secs(ENCODE_ERROR_THROTTLE_SECS); let mut suppressed_encode_errors: HashMap = HashMap::new(); while pipeline.running_flag.load(Ordering::Acquire) { - if handle.block_on(frame_seq_rx.changed()).is_err() { + let Some((frame, reservations)) = + frame_mailbox.receive_when(|| pipeline.reserve_outputs()) + else { break; - } + }; if !pipeline.running_flag.load(Ordering::Acquire) { break; } - let seq = *frame_seq_rx.borrow(); - if seq == last_seq { - continue; - } - last_seq = seq; - - if pipeline.subscriber_count() == 0 { + if reservations.is_empty() { + if parallel_mjpeg_decode { + pipeline + .decoded_frames_not_encoded + .fetch_add(1, Ordering::Relaxed); + } continue; } @@ -1004,26 +1230,28 @@ impl SharedVideoPipeline { } } - let frame = { - let guard = latest_frame.read(); - guard.clone() - }; - let frame = match frame { - Some(f) => f, - None => continue, - }; - input_frame_count = input_frame_count.wrapping_add(1); + pipeline + .encoded_input_frames + .fetch_add(1, Ordering::Relaxed); + let encode_started = Instant::now(); + let encode_result = pipeline.encode_frame_sync(&mut encoder_state, &frame); + pipeline.encode_time_ns.fetch_add( + encode_started.elapsed().as_nanos() as u64, + Ordering::Relaxed, + ); - match pipeline.encode_frame_sync(&mut encoder_state, &frame) { + match encode_result { Ok(encoded_frames) => { - for encoded_frame in encoded_frames { - let encoded_arc = Arc::new(encoded_frame); - pipeline.broadcast_encoded(encoded_arc); - - encoded_frame_count = encoded_frame_count.wrapping_add(1); - fps_frame_count += 1; - } + encoded_frame_count = + encoded_frame_count.wrapping_add(encoded_frames.len() as u64); + fps_frame_count += encoded_frames.len() as u64; + let frames: EncodedBatch = encoded_frames + .into_iter() + .map(Arc::new) + .collect::>() + .into(); + pipeline.broadcast_encoded_batch(frames, reservations); } Err(e) => { log_encoding_error( @@ -1040,16 +1268,24 @@ impl SharedVideoPipeline { fps_frame_count = 0; last_fps_time = Instant::now(); - handle.block_on(async { - let mut s = pipeline.stats.lock().await; - s.current_fps = current_fps; - }); + pipeline.stats.lock().current_fps = current_fps; trace!( - "Shared pipeline processed {} input frames, emitted {} encoded frames", + "Shared pipeline processed {} input frames, emitted {} encoded frames; MJPEG decoded={}, dropped before decode={}, decoded but not encoded={}", input_frame_count, - encoded_frame_count + encoded_frame_count, + pipeline.mjpeg_decoded_frames.load(Ordering::Relaxed), + pipeline.mjpeg_dropped_before_decode.load(Ordering::Relaxed), + pipeline.decoded_frames_not_encoded.load(Ordering::Relaxed), ); } + + if perf_logging && last_perf_time.elapsed() >= Duration::from_secs(5) { + let elapsed = last_perf_time.elapsed().as_secs_f64(); + let stats = pipeline.stats_snapshot(); + log_pipeline_performance(&stats, &last_perf_stats, elapsed); + last_perf_stats = stats; + last_perf_time = Instant::now(); + } } pipeline.clear_cmd_tx(); @@ -1057,17 +1293,24 @@ impl SharedVideoPipeline { drop(encoder_state); let _ = pipeline.encoder_done.send(true); }); + if let Err(error) = encoder_worker { + self.stop(); + let _ = self.running.send(false); + let _ = self.encoder_done.send(true); + return Err(AppError::VideoError(format!( + "Failed to start encoder worker: {error}" + ))); + } } // Capture loop (runs on thread, updates latest frame) { let pipeline = pipeline.clone(); - let latest_frame = latest_frame.clone(); - let frame_seq_tx = frame_seq_tx.clone(); + let frame_mailbox = frame_mailbox.clone(); let buffer_pool = buffer_pool.clone(); let bridge_ctx = BridgeContext::from_parts(subdev_path, parse_bridge_kind(bridge_kind.as_deref())); - std::thread::spawn(move || { + let capture_worker = spawn_video_worker("video-capture", move || { let mut stream: Option = None; let mut initial_geometry: Option<(Resolution, PixelFormat)> = None; let mut resolution = config.resolution; @@ -1078,8 +1321,7 @@ impl SharedVideoPipeline { .then(|| { spawn_mjpeg_decode_workers( &pipeline, - &latest_frame, - &frame_seq_tx, + &frame_mailbox, &buffer_pool, config.resolution, ) @@ -1140,8 +1382,8 @@ impl SharedVideoPipeline { } let mut no_subscribers_since: Option = None; + let mut capture_sequence = 0u64; let grace_period = Duration::from_secs(AUTO_STOP_GRACE_PERIOD_SECS); - let mut sequence: u64 = 0; let mut consecutive_timeouts: u32 = 0; let recovery_policy = CaptureRecoveryPolicy::new(config.control_mode); let capture_error_throttler = LogThrottler::with_secs(5); @@ -1430,6 +1672,18 @@ impl SharedVideoPipeline { } owned.truncate(frame_size); + capture_sequence = capture_sequence.wrapping_add(1); + + if parallel_mjpeg_decode + && pixel_format == PixelFormat::Mjpeg + && frame_mailbox.has_pending() + { + pipeline + .mjpeg_dropped_before_decode + .fetch_add(1, Ordering::Relaxed); + buffer_pool.put(owned); + continue; + } // Notify streaming only after the short-frame guard passes. pipeline.notify_state(PipelineStateNotification::streaming( @@ -1441,7 +1695,7 @@ impl SharedVideoPipeline { if let Some(senders) = mjpeg_decode_senders.as_mut() { let mut pending = Some(MjpegDecodeJob { data: owned, - sequence: meta.sequence, + sequence: capture_sequence, }); for offset in 0..senders.len() { let index = (next_mjpeg_decoder + offset) % senders.len(); @@ -1458,6 +1712,9 @@ impl SharedVideoPipeline { } } if let Some(job) = pending { + pipeline + .mjpeg_dropped_before_decode + .fetch_add(1, Ordering::Relaxed); buffer_pool.put(job.data); } continue; @@ -1468,6 +1725,7 @@ impl SharedVideoPipeline { let nv12_size = resolution.width as usize * resolution.height as usize * 3 / 2; let mut nv12 = buffer_pool.take(nv12_size); + let decode_started = Instant::now(); if let Err(error) = decoder.decode_into(&owned, &mut nv12) { buffer_pool.put(owned); buffer_pool.put(nv12); @@ -1478,6 +1736,13 @@ impl SharedVideoPipeline { continue; } buffer_pool.put(owned); + pipeline.mjpeg_decode_time_ns.fetch_add( + decode_started.elapsed().as_nanos() as u64, + Ordering::Relaxed, + ); + pipeline + .mjpeg_decoded_frames + .fetch_add(1, Ordering::Relaxed); (nv12, PixelFormat::Nv12, resolution.width) } else { (owned, pixel_format, stride) @@ -1487,15 +1752,15 @@ impl SharedVideoPipeline { resolution, frame_format, frame_stride, - meta.sequence, + capture_sequence, )); - sequence = meta.sequence.wrapping_add(1); - + if frame_mailbox.publish(capture_sequence, frame) != PublishResult::Published + && mjpeg_decoder.is_some() { - let mut guard = latest_frame.write(); - *guard = Some(frame); + pipeline + .decoded_frames_not_encoded + .fetch_add(1, Ordering::Relaxed); } - let _ = frame_seq_tx.send(sequence); } // `running` represents completed lifecycle state, not a stop request. @@ -1503,10 +1768,17 @@ impl SharedVideoPipeline { // have all completed before another consumer is told the device is free. drop(stream); pipeline.running_flag.store(false, Ordering::Release); - let _ = frame_seq_tx.send(sequence.wrapping_add(1)); + frame_mailbox.close(); let _ = pipeline.running.send(false); info!("Video pipeline stopped and capture device released"); }); + if let Err(error) = capture_worker { + self.stop(); + let _ = self.running.send(false); + return Err(AppError::VideoError(format!( + "Failed to start capture worker: {error}" + ))); + } } Ok(()) @@ -1638,13 +1910,9 @@ impl SharedVideoPipeline { let (data, is_keyframe) = self.inspect_and_parameterize_packet(codec, encoded.data, encoded.key == 1); let sequence = self.sequence.fetch_add(1, Ordering::Relaxed) + 1; - if codec == VideoEncoderType::H264 { - self.update_h264_profile_level_id(&data); - } - encoded_frames.push(EncodedVideoFrame { data, - pts_ms, + pts_ms: encoded.pts, is_keyframe, sequence, duration: Duration::from_millis(1000 / fps as u64), @@ -1682,7 +1950,11 @@ impl SharedVideoPipeline { /// Stop the pipeline (non-blocking, does not wait for capture thread to exit) pub fn stop(&self) { - if self.running_flag.swap(false, Ordering::AcqRel) { + let was_running = self.running_flag.swap(false, Ordering::AcqRel); + if let Some(frame_mailbox) = self.frame_mailbox.lock().as_ref() { + frame_mailbox.close(); + } + if was_running { self.clear_cmd_tx(); info!("Stopping video pipeline"); } @@ -2058,6 +2330,101 @@ mod tests { assert!(h264_bitstream::is_keyframe(bootstrap.as_ref())); } + #[test] + fn h264_profile_is_updated_from_parameter_only_packet() { + let pipeline = SharedVideoPipeline::new(SharedVideoPipelineConfig::h264( + Resolution::HD720, + BitratePreset::Balanced, + )) + .unwrap(); + let packet = + Bytes::from_static(&[0, 0, 0, 1, 0x67, 0x42, 0xe0, 0x1f, 0, 0, 0, 1, 0x68, 0xce]); + let (_, keyframe) = + pipeline.inspect_and_parameterize_packet(VideoEncoderType::H264, packet, false); + assert!(!keyframe); + assert_eq!( + pipeline.h264_profile_level_id_watch().borrow().as_deref(), + Some("42e01f") + ); + } + + #[tokio::test] + async fn decoder_statistics_report_work_and_drops() { + let pipeline = SharedVideoPipeline::new(SharedVideoPipelineConfig::h264( + Resolution::HD720, + BitratePreset::Balanced, + )) + .unwrap(); + pipeline.mjpeg_decoded_frames.store(30, Ordering::Relaxed); + pipeline + .mjpeg_dropped_before_decode + .store(10, Ordering::Relaxed); + pipeline + .decoded_frames_not_encoded + .store(5, Ordering::Relaxed); + let stats = pipeline.stats().await; + assert_eq!(stats.mjpeg_decoded_frames, 30); + assert_eq!(stats.mjpeg_dropped_before_decode, 10); + assert_eq!(stats.decoded_frames_not_encoded, 5); + } + + #[test] + fn encoder_packet_pts_is_preserved() { + use super::super::encoder_state::{EncodedFrame, VideoEncoderTrait}; + + struct TestEncoder; + + impl VideoEncoderTrait for TestEncoder { + fn encode_raw(&mut self, _data: &[u8], _pts_ms: i64) -> Result> { + Ok(vec![EncodedFrame { + data: Bytes::from_static(&[0, 0, 0, 1, 0x41, 0xc0]), + key: 0, + pts: 321, + }]) + } + + fn set_bitrate(&mut self, _bitrate_kbps: u32) -> Result<()> { + Ok(()) + } + + fn codec_name(&self) -> &str { + "test-h264" + } + + fn request_keyframe(&mut self) {} + } + + let pipeline = SharedVideoPipeline::new(SharedVideoPipelineConfig::h264( + Resolution::HD720, + BitratePreset::Balanced, + )) + .unwrap(); + let mut state = EncoderThreadState { + encoder: Some(Box::new(TestEncoder)), + mjpeg_decoder: None, + nv12_converter: None, + yuv420p_converter: None, + encoder_needs_yuv420p: false, + #[cfg(any(target_arch = "aarch64", target_arch = "arm"))] + ffmpeg_hw_pipeline: None, + #[cfg(any(target_arch = "aarch64", target_arch = "arm"))] + ffmpeg_hw_enabled: false, + fps: 30, + codec: VideoEncoderType::H264, + input_format: PixelFormat::Nv12, + }; + let frame = VideoFrame::from_pooled( + Arc::new(FrameBuffer::new(vec![0; 1280 * 720 * 3 / 2], None)), + Resolution::HD720, + PixelFormat::Nv12, + 1280, + 1, + ); + let encoded = pipeline.encode_frame_sync(&mut state, &frame).unwrap(); + assert_eq!(encoded.len(), 1); + assert_eq!(encoded[0].pts_ms, 321); + } + #[test] fn h265_keyframe_requires_irap_and_parameter_sets() { let pipeline = SharedVideoPipeline::new(SharedVideoPipelineConfig::h265( @@ -2135,7 +2502,7 @@ mod tests { codec: VideoEncoderType::H264, }); - pipeline.broadcast_encoded(bootstrap.clone()); + pipeline.broadcast_encoded_batch(vec![bootstrap.clone()].into(), Vec::new()); let mut subscriber = pipeline.subscribe(); let received = subscriber .try_recv() @@ -2143,6 +2510,205 @@ mod tests { assert!(Arc::ptr_eq(&received, &bootstrap)); } + fn delivery_test_pipeline() -> Arc { + SharedVideoPipeline::new(SharedVideoPipelineConfig::h264( + Resolution::HD720, + BitratePreset::Balanced, + )) + .unwrap() + } + + fn delivery_test_frame(sequence: u64, is_keyframe: bool) -> Arc { + Arc::new(EncodedVideoFrame { + data: Bytes::from_static(&[0, 0, 0, 1, 0x41, 0xc0]), + pts_ms: sequence as i64 * 33, + is_keyframe, + sequence, + duration: Duration::from_millis(33), + codec: VideoEncoderType::H264, + }) + } + + fn delivery_test_raw_frame(sequence: u64) -> Arc { + Arc::new(VideoFrame::from_pooled( + Arc::new(FrameBuffer::new(vec![0; 6], None)), + Resolution { + width: 2, + height: 2, + }, + PixelFormat::Nv12, + 2, + sequence, + )) + } + + #[test] + fn encoded_batch_delivers_all_frames_in_order() { + let pipeline = delivery_test_pipeline(); + let mut receiver = pipeline.subscribe(); + let reservations = pipeline.reserve_outputs().unwrap(); + pipeline.broadcast_encoded_batch( + vec![ + delivery_test_frame(1, true), + delivery_test_frame(2, false), + delivery_test_frame(3, false), + ] + .into(), + reservations, + ); + assert_eq!(receiver.try_recv().unwrap().sequence, 1); + assert_eq!(receiver.try_recv().unwrap().sequence, 2); + assert_eq!(receiver.try_recv().unwrap().sequence, 3); + assert!(matches!( + receiver.try_recv(), + Err(mpsc::error::TryRecvError::Empty) + )); + } + + #[test] + fn single_subscriber_backpressure_preserves_reference_chain() { + let pipeline = delivery_test_pipeline(); + let mut receiver = pipeline.subscribe(); + let reservations = pipeline.reserve_outputs().unwrap(); + pipeline.broadcast_encoded_batch(vec![delivery_test_frame(1, true)].into(), reservations); + assert!(pipeline.reserve_outputs().is_none()); + assert!(pipeline.reserve_outputs().is_none()); + assert_eq!(pipeline.stats_snapshot().output_backpressure_events, 1); + assert_eq!(receiver.try_recv().unwrap().sequence, 1); + let reservations = pipeline.reserve_outputs().unwrap(); + pipeline.broadcast_encoded_batch(vec![delivery_test_frame(2, false)].into(), reservations); + assert_eq!(receiver.try_recv().unwrap().sequence, 2); + let stats = pipeline.stats_snapshot(); + assert_eq!(stats.encoded_frames, 2); + assert_eq!(stats.encoded_frames_skipped_for_slow_subscribers, 0); + } + + #[test] + fn output_capacity_wakes_encoder_with_latest_raw_frame() { + use std::sync::mpsc as sync_mpsc; + + let pipeline = delivery_test_pipeline(); + let mailbox = Arc::new(VideoFrameMailbox::new()); + *pipeline.frame_mailbox.lock() = Some(mailbox.clone()); + let mut receiver = pipeline.subscribe(); + let reservations = pipeline.reserve_outputs().unwrap(); + pipeline.broadcast_encoded_batch(vec![delivery_test_frame(1, true)].into(), reservations); + mailbox.publish(1, delivery_test_raw_frame(1)); + mailbox.publish(2, delivery_test_raw_frame(2)); + let (waiting_sender, waiting_receiver) = sync_mpsc::sync_channel(1); + let (result_sender, result_receiver) = sync_mpsc::channel(); + let worker = std::thread::spawn(move || { + let result = mailbox.receive_when(|| { + let reservations = pipeline.reserve_outputs(); + if reservations.is_none() { + let _ = waiting_sender.try_send(()); + } + reservations + }); + let (frame, reservations) = result.unwrap(); + result_sender + .send((frame.sequence, reservations.len())) + .unwrap(); + }); + waiting_receiver + .recv_timeout(Duration::from_secs(2)) + .unwrap(); + assert_eq!(receiver.try_recv().unwrap().sequence, 1); + assert_eq!( + result_receiver + .recv_timeout(Duration::from_secs(2)) + .unwrap(), + (2, 1) + ); + worker.join().unwrap(); + } + + #[test] + fn receiver_drop_wakes_encoder_waiting_for_capacity() { + use std::sync::mpsc as sync_mpsc; + + let pipeline = delivery_test_pipeline(); + let mailbox = Arc::new(VideoFrameMailbox::new()); + *pipeline.frame_mailbox.lock() = Some(mailbox.clone()); + let receiver = pipeline.subscribe(); + let reservations = pipeline.reserve_outputs().unwrap(); + pipeline.broadcast_encoded_batch(vec![delivery_test_frame(1, true)].into(), reservations); + mailbox.publish(1, delivery_test_raw_frame(1)); + let (waiting_sender, waiting_receiver) = sync_mpsc::sync_channel(1); + let (result_sender, result_receiver) = sync_mpsc::channel(); + let worker = std::thread::spawn(move || { + let result = mailbox.receive_when(|| { + let reservations = pipeline.reserve_outputs(); + if reservations.is_none() { + let _ = waiting_sender.try_send(()); + } + reservations + }); + result_sender.send(result.unwrap().1.is_empty()).unwrap(); + }); + waiting_receiver + .recv_timeout(Duration::from_secs(2)) + .unwrap(); + drop(receiver); + assert!(result_receiver + .recv_timeout(Duration::from_secs(2)) + .unwrap()); + worker.join().unwrap(); + } + + #[test] + fn slow_subscriber_is_isolated_and_resumes_at_keyframe() { + let pipeline = delivery_test_pipeline(); + let mut fast_receiver = pipeline.subscribe(); + let mut slow_receiver = pipeline.subscribe(); + let reservations = pipeline.reserve_outputs().unwrap(); + pipeline.broadcast_encoded_batch(vec![delivery_test_frame(1, true)].into(), reservations); + pipeline.keyframe_requested.store(false, Ordering::Release); + assert_eq!(fast_receiver.try_recv().unwrap().sequence, 1); + let reservations = pipeline.reserve_outputs().unwrap(); + assert_eq!(reservations.len(), 1); + pipeline.broadcast_encoded_batch(vec![delivery_test_frame(2, false)].into(), reservations); + assert_eq!(fast_receiver.try_recv().unwrap().sequence, 2); + assert!(!pipeline.keyframe_requested.load(Ordering::Acquire)); + assert_eq!(slow_receiver.try_recv().unwrap().sequence, 1); + let reservations = pipeline.reserve_outputs().unwrap(); + assert_eq!(reservations.len(), 2); + assert!(pipeline.keyframe_requested.swap(false, Ordering::AcqRel)); + pipeline.broadcast_encoded_batch(vec![delivery_test_frame(3, false)].into(), reservations); + assert_eq!(fast_receiver.try_recv().unwrap().sequence, 3); + assert!(matches!( + slow_receiver.try_recv(), + Err(mpsc::error::TryRecvError::Empty) + )); + let reservations = pipeline.reserve_outputs().unwrap(); + assert!(!pipeline.keyframe_requested.load(Ordering::Acquire)); + pipeline.broadcast_encoded_batch(vec![delivery_test_frame(4, true)].into(), reservations); + assert_eq!(fast_receiver.try_recv().unwrap().sequence, 4); + assert_eq!(slow_receiver.try_recv().unwrap().sequence, 4); + let reservations = pipeline.reserve_outputs().unwrap(); + pipeline.broadcast_encoded_batch(vec![delivery_test_frame(5, false)].into(), reservations); + assert_eq!(fast_receiver.try_recv().unwrap().sequence, 5); + assert_eq!(slow_receiver.try_recv().unwrap().sequence, 5); + assert_eq!( + pipeline + .stats_snapshot() + .encoded_frames_skipped_for_slow_subscribers, + 2 + ); + } + + #[test] + fn send_statistics_include_successes_errors_and_duration() { + let pipeline = delivery_test_pipeline(); + let receiver = pipeline.subscribe(); + receiver.record_send(Duration::from_micros(100), true); + receiver.record_send(Duration::from_micros(200), false); + let stats = pipeline.stats_snapshot(); + assert_eq!(stats.sent_frames, 1); + assert_eq!(stats.send_errors, 1); + assert_eq!(stats.send_time_ns, 300_000); + } + #[test] fn mjpeg_workers_match_available_cpu_count() { assert_eq!(mjpeg_decode_worker_count(1), 1); @@ -2151,6 +2717,24 @@ mod tests { assert_eq!(mjpeg_decode_worker_count(64), 64); } + #[test] + fn stop_wakes_idle_encoder_even_after_capture_requested_stop() { + let pipeline = SharedVideoPipeline::new(SharedVideoPipelineConfig::h264( + Resolution::HD720, + BitratePreset::Balanced, + )) + .unwrap(); + let mailbox = Arc::new(VideoFrameMailbox::new()); + *pipeline.frame_mailbox.lock() = Some(mailbox.clone()); + let (sender, receiver) = std::sync::mpsc::channel(); + let worker = std::thread::spawn(move || { + sender.send(mailbox.receive().is_none()).unwrap(); + }); + pipeline.stop(); + assert!(receiver.recv_timeout(Duration::from_secs(2)).unwrap()); + worker.join().unwrap(); + } + #[test] fn stop_request_does_not_publish_worker_exit() { let pipeline = SharedVideoPipeline::new(SharedVideoPipelineConfig::h264( diff --git a/src/video/stream_manager.rs b/src/video/stream_manager.rs index 761a90c6..3ae2bbcb 100644 --- a/src/video/stream_manager.rs +++ b/src/video/stream_manager.rs @@ -697,9 +697,7 @@ impl VideoStreamManager { /// Returns None if video capture cannot be started or pipeline creation fails. pub async fn subscribe_encoded_frames( &self, - ) -> Option< - tokio::sync::mpsc::Receiver>, - > { + ) -> Option { // 1. Ensure video capture is initialized (for config discovery) if self.streamer.state().await == StreamerState::Uninitialized { tracing::info!("Initializing video capture for encoded frame subscription"); diff --git a/src/web/handlers/config/types.rs b/src/web/handlers/config/types.rs index 9e843236..59275182 100644 --- a/src/web/handlers/config/types.rs +++ b/src/web/handlers/config/types.rs @@ -1328,7 +1328,7 @@ impl WebConfigUpdate { } } // Cert and key must be provided together (cryptographic validity is checked in the - // handler via `RustlsConfig::from_pem`, same stack as the running HTTPS server). + // handler via `server_config_from_pem`, same stack as the running HTTPS server). match (&self.ssl_cert_pem, &self.ssl_key_pem) { (Some(_cert), Some(_key)) => {} (Some(_), None) => { diff --git a/src/web/handlers/config/web.rs b/src/web/handlers/config/web.rs index 890f24d4..39e44ce0 100644 --- a/src/web/handlers/config/web.rs +++ b/src/web/handlers/config/web.rs @@ -1,9 +1,9 @@ use axum::{extract::State, Json}; -use axum_server::tls_rustls::RustlsConfig; use std::sync::Arc; use crate::error::{AppError, Result}; use crate::state::AppState; +use crate::utils::tls::server_config_from_pem; use super::types::{WebConfigResponse, WebConfigUpdate}; @@ -27,8 +27,7 @@ pub async fn update_web_config( ) = (&req.ssl_cert_pem, &req.ssl_key_pem) { - RustlsConfig::from_pem(cert_pem.as_bytes().to_vec(), key_pem.as_bytes().to_vec()) - .await + server_config_from_pem(cert_pem.as_bytes(), key_pem.as_bytes()) .map_err(|e| { AppError::BadRequest( format!( @@ -88,16 +87,12 @@ pub async fn update_web_config( #[cfg(test)] mod tests { use super::*; - use rustls::crypto::{ring, CryptoProvider}; - #[tokio::test] - async fn rustls_accepts_rcgen_self_signed_pem() { - let _ = CryptoProvider::install_default(ring::default_provider()); + #[test] + fn openssl_accepts_rcgen_self_signed_pem() { let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()]).unwrap(); let cert_pem = cert.cert.pem(); let key_pem = cert.signing_key.serialize_pem(); - RustlsConfig::from_pem(cert_pem.into_bytes(), key_pem.into_bytes()) - .await - .unwrap(); + server_config_from_pem(cert_pem.as_bytes(), key_pem.as_bytes()).unwrap(); } } diff --git a/src/webrtc/universal_session.rs b/src/webrtc/universal_session.rs index 26cd245b..656aed20 100644 --- a/src/webrtc/universal_session.rs +++ b/src/webrtc/universal_session.rs @@ -37,9 +37,8 @@ use crate::error::{AppError, Result}; use crate::hid::datachannel::{parse_hid_message, HidChannelEvent}; use crate::hid::HidController; use crate::video::codec::{h264_bitstream, h265_bitstream}; -use crate::video::types::{ - BitratePreset, EncodedVideoFrame, PixelFormat, Resolution, VideoEncoderType, -}; +use crate::video::pipeline::EncodedVideoFrameReceiver; +use crate::video::types::{BitratePreset, PixelFormat, Resolution, VideoEncoderType}; const MIME_TYPE_H265: &str = "video/H265"; const KEYFRAME_RETRY_LIMIT: u8 = 3; @@ -527,7 +526,7 @@ impl UniversalSession { /// `on_connected` runs once ICE is up (e.g. request a keyframe). pub async fn start_from_video_pipeline( &self, - mut frame_rx: tokio::sync::mpsc::Receiver>, + mut frame_rx: EncodedVideoFrameReceiver, request_keyframe: Arc, ) { if let Some(handle) = self.video_receiver_handle.lock().await.take() { @@ -670,6 +669,7 @@ impl UniversalSession { } } + let send_started = Instant::now(); let send_result = video_track .write_frame_bytes_at( encoded_frame.data.clone(), @@ -677,6 +677,7 @@ impl UniversalSession { Some(encoded_frame.pts_ms), ) .await; + frame_rx.record_send(send_started.elapsed(), send_result.is_ok()); match send_result { Ok(()) => { frames_sent += 1; diff --git a/src/webrtc/video_track.rs b/src/webrtc/video_track.rs index 088dd5cc..4367e616 100644 --- a/src/webrtc/video_track.rs +++ b/src/webrtc/video_track.rs @@ -4,9 +4,9 @@ use bytes::Bytes; use rtp::codecs::h264::H264Payloader; use rtp::packetizer::Payloader; use std::sync::Arc; -use std::time::Duration; +use std::time::{Duration, Instant}; use tokio::sync::Mutex; -use tracing::{debug, trace, warn}; +use tracing::{debug, info, trace, warn}; use webrtc::media::Sample; use webrtc::rtp_transceiver::rtp_codec::RTCRtpCodecCapability; use webrtc::track::track_local::track_local_static_rtp::TrackLocalStaticRTP; @@ -191,6 +191,63 @@ struct H264TrackState { clock: H264RtpClock, sps: Option>, pps: Option>, + performance: H264SendPerformance, +} + +#[derive(Default)] +struct H264SendTiming { + prepare: Duration, + lock: Duration, + packetize: Duration, + write: Duration, +} + +struct H264SendPerformance { + last_report: Instant, + frames: u64, + packets: u64, + bytes: u64, + timing: H264SendTiming, +} + +impl H264SendPerformance { + fn new() -> Self { + Self { + last_report: Instant::now(), + frames: 0, + packets: 0, + bytes: 0, + timing: H264SendTiming::default(), + } + } + + fn record(&mut self, bytes: usize, packets: usize, timing: H264SendTiming) { + self.frames += 1; + self.packets += packets as u64; + self.bytes += bytes as u64; + self.timing.prepare += timing.prepare; + self.timing.lock += timing.lock; + self.timing.packetize += timing.packetize; + self.timing.write += timing.write; + let elapsed = self.last_report.elapsed(); + if elapsed < Duration::from_secs(5) { + return; + } + let average_ms = |duration: Duration| duration.as_secs_f64() * 1000.0 / self.frames as f64; + info!( + "[VideoSendPerf] window_s={:.2} frames={} packets_per_frame={:.2} frame_kib={:.2} prepare_ms={:.3} lock_ms={:.3} packetize_ms={:.3} write_ms={:.3} write_us_per_packet={:.3}", + elapsed.as_secs_f64(), + self.frames, + self.packets as f64 / self.frames as f64, + self.bytes as f64 / self.frames as f64 / 1024.0, + average_ms(self.timing.prepare), + average_ms(self.timing.lock), + average_ms(self.timing.packetize), + average_ms(self.timing.write), + self.timing.write.as_secs_f64() * 1_000_000.0 / self.packets.max(1) as f64, + ); + *self = Self::new(); + } } pub struct UniversalVideoTrack { @@ -199,6 +256,7 @@ pub struct UniversalVideoTrack { config: UniversalVideoTrackConfig, h265_state: Option>, h264_state: Mutex, + h264_perf_enabled: bool, } impl UniversalVideoTrack { @@ -253,6 +311,7 @@ impl UniversalVideoTrack { increment: 90000 / config.fps.max(1), started: false, }, + performance: H264SendPerformance::new(), }; Self { track, @@ -260,6 +319,7 @@ impl UniversalVideoTrack { config, h265_state, h264_state: Mutex::new(h264_state), + h264_perf_enabled: std::env::var("ONE_KVM_VIDEO_STATS").as_deref() == Ok("1"), } } @@ -309,20 +369,24 @@ impl UniversalVideoTrack { /// Keep the stack's H.264 STAP/FU payloader, with explicit RTP timestamps. async fn write_h264_frame(&self, data: Bytes, pts_ms: Option) -> Result<()> { - let normalized = h264_bitstream::normalize_for_webrtc(data.as_ref()); - let mut data = Bytes::from(normalized); + let prepare_started = self.h264_perf_enabled.then(Instant::now); + let mut data = h264_bitstream::normalize_annex_b(data); - let idr = h264_bitstream::is_keyframe(data.as_ref()); - let has_parameter_sets = h264_bitstream::has_sps_pps(data.as_ref()); + let inspection = h264_bitstream::inspect_annex_b(data.as_ref()); + let idr = inspection.is_idr; + let has_parameter_sets = inspection.sps.is_some() && inspection.pps.is_some(); + let prepare_time = prepare_started.map(|started| started.elapsed()); + let lock_started = self.h264_perf_enabled.then(Instant::now); let mut state = self.h264_state.lock().await; + let lock_time = lock_started.map(|started| started.elapsed()); + let packetize_started = self.h264_perf_enabled.then(Instant::now); { - let (sps, pps) = h264_bitstream::extract_sps_pps(data.as_ref()); - if let Some(sps) = sps { - state.sps = Some(sps); + if let Some(sps) = inspection.sps { + state.sps = Some(sps.to_vec()); } - if let Some(pps) = pps { - state.pps = Some(pps); + if let Some(pps) = inspection.pps { + state.pps = Some(pps.to_vec()); } if idr && !has_parameter_sets { @@ -351,6 +415,8 @@ impl UniversalVideoTrack { } let timestamp = state.clock.next(pts_ms); let count = payloads.len(); + let packetize_time = packetize_started.map(|started| started.elapsed()); + let write_started = self.h264_perf_enabled.then(Instant::now); // Serialize complete access units, including their sequence allocation. for (index, payload) in payloads.into_iter().enumerate() { let sequence_number = state.sequence_number; @@ -370,6 +436,18 @@ impl UniversalVideoTrack { .await .map_err(|e| AppError::WebRtcError(format!("H264 RTP write failed: {e}")))?; } + if let Some(started) = write_started { + state.performance.record( + data.len(), + count, + H264SendTiming { + prepare: prepare_time.unwrap_or_default(), + lock: lock_time.unwrap_or_default(), + packetize: packetize_time.unwrap_or_default(), + write: started.elapsed(), + }, + ); + } Ok(()) } @@ -533,6 +611,108 @@ mod tests { assert_eq!(clock.next(None), 6092); } + #[test] + fn h264_zero_copy_input_preserves_rtp_payloads() { + let mut annex_b = vec![ + 0, 0, 0, 1, 0x09, 0xf0, 0, 0, 1, 0x67, 0x42, 0xe0, 0x1f, 0, 0, 0, 1, 0x68, 0xce, 0, 0, + 1, 0x0c, 0xff, 0, 0, 0, 1, 0x65, + ]; + annex_b.extend_from_slice(&vec![0x55; 2500]); + let mut avcc = vec![0, 0, 0, 2, 0x09, 0xf0, 0, 0, 1, 1, 0x65]; + avcc.extend_from_slice(&vec![0x55; 256]); + let inputs = vec![ + annex_b, + avcc, + vec![0, 0, 1, 0x41, 0x88, 0x55], + vec![0x41, 0x88, 0x55], + vec![0, 0, 0, 1, 0x09, 0xf0, 0, 0, 1, 0x0c, 0xff], + Vec::new(), + ]; + for input in inputs { + let previous = Bytes::from(h264_bitstream::normalize_for_webrtc(&input)); + let current = h264_bitstream::normalize_annex_b(Bytes::from(input)); + let mut previous_payloader = H264Payloader::default(); + let mut current_payloader = H264Payloader::default(); + assert_eq!( + previous_payloader.payload(RTP_MTU - 12, &previous).unwrap(), + current_payloader.payload(RTP_MTU - 12, ¤t).unwrap(), + ); + } + } + + #[test] + fn h264_zero_copy_input_preserves_cached_parameter_sets() { + let mut previous_payloader = H264Payloader::default(); + let mut current_payloader = H264Payloader::default(); + for input in [ + Bytes::from_static(&[0, 0, 0, 1, 0x67, 0x42, 0xe0, 0x1f]), + Bytes::from_static(&[0, 0, 0, 1, 0x09, 0xf0, 0, 0, 1, 0x68, 0xce]), + Bytes::from_static(&[0, 0, 0, 1, 0x65, 0x88, 0x55]), + ] { + let previous = Bytes::from(h264_bitstream::normalize_for_webrtc(&input)); + let current = h264_bitstream::normalize_annex_b(input.clone()); + assert_eq!(current.as_ptr(), input.as_ptr()); + assert_eq!( + previous_payloader.payload(RTP_MTU - 12, &previous).unwrap(), + current_payloader.payload(RTP_MTU - 12, ¤t).unwrap(), + ); + } + } + + #[test] + fn h264_send_performance_accumulates_stage_timings() { + let mut performance = H264SendPerformance::new(); + for _ in 0..2 { + performance.record( + 1200, + 2, + H264SendTiming { + prepare: Duration::from_micros(100), + lock: Duration::from_micros(20), + packetize: Duration::from_micros(200), + write: Duration::from_micros(300), + }, + ); + } + assert_eq!(performance.frames, 2); + assert_eq!(performance.bytes, 2400); + assert_eq!(performance.packets, 4); + assert_eq!(performance.timing.prepare, Duration::from_micros(200)); + assert_eq!(performance.timing.lock, Duration::from_micros(40)); + assert_eq!(performance.timing.packetize, Duration::from_micros(400)); + assert_eq!(performance.timing.write, Duration::from_micros(600)); + } + + #[test] + fn srtp_backend_preserves_authentication_and_sequence_rollover() { + use webrtc::srtp::context::Context; + use webrtc::srtp::protection_profile::ProtectionProfile; + + for profile in [ + ProtectionProfile::Aes128CmHmacSha1_80, + ProtectionProfile::AeadAes128Gcm, + ] { + let key = vec![42; profile.key_len()]; + let salt = vec![13; profile.salt_len()]; + let mut sender = Context::new(&key, &salt, profile, None, None).unwrap(); + let mut receiver = Context::new(&key, &salt, profile, None, None).unwrap(); + for sequence in [u16::MAX - 1, u16::MAX, 0, 1] { + for ssrc in [17u32, 18] { + let mut packet = vec![0x55; 1200]; + packet[..12].copy_from_slice(&[0x80, 108, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + packet[2..4].copy_from_slice(&sequence.to_be_bytes()); + packet[8..12].copy_from_slice(&ssrc.to_be_bytes()); + let encrypted = sender.encrypt_rtp(&packet).unwrap(); + assert_ne!(&encrypted[12..1200], &packet[12..]); + let mut corrupted = encrypted.to_vec(); + corrupted[13] ^= 1; + assert!(receiver.decrypt_rtp(&corrupted).is_err()); + assert_eq!(receiver.decrypt_rtp(&encrypted).unwrap().as_ref(), packet); + } + } + } + } + #[test] fn test_video_codec_properties() { assert_eq!(VideoCodec::H264.mime_type(), "video/H264"); diff --git a/vendor/dtls/Cargo.toml b/vendor/dtls/Cargo.toml new file mode 100644 index 00000000..49212d1e --- /dev/null +++ b/vendor/dtls/Cargo.toml @@ -0,0 +1,155 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "dtls" +version = "0.17.2" +authors = ["Rain Liu "] +build = false +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "A pure Rust implementation of DTLS" +homepage = "https://webrtc.rs" +documentation = "https://docs.rs/dtls" +readme = "README.md" +license = "MIT/Apache-2.0" +repository = "https://github.com/webrtc-rs/webrtc/tree/master/dtls" + +[features] +pem = ["dep:pem"] + +[lib] +name = "dtls" +path = "src/lib.rs" + +[dependencies.aes] +version = "0.8" + +[dependencies.aes-gcm] +version = "0.10" + +[dependencies.async-trait] +version = "0.1" + +[dependencies.bytecheck] +version = "0.8" + +[dependencies.byteorder] +version = "1" + +[dependencies.cbc] +version = "0.1" +features = [ + "block-padding", + "alloc", +] + +[dependencies.ccm] +version = "0.5" + +[dependencies.chacha20poly1305] +version = "0.10.1" + +[dependencies.der-parser] +version = "9.0" + +[dependencies.hmac] +version = "0.12" + +[dependencies.log] +version = "0.4" + +[dependencies.p256] +version = "0.13" +features = [ + "default", + "ecdh", + "ecdsa", +] + +[dependencies.p384] +version = "0.13" + +[dependencies.pem] +version = "3" +optional = true + +[dependencies.portable-atomic] +version = "1.6" + +[dependencies.rand] +version = "0.9" + +[dependencies.rand_core] +version = "0.6" + +[dependencies.rcgen] +version = "0.13" + +[dependencies.ring] +version = "0.17.14" + +[dependencies.rkyv] +version = "0.8" + +[dependencies.openssl] +version = "0.10" +features = ["vendored"] + +[dependencies.rustls-pki-types] +version = "1" + +[dependencies.sec1] +version = "0.7" +features = ["std"] + +[dependencies.sha1] +version = "0.10" + +[dependencies.sha2] +version = "0.10" + +[dependencies.thiserror] +version = "1" + +[dependencies.tokio] +version = "1.32.0" +features = [ + "fs", + "io-util", + "io-std", + "macros", + "net", + "parking_lot", + "rt", + "rt-multi-thread", + "sync", + "time", +] + +[dependencies.util] +version = "0.17.1" +features = ["conn"] +default-features = false +package = "webrtc-util" + +[dependencies.x25519-dalek] +version = "2" +features = ["static_secrets"] + +[dependencies.x509-parser] +version = "0.16" + +[dev-dependencies] diff --git a/vendor/dtls/LICENSE-APACHE b/vendor/dtls/LICENSE-APACHE new file mode 100644 index 00000000..16fe87b0 --- /dev/null +++ b/vendor/dtls/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/dtls/LICENSE-MIT b/vendor/dtls/LICENSE-MIT new file mode 100644 index 00000000..e11d93be --- /dev/null +++ b/vendor/dtls/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 WebRTC.rs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/dtls/README.md b/vendor/dtls/README.md new file mode 100644 index 00000000..daa8002c --- /dev/null +++ b/vendor/dtls/README.md @@ -0,0 +1,20 @@ +# DTLS certificate verification backend + +This directory contains `dtls` 0.17.2 from the crates.io release of webrtc-rs. +The original MIT and Apache-2.0 licenses are preserved. + +One-KVM patches only certificate-chain verification and its configuration to +use vendored OpenSSL instead of rustls/WebPKI. DTLS flights, record protection, +certificate signatures, SRTP negotiation and the application fingerprint check +remain unchanged. `rustls-pki-types` remains a certificate-data representation, +not a TLS implementation. + +`certificate_verifier.rs` implements trust-anchor, purpose, certificate-validity +and DNS/IP identity checks. Server names do not fall back to the certificate +subject, and partial-label wildcards are rejected. No trust checks are skipped +by this backend. The upstream application-controlled `insecure_skip_verify` +policy is preserved, including WebRTC's separate certificate fingerprint check. +Authentication security level 2 rejects weak RSA keys and SHA-1 leaf signatures. + +Run `cargo test --manifest-path vendor/dtls/Cargo.toml --lib` after updating this +fork, and verify the application dependency tree contains no `rustls` engine. diff --git a/vendor/dtls/src/alert/alert_test.rs b/vendor/dtls/src/alert/alert_test.rs new file mode 100644 index 00000000..ad9fff9f --- /dev/null +++ b/vendor/dtls/src/alert/alert_test.rs @@ -0,0 +1,50 @@ +use std::io::{BufReader, BufWriter}; + +use super::*; +use crate::error::Error; + +#[test] +fn test_alert() -> Result<()> { + let tests = vec![ + ( + "Valid Alert", + vec![0x02, 0x0A], + Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::UnexpectedMessage, + }, + None, + ), + ( + "Invalid alert length", + vec![0x00], + Alert { + alert_level: AlertLevel::Invalid, + alert_description: AlertDescription::Invalid, + }, + Some(Error::Other("io".to_owned())), + ), + ]; + + for (name, data, wanted, unmarshal_error) in tests { + let mut reader = BufReader::new(data.as_slice()); + let result = Alert::unmarshal(&mut reader); + + if let Some(err) = unmarshal_error { + assert!(result.is_err(), "{name} expected error: {err}"); + } else if let Ok(alert) = result { + assert_eq!(wanted, alert, "{name} expected {wanted}, but got {alert}"); + + let mut data2: Vec = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(data2.as_mut()); + alert.marshal(&mut writer)?; + } + assert_eq!(data, data2, "{name} expected {data:?}, but got {data2:?}"); + } else { + assert!(result.is_ok(), "{name} expected Ok, but has error"); + } + } + + Ok(()) +} diff --git a/vendor/dtls/src/alert/mod.rs b/vendor/dtls/src/alert/mod.rs new file mode 100644 index 00000000..8c58210b --- /dev/null +++ b/vendor/dtls/src/alert/mod.rs @@ -0,0 +1,189 @@ +#[cfg(test)] +mod alert_test; + +use std::fmt; +use std::io::{Read, Write}; + +use byteorder::{ReadBytesExt, WriteBytesExt}; + +use super::content::*; +use crate::error::Result; + +#[derive(Copy, Clone, PartialEq, Debug)] +pub(crate) enum AlertLevel { + Warning = 1, + Fatal = 2, + Invalid, +} + +impl fmt::Display for AlertLevel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + AlertLevel::Warning => write!(f, "LevelWarning"), + AlertLevel::Fatal => write!(f, "LevelFatal"), + _ => write!(f, "Invalid alert level"), + } + } +} + +impl From for AlertLevel { + fn from(val: u8) -> Self { + match val { + 1 => AlertLevel::Warning, + 2 => AlertLevel::Fatal, + _ => AlertLevel::Invalid, + } + } +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub(crate) enum AlertDescription { + CloseNotify = 0, + UnexpectedMessage = 10, + BadRecordMac = 20, + DecryptionFailed = 21, + RecordOverflow = 22, + DecompressionFailure = 30, + HandshakeFailure = 40, + NoCertificate = 41, + BadCertificate = 42, + UnsupportedCertificate = 43, + CertificateRevoked = 44, + CertificateExpired = 45, + CertificateUnknown = 46, + IllegalParameter = 47, + UnknownCa = 48, + AccessDenied = 49, + DecodeError = 50, + DecryptError = 51, + ExportRestriction = 60, + ProtocolVersion = 70, + InsufficientSecurity = 71, + InternalError = 80, + UserCanceled = 90, + NoRenegotiation = 100, + UnsupportedExtension = 110, + UnknownPskIdentity = 115, + Invalid, +} + +impl fmt::Display for AlertDescription { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + AlertDescription::CloseNotify => write!(f, "CloseNotify"), + AlertDescription::UnexpectedMessage => write!(f, "UnexpectedMessage"), + AlertDescription::BadRecordMac => write!(f, "BadRecordMac"), + AlertDescription::DecryptionFailed => write!(f, "DecryptionFailed"), + AlertDescription::RecordOverflow => write!(f, "RecordOverflow"), + AlertDescription::DecompressionFailure => write!(f, "DecompressionFailure"), + AlertDescription::HandshakeFailure => write!(f, "HandshakeFailure"), + AlertDescription::NoCertificate => write!(f, "NoCertificate"), + AlertDescription::BadCertificate => write!(f, "BadCertificate"), + AlertDescription::UnsupportedCertificate => write!(f, "UnsupportedCertificate"), + AlertDescription::CertificateRevoked => write!(f, "CertificateRevoked"), + AlertDescription::CertificateExpired => write!(f, "CertificateExpired"), + AlertDescription::CertificateUnknown => write!(f, "CertificateUnknown"), + AlertDescription::IllegalParameter => write!(f, "IllegalParameter"), + AlertDescription::UnknownCa => write!(f, "UnknownCA"), + AlertDescription::AccessDenied => write!(f, "AccessDenied"), + AlertDescription::DecodeError => write!(f, "DecodeError"), + AlertDescription::DecryptError => write!(f, "DecryptError"), + AlertDescription::ExportRestriction => write!(f, "ExportRestriction"), + AlertDescription::ProtocolVersion => write!(f, "ProtocolVersion"), + AlertDescription::InsufficientSecurity => write!(f, "InsufficientSecurity"), + AlertDescription::InternalError => write!(f, "InternalError"), + AlertDescription::UserCanceled => write!(f, "UserCanceled"), + AlertDescription::NoRenegotiation => write!(f, "NoRenegotiation"), + AlertDescription::UnsupportedExtension => write!(f, "UnsupportedExtension"), + AlertDescription::UnknownPskIdentity => write!(f, "UnknownPskIdentity"), + _ => write!(f, "Invalid alert description"), + } + } +} + +impl From for AlertDescription { + fn from(val: u8) -> Self { + match val { + 0 => AlertDescription::CloseNotify, + 10 => AlertDescription::UnexpectedMessage, + 20 => AlertDescription::BadRecordMac, + 21 => AlertDescription::DecryptionFailed, + 22 => AlertDescription::RecordOverflow, + 30 => AlertDescription::DecompressionFailure, + 40 => AlertDescription::HandshakeFailure, + 41 => AlertDescription::NoCertificate, + 42 => AlertDescription::BadCertificate, + 43 => AlertDescription::UnsupportedCertificate, + 44 => AlertDescription::CertificateRevoked, + 45 => AlertDescription::CertificateExpired, + 46 => AlertDescription::CertificateUnknown, + 47 => AlertDescription::IllegalParameter, + 48 => AlertDescription::UnknownCa, + 49 => AlertDescription::AccessDenied, + 50 => AlertDescription::DecodeError, + 51 => AlertDescription::DecryptError, + 60 => AlertDescription::ExportRestriction, + 70 => AlertDescription::ProtocolVersion, + 71 => AlertDescription::InsufficientSecurity, + 80 => AlertDescription::InternalError, + 90 => AlertDescription::UserCanceled, + 100 => AlertDescription::NoRenegotiation, + 110 => AlertDescription::UnsupportedExtension, + 115 => AlertDescription::UnknownPskIdentity, + _ => AlertDescription::Invalid, + } + } +} + +// One of the content types supported by the TLS record layer is the +// alert type. Alert messages convey the severity of the message +// (warning or fatal) and a description of the alert. Alert messages +// with a level of fatal result in the immediate termination of the +// connection. In this case, other connections corresponding to the +// session may continue, but the session identifier MUST be invalidated, +// preventing the failed session from being used to establish new +// connections. Like other messages, alert messages are encrypted and +// compressed, as specified by the current connection state. +/// ## Specifications +/// +/// * [RFC 5246 §7.2] +/// +/// [RFC 5246 §7.2]: https://tools.ietf.org/html/rfc5246#section-7.2 +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct Alert { + pub(crate) alert_level: AlertLevel, + pub(crate) alert_description: AlertDescription, +} + +impl fmt::Display for Alert { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Alert {}: {}", self.alert_level, self.alert_description) + } +} + +impl Alert { + pub fn content_type(&self) -> ContentType { + ContentType::Alert + } + + pub fn size(&self) -> usize { + 2 + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + writer.write_u8(self.alert_level as u8)?; + writer.write_u8(self.alert_description as u8)?; + + Ok(writer.flush()?) + } + + pub fn unmarshal(reader: &mut R) -> Result { + let alert_level = reader.read_u8()?.into(); + let alert_description = reader.read_u8()?.into(); + + Ok(Alert { + alert_level, + alert_description, + }) + } +} diff --git a/vendor/dtls/src/application_data.rs b/vendor/dtls/src/application_data.rs new file mode 100644 index 00000000..4cea4df8 --- /dev/null +++ b/vendor/dtls/src/application_data.rs @@ -0,0 +1,41 @@ +use std::io::{Read, Write}; + +use super::content::*; +use crate::error::Result; + +// Application data messages are carried by the record layer and are +// fragmented, compressed, and encrypted based on the current connection +// state. The messages are treated as transparent data to the record +// layer. +/// ## Specifications +/// +/// * [RFC 5246 §10] +/// +/// [RFC 5246 §10]: https://tools.ietf.org/html/rfc5246#section-10 +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct ApplicationData { + pub data: Vec, +} + +impl ApplicationData { + pub fn content_type(&self) -> ContentType { + ContentType::ApplicationData + } + + pub fn size(&self) -> usize { + self.data.len() + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + writer.write_all(&self.data)?; + + Ok(writer.flush()?) + } + + pub fn unmarshal(reader: &mut R) -> Result { + let mut data: Vec = vec![]; + reader.read_to_end(&mut data)?; + + Ok(ApplicationData { data }) + } +} diff --git a/vendor/dtls/src/certificate_verifier.rs b/vendor/dtls/src/certificate_verifier.rs new file mode 100644 index 00000000..6d37797c --- /dev/null +++ b/vendor/dtls/src/certificate_verifier.rs @@ -0,0 +1,378 @@ +use openssl::stack::Stack; +use openssl::x509::store::X509StoreBuilder; +use openssl::x509::verify::{X509CheckFlags, X509VerifyParam}; +use openssl::x509::{X509PurposeId, X509StoreContext, X509}; +use rustls_pki_types::{CertificateDer, ServerName}; + +use crate::error::{Error, Result}; + +#[derive(Clone, Debug, Default)] +pub struct RootCertStore { + certificates: Vec, +} + +impl RootCertStore { + pub fn empty() -> Self { + Self::default() + } + + pub fn add(&mut self, certificate: CertificateDer<'_>) -> Result<()> { + self.certificates + .push(X509::from_der(certificate.as_ref()).map_err(openssl_error)?); + Ok(()) + } +} + +pub(crate) struct CertificateVerifier { + roots: RootCertStore, +} + +impl CertificateVerifier { + pub(crate) fn new(roots: RootCertStore) -> Self { + Self { roots } + } + + pub(crate) fn verify_client_cert( + &self, + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], + ) -> Result<()> { + self.verify(end_entity, intermediates, X509PurposeId::SSL_CLIENT, None) + } + + pub(crate) fn verify_server_cert( + &self, + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], + server_name: &str, + ) -> Result<()> { + ServerName::try_from(server_name).map_err(|error| Error::Other(error.to_string()))?; + self.verify( + end_entity, + intermediates, + X509PurposeId::SSL_SERVER, + Some(server_name), + ) + } + + fn verify( + &self, + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], + purpose: X509PurposeId, + server_name: Option<&str>, + ) -> Result<()> { + let leaf = X509::from_der(end_entity.as_ref()).map_err(openssl_error)?; + let mut chain = Stack::new().map_err(openssl_error)?; + for certificate in intermediates { + chain + .push(X509::from_der(certificate.as_ref()).map_err(openssl_error)?) + .map_err(openssl_error)?; + } + let mut parameters = X509VerifyParam::new().map_err(openssl_error)?; + parameters.set_auth_level(2); + parameters.set_purpose(purpose).map_err(openssl_error)?; + parameters.set_hostflags( + X509CheckFlags::NO_PARTIAL_WILDCARDS | X509CheckFlags::NEVER_CHECK_SUBJECT, + ); + if let Some(server_name) = server_name { + if let Ok(address) = server_name.parse() { + parameters.set_ip(address).map_err(openssl_error)?; + } else { + parameters.set_host(server_name).map_err(openssl_error)?; + } + } + let mut store = X509StoreBuilder::new().map_err(openssl_error)?; + store.set_param(¶meters).map_err(openssl_error)?; + for certificate in &self.roots.certificates { + store.add_cert(certificate.clone()).map_err(openssl_error)?; + } + let store = store.build(); + let mut context = X509StoreContext::new().map_err(openssl_error)?; + context + .init(&store, &leaf, &chain, |context| { + if context.verify_cert()? { + Ok(None) + } else { + Ok(Some(context.error().to_string())) + } + }) + .map_err(openssl_error)? + .map_or(Ok(()), |error| Err(Error::Other(error))) + } +} + +fn openssl_error(error: openssl::error::ErrorStack) -> Error { + Error::Other(error.to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + use rcgen::{BasicConstraints, CertificateParams, ExtendedKeyUsagePurpose, IsCa, KeyPair}; + + fn signed_leaf( + parameters: CertificateParams, + ) -> (CertificateVerifier, CertificateDer<'static>) { + let mut issuer_parameters = CertificateParams::new(Vec::::new()).unwrap(); + issuer_parameters + .distinguished_name + .push(rcgen::DnType::CommonName, "test root"); + issuer_parameters.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); + let issuer_key = KeyPair::generate().unwrap(); + let issuer = issuer_parameters.self_signed(&issuer_key).unwrap(); + let leaf_key = KeyPair::generate().unwrap(); + let leaf = parameters + .signed_by(&leaf_key, &issuer, &issuer_key) + .unwrap(); + let mut roots = RootCertStore::empty(); + roots.add(issuer.der().clone()).unwrap(); + (CertificateVerifier::new(roots), leaf.der().clone()) + } + + #[test] + fn trusted_server_matches_dns_and_ip_subject_alternative_names() { + let parameters = + CertificateParams::new(vec!["localhost".into(), "127.0.0.1".into()]).unwrap(); + let (verifier, leaf) = signed_leaf(parameters); + verifier + .verify_server_cert(&leaf, &[], "localhost") + .unwrap(); + assert!(verifier.verify_server_cert(&leaf, &[], "127.0.0.1").is_ok()); + assert!(verifier + .verify_server_cert(&leaf, &[], "wrong.local") + .is_err()); + assert!(verifier + .verify_server_cert(&leaf, &[], "127.0.0.2") + .is_err()); + assert!(verifier.verify_server_cert(&leaf, &[], "").is_err()); + } + + #[test] + fn rejects_untrusted_and_tampered_certificates() { + let (verifier, leaf) = + signed_leaf(CertificateParams::new(vec!["localhost".into()]).unwrap()); + let untrusted = CertificateVerifier::new(RootCertStore::empty()); + assert!(untrusted + .verify_server_cert(&leaf, &[], "localhost") + .is_err()); + let mut tampered = leaf.as_ref().to_vec(); + let last = tampered.len() - 1; + tampered[last] ^= 1; + assert!(verifier + .verify_server_cert(&CertificateDer::from(tampered), &[], "localhost") + .is_err()); + assert!(verifier + .verify_server_cert(&CertificateDer::from(vec![0]), &[], "localhost") + .is_err()); + } + + #[test] + fn rejects_expired_certificate_and_wrong_extended_key_usage() { + let mut expired = CertificateParams::new(vec!["localhost".into()]).unwrap(); + expired.not_before = rcgen::date_time_ymd(2019, 1, 1); + expired.not_after = rcgen::date_time_ymd(2020, 1, 1); + let (verifier, leaf) = signed_leaf(expired); + assert!(verifier + .verify_server_cert(&leaf, &[], "localhost") + .is_err()); + let mut server_only = CertificateParams::new(vec!["localhost".into()]).unwrap(); + server_only.extended_key_usages = vec![ExtendedKeyUsagePurpose::ServerAuth]; + let (verifier, leaf) = signed_leaf(server_only); + verifier + .verify_server_cert(&leaf, &[], "localhost") + .unwrap(); + assert!(verifier.verify_client_cert(&leaf, &[]).is_err()); + let mut client_only = CertificateParams::new(vec!["localhost".into()]).unwrap(); + client_only.extended_key_usages = vec![ExtendedKeyUsagePurpose::ClientAuth]; + let (verifier, leaf) = signed_leaf(client_only); + assert!(verifier.verify_client_cert(&leaf, &[]).is_ok()); + assert!(verifier + .verify_server_cert(&leaf, &[], "localhost") + .is_err()); + } + + #[test] + fn does_not_fall_back_to_common_name_or_partial_wildcard() { + let mut parameters = CertificateParams::new(Vec::::new()).unwrap(); + parameters + .distinguished_name + .push(rcgen::DnType::CommonName, "localhost"); + let (verifier, leaf) = signed_leaf(parameters); + assert!(verifier + .verify_server_cert(&leaf, &[], "localhost") + .is_err()); + let (verifier, leaf) = + signed_leaf(CertificateParams::new(vec!["local*.example".into()]).unwrap()); + assert!(verifier + .verify_server_cert(&leaf, &[], "localhost.example") + .is_err()); + } + + #[test] + fn rejects_weak_rsa_keys_and_sha1_leaf_signatures() { + use openssl::asn1::Asn1Time; + use openssl::hash::MessageDigest; + use openssl::pkey::PKey; + use openssl::rsa::Rsa; + use openssl::x509::extension::{BasicConstraints, SubjectAlternativeName}; + use openssl::x509::X509NameBuilder; + + let issuer_key = PKey::from_rsa(Rsa::generate(2048).unwrap()).unwrap(); + let mut issuer_name = X509NameBuilder::new().unwrap(); + issuer_name + .append_entry_by_text("CN", "strong root") + .unwrap(); + let issuer_name = issuer_name.build(); + let mut issuer = X509::builder().unwrap(); + issuer.set_version(2).unwrap(); + issuer.set_subject_name(&issuer_name).unwrap(); + issuer.set_issuer_name(&issuer_name).unwrap(); + issuer.set_pubkey(&issuer_key).unwrap(); + issuer + .set_not_before(&Asn1Time::days_from_now(0).unwrap()) + .unwrap(); + issuer + .set_not_after(&Asn1Time::days_from_now(1).unwrap()) + .unwrap(); + issuer + .append_extension(BasicConstraints::new().critical().ca().build().unwrap()) + .unwrap(); + issuer.sign(&issuer_key, MessageDigest::sha256()).unwrap(); + let issuer = issuer.build(); + let mut roots = RootCertStore::empty(); + roots + .add(CertificateDer::from(issuer.to_der().unwrap())) + .unwrap(); + let verifier = CertificateVerifier::new(roots); + for (bits, digest, expected) in [ + (2048, MessageDigest::sha256(), true), + (1024, MessageDigest::sha256(), false), + (2048, MessageDigest::sha1(), false), + ] { + let leaf_key = PKey::from_rsa(Rsa::generate(bits).unwrap()).unwrap(); + let mut leaf_name = X509NameBuilder::new().unwrap(); + leaf_name.append_entry_by_text("CN", "localhost").unwrap(); + let leaf_name = leaf_name.build(); + let mut leaf = X509::builder().unwrap(); + leaf.set_version(2).unwrap(); + leaf.set_subject_name(&leaf_name).unwrap(); + leaf.set_issuer_name(issuer.subject_name()).unwrap(); + leaf.set_pubkey(&leaf_key).unwrap(); + leaf.set_not_before(&Asn1Time::days_from_now(0).unwrap()) + .unwrap(); + leaf.set_not_after(&Asn1Time::days_from_now(1).unwrap()) + .unwrap(); + let names = SubjectAlternativeName::new() + .dns("localhost") + .build(&leaf.x509v3_context(Some(&issuer), None)) + .unwrap(); + leaf.append_extension(names).unwrap(); + leaf.sign(&issuer_key, digest).unwrap(); + let leaf = CertificateDer::from(leaf.build().to_der().unwrap()); + assert_eq!( + verifier.verify_server_cert(&leaf, &[], "localhost").is_ok(), + expected + ); + } + } + + #[tokio::test] + async fn dtls_handshake_preserves_mutual_certificate_authentication() { + use crate::config::{ClientAuthType, Config}; + use crate::conn::DTLSConn; + use crate::crypto::Certificate; + use std::sync::Arc; + use std::time::Duration; + + let client_certificate = + Certificate::generate_self_signed(vec!["localhost".into()]).unwrap(); + let server_certificate = + Certificate::generate_self_signed(vec!["localhost".into()]).unwrap(); + let mut client_roots = RootCertStore::empty(); + client_roots + .add(server_certificate.certificate[0].clone()) + .unwrap(); + let mut server_roots = RootCertStore::empty(); + server_roots + .add(client_certificate.certificate[0].clone()) + .unwrap(); + let client_socket = Arc::new(tokio::net::UdpSocket::bind("127.0.0.1:0").await.unwrap()); + let server_socket = Arc::new(tokio::net::UdpSocket::bind("127.0.0.1:0").await.unwrap()); + client_socket + .connect(server_socket.local_addr().unwrap()) + .await + .unwrap(); + server_socket + .connect(client_socket.local_addr().unwrap()) + .await + .unwrap(); + let client_config = Config { + certificates: vec![client_certificate], + roots_cas: client_roots, + server_name: "localhost".into(), + ..Default::default() + }; + let server_config = Config { + certificates: vec![server_certificate], + client_cas: server_roots, + client_auth: ClientAuthType::RequireAndVerifyClientCert, + ..Default::default() + }; + let (client, server) = tokio::time::timeout(Duration::from_secs(10), async { + tokio::try_join!( + DTLSConn::new(client_socket, client_config, true, None), + DTLSConn::new(server_socket, server_config, false, None), + ) + }) + .await + .unwrap() + .unwrap(); + client + .write(b"authenticated", Some(Duration::from_secs(5))) + .await + .unwrap(); + let mut buffer = [0; 64]; + let count = server + .read(&mut buffer, Some(Duration::from_secs(5))) + .await + .unwrap(); + assert_eq!(&buffer[..count], b"authenticated"); + client.close().await.unwrap(); + server.close().await.unwrap(); + } + + #[test] + fn verifies_intermediate_chain_without_implicitly_trusting_it() { + let mut root_parameters = CertificateParams::new(Vec::::new()).unwrap(); + root_parameters + .distinguished_name + .push(rcgen::DnType::CommonName, "test root"); + root_parameters.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); + let root_key = KeyPair::generate().unwrap(); + let root = root_parameters.self_signed(&root_key).unwrap(); + let mut intermediate_parameters = CertificateParams::new(Vec::::new()).unwrap(); + intermediate_parameters + .distinguished_name + .push(rcgen::DnType::CommonName, "test intermediate"); + intermediate_parameters.is_ca = IsCa::Ca(BasicConstraints::Constrained(0)); + let intermediate_key = KeyPair::generate().unwrap(); + let intermediate = intermediate_parameters + .signed_by(&intermediate_key, &root, &root_key) + .unwrap(); + let leaf_key = KeyPair::generate().unwrap(); + let leaf = CertificateParams::new(vec!["localhost".into()]) + .unwrap() + .signed_by(&leaf_key, &intermediate, &intermediate_key) + .unwrap(); + let mut roots = RootCertStore::empty(); + roots.add(root.der().clone()).unwrap(); + let verifier = CertificateVerifier::new(roots); + assert!(verifier + .verify_server_cert(leaf.der(), &[intermediate.der().clone()], "localhost") + .is_ok()); + assert!(verifier + .verify_server_cert(leaf.der(), &[], "localhost") + .is_err()); + } +} diff --git a/vendor/dtls/src/change_cipher_spec/change_cipher_spec_test.rs b/vendor/dtls/src/change_cipher_spec/change_cipher_spec_test.rs new file mode 100644 index 00000000..868d96df --- /dev/null +++ b/vendor/dtls/src/change_cipher_spec/change_cipher_spec_test.rs @@ -0,0 +1,37 @@ +use std::io::{BufReader, BufWriter}; + +use super::*; + +#[test] +fn test_change_cipher_spec_round_trip() -> Result<()> { + let c = ChangeCipherSpec {}; + let mut raw = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw.as_mut()); + c.marshal(&mut writer)?; + } + + let mut reader = BufReader::new(raw.as_slice()); + let cnew = ChangeCipherSpec::unmarshal(&mut reader)?; + assert_eq!( + c, cnew, + "ChangeCipherSpec round trip: got {cnew:?}, want {c:?}" + ); + + Ok(()) +} + +#[test] +fn test_change_cipher_spec_invalid() -> Result<()> { + let data = vec![0x00]; + + let mut reader = BufReader::new(data.as_slice()); + let result = ChangeCipherSpec::unmarshal(&mut reader); + + match result { + Ok(_) => panic!("must be error"), + Err(err) => assert_eq!(err.to_string(), Error::ErrInvalidCipherSpec.to_string()), + }; + + Ok(()) +} diff --git a/vendor/dtls/src/change_cipher_spec/mod.rs b/vendor/dtls/src/change_cipher_spec/mod.rs new file mode 100644 index 00000000..3379d208 --- /dev/null +++ b/vendor/dtls/src/change_cipher_spec/mod.rs @@ -0,0 +1,46 @@ +#[cfg(test)] +mod change_cipher_spec_test; + +use std::io::{Read, Write}; + +use byteorder::{ReadBytesExt, WriteBytesExt}; + +use super::content::*; +use super::error::*; + +// The change cipher spec protocol exists to signal transitions in +// ciphering strategies. The protocol consists of a single message, +// which is encrypted and compressed under the current (not the pending) +// connection state. The message consists of a single byte of value 1. +/// ## Specifications +/// +/// * [RFC 5246 §7.1] +/// +/// [RFC 5246 §7.1]: https://tools.ietf.org/html/rfc5246#section-7.1 +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct ChangeCipherSpec; + +impl ChangeCipherSpec { + pub fn content_type(&self) -> ContentType { + ContentType::ChangeCipherSpec + } + + pub fn size(&self) -> usize { + 1 + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + writer.write_u8(0x01)?; + + Ok(writer.flush()?) + } + + pub fn unmarshal(reader: &mut R) -> Result { + let data = reader.read_u8()?; + if data != 0x01 { + return Err(Error::ErrInvalidCipherSpec); + } + + Ok(ChangeCipherSpec {}) + } +} diff --git a/vendor/dtls/src/cipher_suite/cipher_suite_aes_128_ccm.rs b/vendor/dtls/src/cipher_suite/cipher_suite_aes_128_ccm.rs new file mode 100644 index 00000000..3ab6fbdc --- /dev/null +++ b/vendor/dtls/src/cipher_suite/cipher_suite_aes_128_ccm.rs @@ -0,0 +1,112 @@ +use super::*; +use crate::client_certificate_type::ClientCertificateType; +use crate::crypto::crypto_ccm::{CryptoCcm, CryptoCcmTagLen}; +use crate::prf::*; + +#[derive(Clone)] +pub struct CipherSuiteAes128Ccm { + ccm: Option, + client_certificate_type: ClientCertificateType, + id: CipherSuiteId, + psk: bool, + crypto_ccm_tag_len: CryptoCcmTagLen, +} + +impl CipherSuiteAes128Ccm { + const PRF_MAC_LEN: usize = 0; + const PRF_KEY_LEN: usize = 16; + const PRF_IV_LEN: usize = 4; + + pub fn new( + client_certificate_type: ClientCertificateType, + id: CipherSuiteId, + psk: bool, + crypto_ccm_tag_len: CryptoCcmTagLen, + ) -> Self { + CipherSuiteAes128Ccm { + ccm: None, + client_certificate_type, + id, + psk, + crypto_ccm_tag_len, + } + } +} + +impl CipherSuite for CipherSuiteAes128Ccm { + fn to_string(&self) -> String { + format!("{}", self.id) + } + + fn id(&self) -> CipherSuiteId { + self.id + } + + fn certificate_type(&self) -> ClientCertificateType { + self.client_certificate_type + } + + fn hash_func(&self) -> CipherSuiteHash { + CipherSuiteHash::Sha256 + } + + fn is_psk(&self) -> bool { + self.psk + } + + fn is_initialized(&self) -> bool { + self.ccm.is_some() + } + + fn init( + &mut self, + master_secret: &[u8], + client_random: &[u8], + server_random: &[u8], + is_client: bool, + ) -> Result<()> { + let keys = prf_encryption_keys( + master_secret, + client_random, + server_random, + CipherSuiteAes128Ccm::PRF_MAC_LEN, + CipherSuiteAes128Ccm::PRF_KEY_LEN, + CipherSuiteAes128Ccm::PRF_IV_LEN, + self.hash_func(), + )?; + + if is_client { + self.ccm = Some(CryptoCcm::new( + &self.crypto_ccm_tag_len, + &keys.client_write_key, + &keys.client_write_iv, + &keys.server_write_key, + &keys.server_write_iv, + )); + } else { + self.ccm = Some(CryptoCcm::new( + &self.crypto_ccm_tag_len, + &keys.server_write_key, + &keys.server_write_iv, + &keys.client_write_key, + &keys.client_write_iv, + )); + } + + Ok(()) + } + + fn encrypt(&self, pkt_rlh: &RecordLayerHeader, raw: &[u8]) -> Result> { + let ccm = self.ccm.as_ref().ok_or(Error::Other( + "CipherSuite has not been initialized, unable to encrypt".to_owned(), + ))?; + ccm.encrypt(pkt_rlh, raw) + } + + fn decrypt(&self, input: &[u8]) -> Result> { + let ccm = self.ccm.as_ref().ok_or(Error::Other( + "CipherSuite has not been initialized, unable to decrypt".to_owned(), + ))?; + ccm.decrypt(input) + } +} diff --git a/vendor/dtls/src/cipher_suite/cipher_suite_aes_128_gcm_sha256.rs b/vendor/dtls/src/cipher_suite/cipher_suite_aes_128_gcm_sha256.rs new file mode 100644 index 00000000..0c3d5ffd --- /dev/null +++ b/vendor/dtls/src/cipher_suite/cipher_suite_aes_128_gcm_sha256.rs @@ -0,0 +1,107 @@ +use super::*; +use crate::crypto::crypto_gcm::*; +use crate::prf::*; + +#[derive(Clone)] +pub struct CipherSuiteAes128GcmSha256 { + gcm: Option, + rsa: bool, +} + +impl CipherSuiteAes128GcmSha256 { + const PRF_MAC_LEN: usize = 0; + const PRF_KEY_LEN: usize = 16; + const PRF_IV_LEN: usize = 4; + + pub fn new(rsa: bool) -> Self { + CipherSuiteAes128GcmSha256 { gcm: None, rsa } + } +} + +impl CipherSuite for CipherSuiteAes128GcmSha256 { + fn to_string(&self) -> String { + if self.rsa { + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256".to_owned() + } else { + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256".to_owned() + } + } + + fn id(&self) -> CipherSuiteId { + if self.rsa { + CipherSuiteId::Tls_Ecdhe_Rsa_With_Aes_128_Gcm_Sha256 + } else { + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256 + } + } + + fn certificate_type(&self) -> ClientCertificateType { + if self.rsa { + ClientCertificateType::RsaSign + } else { + ClientCertificateType::EcdsaSign + } + } + + fn hash_func(&self) -> CipherSuiteHash { + CipherSuiteHash::Sha256 + } + + fn is_psk(&self) -> bool { + false + } + + fn is_initialized(&self) -> bool { + self.gcm.is_some() + } + + fn init( + &mut self, + master_secret: &[u8], + client_random: &[u8], + server_random: &[u8], + is_client: bool, + ) -> Result<()> { + let keys = prf_encryption_keys( + master_secret, + client_random, + server_random, + CipherSuiteAes128GcmSha256::PRF_MAC_LEN, + CipherSuiteAes128GcmSha256::PRF_KEY_LEN, + CipherSuiteAes128GcmSha256::PRF_IV_LEN, + self.hash_func(), + )?; + + if is_client { + self.gcm = Some(CryptoGcm::new( + &keys.client_write_key, + &keys.client_write_iv, + &keys.server_write_key, + &keys.server_write_iv, + )); + } else { + self.gcm = Some(CryptoGcm::new( + &keys.server_write_key, + &keys.server_write_iv, + &keys.client_write_key, + &keys.client_write_iv, + )); + } + + Ok(()) + } + + fn encrypt(&self, pkt_rlh: &RecordLayerHeader, raw: &[u8]) -> Result> { + let cg = self.gcm.as_ref().ok_or(Error::Other( + "CipherSuite has not been initialized, unable to encrypt".to_owned(), + ))?; + cg.encrypt(pkt_rlh, raw) + } + + fn decrypt(&self, input: &[u8]) -> Result> { + let cg = self.gcm.as_ref().ok_or(Error::Other( + "CipherSuite has not been initialized, unable to decrypt".to_owned(), + ))?; + cg.decrypt(input) + } +} diff --git a/vendor/dtls/src/cipher_suite/cipher_suite_aes_256_cbc_sha.rs b/vendor/dtls/src/cipher_suite/cipher_suite_aes_256_cbc_sha.rs new file mode 100644 index 00000000..3654f93f --- /dev/null +++ b/vendor/dtls/src/cipher_suite/cipher_suite_aes_256_cbc_sha.rs @@ -0,0 +1,107 @@ +use super::*; +use crate::crypto::crypto_cbc::*; +use crate::prf::*; + +#[derive(Clone)] +pub struct CipherSuiteAes256CbcSha { + cbc: Option, + rsa: bool, +} + +impl CipherSuiteAes256CbcSha { + const PRF_MAC_LEN: usize = 20; + const PRF_KEY_LEN: usize = 32; + const PRF_IV_LEN: usize = 16; + + pub fn new(rsa: bool) -> Self { + CipherSuiteAes256CbcSha { cbc: None, rsa } + } +} + +impl CipherSuite for CipherSuiteAes256CbcSha { + fn to_string(&self) -> String { + if self.rsa { + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA".to_owned() + } else { + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA".to_owned() + } + } + + fn id(&self) -> CipherSuiteId { + if self.rsa { + CipherSuiteId::Tls_Ecdhe_Rsa_With_Aes_256_Cbc_Sha + } else { + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_256_Cbc_Sha + } + } + + fn certificate_type(&self) -> ClientCertificateType { + if self.rsa { + ClientCertificateType::RsaSign + } else { + ClientCertificateType::EcdsaSign + } + } + + fn hash_func(&self) -> CipherSuiteHash { + CipherSuiteHash::Sha256 + } + + fn is_psk(&self) -> bool { + false + } + + fn is_initialized(&self) -> bool { + self.cbc.is_some() + } + + fn init( + &mut self, + master_secret: &[u8], + client_random: &[u8], + server_random: &[u8], + is_client: bool, + ) -> Result<()> { + let keys = prf_encryption_keys( + master_secret, + client_random, + server_random, + CipherSuiteAes256CbcSha::PRF_MAC_LEN, + CipherSuiteAes256CbcSha::PRF_KEY_LEN, + CipherSuiteAes256CbcSha::PRF_IV_LEN, + self.hash_func(), + )?; + + if is_client { + self.cbc = Some(CryptoCbc::new( + &keys.client_write_key, + &keys.client_mac_key, + &keys.server_write_key, + &keys.server_mac_key, + )?); + } else { + self.cbc = Some(CryptoCbc::new( + &keys.server_write_key, + &keys.server_mac_key, + &keys.client_write_key, + &keys.client_mac_key, + )?); + } + + Ok(()) + } + + fn encrypt(&self, pkt_rlh: &RecordLayerHeader, raw: &[u8]) -> Result> { + let cg = self.cbc.as_ref().ok_or(Error::Other( + "CipherSuite has not been initialized, unable to encrypt".to_owned(), + ))?; + cg.encrypt(pkt_rlh, raw) + } + + fn decrypt(&self, input: &[u8]) -> Result> { + let cg = self.cbc.as_ref().ok_or(Error::Other( + "CipherSuite has not been initialized, unable to decrypt".to_owned(), + ))?; + cg.decrypt(input) + } +} diff --git a/vendor/dtls/src/cipher_suite/cipher_suite_chacha20_poly1305_sha256.rs b/vendor/dtls/src/cipher_suite/cipher_suite_chacha20_poly1305_sha256.rs new file mode 100644 index 00000000..7066a4bf --- /dev/null +++ b/vendor/dtls/src/cipher_suite/cipher_suite_chacha20_poly1305_sha256.rs @@ -0,0 +1,107 @@ +use super::*; +use crate::crypto::crypto_chacha20::*; +use crate::prf::*; + +#[derive(Clone)] +pub struct CipherSuiteChaCha20Poly1305Sha256 { + rsa: bool, + cipher: Option, +} + +impl CipherSuiteChaCha20Poly1305Sha256 { + const PRF_MAC_LEN: usize = 0; + const PRF_KEY_LEN: usize = 32; + const PRF_IV_LEN: usize = 12; + + pub fn new(rsa: bool) -> Self { + CipherSuiteChaCha20Poly1305Sha256 { rsa, cipher: None } + } +} + +impl CipherSuite for CipherSuiteChaCha20Poly1305Sha256 { + fn to_string(&self) -> String { + if self.rsa { + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256".to_owned() + } else { + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256".to_owned() + } + } + + fn id(&self) -> CipherSuiteId { + if self.rsa { + CipherSuiteId::Tls_Ecdhe_Rsa_With_ChaCha20_Poly1305_Sha256 + } else { + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_ChaCha20_Poly1305_Sha256 + } + } + + fn certificate_type(&self) -> ClientCertificateType { + if self.rsa { + ClientCertificateType::RsaSign + } else { + ClientCertificateType::EcdsaSign + } + } + + fn hash_func(&self) -> CipherSuiteHash { + CipherSuiteHash::Sha256 + } + + fn is_psk(&self) -> bool { + false + } + + fn is_initialized(&self) -> bool { + self.cipher.is_some() + } + + fn init( + &mut self, + master_secret: &[u8], + client_random: &[u8], + server_random: &[u8], + is_client: bool, + ) -> Result<()> { + let keys = prf_encryption_keys( + master_secret, + client_random, + server_random, + CipherSuiteChaCha20Poly1305Sha256::PRF_MAC_LEN, + CipherSuiteChaCha20Poly1305Sha256::PRF_KEY_LEN, + CipherSuiteChaCha20Poly1305Sha256::PRF_IV_LEN, + self.hash_func(), + )?; + + if is_client { + self.cipher = Some(CryptoChaCha20::new( + &keys.client_write_key, + &keys.client_write_iv, + &keys.server_write_key, + &keys.server_write_iv, + )); + } else { + self.cipher = Some(CryptoChaCha20::new( + &keys.server_write_key, + &keys.server_write_iv, + &keys.client_write_key, + &keys.client_write_iv, + )); + } + + Ok(()) + } + + fn encrypt(&self, pkt_rlh: &RecordLayerHeader, raw: &[u8]) -> Result> { + let cg = self.cipher.as_ref().ok_or(Error::Other( + "CipherSuite has not been initialized, unable to encrypt".to_owned(), + ))?; + cg.encrypt(pkt_rlh, raw) + } + + fn decrypt(&self, input: &[u8]) -> Result> { + let cg = self.cipher.as_ref().ok_or(Error::Other( + "CipherSuite has not been initialized, unable to decrypt".to_owned(), + ))?; + cg.decrypt(input) + } +} diff --git a/vendor/dtls/src/cipher_suite/cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm.rs b/vendor/dtls/src/cipher_suite/cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm.rs new file mode 100644 index 00000000..c5bf2dfd --- /dev/null +++ b/vendor/dtls/src/cipher_suite/cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm.rs @@ -0,0 +1,12 @@ +use super::*; +use crate::cipher_suite::cipher_suite_aes_128_ccm::CipherSuiteAes128Ccm; +use crate::crypto::crypto_ccm::CryptoCcmTagLen; + +pub fn new_cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm() -> CipherSuiteAes128Ccm { + CipherSuiteAes128Ccm::new( + ClientCertificateType::EcdsaSign, + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm, + false, + CryptoCcmTagLen::CryptoCcmTagLength, + ) +} diff --git a/vendor/dtls/src/cipher_suite/cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm8.rs b/vendor/dtls/src/cipher_suite/cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm8.rs new file mode 100644 index 00000000..aa9a92ce --- /dev/null +++ b/vendor/dtls/src/cipher_suite/cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm8.rs @@ -0,0 +1,12 @@ +use super::*; +use crate::cipher_suite::cipher_suite_aes_128_ccm::CipherSuiteAes128Ccm; +use crate::crypto::crypto_ccm::CryptoCcmTagLen; + +pub fn new_cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm8() -> CipherSuiteAes128Ccm { + CipherSuiteAes128Ccm::new( + ClientCertificateType::EcdsaSign, + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm_8, + false, + CryptoCcmTagLen::CryptoCcm8TagLength, + ) +} diff --git a/vendor/dtls/src/cipher_suite/cipher_suite_tls_psk_with_aes_128_ccm.rs b/vendor/dtls/src/cipher_suite/cipher_suite_tls_psk_with_aes_128_ccm.rs new file mode 100644 index 00000000..6f506e0e --- /dev/null +++ b/vendor/dtls/src/cipher_suite/cipher_suite_tls_psk_with_aes_128_ccm.rs @@ -0,0 +1,12 @@ +use super::*; +use crate::cipher_suite::cipher_suite_aes_128_ccm::CipherSuiteAes128Ccm; +use crate::crypto::crypto_ccm::CryptoCcmTagLen; + +pub fn new_cipher_suite_tls_psk_with_aes_128_ccm() -> CipherSuiteAes128Ccm { + CipherSuiteAes128Ccm::new( + ClientCertificateType::Unsupported, + CipherSuiteId::Tls_Psk_With_Aes_128_Ccm, + true, + CryptoCcmTagLen::CryptoCcmTagLength, + ) +} diff --git a/vendor/dtls/src/cipher_suite/cipher_suite_tls_psk_with_aes_128_ccm8.rs b/vendor/dtls/src/cipher_suite/cipher_suite_tls_psk_with_aes_128_ccm8.rs new file mode 100644 index 00000000..64b9f1a5 --- /dev/null +++ b/vendor/dtls/src/cipher_suite/cipher_suite_tls_psk_with_aes_128_ccm8.rs @@ -0,0 +1,12 @@ +use super::*; +use crate::cipher_suite::cipher_suite_aes_128_ccm::CipherSuiteAes128Ccm; +use crate::crypto::crypto_ccm::CryptoCcmTagLen; + +pub fn new_cipher_suite_tls_psk_with_aes_128_ccm8() -> CipherSuiteAes128Ccm { + CipherSuiteAes128Ccm::new( + ClientCertificateType::Unsupported, + CipherSuiteId::Tls_Psk_With_Aes_128_Ccm_8, + true, + CryptoCcmTagLen::CryptoCcm8TagLength, + ) +} diff --git a/vendor/dtls/src/cipher_suite/cipher_suite_tls_psk_with_aes_128_gcm_sha256.rs b/vendor/dtls/src/cipher_suite/cipher_suite_tls_psk_with_aes_128_gcm_sha256.rs new file mode 100644 index 00000000..cb0f3e51 --- /dev/null +++ b/vendor/dtls/src/cipher_suite/cipher_suite_tls_psk_with_aes_128_gcm_sha256.rs @@ -0,0 +1,90 @@ +use super::*; +use crate::crypto::crypto_gcm::*; +use crate::prf::*; + +#[derive(Clone, Default)] +pub struct CipherSuiteTlsPskWithAes128GcmSha256 { + gcm: Option, +} + +impl CipherSuiteTlsPskWithAes128GcmSha256 { + const PRF_MAC_LEN: usize = 0; + const PRF_KEY_LEN: usize = 16; + const PRF_IV_LEN: usize = 4; +} + +impl CipherSuite for CipherSuiteTlsPskWithAes128GcmSha256 { + fn to_string(&self) -> String { + "TLS_PSK_WITH_AES_128_GCM_SHA256".to_owned() + } + + fn id(&self) -> CipherSuiteId { + CipherSuiteId::Tls_Psk_With_Aes_128_Gcm_Sha256 + } + + fn certificate_type(&self) -> ClientCertificateType { + ClientCertificateType::Unsupported + } + + fn hash_func(&self) -> CipherSuiteHash { + CipherSuiteHash::Sha256 + } + + fn is_psk(&self) -> bool { + true + } + + fn is_initialized(&self) -> bool { + self.gcm.is_some() + } + + fn init( + &mut self, + master_secret: &[u8], + client_random: &[u8], + server_random: &[u8], + is_client: bool, + ) -> Result<()> { + let keys = prf_encryption_keys( + master_secret, + client_random, + server_random, + CipherSuiteTlsPskWithAes128GcmSha256::PRF_MAC_LEN, + CipherSuiteTlsPskWithAes128GcmSha256::PRF_KEY_LEN, + CipherSuiteTlsPskWithAes128GcmSha256::PRF_IV_LEN, + self.hash_func(), + )?; + + if is_client { + self.gcm = Some(CryptoGcm::new( + &keys.client_write_key, + &keys.client_write_iv, + &keys.server_write_key, + &keys.server_write_iv, + )); + } else { + self.gcm = Some(CryptoGcm::new( + &keys.server_write_key, + &keys.server_write_iv, + &keys.client_write_key, + &keys.client_write_iv, + )); + } + + Ok(()) + } + + fn encrypt(&self, pkt_rlh: &RecordLayerHeader, raw: &[u8]) -> Result> { + let cg = self.gcm.as_ref().ok_or(Error::Other( + "CipherSuite has not been initialized, unable to encrypt".to_owned(), + ))?; + cg.encrypt(pkt_rlh, raw) + } + + fn decrypt(&self, input: &[u8]) -> Result> { + let cg = self.gcm.as_ref().ok_or(Error::Other( + "CipherSuite has not been initialized, unable to decrypt".to_owned(), + ))?; + cg.decrypt(input) + } +} diff --git a/vendor/dtls/src/cipher_suite/mod.rs b/vendor/dtls/src/cipher_suite/mod.rs new file mode 100644 index 00000000..f6a72c7e --- /dev/null +++ b/vendor/dtls/src/cipher_suite/mod.rs @@ -0,0 +1,287 @@ +pub mod cipher_suite_aes_128_ccm; +pub mod cipher_suite_aes_128_gcm_sha256; +pub mod cipher_suite_aes_256_cbc_sha; +pub mod cipher_suite_chacha20_poly1305_sha256; +pub mod cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm; +pub mod cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm8; +pub mod cipher_suite_tls_psk_with_aes_128_ccm; +pub mod cipher_suite_tls_psk_with_aes_128_ccm8; +pub mod cipher_suite_tls_psk_with_aes_128_gcm_sha256; + +use std::fmt; +use std::marker::{Send, Sync}; + +use cipher_suite_aes_128_gcm_sha256::*; +use cipher_suite_aes_256_cbc_sha::*; +use cipher_suite_chacha20_poly1305_sha256::*; +use cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm::*; +use cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm8::*; +use cipher_suite_tls_psk_with_aes_128_ccm::*; +use cipher_suite_tls_psk_with_aes_128_ccm8::*; +use cipher_suite_tls_psk_with_aes_128_gcm_sha256::*; + +use super::client_certificate_type::*; +use super::error::*; +use super::record_layer::record_layer_header::*; + +// CipherSuiteID is an ID for our supported CipherSuites +// Supported Cipher Suites +#[allow(non_camel_case_types)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum CipherSuiteId { + // AES-128-CCM + Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm = 0xc0ac, + Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm_8 = 0xc0ae, + + // AES-128-GCM-SHA256 + Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256 = 0xc02b, + Tls_Ecdhe_Rsa_With_Aes_128_Gcm_Sha256 = 0xc02f, + + // AES-256-CBC-SHA + Tls_Ecdhe_Ecdsa_With_Aes_256_Cbc_Sha = 0xc00a, + Tls_Ecdhe_Rsa_With_Aes_256_Cbc_Sha = 0xc014, + + Tls_Psk_With_Aes_128_Ccm = 0xc0a4, + Tls_Psk_With_Aes_128_Ccm_8 = 0xc0a8, + Tls_Psk_With_Aes_128_Gcm_Sha256 = 0x00a8, + + // CHACHA20_POLY1305_SHA256 + Tls_Ecdhe_Rsa_With_ChaCha20_Poly1305_Sha256 = 0xcca8, + Tls_Ecdhe_Ecdsa_With_ChaCha20_Poly1305_Sha256 = 0xcca9, + + Unsupported, +} + +impl fmt::Display for CipherSuiteId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm => { + write!(f, "TLS_ECDHE_ECDSA_WITH_AES_128_CCM") + } + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm_8 => { + write!(f, "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8") + } + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256 => { + write!(f, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256") + } + CipherSuiteId::Tls_Ecdhe_Rsa_With_Aes_128_Gcm_Sha256 => { + write!(f, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") + } + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_256_Cbc_Sha => { + write!(f, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA") + } + CipherSuiteId::Tls_Ecdhe_Rsa_With_Aes_256_Cbc_Sha => { + write!(f, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA") + } + CipherSuiteId::Tls_Psk_With_Aes_128_Ccm => write!(f, "TLS_PSK_WITH_AES_128_CCM"), + CipherSuiteId::Tls_Psk_With_Aes_128_Ccm_8 => write!(f, "TLS_PSK_WITH_AES_128_CCM_8"), + CipherSuiteId::Tls_Psk_With_Aes_128_Gcm_Sha256 => { + write!(f, "TLS_PSK_WITH_AES_128_GCM_SHA256") + } + CipherSuiteId::Tls_Ecdhe_Rsa_With_ChaCha20_Poly1305_Sha256 => { + write!(f, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256") + } + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_ChaCha20_Poly1305_Sha256 => { + write!(f, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256") + } + + _ => write!(f, "Unsupported CipherSuiteID"), + } + } +} + +impl From for CipherSuiteId { + fn from(val: u16) -> Self { + match val { + // AES-128-CCM + 0xc0ac => CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm, + 0xc0ae => CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm_8, + + // AES-128-GCM-SHA256 + 0xc02b => CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256, + 0xc02f => CipherSuiteId::Tls_Ecdhe_Rsa_With_Aes_128_Gcm_Sha256, + + // AES-256-CBC-SHA + 0xc00a => CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_256_Cbc_Sha, + 0xc014 => CipherSuiteId::Tls_Ecdhe_Rsa_With_Aes_256_Cbc_Sha, + + 0xc0a4 => CipherSuiteId::Tls_Psk_With_Aes_128_Ccm, + 0xc0a8 => CipherSuiteId::Tls_Psk_With_Aes_128_Ccm_8, + 0x00a8 => CipherSuiteId::Tls_Psk_With_Aes_128_Gcm_Sha256, + + // CHACHA20_POLY1305_SHA256 + 0xcca8 => CipherSuiteId::Tls_Ecdhe_Rsa_With_ChaCha20_Poly1305_Sha256, + 0xcca9 => CipherSuiteId::Tls_Ecdhe_Ecdsa_With_ChaCha20_Poly1305_Sha256, + + _ => CipherSuiteId::Unsupported, + } + } +} + +impl From<&str> for CipherSuiteId { + fn from(val: &str) -> Self { + match val { + "TLS_ECDHE_ECDSA_WITH_AES_128_CCM" => CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm, + "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8" => { + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm_8 + } + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" => { + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256 + } + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" => { + CipherSuiteId::Tls_Ecdhe_Rsa_With_Aes_128_Gcm_Sha256 + } + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA" => { + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_256_Cbc_Sha + } + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" => { + CipherSuiteId::Tls_Ecdhe_Rsa_With_Aes_256_Cbc_Sha + } + "TLS_PSK_WITH_AES_128_CCM" => CipherSuiteId::Tls_Psk_With_Aes_128_Ccm, + "TLS_PSK_WITH_AES_128_CCM_8" => CipherSuiteId::Tls_Psk_With_Aes_128_Ccm_8, + "TLS_PSK_WITH_AES_128_GCM_SHA256" => CipherSuiteId::Tls_Psk_With_Aes_128_Gcm_Sha256, + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" => { + CipherSuiteId::Tls_Ecdhe_Rsa_With_ChaCha20_Poly1305_Sha256 + } + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" => { + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_ChaCha20_Poly1305_Sha256 + } + _ => CipherSuiteId::Unsupported, + } + } +} + +#[derive(Copy, Clone, Debug)] +pub enum CipherSuiteHash { + Sha256, +} + +impl CipherSuiteHash { + pub(crate) fn size(&self) -> usize { + match *self { + CipherSuiteHash::Sha256 => 32, + } + } +} + +pub trait CipherSuite { + fn to_string(&self) -> String; + fn id(&self) -> CipherSuiteId; + fn certificate_type(&self) -> ClientCertificateType; + fn hash_func(&self) -> CipherSuiteHash; + fn is_psk(&self) -> bool; + fn is_initialized(&self) -> bool; + + // Generate the internal encryption state + fn init( + &mut self, + master_secret: &[u8], + client_random: &[u8], + server_random: &[u8], + is_client: bool, + ) -> Result<()>; + + fn encrypt(&self, pkt_rlh: &RecordLayerHeader, raw: &[u8]) -> Result>; + fn decrypt(&self, input: &[u8]) -> Result>; +} + +// Taken from https://www.iana.org/assignments/tls-parameters/tls-parameters.xml +// A cipher_suite is a specific combination of key agreement, cipher and MAC +// function. +pub fn cipher_suite_for_id(id: CipherSuiteId) -> Result> { + match id { + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm => { + Ok(Box::new(new_cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm())) + } + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm_8 => Ok(Box::new( + new_cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm8(), + )), + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256 => { + Ok(Box::new(CipherSuiteAes128GcmSha256::new(false))) + } + CipherSuiteId::Tls_Ecdhe_Rsa_With_Aes_128_Gcm_Sha256 => { + Ok(Box::new(CipherSuiteAes128GcmSha256::new(true))) + } + CipherSuiteId::Tls_Ecdhe_Rsa_With_Aes_256_Cbc_Sha => { + Ok(Box::new(CipherSuiteAes256CbcSha::new(true))) + } + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_256_Cbc_Sha => { + Ok(Box::new(CipherSuiteAes256CbcSha::new(false))) + } + CipherSuiteId::Tls_Psk_With_Aes_128_Ccm => { + Ok(Box::new(new_cipher_suite_tls_psk_with_aes_128_ccm())) + } + CipherSuiteId::Tls_Psk_With_Aes_128_Ccm_8 => { + Ok(Box::new(new_cipher_suite_tls_psk_with_aes_128_ccm8())) + } + CipherSuiteId::Tls_Psk_With_Aes_128_Gcm_Sha256 => { + Ok(Box::::default()) + } + CipherSuiteId::Tls_Ecdhe_Rsa_With_ChaCha20_Poly1305_Sha256 => { + Ok(Box::new(CipherSuiteChaCha20Poly1305Sha256::new(true))) + } + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_ChaCha20_Poly1305_Sha256 => { + Ok(Box::new(CipherSuiteChaCha20Poly1305Sha256::new(false))) + } + + _ => Err(Error::ErrInvalidCipherSuite), + } +} + +// CipherSuites we support in order of preference +pub(crate) fn default_cipher_suites() -> Vec> { + vec![ + Box::new(CipherSuiteAes128GcmSha256::new(false)), + Box::new(CipherSuiteAes256CbcSha::new(false)), + Box::new(CipherSuiteAes128GcmSha256::new(true)), + Box::new(CipherSuiteAes256CbcSha::new(true)), + Box::new(CipherSuiteChaCha20Poly1305Sha256::new(false)), + ] +} + +fn all_cipher_suites() -> Vec> { + vec![ + Box::new(new_cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm()), + Box::new(new_cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm8()), + Box::new(CipherSuiteAes128GcmSha256::new(false)), + Box::new(CipherSuiteAes128GcmSha256::new(true)), + Box::new(CipherSuiteAes256CbcSha::new(false)), + Box::new(CipherSuiteAes256CbcSha::new(true)), + Box::new(new_cipher_suite_tls_psk_with_aes_128_ccm()), + Box::new(new_cipher_suite_tls_psk_with_aes_128_ccm8()), + Box::::default(), + Box::new(CipherSuiteChaCha20Poly1305Sha256::new(false)), + Box::new(CipherSuiteChaCha20Poly1305Sha256::new(true)), + ] +} + +fn cipher_suites_for_ids(ids: &[CipherSuiteId]) -> Result>> { + let mut cipher_suites = vec![]; + for id in ids { + cipher_suites.push(cipher_suite_for_id(*id)?); + } + Ok(cipher_suites) +} + +pub(crate) fn parse_cipher_suites( + user_selected_suites: &[CipherSuiteId], + exclude_psk: bool, + exclude_non_psk: bool, +) -> Result>> { + let cipher_suites = if !user_selected_suites.is_empty() { + cipher_suites_for_ids(user_selected_suites)? + } else { + default_cipher_suites() + }; + + let filtered_cipher_suites: Vec> = cipher_suites + .into_iter() + .filter(|c| !((exclude_psk && c.is_psk()) || (exclude_non_psk && !c.is_psk()))) + .collect(); + + if filtered_cipher_suites.is_empty() { + Err(Error::ErrNoAvailableCipherSuites) + } else { + Ok(filtered_cipher_suites) + } +} diff --git a/vendor/dtls/src/client_certificate_type.rs b/vendor/dtls/src/client_certificate_type.rs new file mode 100644 index 00000000..d87aacb6 --- /dev/null +++ b/vendor/dtls/src/client_certificate_type.rs @@ -0,0 +1,16 @@ +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ClientCertificateType { + RsaSign = 1, + EcdsaSign = 64, + Unsupported, +} + +impl From for ClientCertificateType { + fn from(val: u8) -> Self { + match val { + 1 => ClientCertificateType::RsaSign, + 64 => ClientCertificateType::EcdsaSign, + _ => ClientCertificateType::Unsupported, + } + } +} diff --git a/vendor/dtls/src/compression_methods.rs b/vendor/dtls/src/compression_methods.rs new file mode 100644 index 00000000..a03195ec --- /dev/null +++ b/vendor/dtls/src/compression_methods.rs @@ -0,0 +1,60 @@ +use std::io::{Read, Write}; + +use byteorder::{ReadBytesExt, WriteBytesExt}; + +use crate::error::Result; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum CompressionMethodId { + Null = 0, + Unsupported, +} + +impl From for CompressionMethodId { + fn from(val: u8) -> Self { + match val { + 0 => CompressionMethodId::Null, + _ => CompressionMethodId::Unsupported, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CompressionMethods { + pub ids: Vec, +} + +impl CompressionMethods { + pub fn size(&self) -> usize { + 1 + self.ids.len() + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + writer.write_u8(self.ids.len() as u8)?; + + for id in &self.ids { + writer.write_u8(*id as u8)?; + } + + Ok(writer.flush()?) + } + + pub fn unmarshal(reader: &mut R) -> Result { + let compression_methods_count = reader.read_u8()? as usize; + let mut ids = vec![]; + for _ in 0..compression_methods_count { + let id = reader.read_u8()?.into(); + if id != CompressionMethodId::Unsupported { + ids.push(id); + } + } + + Ok(CompressionMethods { ids }) + } +} + +pub fn default_compression_methods() -> CompressionMethods { + CompressionMethods { + ids: vec![CompressionMethodId::Null], + } +} diff --git a/vendor/dtls/src/config.rs b/vendor/dtls/src/config.rs new file mode 100644 index 00000000..f1c381ea --- /dev/null +++ b/vendor/dtls/src/config.rs @@ -0,0 +1,193 @@ +use std::future::Future; +use std::pin::Pin; +use std::sync::Arc; + +use tokio::time::Duration; + +use crate::cipher_suite::*; +use crate::crypto::*; +use crate::error::*; +use crate::extension::extension_use_srtp::SrtpProtectionProfile; +use crate::handshaker::VerifyPeerCertificateFn; +use crate::signature_hash_algorithm::SignatureScheme; + +/// Config is used to configure a DTLS client or server. +/// After a Config is passed to a DTLS function it must not be modified. +#[derive(Clone)] +pub struct Config { + /// certificates contains certificate chain to present to the other side of the connection. + /// Server MUST set this if psk is non-nil + /// client SHOULD sets this so CertificateRequests can be handled if psk is non-nil + pub certificates: Vec, + + /// cipher_suites is a list of supported cipher suites. + /// If cipher_suites is nil, a default list is used + pub cipher_suites: Vec, + + /// signature_schemes contains the signature and hash schemes that the peer requests to verify. + pub signature_schemes: Vec, + + /// srtp_protection_profiles are the supported protection profiles + /// Clients will send this via use_srtp and assert that the server properly responds + /// Servers will assert that clients send one of these profiles and will respond as needed + pub srtp_protection_profiles: Vec, + + /// client_auth determines the server's policy for + /// TLS Client Authentication. The default is NoClientCert. + pub client_auth: ClientAuthType, + + /// extended_master_secret determines if the "Extended Master Secret" extension + /// should be disabled, requested, or required (default requested). + pub extended_master_secret: ExtendedMasterSecretType, + + /// flight_interval controls how often we send outbound handshake messages + /// defaults to time.Second + pub flight_interval: Duration, + + /// psk sets the pre-shared key used by this DTLS connection + /// If psk is non-nil only psk cipher_suites will be used + pub psk: Option, + pub psk_identity_hint: Option>, + + /// insecure_skip_verify controls whether a client verifies the + /// server's certificate chain and host name. + /// If insecure_skip_verify is true, TLS accepts any certificate + /// presented by the server and any host name in that certificate. + /// In this mode, TLS is susceptible to man-in-the-middle attacks. + /// This should be used only for testing. + pub insecure_skip_verify: bool, + + /// insecure_hashes allows the use of hashing algorithms that are known + /// to be vulnerable. + pub insecure_hashes: bool, + + /// insecure_verification allows the use of verification algorithms that are + /// known to be vulnerable or deprecated + pub insecure_verification: bool, + /// VerifyPeerCertificate, if not nil, is called after normal + /// certificate verification by either a client or server. It + /// receives the certificate provided by the peer and also a flag + /// that tells if normal verification has succeeded. If it returns a + /// non-nil error, the handshake is aborted and that error results. + /// + /// If normal verification fails then the handshake will abort before + /// considering this callback. If normal verification is disabled by + /// setting insecure_skip_verify, or (for a server) when client_auth is + /// RequestClientCert or RequireAnyClientCert, then this callback will + /// be considered but the verifiedChains will always be nil. + pub verify_peer_certificate: Option, + + /// roots_cas defines the set of root certificate authorities + /// that one peer uses when verifying the other peer's certificates. + /// If RootCAs is nil, TLS uses the host's root CA set. + /// Used by Client to verify server's certificate + pub roots_cas: crate::certificate_verifier::RootCertStore, + + /// client_cas defines the set of root certificate authorities + /// that servers use if required to verify a client certificate + /// by the policy in client_auth. + /// Used by Server to verify client's certificate + pub client_cas: crate::certificate_verifier::RootCertStore, + + /// server_name is used to verify the hostname on the returned + /// certificates unless insecure_skip_verify is given. + pub server_name: String, + + /// mtu is the length at which handshake messages will be fragmented to + /// fit within the maximum transmission unit (default is 1200 bytes) + pub mtu: usize, + + /// replay_protection_window is the size of the replay attack protection window. + /// Duplication of the sequence number is checked in this window size. + /// Packet with sequence number older than this value compared to the latest + /// accepted packet will be discarded. (default is 64) + pub replay_protection_window: usize, +} + +impl Default for Config { + fn default() -> Self { + Config { + certificates: vec![], + cipher_suites: vec![], + signature_schemes: vec![], + srtp_protection_profiles: vec![], + client_auth: ClientAuthType::default(), + extended_master_secret: ExtendedMasterSecretType::default(), + flight_interval: Duration::default(), + psk: None, + psk_identity_hint: None, + insecure_skip_verify: false, + insecure_hashes: false, + insecure_verification: false, + verify_peer_certificate: None, + roots_cas: crate::certificate_verifier::RootCertStore::empty(), + client_cas: crate::certificate_verifier::RootCertStore::empty(), + server_name: String::default(), + mtu: 0, + replay_protection_window: 0, + } + } +} + +pub(crate) const DEFAULT_MTU: usize = 1200; // bytes + +// PSKCallback is called once we have the remote's psk_identity_hint. +// If the remote provided none it will be nil +pub(crate) type PskCallback = + Arc Pin>> + Send>>) + Send + Sync>; + +// ClientAuthType declares the policy the server will follow for +// TLS Client Authentication. +#[derive(Default, Copy, Clone, PartialEq, Eq)] +pub enum ClientAuthType { + #[default] + NoClientCert = 0, + RequestClientCert = 1, + RequireAnyClientCert = 2, + VerifyClientCertIfGiven = 3, + RequireAndVerifyClientCert = 4, +} + +// ExtendedMasterSecretType declares the policy the client and server +// will follow for the Extended Master Secret extension +#[derive(Default, PartialEq, Eq, Copy, Clone)] +pub enum ExtendedMasterSecretType { + #[default] + Request = 0, + Require = 1, + Disable = 2, +} + +pub(crate) fn validate_config(is_client: bool, config: &Config) -> Result<()> { + if is_client && config.psk.is_some() && config.psk_identity_hint.is_none() { + return Err(Error::ErrPskAndIdentityMustBeSetForClient); + } + + if !is_client && config.psk.is_none() && config.certificates.is_empty() { + return Err(Error::ErrServerMustHaveCertificate); + } + + if !config.certificates.is_empty() && config.psk.is_some() { + return Err(Error::ErrPskAndCertificate); + } + + if config.psk_identity_hint.is_some() && config.psk.is_none() { + return Err(Error::ErrIdentityNoPsk); + } + + for cert in &config.certificates { + match cert.private_key.kind { + CryptoPrivateKeyKind::Ed25519(_) => {} + CryptoPrivateKeyKind::Ecdsa256(_) => {} + _ => return Err(Error::ErrInvalidPrivateKey), + } + } + + parse_cipher_suites( + &config.cipher_suites, + config.psk.is_none(), + config.psk.is_some(), + )?; + + Ok(()) +} diff --git a/vendor/dtls/src/conn/conn_test.rs b/vendor/dtls/src/conn/conn_test.rs new file mode 100644 index 00000000..d8c69194 --- /dev/null +++ b/vendor/dtls/src/conn/conn_test.rs @@ -0,0 +1,2464 @@ +use std::future::Future; +use std::pin::Pin; +use std::time::SystemTime; + +use rand::Rng; +use rustls_pki_types::CertificateDer; +use tokio::time::sleep; +use util::conn::conn_pipe::*; +use util::KeyingMaterialExporter; + +use super::*; +use crate::cipher_suite::cipher_suite_aes_128_gcm_sha256::*; +use crate::cipher_suite::*; +use crate::compression_methods::*; +use crate::crypto::*; +use crate::curve::*; +use crate::error::*; +use crate::extension::extension_supported_elliptic_curves::*; +use crate::extension::extension_supported_point_formats::*; +use crate::extension::extension_supported_signature_algorithms::*; +use crate::extension::renegotiation_info::ExtensionRenegotiationInfo; +use crate::extension::*; +use crate::handshake::handshake_message_certificate::*; +use crate::handshake::handshake_message_client_hello::*; +use crate::handshake::handshake_message_hello_verify_request::*; +use crate::handshake::handshake_message_server_hello::*; +use crate::handshake::handshake_message_server_hello_done::*; +use crate::handshake::handshake_message_server_key_exchange::*; +use crate::handshake::handshake_random::*; +use crate::signature_hash_algorithm::*; + +const ERR_TEST_PSK_INVALID_IDENTITY: &str = "TestPSK: Server got invalid identity"; +const ERR_PSK_REJECTED: &str = "PSK Rejected"; +const ERR_NOT_EXPECTED_CHAIN: &str = "not expected chain"; +const ERR_EXPECTED_CHAIN: &str = "expected chain"; +const ERR_WRONG_CERT: &str = "wrong cert"; + +async fn build_pipe() -> Result<(DTLSConn, DTLSConn)> { + let (ua, ub) = pipe(); + + pipe_conn(Arc::new(ua), Arc::new(ub)).await +} + +async fn pipe_conn( + ca: Arc, + cb: Arc, +) -> Result<(DTLSConn, DTLSConn)> { + let (c_tx, mut c_rx) = mpsc::channel(1); + + // Setup client + tokio::spawn(async move { + let client = create_test_client( + ca, + Config { + srtp_protection_profiles: vec![SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_80], + ..Default::default() + }, + true, + ) + .await; + + let _ = c_tx.send(client).await; + }); + + // Setup server + let sever = create_test_server( + cb, + Config { + srtp_protection_profiles: vec![SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_80], + ..Default::default() + }, + true, + ) + .await?; + + // Receive client + let client = match c_rx.recv().await.unwrap() { + Ok(client) => client, + Err(err) => return Err(err), + }; + + Ok((client, sever)) +} + +fn psk_callback_client(hint: &[u8]) -> Pin>> + Send>> { + trace!( + "Server's hint: {}", + String::from_utf8(hint.to_vec()).unwrap() + ); + Box::pin(async move { Ok(vec![0xAB, 0xC1, 0x23]) }) +} + +fn psk_callback_server(hint: &[u8]) -> Pin>> + Send>> { + trace!( + "Client's hint: {}", + String::from_utf8(hint.to_vec()).unwrap() + ); + Box::pin(async move { + sleep(Duration::from_millis(1)).await; // Now it's possible to await in the psk callback + Ok(vec![0xAB, 0xC1, 0x23]) + }) +} + +fn psk_callback_hint_fail(_hint: &[u8]) -> Pin>> + Send>> { + Box::pin(async move { Err(Error::Other(ERR_PSK_REJECTED.to_owned())) }) +} + +async fn create_test_client( + ca: Arc, + mut cfg: Config, + generate_certificate: bool, +) -> Result { + if generate_certificate { + let client_cert = Certificate::generate_self_signed(vec!["localhost".to_owned()])?; + cfg.certificates = vec![client_cert]; + } + + cfg.insecure_skip_verify = true; + DTLSConn::new(ca, cfg, true, None).await +} + +async fn create_test_server( + cb: Arc, + mut cfg: Config, + generate_certificate: bool, +) -> Result { + if generate_certificate { + let server_cert = Certificate::generate_self_signed(vec!["localhost".to_owned()])?; + cfg.certificates = vec![server_cert]; + } + + DTLSConn::new(cb, cfg, false, None).await +} + +#[tokio::test] +async fn test_routine_leak_on_close() -> Result<()> { + /*env_logger::Builder::new() + .format(|buf, record| { + writeln!( + buf, + "{}:{} [{}] {} - {}", + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.level(), + chrono::Local::now().format("%H:%M:%S.%6f"), + record.args() + ) + }) + .filter(None, LevelFilter::Trace) + .init();*/ + + let (ca, cb) = build_pipe().await?; + + let buf_a = vec![0xFA; 100]; + let n_a = ca.write(&buf_a, Some(Duration::from_secs(5))).await?; + assert_eq!(n_a, 100); + + let mut buf_b = vec![0; 1024]; + let n_b = cb.read(&mut buf_b, Some(Duration::from_secs(5))).await?; + assert_eq!(n_a, 100); + assert_eq!(&buf_a[..], &buf_b[0..n_b]); + + cb.close().await?; + ca.close().await?; + + { + drop(ca); + drop(cb); + } + + tokio::time::sleep(Duration::from_millis(1)).await; + + Ok(()) +} + +#[tokio::test] +async fn test_sequence_number_overflow_on_application_data() -> Result<()> { + /*env_logger::Builder::new() + .format(|buf, record| { + writeln!( + buf, + "{}:{} [{}] {} - {}", + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.level(), + chrono::Local::now().format("%H:%M:%S.%6f"), + record.args() + ) + }) + .filter(None, LevelFilter::Trace) + .init();*/ + + let (ca, cb) = build_pipe().await?; + + { + let mut lsn = ca.state.local_sequence_number.lock().await; + lsn[1] = MAX_SEQUENCE_NUMBER; + } + + let buf_a = vec![0xFA; 100]; + let n_a = ca.write(&buf_a, Some(Duration::from_secs(5))).await?; + assert_eq!(n_a, 100); + + let mut buf_b = vec![0; 1024]; + let n_b = cb.read(&mut buf_b, Some(Duration::from_secs(5))).await?; + assert_eq!(n_a, 100); + assert_eq!(&buf_a[..], &buf_b[0..n_b]); + + let result = ca.write(&buf_a, Some(Duration::from_secs(5))).await; + if let Err(err) = result { + assert_eq!( + err.to_string(), + Error::ErrSequenceNumberOverflow.to_string() + ); + } else { + panic!("Expected error but it is OK"); + } + + cb.close().await?; + + if let Err(err) = ca.close().await { + assert_eq!( + err.to_string(), + Error::ErrSequenceNumberOverflow.to_string() + ); + } else { + panic!("Expected error but it is OK"); + } + + { + drop(ca); + drop(cb); + } + + tokio::time::sleep(Duration::from_millis(1)).await; + + Ok(()) +} + +#[tokio::test] +async fn test_sequence_number_overflow_on_handshake() -> Result<()> { + /*env_logger::Builder::new() + .format(|buf, record| { + writeln!( + buf, + "{}:{} [{}] {} - {}", + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.level(), + chrono::Local::now().format("%H:%M:%S.%6f"), + record.args() + ) + }) + .filter(None, LevelFilter::Trace) + .init();*/ + + let (ca, cb) = build_pipe().await?; + + { + let mut lsn = ca.state.local_sequence_number.lock().await; + lsn[0] = MAX_SEQUENCE_NUMBER + 1; + } + + // Try to send handshake packet. + if let Err(err) = ca + .write_packets(vec![Packet { + record: RecordLayer::new( + PROTOCOL_VERSION1_2, + 0, + Content::Handshake(Handshake::new(HandshakeMessage::ClientHello( + HandshakeMessageClientHello { + version: PROTOCOL_VERSION1_2, + random: HandshakeRandom::default(), + cookie: vec![0; 64], + + cipher_suites: vec![CipherSuiteId::Tls_Psk_With_Aes_128_Gcm_Sha256], + compression_methods: default_compression_methods(), + extensions: vec![], + }, + ))), + ), + should_encrypt: false, + reset_local_sequence_number: false, + }]) + .await + { + assert_eq!( + err.to_string(), + Error::ErrSequenceNumberOverflow.to_string() + ); + } else { + panic!("Expected error but it is OK"); + } + + cb.close().await?; + ca.close().await?; + + { + drop(ca); + drop(cb); + } + + tokio::time::sleep(Duration::from_millis(1)).await; + + Ok(()) +} + +#[tokio::test] +async fn test_handshake_with_alert() -> Result<()> { + /*env_logger::Builder::new() + .format(|buf, record| { + writeln!( + buf, + "{}:{} [{}] {} - {}", + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.level(), + chrono::Local::now().format("%H:%M:%S.%6f"), + record.args() + ) + }) + .filter(None, LevelFilter::Trace) + .init();*/ + + let cases = vec![ + ( + "CipherSuiteNoIntersection", + Config { + // Server + cipher_suites: vec![CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256], + ..Default::default() + }, + Config { + // Client + cipher_suites: vec![CipherSuiteId::Tls_Ecdhe_Rsa_With_Aes_128_Gcm_Sha256], + ..Default::default() + }, + Error::ErrCipherSuiteNoIntersection, + Error::ErrAlertFatalOrClose, //errClient: &errAlert{&alert{alertLevelFatal, alertInsufficientSecurity}}, + ), + ( + "SignatureSchemesNoIntersection", + Config { + // Server + cipher_suites: vec![CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256], + signature_schemes: vec![SignatureScheme::EcdsaWithP256AndSha256], + ..Default::default() + }, + Config { + // Client + cipher_suites: vec![CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256], + signature_schemes: vec![SignatureScheme::EcdsaWithP521AndSha512], + ..Default::default() + }, + Error::ErrAlertFatalOrClose, //errServer: &errAlert{&alert{alertLevelFatal, alertInsufficientSecurity}}, + Error::ErrNoAvailableSignatureSchemes, //NoAvailableSignatureSchemes, + ), + ]; + + for (name, config_server, config_client, err_server, err_client) in cases { + let (client_err_tx, mut client_err_rx) = mpsc::channel(1); + + let (ca, cb) = pipe(); + tokio::spawn(async move { + let result = create_test_client(Arc::new(ca), config_client, true).await; + let _ = client_err_tx.send(result).await; + }); + + let result_server = create_test_server(Arc::new(cb), config_server, true).await; + if let Err(err) = result_server { + assert_eq!( + err.to_string(), + err_server.to_string(), + "{name} Server error exp({err_server}) failed({err})" + ); + } else { + panic!("{name} expected error but create_test_server return OK"); + } + + let result_client = client_err_rx.recv().await; + if let Some(result_client) = result_client { + if let Err(err) = result_client { + assert_eq!( + err.to_string(), + err_client.to_string(), + "{name} Client error exp({err_client}) failed({err})" + ); + } else { + panic!("{name} expected error but create_test_client return OK"); + } + } + } + + Ok(()) +} + +#[tokio::test] +async fn test_export_keying_material() -> Result<()> { + let export_label = "EXTRACTOR-dtls_srtp"; + let expected_server_key = vec![0x61, 0x09, 0x9d, 0x7d, 0xcb, 0x08, 0x52, 0x2c, 0xe7, 0x7b]; + let expected_client_key = vec![0x87, 0xf0, 0x40, 0x02, 0xf6, 0x1c, 0xf1, 0xfe, 0x8c, 0x77]; + + let (_decrypted_tx, decrypted_rx) = mpsc::channel(1); + let (_handshake_tx, handshake_rx) = mpsc::channel(1); + let (packet_tx, _packet_rx) = mpsc::channel(1); + let (handle_queue_tx, _handle_queue_rx) = mpsc::channel(1); + let (ca, _cb) = pipe(); + + let mut c = DTLSConn { + conn: Arc::new(ca), + state: State { + local_random: HandshakeRandom { + gmt_unix_time: SystemTime::UNIX_EPOCH + .checked_add(Duration::new(500, 0)) + .unwrap(), + ..Default::default() + }, + remote_random: HandshakeRandom { + gmt_unix_time: SystemTime::UNIX_EPOCH + .checked_add(Duration::new(1000, 0)) + .unwrap(), + ..Default::default() + }, + local_sequence_number: Arc::new(Mutex::new(vec![0, 0])), + cipher_suite: Arc::new(Mutex::new(Some(Box::new(CipherSuiteAes128GcmSha256::new( + false, + ))))), + ..Default::default() + }, + cache: HandshakeCache::new(), + decrypted_rx: Mutex::new(decrypted_rx), + handshake_completed_successfully: Arc::new(AtomicBool::new(false)), + connection_closed_by_user: false, + closed: AtomicBool::new(false), + current_flight: Box::new(Flight0 {}) as Box, + flights: None, + cfg: HandshakeConfig::default(), + retransmit: false, + handshake_rx, + + packet_tx: Arc::new(packet_tx), + handle_queue_tx, + handshake_done_tx: None, + + reader_close_tx: Mutex::new(None), + }; + + c.set_local_epoch(0); + let state = c.connection_state().await; + if let Err(err) = state.export_keying_material(export_label, &[], 0).await { + assert!( + err.to_string() + .contains(&Error::ErrHandshakeInProgress.to_string()), + "ExportKeyingMaterial when epoch == 0: expected '{}' actual '{}'", + Error::ErrHandshakeInProgress, + err, + ); + } else { + panic!("expect error but export_keying_material returns OK"); + } + + c.set_local_epoch(1); + let state = c.connection_state().await; + if let Err(err) = state.export_keying_material(export_label, &[0x00], 0).await { + assert!( + err.to_string() + .contains(&Error::ErrContextUnsupported.to_string()), + "ExportKeyingMaterial with context: expected '{}' actual '{}'", + Error::ErrContextUnsupported, + err + ); + } else { + panic!("expect error but export_keying_material returns OK"); + } + + for k in INVALID_KEYING_LABELS.iter() { + let state = c.connection_state().await; + if let Err(err) = state.export_keying_material(k, &[], 0).await { + assert!( + err.to_string() + .contains(&Error::ErrReservedExportKeyingMaterial.to_string()), + "ExportKeyingMaterial reserved label: expected '{}' actual '{}'", + Error::ErrReservedExportKeyingMaterial, + err, + ); + } else { + panic!("expect error but export_keying_material returns OK"); + } + } + + let state = c.connection_state().await; + let keying_material = state.export_keying_material(export_label, &[], 10).await?; + assert_eq!( + &keying_material, &expected_server_key, + "ExportKeyingMaterial client export: expected ({:?}) actual ({:?})", + &expected_server_key, &keying_material, + ); + + c.state.is_client = true; + let state = c.connection_state().await; + let keying_material = state.export_keying_material(export_label, &[], 10).await?; + assert_eq!( + &keying_material, &expected_client_key, + "ExportKeyingMaterial client export: expected ({:?}) actual ({:?})", + &expected_client_key, &keying_material, + ); + + Ok(()) +} + +#[tokio::test] +async fn test_psk() -> Result<()> { + /*env_logger::Builder::new() + .format(|buf, record| { + writeln!( + buf, + "{}:{} [{}] {} - {}", + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.level(), + chrono::Local::now().format("%H:%M:%S.%6f"), + record.args() + ) + }) + .filter(None, LevelFilter::Trace) + .init();*/ + + let tests = vec![ + ( + "Server identity specified", + Some("Test Identity".as_bytes().to_vec()), + ), + ("Server identity nil", None), + ]; + + for (name, server_identity) in tests { + let client_identity = "Client Identity".as_bytes(); + let (client_res_tx, mut client_res_rx) = mpsc::channel(1); + + let (ca, cb) = pipe(); + tokio::spawn(async move { + let conf = Config { + psk: Some(Arc::new(psk_callback_client)), + psk_identity_hint: Some(client_identity.to_vec()), + cipher_suites: vec![CipherSuiteId::Tls_Psk_With_Aes_128_Ccm_8], + ..Default::default() + }; + + let result = create_test_client(Arc::new(ca), conf, false).await; + let _ = client_res_tx.send(result).await; + }); + + let config = Config { + psk: Some(Arc::new(psk_callback_server)), + psk_identity_hint: server_identity, + cipher_suites: vec![CipherSuiteId::Tls_Psk_With_Aes_128_Ccm_8], + ..Default::default() + }; + + let server = create_test_server(Arc::new(cb), config, false).await?; + + let actual_psk_identity_hint = &server.connection_state().await.identity_hint; + assert_eq!( + actual_psk_identity_hint, client_identity, + "TestPSK: Server ClientPSKIdentity Mismatch '{name}': expected({client_identity:?}) actual({actual_psk_identity_hint:?})", + ); + + if let Some(result) = client_res_rx.recv().await { + if let Ok(client) = result { + client.close().await?; + } else { + panic!("{name}: Expected create_test_client successfully, but got error",); + } + } + + let _ = server.close().await; + } + + Ok(()) +} + +#[tokio::test] +async fn test_psk_hint_fail() -> Result<()> { + /*env_logger::Builder::new() + .format(|buf, record| { + writeln!( + buf, + "{}:{} [{}] {} - {}", + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.level(), + chrono::Local::now().format("%H:%M:%S.%6f"), + record.args() + ) + }) + .filter(None, LevelFilter::Trace) + .init();*/ + + let (client_res_tx, mut client_res_rx) = mpsc::channel(1); + + let (ca, cb) = pipe(); + tokio::spawn(async move { + let conf = Config { + psk: Some(Arc::new(psk_callback_hint_fail)), + psk_identity_hint: Some(vec![]), + cipher_suites: vec![CipherSuiteId::Tls_Psk_With_Aes_128_Ccm_8], + ..Default::default() + }; + + let result = create_test_client(Arc::new(ca), conf, false).await; + let _ = client_res_tx.send(result).await; + }); + + let config = Config { + psk: Some(Arc::new(psk_callback_hint_fail)), + psk_identity_hint: Some(vec![]), + cipher_suites: vec![CipherSuiteId::Tls_Psk_With_Aes_128_Ccm_8], + ..Default::default() + }; + + if let Err(server_err) = create_test_server(Arc::new(cb), config, false).await { + assert_eq!( + server_err.to_string(), + Error::ErrAlertFatalOrClose.to_string(), + "TestPSK: Server error exp({}) failed({})", + Error::ErrAlertFatalOrClose, + server_err, + ); + } else { + panic!("Expected server error, but got OK"); + } + + let result = client_res_rx.recv().await; + if let Some(client) = result { + if let Err(client_err) = client { + assert!( + client_err.to_string().contains(ERR_PSK_REJECTED), + "TestPSK: Client error exp({ERR_PSK_REJECTED}) failed({client_err})", + ); + } else { + panic!("Expected client error, but got OK"); + } + } + + Ok(()) +} + +#[tokio::test] +async fn test_client_timeout() -> Result<()> { + /*env_logger::Builder::new() + .format(|buf, record| { + writeln!( + buf, + "{}:{} [{}] {} - {}", + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.level(), + chrono::Local::now().format("%H:%M:%S.%6f"), + record.args() + ) + }) + .filter(None, LevelFilter::Trace) + .init();*/ + + let (client_res_tx, mut client_res_rx) = mpsc::channel(1); + + let (ca, _cb) = pipe(); + tokio::spawn(async move { + let conf = Config::default(); + let result = tokio::time::timeout( + Duration::from_millis(100), + create_test_client(Arc::new(ca), conf, true), + ) + .await; + let _ = client_res_tx.send(result).await; + }); + + // no server! + let result = client_res_rx.recv().await; + if let Some(client_timeout_result) = result { + assert!(client_timeout_result.is_err(), "Expected Error but got Ok"); + } + + Ok(()) +} + +//use std::io::Write; + +#[tokio::test] +async fn test_srtp_configuration() -> Result<()> { + /*env_logger::Builder::new() + .format(|buf, record| { + writeln!( + buf, + "{}:{} [{}] {} - {}", + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.level(), + chrono::Local::now().format("%H:%M:%S.%6f"), + record.args() + ) + }) + .filter(None, LevelFilter::Trace) + .init();*/ + + #[allow(clippy::type_complexity)] + let tests: Vec<( + &str, + Vec, + Vec, + SrtpProtectionProfile, + Option, + Option, + )> = vec![ + ( + "No SRTP in use", + vec![], + vec![], + SrtpProtectionProfile::Unsupported, + None, + None, + ), + ( + "SRTP both ends", + vec![SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_80], + vec![SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_80], + SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_80, + None, + None, + ), + ( + "SRTP client only", + vec![SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_80], + vec![], + SrtpProtectionProfile::Unsupported, + Some(Error::ErrAlertFatalOrClose), + Some(Error::ErrServerNoMatchingSrtpProfile), + ), + ( + "SRTP server only", + vec![], + vec![SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_80], + SrtpProtectionProfile::Unsupported, + None, + None, + ), + ( + "Multiple Suites", + vec![ + SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_80, + SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_32, + ], + vec![ + SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_80, + SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_32, + ], + SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_80, + None, + None, + ), + ( + "Multiple Suites, Client Chooses", + vec![ + SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_80, + SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_32, + ], + vec![ + SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_32, + SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_80, + ], + SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_80, + None, + None, + ), + ]; + + for (name, client_srtp, server_srtp, expected_profile, want_client_err, want_server_err) in + tests + { + let (client_res_tx, mut client_res_rx) = mpsc::channel(1); + let (ca, cb) = pipe(); + tokio::spawn(async move { + let conf = Config { + srtp_protection_profiles: client_srtp, + ..Default::default() + }; + + let result = create_test_client(Arc::new(ca), conf, true).await; + let _ = client_res_tx.send(result).await; + }); + + let config = Config { + srtp_protection_profiles: server_srtp, + ..Default::default() + }; + + let result = create_test_server(Arc::new(cb), config, true).await; + if let Some(expected_err) = want_server_err { + if let Err(err) = result { + assert_eq!( + err.to_string(), + expected_err.to_string(), + "{name} TestPSK: Server error exp({expected_err}) failed({err})", + ); + } else { + panic!("{name} expected error, but got ok"); + } + } else { + match result { + Ok(server) => { + let actual_server_srtp = server.selected_srtpprotection_profile(); + assert_eq!(actual_server_srtp, expected_profile, + "test_srtp_configuration: Server SRTPProtectionProfile Mismatch '{name}': expected({expected_profile:?}) actual({actual_server_srtp:?})"); + } + Err(err) => { + panic!("{name} expected no error: {err}"); + } + }; + } + + let client_result = client_res_rx.recv().await; + if let Some(result) = client_result { + if let Some(expected_err) = want_client_err { + if let Err(err) = result { + assert_eq!( + err.to_string(), + expected_err.to_string(), + "TestPSK: Client error exp({expected_err}) failed({err})", + ); + } else { + panic!("{name} expected error, but got ok"); + } + } else if let Ok(client) = result { + let actual_client_srtp = client.selected_srtpprotection_profile(); + assert_eq!(actual_client_srtp, expected_profile, + "test_srtp_configuration: Client SRTPProtectionProfile Mismatch '{name}': expected({expected_profile:?}) actual({actual_client_srtp:?})"); + } else { + panic!("{name} expected no error"); + } + } else { + panic!("{name} expected client, but got none"); + } + } + + Ok(()) +} + +#[tokio::test] +async fn test_client_certificate() -> Result<()> { + /*env_logger::Builder::new() + .format(|buf, record| { + writeln!( + buf, + "{}:{} [{}] {} - {}", + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.level(), + chrono::Local::now().format("%H:%M:%S.%6f"), + record.args() + ) + }) + .filter(None, LevelFilter::Trace) + .init();*/ + + let server_name = "localhost".to_owned(); + + let srv_cert = Certificate::generate_self_signed(vec!["localhost".to_owned()])?; + let mut srv_ca_pool = crate::certificate_verifier::RootCertStore::empty(); + srv_ca_pool + .add(srv_cert.certificate[0].to_owned()) + .map_err(|_err| Error::Other("add srv_cert error".to_owned()))?; + + let cert = Certificate::generate_self_signed(vec!["localhost".to_owned()])?; + let mut ca_pool = crate::certificate_verifier::RootCertStore::empty(); + ca_pool + .add(cert.certificate[0].to_owned()) + .map_err(|_err| Error::Other("add cert error".to_owned()))?; + + let tests = vec![ + ( + "NoClientCert", + Config { + roots_cas: srv_ca_pool.clone(), + server_name: server_name.clone(), + ..Default::default() + }, + Config { + certificates: vec![srv_cert.clone()], + client_auth: ClientAuthType::NoClientCert, + ..Default::default() + }, + false, + ), + ( + "NoClientCert_cert", + Config { + roots_cas: srv_ca_pool.clone(), + server_name: server_name.clone(), + certificates: vec![cert.clone()], + ..Default::default() + }, + Config { + certificates: vec![srv_cert.clone()], + client_auth: ClientAuthType::RequireAnyClientCert, + ..Default::default() + }, + false, + ), + ( + "RequestClientCert_cert", + Config { + roots_cas: srv_ca_pool.clone(), + server_name: server_name.clone(), + certificates: vec![cert.clone()], + ..Default::default() + }, + Config { + certificates: vec![srv_cert.clone()], + client_auth: ClientAuthType::RequestClientCert, + ..Default::default() + }, + false, + ), + ( + "RequestClientCert_no_cert", + Config { + roots_cas: srv_ca_pool.clone(), + server_name: server_name.clone(), + ..Default::default() + }, + Config { + certificates: vec![srv_cert.clone()], + client_auth: ClientAuthType::RequestClientCert, + ..Default::default() + }, + false, + ), + ( + "RequireAnyClientCert", + Config { + roots_cas: srv_ca_pool.clone(), + server_name: server_name.clone(), + certificates: vec![cert.clone()], + ..Default::default() + }, + Config { + certificates: vec![srv_cert.clone()], + client_auth: ClientAuthType::RequireAnyClientCert, + ..Default::default() + }, + false, + ), + ( + "RequireAnyClientCert_error", + Config { + roots_cas: srv_ca_pool.clone(), + server_name: server_name.clone(), + ..Default::default() + }, + Config { + certificates: vec![srv_cert.clone()], + client_auth: ClientAuthType::RequireAnyClientCert, + ..Default::default() + }, + true, + ), + ( + "VerifyClientCertIfGiven_no_cert", + Config { + roots_cas: srv_ca_pool.clone(), + server_name: server_name.clone(), + ..Default::default() + }, + Config { + certificates: vec![srv_cert.clone()], + client_auth: ClientAuthType::VerifyClientCertIfGiven, + client_cas: ca_pool.clone(), + ..Default::default() + }, + false, + ), + ( + "VerifyClientCertIfGiven_cert", + Config { + roots_cas: srv_ca_pool.clone(), + server_name: server_name.clone(), + certificates: vec![cert.clone()], + ..Default::default() + }, + Config { + certificates: vec![srv_cert.clone()], + client_auth: ClientAuthType::VerifyClientCertIfGiven, + client_cas: ca_pool.clone(), + ..Default::default() + }, + false, + ), + ( + "VerifyClientCertIfGiven_error", + Config { + roots_cas: srv_ca_pool.clone(), + server_name: server_name.clone(), + certificates: vec![cert.clone()], + ..Default::default() + }, + Config { + certificates: vec![srv_cert.clone()], + client_auth: ClientAuthType::VerifyClientCertIfGiven, + ..Default::default() + }, + true, + ), + ( + "RequireAndVerifyClientCert", + Config { + roots_cas: srv_ca_pool.clone(), + server_name: server_name.clone(), + certificates: vec![cert.clone()], + ..Default::default() + }, + Config { + certificates: vec![srv_cert.clone()], + client_auth: ClientAuthType::RequireAndVerifyClientCert, + client_cas: ca_pool.clone(), + ..Default::default() + }, + false, + ), + ]; + + for (name, client_cfg, server_cfg, want_err) in tests { + let (client_res_tx, mut client_res_rx) = mpsc::channel(1); + let (ca, cb) = pipe(); + let client_cfg_clone = client_cfg.clone(); + tokio::spawn(async move { + let result = DTLSConn::new(Arc::new(ca), client_cfg_clone, true, None).await; + let _ = client_res_tx.send(result).await; + }); + + let result = DTLSConn::new(Arc::new(cb), server_cfg.clone(), false, None).await; + let client_result = client_res_rx.recv().await; + + if want_err { + if result.is_err() { + continue; + } + panic!("{name} Error expected"); + } + + assert!( + result.is_ok(), + "{} Server failed({:?})", + name, + result.err().unwrap() + ); + assert!(client_result.is_some(), "{name}, expected client conn"); + + let res = client_result.unwrap(); + assert!( + res.is_ok(), + "{} Client failed({:?})", + name, + res.err().unwrap() + ); + + let server = result.unwrap(); + let client = res.unwrap(); + + let actual_client_cert = &server.connection_state().await.peer_certificates; + if server_cfg.client_auth == ClientAuthType::RequireAnyClientCert + || server_cfg.client_auth == ClientAuthType::RequireAndVerifyClientCert + { + assert!( + !actual_client_cert.is_empty(), + "{name} Client did not provide a certificate", + ); + //if actual_client_cert.len() != len(tt.clientCfg.Certificates[0].Certificate) || !bytes.Equal(tt.clientCfg.Certificates[0].Certificate[0], actual_client_cert[0]) { + assert_eq!( + actual_client_cert[0], + client_cfg.certificates[0].certificate[0].as_ref(), + "{name} Client certificate was not communicated correctly", + ); + } + + if server_cfg.client_auth == ClientAuthType::NoClientCert { + assert!( + actual_client_cert.is_empty(), + "{name} Client certificate wasn't expected", + ); + } + + let actual_server_cert = &client.connection_state().await.peer_certificates; + assert!( + !actual_server_cert.is_empty(), + "{name} Server did not provide a certificate", + ); + + /*if len(actual_server_cert) != len(tt.serverCfg.Certificates[0].Certificate) + || !bytes.Equal( + tt.serverCfg.Certificates[0].Certificate[0], + actual_server_cert[0], + )*/ + assert_eq!( + actual_server_cert[0].len(), + server_cfg.certificates[0].certificate[0].as_ref().len(), + "{name} Server certificate was not communicated correctly", + ); + assert_eq!( + actual_server_cert[0], + server_cfg.certificates[0].certificate[0].as_ref(), + "{name} Server certificate was not communicated correctly", + ); + } + + Ok(()) +} + +#[tokio::test] +async fn test_extended_master_secret() -> Result<()> { + /*env_logger::Builder::new() + .format(|buf, record| { + writeln!( + buf, + "{}:{} [{}] {} - {}", + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.level(), + chrono::Local::now().format("%H:%M:%S.%6f"), + record.args() + ) + }) + .filter(None, LevelFilter::Trace) + .init();*/ + + let tests = vec![ + ( + "Request_Request_ExtendedMasterSecret", + Config { + extended_master_secret: ExtendedMasterSecretType::Request, + ..Default::default() + }, + Config { + extended_master_secret: ExtendedMasterSecretType::Request, + ..Default::default() + }, + None, + None, + ), + ( + "Request_Require_ExtendedMasterSecret", + Config { + extended_master_secret: ExtendedMasterSecretType::Request, + ..Default::default() + }, + Config { + extended_master_secret: ExtendedMasterSecretType::Require, + ..Default::default() + }, + None, + None, + ), + ( + "Request_Disable_ExtendedMasterSecret", + Config { + extended_master_secret: ExtendedMasterSecretType::Request, + ..Default::default() + }, + Config { + extended_master_secret: ExtendedMasterSecretType::Disable, + ..Default::default() + }, + None, + None, + ), + ( + "Require_Request_ExtendedMasterSecret", + Config { + extended_master_secret: ExtendedMasterSecretType::Require, + ..Default::default() + }, + Config { + extended_master_secret: ExtendedMasterSecretType::Request, + ..Default::default() + }, + None, + None, + ), + ( + "Require_Require_ExtendedMasterSecret", + Config { + extended_master_secret: ExtendedMasterSecretType::Require, + ..Default::default() + }, + Config { + extended_master_secret: ExtendedMasterSecretType::Require, + ..Default::default() + }, + None, + None, + ), + ( + "Require_Disable_ExtendedMasterSecret", + Config { + extended_master_secret: ExtendedMasterSecretType::Require, + ..Default::default() + }, + Config { + extended_master_secret: ExtendedMasterSecretType::Disable, + ..Default::default() + }, + Some(Error::ErrClientRequiredButNoServerEms), + Some(Error::ErrAlertFatalOrClose), + ), + ( + "Disable_Request_ExtendedMasterSecret", + Config { + extended_master_secret: ExtendedMasterSecretType::Disable, + ..Default::default() + }, + Config { + extended_master_secret: ExtendedMasterSecretType::Request, + ..Default::default() + }, + None, + None, + ), + ( + "Disable_Require_ExtendedMasterSecret", + Config { + extended_master_secret: ExtendedMasterSecretType::Disable, + ..Default::default() + }, + Config { + extended_master_secret: ExtendedMasterSecretType::Require, + ..Default::default() + }, + Some(Error::ErrAlertFatalOrClose), + Some(Error::ErrServerRequiredButNoClientEms), + ), + ( + "Disable_Disable_ExtendedMasterSecret", + Config { + extended_master_secret: ExtendedMasterSecretType::Disable, + ..Default::default() + }, + Config { + extended_master_secret: ExtendedMasterSecretType::Disable, + ..Default::default() + }, + None, + None, + ), + ]; + + for (name, client_cfg, server_cfg, expected_client_err, expected_server_err) in tests { + let (client_res_tx, mut client_res_rx) = mpsc::channel(1); + let (ca, cb) = pipe(); + let client_cfg_clone = client_cfg.clone(); + tokio::spawn(async move { + let result = create_test_client(Arc::new(ca), client_cfg_clone, true).await; + let _ = client_res_tx.send(result).await; + }); + + let result = create_test_server(Arc::new(cb), server_cfg.clone(), true).await; + let client_result = client_res_rx.recv().await; + assert!(client_result.is_some(), "{name}, expected client conn"); + let res = client_result.unwrap(); + + if let Some(client_err) = expected_client_err { + if let Err(err) = res { + assert_eq!( + err.to_string(), + client_err.to_string(), + "Client error expected: \"{client_err}\" but got \"{err}\"", + ); + } else { + panic!("{name} expected err, but got ok"); + } + } else { + assert!(res.is_ok(), "{name} expected ok, but got err"); + } + + if let Some(server_err) = expected_server_err { + if let Err(err) = result { + assert_eq!( + err.to_string(), + server_err.to_string(), + "Server error expected: \"{server_err}\" but got \"{err}\"", + ); + } else { + panic!("{name} expected err, but got ok"); + } + } else { + assert!(result.is_ok(), "{name} expected ok, but got err"); + } + } + + Ok(()) +} + +fn fn_not_expected_chain(_cert: &[Vec], chain: &[CertificateDer<'static>]) -> Result<()> { + if !chain.is_empty() { + return Err(Error::Other(ERR_NOT_EXPECTED_CHAIN.to_owned())); + } + Ok(()) +} + +fn fn_expected_chain(_cert: &[Vec], chain: &[CertificateDer<'static>]) -> Result<()> { + if chain.is_empty() { + return Err(Error::Other(ERR_EXPECTED_CHAIN.to_owned())); + } + Ok(()) +} + +fn fn_wrong_cert(_cert: &[Vec], _chain: &[CertificateDer<'static>]) -> Result<()> { + Err(Error::Other(ERR_WRONG_CERT.to_owned())) +} + +#[tokio::test] +async fn test_server_certificate() -> Result<()> { + /*env_logger::Builder::new() + .format(|buf, record| { + writeln!( + buf, + "{}:{} [{}] {} - {}", + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.level(), + chrono::Local::now().format("%H:%M:%S.%6f"), + record.args() + ) + }) + .filter(None, LevelFilter::Trace) + .init();*/ + + let server_name = "localhost".to_owned(); + let cert = Certificate::generate_self_signed(vec![server_name.clone()])?; + let mut ca_pool = crate::certificate_verifier::RootCertStore::empty(); + ca_pool + .add(cert.certificate[0].clone()) + .map_err(|_err| Error::Other("add cert error".to_owned()))?; + + let tests = vec![ + ( + "no_ca", + Config { + server_name: server_name.clone(), + ..Default::default() + }, + Config { + certificates: vec![cert.clone()], + client_auth: ClientAuthType::NoClientCert, + ..Default::default() + }, + true, + ), + ( + "good_ca", + Config { + roots_cas: ca_pool.clone(), + server_name: server_name.clone(), + ..Default::default() + }, + Config { + certificates: vec![cert.clone()], + client_auth: ClientAuthType::NoClientCert, + ..Default::default() + }, + false, + ), + ( + "no_ca_skip_verify", + Config { + insecure_skip_verify: true, + server_name: server_name.clone(), + ..Default::default() + }, + Config { + certificates: vec![cert.clone()], + client_auth: ClientAuthType::NoClientCert, + ..Default::default() + }, + false, + ), + ( + "good_ca_skip_verify_custom_verify_peer", + Config { + roots_cas: ca_pool.clone(), + server_name: server_name.clone(), + certificates: vec![cert.clone()], + ..Default::default() + }, + Config { + certificates: vec![cert.clone()], + client_auth: ClientAuthType::RequireAnyClientCert, + verify_peer_certificate: Some(Arc::new(fn_not_expected_chain)), + ..Default::default() + }, + false, + ), + ( + "good_ca_verify_custom_verify_peer", + Config { + roots_cas: ca_pool.clone(), + server_name: server_name.clone(), + certificates: vec![cert.clone()], + ..Default::default() + }, + Config { + certificates: vec![cert.clone()], + client_auth: ClientAuthType::RequireAndVerifyClientCert, + client_cas: ca_pool.clone(), + verify_peer_certificate: Some(Arc::new(fn_expected_chain)), + ..Default::default() + }, + false, + ), + ( + "good_ca_custom_verify_peer", + Config { + roots_cas: ca_pool.clone(), + server_name: server_name.clone(), + verify_peer_certificate: Some(Arc::new(fn_wrong_cert)), + ..Default::default() + }, + Config { + certificates: vec![cert.clone()], + client_auth: ClientAuthType::NoClientCert, + ..Default::default() + }, + true, + ), + ( + "server_name", + Config { + roots_cas: ca_pool.clone(), + server_name: server_name.clone(), + ..Default::default() + }, + Config { + certificates: vec![cert.clone()], + client_auth: ClientAuthType::NoClientCert, + ..Default::default() + }, + false, + ), + ( + "server_name_error", + Config { + roots_cas: ca_pool.clone(), + server_name: "barfoo".to_owned(), + ..Default::default() + }, + Config { + certificates: vec![cert.clone()], + client_auth: ClientAuthType::NoClientCert, + ..Default::default() + }, + true, + ), + ]; + + for (name, client_cfg, server_cfg, want_err) in tests { + let (res_tx, mut res_rx) = mpsc::channel(1); + let (ca, cb) = pipe(); + + tokio::spawn(async move { + let result = DTLSConn::new(Arc::new(cb), server_cfg, false, None).await; + let _ = res_tx.send(result).await; + }); + + let cli_result = DTLSConn::new(Arc::new(ca), client_cfg, true, None).await; + + if !want_err && cli_result.is_err() { + panic!("{}: Client failed({})", name, cli_result.err().unwrap()); + } + if want_err && cli_result.is_ok() { + panic!("{name}: Error expected"); + } + + let _ = res_rx.recv().await; + } + Ok(()) +} + +#[tokio::test] +async fn test_cipher_suite_configuration() -> Result<()> { + /*env_logger::Builder::new() + .format(|buf, record| { + writeln!( + buf, + "{}:{} [{}] {} - {}", + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.level(), + chrono::Local::now().format("%H:%M:%S.%6f"), + record.args() + ) + }) + .filter(None, LevelFilter::Trace) + .init();*/ + + let tests = vec![ + ( + "No CipherSuites specified", + vec![], + vec![], + None, + None, + None, + ), + ( + "Invalid CipherSuite", + vec![CipherSuiteId::Unsupported], + vec![CipherSuiteId::Unsupported], + Some(Error::ErrInvalidCipherSuite), + Some(Error::ErrInvalidCipherSuite), + None, + ), + ( + "Valid CipherSuites specified", + vec![CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256], + vec![CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256], + None, + None, + Some(CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256), + ), + ( + "CipherSuites mismatch", + vec![CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256], + vec![CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_256_Cbc_Sha], + Some(Error::ErrAlertFatalOrClose), + Some(Error::ErrCipherSuiteNoIntersection), + None, + ), + ( + "Valid CipherSuites CCM specified", + vec![CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm], + vec![CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm], + None, + None, + Some(CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm), + ), + ( + "Valid CipherSuites CCM-8 specified", + vec![CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm_8], + vec![CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm_8], + None, + None, + Some(CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm_8), + ), + ( + "Server supports subset of client suites", + vec![ + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256, + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_256_Cbc_Sha, + ], + vec![CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_256_Cbc_Sha], + None, + None, + Some(CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_256_Cbc_Sha), + ), + ]; + + for ( + name, + client_cipher_suites, + server_cipher_suites, + want_client_error, + want_server_error, + want_selected_cipher_suite, + ) in tests + { + let (client_res_tx, mut client_res_rx) = mpsc::channel(1); + let (ca, cb) = pipe(); + tokio::spawn(async move { + let conf = Config { + cipher_suites: client_cipher_suites, + ..Default::default() + }; + + let result = create_test_client(Arc::new(ca), conf, true).await; + let _ = client_res_tx.send(result).await; + }); + + let config = Config { + cipher_suites: server_cipher_suites, + ..Default::default() + }; + + let result = create_test_server(Arc::new(cb), config, true).await; + if let Some(expected_err) = want_server_error { + if let Err(err) = result { + assert_eq!( + err.to_string(), + expected_err.to_string(), + "{name} test_cipher_suite_configuration: Server error exp({expected_err}) failed({err})", + ); + } else { + panic!("{name} expected error, but got ok"); + } + } else { + assert!(result.is_ok(), "{name} expected ok, but got error") + } + + let client_result = client_res_rx.recv().await; + if let Some(result) = client_result { + if let Some(expected_err) = want_client_error { + if let Err(err) = result { + assert_eq!( + err.to_string(), + expected_err.to_string(), + "{name} test_cipher_suite_configuration: Client error exp({expected_err}) failed({err})", + ); + } else { + panic!("{name} expected error, but got ok"); + } + } else { + assert!(result.is_ok(), "{name} expected ok, but got error"); + let client = result.unwrap(); + if let Some(want_cs) = want_selected_cipher_suite { + let cipher_suite = client.state.cipher_suite.lock().await; + assert!(cipher_suite.is_some(), "{name} expected some, but got none"); + if let Some(cs) = &*cipher_suite { + assert_eq!(cs.id(), want_cs, + "test_cipher_suite_configuration: Server Selected Bad Cipher Suite '{}': expected({}) actual({})", + name, want_cs, cs.id()); + } + } + } + } else { + panic!("{name} expected Some, but got None"); + } + } + + Ok(()) +} + +fn psk_callback(_b: &[u8]) -> Pin>> + Send>> { + Box::pin(async move { Ok(vec![0x00, 0x01, 0x02]) }) +} + +#[tokio::test] +async fn test_psk_configuration() -> Result<()> { + /*env_logger::Builder::new() + .format(|buf, record| { + writeln!( + buf, + "{}:{} [{}] {} - {}", + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.level(), + chrono::Local::now().format("%H:%M:%S.%6f"), + record.args() + ) + }) + .filter(None, LevelFilter::Trace) + .init();*/ + + let tests = vec![ + ( + "PSK specified", + false, + false, + true, //Some(psk_callback), + true, //Some(psk_callback), + Some(vec![0x00]), + Some(vec![0x00]), + Some(Error::ErrNoAvailableCipherSuites), + Some(Error::ErrNoAvailableCipherSuites), + ), + ( + "PSK and certificate specified", + true, + true, + true, //Some(psk_callback), + true, //Some(psk_callback), + Some(vec![0x00]), + Some(vec![0x00]), + Some(Error::ErrPskAndCertificate), + Some(Error::ErrPskAndCertificate), + ), + ( + "PSK and no identity specified", + false, + false, + true, //Some(psk_callback), + true, //Some(psk_callback), + None, + None, + Some(Error::ErrPskAndIdentityMustBeSetForClient), + Some(Error::ErrNoAvailableCipherSuites), + ), + ( + "No PSK and identity specified", + false, + false, + false, + false, + Some(vec![0x00]), + Some(vec![0x00]), + Some(Error::ErrIdentityNoPsk), + Some(Error::ErrServerMustHaveCertificate), + ), + ]; + + for ( + name, + client_has_certificate, + server_has_certificate, + client_psk, + server_psk, + client_psk_identity, + server_psk_identity, + want_client_error, + want_server_error, + ) in tests + { + let (client_res_tx, mut client_res_rx) = mpsc::channel(1); + let (ca, cb) = pipe(); + tokio::spawn(async move { + let conf = Config { + psk: if client_psk { + Some(Arc::new(psk_callback)) + } else { + None + }, + psk_identity_hint: client_psk_identity, + ..Default::default() + }; + + let result = create_test_client(Arc::new(ca), conf, client_has_certificate).await; + let _ = client_res_tx.send(result).await; + }); + + let config = Config { + psk: if server_psk { + Some(Arc::new(psk_callback)) + } else { + None + }, + psk_identity_hint: server_psk_identity, + ..Default::default() + }; + + let result = create_test_server(Arc::new(cb), config, server_has_certificate).await; + if let Some(expected_err) = want_server_error { + if let Err(err) = result { + assert_eq!( + err.to_string(), + expected_err.to_string(), + "{name} test_psk_configuration: Server error exp({expected_err}) failed({err})", + ); + } else { + panic!("{name} expected error, but got ok"); + } + } else { + assert!(result.is_ok(), "{name} expected ok, but got error") + } + + let client_result = client_res_rx.recv().await; + if let Some(result) = client_result { + if let Some(expected_err) = want_client_error { + if let Err(err) = result { + assert_eq!( + err.to_string(), + expected_err.to_string(), + "{name} test_psk_configuration: Client error exp({expected_err}) failed({err})", + ); + } else { + panic!("{name} expected error, but got ok"); + } + } else { + assert!(result.is_ok(), "{name} expected ok, but got error"); + } + } else { + panic!("{name} expected Some, but got None"); + } + } + + Ok(()) +} + +#[tokio::test] +async fn test_server_timeout() -> Result<()> { + /*env_logger::Builder::new() + .format(|buf, record| { + writeln!( + buf, + "{}:{} [{}] {} - {}", + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.level(), + chrono::Local::now().format("%H:%M:%S.%6f"), + record.args() + ) + }) + .filter(None, LevelFilter::Trace) + .init();*/ + + let mut cookie = vec![0u8; 20]; + rand::rng().fill(cookie.as_mut_slice()); + + let random_bytes = [0u8; RANDOM_BYTES_LENGTH]; + let gmt_unix_time = SystemTime::UNIX_EPOCH + .checked_add(Duration::new(500, 0)) + .unwrap(); + let random = HandshakeRandom { + gmt_unix_time, + random_bytes, + }; + + let cipher_suites = vec![ + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256, //&cipherSuiteTLSEcdheEcdsaWithAes128GcmSha256{}, + CipherSuiteId::Tls_Ecdhe_Rsa_With_Aes_128_Gcm_Sha256, //&cipherSuiteTLSEcdheRsaWithAes128GcmSha256{}, + ]; + + let extensions = vec![ + Extension::SupportedSignatureAlgorithms(ExtensionSupportedSignatureAlgorithms { + signature_hash_algorithms: vec![ + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha256, + signature: SignatureAlgorithm::Ecdsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha384, + signature: SignatureAlgorithm::Ecdsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha512, + signature: SignatureAlgorithm::Ecdsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha256, + signature: SignatureAlgorithm::Rsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha384, + signature: SignatureAlgorithm::Rsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha512, + signature: SignatureAlgorithm::Rsa, + }, + ], + }), + Extension::SupportedEllipticCurves(ExtensionSupportedEllipticCurves { + elliptic_curves: vec![NamedCurve::X25519, NamedCurve::P256, NamedCurve::P384], + }), + Extension::SupportedPointFormats(ExtensionSupportedPointFormats { + point_formats: vec![ELLIPTIC_CURVE_POINT_FORMAT_UNCOMPRESSED], + }), + ]; + + let record = RecordLayer::new( + PROTOCOL_VERSION1_2, + 0, + Content::Handshake(Handshake::new(HandshakeMessage::ClientHello( + HandshakeMessageClientHello { + version: PROTOCOL_VERSION1_2, + cookie, + random, + cipher_suites, + compression_methods: default_compression_methods(), + extensions, + }, + ))), + ); + + let mut packet = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(packet.as_mut()); + record.marshal(&mut writer)?; + } + + use util::Conn; + let (ca, cb) = pipe(); + + // Client reader + let (ca_read_chan_tx, mut ca_read_chan_rx) = mpsc::channel(1000); + + let ca_rx = Arc::new(ca); + let ca_tx = Arc::clone(&ca_rx); + + tokio::spawn(async move { + let mut data = vec![0; 8192]; + loop { + if let Ok(n) = ca_rx.recv(&mut data).await { + let result = ca_read_chan_tx.send(data[..n].to_vec()).await; + if result.is_ok() { + return; + } + } else { + return; + } + } + }); + + // Start sending ClientHello packets until server responds with first packet + tokio::spawn(async move { + loop { + let timer = tokio::time::sleep(Duration::from_millis(10)); + tokio::pin!(timer); + + tokio::select! { + _ = timer.as_mut() => { + let result = ca_tx.send(&packet).await; + if result.is_err() { + return; + } + } + _ = ca_read_chan_rx.recv() => return, + } + } + }); + + let config = Config { + cipher_suites: vec![CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256], + flight_interval: Duration::from_millis(100), + ..Default::default() + }; + + let result = tokio::time::timeout( + Duration::from_millis(50), + create_test_server(Arc::new(cb), config, true), + ) + .await; + assert!(result.is_err(), "Expected Error but got Ok"); + + // Wait a little longer to ensure no additional messages have been sent by the server + //tokio::time::sleep(Duration::from_millis(300)).await; + + /*tokio::select! { + case msg := <-caReadChan: + t.Fatalf("Expected no additional messages from server, got: %+v", msg) + default: + }*/ + + Ok(()) +} + +#[tokio::test] +async fn test_protocol_version_validation() -> Result<()> { + /*env_logger::Builder::new() + .format(|buf, record| { + writeln!( + buf, + "{}:{} [{}] {} - {}", + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.level(), + chrono::Local::now().format("%H:%M:%S.%6f"), + record.args() + ) + }) + .filter(None, LevelFilter::Trace) + .init();*/ + + let mut cookie = vec![0; 20]; + rand::rng().fill(cookie.as_mut_slice()); + + let random_bytes = [0u8; RANDOM_BYTES_LENGTH]; + let gmt_unix_time = SystemTime::UNIX_EPOCH + .checked_add(Duration::new(500, 0)) + .unwrap(); + let random = HandshakeRandom { + gmt_unix_time, + random_bytes, + }; + + let local_keypair = NamedCurve::X25519.generate_keypair()?; + + //|"Server"| + { + let server_cases = vec![ + ( + "ClientHelloVersion", + vec![RecordLayer::new( + PROTOCOL_VERSION1_2, + 0, + Content::Handshake(Handshake::new(HandshakeMessage::ClientHello( + HandshakeMessageClientHello { + version: ProtocolVersion { + major: 0xfe, + minor: 0xff, + }, // try to downgrade + cookie: cookie.clone(), + random: random.clone(), + cipher_suites: vec![ + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256, + ], + compression_methods: default_compression_methods(), + extensions: vec![], + }, + ))), + )], + ), + ( + "SecondsClientHelloVersion", + vec![ + RecordLayer::new( + PROTOCOL_VERSION1_2, + 0, + Content::Handshake(Handshake::new(HandshakeMessage::ClientHello( + HandshakeMessageClientHello { + version: PROTOCOL_VERSION1_2, + cookie: cookie.clone(), + random: random.clone(), + cipher_suites: vec![ + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256, + ], + compression_methods: default_compression_methods(), + extensions: vec![], + }, + ))), + ), + { + let mut handshake = Handshake::new(HandshakeMessage::ClientHello( + HandshakeMessageClientHello { + version: ProtocolVersion { + major: 0xfe, + minor: 0xff, + }, // try to downgrade + cookie: cookie.clone(), + random: random.clone(), + cipher_suites: vec![ + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256, + ], + compression_methods: default_compression_methods(), + extensions: vec![], + }, + )); + handshake.handshake_header.message_sequence = 1; + let mut record_layer = + RecordLayer::new(PROTOCOL_VERSION1_2, 0, Content::Handshake(handshake)); + record_layer.record_layer_header.sequence_number = 1; + + record_layer + }, + ], + ), + ]; + + use util::Conn; + for (name, records) in server_cases { + let (ca, cb) = pipe(); + + tokio::spawn(async move { + let config = Config { + cipher_suites: vec![CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256], + flight_interval: Duration::from_millis(100), + ..Default::default() + }; + let timeout_result = tokio::time::timeout( + Duration::from_millis(1000), + create_test_server(Arc::new(cb), config, true), + ) + .await; + match timeout_result { + Ok(result) => { + if let Err(err) = result { + assert_eq!( + err.to_string(), + Error::ErrUnsupportedProtocolVersion.to_string(), + "{} Client error exp({}) failed({})", + name, + Error::ErrUnsupportedProtocolVersion, + err, + ); + } else { + panic!("{name} expected error, but got ok"); + } + } + Err(err) => { + panic!("server timeout {err}"); + } + }; + }); + + tokio::time::sleep(Duration::from_millis(50)).await; + + let mut resp = vec![0; 1024]; + let mut n = 0; + for record in records { + let mut packet = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(packet.as_mut()); + record.marshal(&mut writer)?; + } + + let _ = ca.send(&packet).await; + n = ca.recv(&mut resp).await?; + } + + let mut reader = BufReader::new(&resp[..n]); + let h = RecordLayerHeader::unmarshal(&mut reader)?; + assert_eq!( + h.content_type, + ContentType::Alert, + "Peer must return alert to unsupported protocol version" + ); + } + } + + //"Client" + { + let client_cases = vec![( + "ServerHelloVersion", + vec![ + RecordLayer::new( + PROTOCOL_VERSION1_2, + 0, + Content::Handshake(Handshake::new(HandshakeMessage::HelloVerifyRequest( + HandshakeMessageHelloVerifyRequest { + version: PROTOCOL_VERSION1_2, + cookie: cookie.clone(), + }, + ))), + ), + { + let mut handshake = Handshake::new(HandshakeMessage::ServerHello( + HandshakeMessageServerHello { + version: ProtocolVersion { + major: 0xfe, + minor: 0xff, + }, // try to downgrade + random: random.clone(), + cipher_suite: CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256, + compression_method: default_compression_methods().ids[0], + extensions: vec![], + }, + )); + handshake.handshake_header.message_sequence = 1; + let mut record = + RecordLayer::new(PROTOCOL_VERSION1_2, 0, Content::Handshake(handshake)); + record.record_layer_header.sequence_number = 1; + record + }, + { + let mut handshake = Handshake::new(HandshakeMessage::Certificate( + HandshakeMessageCertificate { + certificate: vec![], + }, + )); + handshake.handshake_header.message_sequence = 2; + let mut record = + RecordLayer::new(PROTOCOL_VERSION1_2, 0, Content::Handshake(handshake)); + record.record_layer_header.sequence_number = 2; + record + }, + { + let mut handshake = Handshake::new(HandshakeMessage::ServerKeyExchange( + HandshakeMessageServerKeyExchange { + identity_hint: vec![], + elliptic_curve_type: EllipticCurveType::NamedCurve, + named_curve: NamedCurve::X25519, + public_key: local_keypair.public_key.clone(), + algorithm: SignatureHashAlgorithm { + hash: HashAlgorithm::Sha256, + signature: SignatureAlgorithm::Ecdsa, + }, + signature: vec![0; 64], + }, + )); + handshake.handshake_header.message_sequence = 3; + let mut record = + RecordLayer::new(PROTOCOL_VERSION1_2, 0, Content::Handshake(handshake)); + record.record_layer_header.sequence_number = 3; + record + }, + { + let mut handshake = Handshake::new(HandshakeMessage::ServerHelloDone( + HandshakeMessageServerHelloDone {}, + )); + handshake.handshake_header.message_sequence = 4; + let mut record = + RecordLayer::new(PROTOCOL_VERSION1_2, 0, Content::Handshake(handshake)); + record.record_layer_header.sequence_number = 4; + record + }, + ], + )]; + + use util::Conn; + for (name, records) in client_cases { + let (ca, cb) = pipe(); + + tokio::spawn(async move { + let config = Config { + cipher_suites: vec![CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256], + flight_interval: Duration::from_millis(100), + ..Default::default() + }; + let timeout_result = tokio::time::timeout( + Duration::from_millis(1000), + create_test_client(Arc::new(cb), config, true), + ) + .await; + match timeout_result { + Ok(result) => { + if let Err(err) = result { + assert_eq!( + err.to_string(), + Error::ErrUnsupportedProtocolVersion.to_string(), + "{} Server error exp({}) failed({})", + name, + Error::ErrUnsupportedProtocolVersion, + err, + ); + } else { + panic!("{name} expected error, but got ok"); + } + } + Err(err) => { + panic!("server timeout {err}"); + } + }; + }); + + tokio::time::sleep(Duration::from_millis(50)).await; + + let mut resp = vec![0; 1024]; + for record in records { + let _ = ca.recv(&mut resp).await?; + + let mut packet = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(packet.as_mut()); + record.marshal(&mut writer)?; + } + let _ = ca.send(&packet).await; + } + + let n = ca.recv(&mut resp).await?; + + let mut reader = BufReader::new(&resp[..n]); + let h = RecordLayerHeader::unmarshal(&mut reader)?; + + assert_eq!( + h.content_type, + ContentType::Alert, + "Peer must return alert to unsupported protocol version" + ); + } + } + + Ok(()) +} + +#[tokio::test] +async fn test_multiple_hello_verify_request() -> Result<()> { + /*env_logger::Builder::new() + .format(|buf, record| { + writeln!( + buf, + "{}:{} [{}] {} - {}", + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.level(), + chrono::Local::now().format("%H:%M:%S.%6f"), + record.args() + ) + }) + .filter(None, LevelFilter::Trace) + .init();*/ + + let mut cookies = vec![ + // first clientHello contains an empty cookie + vec![], + ]; + + let mut packets = vec![]; + for i in 0..2 { + let mut cookie = vec![0; 20]; + rand::rng().fill(cookie.as_mut_slice()); + cookies.push(cookie.clone()); + + let mut handshake = Handshake::new(HandshakeMessage::HelloVerifyRequest( + HandshakeMessageHelloVerifyRequest { + version: PROTOCOL_VERSION1_2, + cookie, + }, + )); + handshake.handshake_header.message_sequence = i as u16; + + let mut record = RecordLayer::new(PROTOCOL_VERSION1_2, 0, Content::Handshake(handshake)); + record.record_layer_header.sequence_number = i as u64; + + let mut packet = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(packet.as_mut()); + record.marshal(&mut writer)?; + } + + packets.push(packet); + } + + let (ca, cb) = pipe(); + + tokio::spawn(async move { + let conf = Config::default(); + let _ = tokio::time::timeout( + Duration::from_millis(100), + create_test_client(Arc::new(ca), conf, true), + ) + .await; + }); + + for i in 0..cookies.len() { + let cookie = &cookies[i]; + trace!("cookie {i}: {cookie:?}"); + + // read client hello + let mut resp = vec![0; 1024]; + let n = cb.recv(&mut resp).await?; + let mut reader = BufReader::new(&resp[..n]); + let record = RecordLayer::unmarshal(&mut reader)?; + match record.content { + Content::Handshake(h) => match h.handshake_message { + HandshakeMessage::ClientHello(client_hello) => { + assert_eq!( + &client_hello.cookie, cookie, + "Wrong cookie {}, expected: {:?}, got: {:?}", + i, &client_hello.cookie, cookie + ); + } + _ => panic!("unexpected handshake message"), + }, + _ => panic!("unexpected content"), + }; + + if packets.len() <= i { + break; + } + // write hello verify request + cb.send(&packets[i]).await?; + } + + Ok(()) +} + +async fn send_client_hello( + cookie: Vec, + ca: &Arc, + sequence_number: u64, + send_renegotiation_info: bool, +) -> Result<()> { + let mut extensions = vec![]; + if send_renegotiation_info { + extensions.push(Extension::RenegotiationInfo(ExtensionRenegotiationInfo { + renegotiated_connection: 0, + })); + } + + let mut h = Handshake::new(HandshakeMessage::ClientHello(HandshakeMessageClientHello { + version: PROTOCOL_VERSION1_2, + random: HandshakeRandom::default(), + cookie, + + cipher_suites: vec![CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256], + compression_methods: default_compression_methods(), + extensions, + })); + h.handshake_header.message_sequence = sequence_number as u16; + + let mut record = RecordLayer::new(PROTOCOL_VERSION1_2, 0, Content::Handshake(h)); + record.record_layer_header.sequence_number = sequence_number; + + let mut packet = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(packet.as_mut()); + record.marshal(&mut writer)?; + } + + ca.send(&packet).await?; + + Ok(()) +} + +// Assert that a DTLS Server always responds with RenegotiationInfo if +// a ClientHello contained that extension or not +#[cfg(not(target_os = "windows"))] // this times out in CI on windows. +#[tokio::test] +async fn test_renegotiation_info() -> Result<()> { + let mut resp = vec![0u8; 1024]; + + let tests = vec![ + ("Include RenegotiationInfo", true), + ("No RenegotiationInfo", false), + ]; + + for (name, send_renegotiation_info) in tests { + let (ca, cb) = pipe(); + + tokio::spawn(async move { + let conf = Config::default(); + let _ = tokio::time::timeout( + Duration::from_millis(100), + create_test_server(Arc::new(cb), conf, true), + ) + .await; + }); + + tokio::time::sleep(Duration::from_millis(5)).await; + + let ca: Arc = Arc::new(ca); + send_client_hello(vec![], &ca, 0, send_renegotiation_info).await?; + + let n = ca.recv(&mut resp).await?; + let mut reader = BufReader::new(&resp[..n]); + let record = RecordLayer::unmarshal(&mut reader)?; + + let hello_verify_request = match record.content { + Content::Handshake(h) => match h.handshake_message { + HandshakeMessage::HelloVerifyRequest(hvr) => hvr, + _ => { + panic!("unexpected handshake message"); + } + }, + _ => { + panic!("unexpected content"); + } + }; + + send_client_hello( + hello_verify_request.cookie.clone(), + &ca, + 1, + send_renegotiation_info, + ) + .await?; + let n = ca.recv(&mut resp).await?; + let messages = unpack_datagram(&resp[..n])?; + + let mut reader = BufReader::new(&messages[0][..]); + let record = RecordLayer::unmarshal(&mut reader)?; + + let server_hello = match record.content { + Content::Handshake(h) => match h.handshake_message { + HandshakeMessage::ServerHello(sh) => sh, + _ => { + panic!("unexpected handshake message"); + } + }, + _ => { + panic!("unexpected content"); + } + }; + + let got_negotiation_info = server_hello + .extensions + .iter() + .any(|v| matches!(v, Extension::RenegotiationInfo(_))); + + assert!( + got_negotiation_info, + "{name}: Received ServerHello without RenegotiationInfo" + ); + + ca.close().await?; + } + + Ok(()) +} diff --git a/vendor/dtls/src/conn/mod.rs b/vendor/dtls/src/conn/mod.rs new file mode 100644 index 00000000..5a016c7d --- /dev/null +++ b/vendor/dtls/src/conn/mod.rs @@ -0,0 +1,1212 @@ +#[cfg(test)] +mod conn_test; + +use std::io::{BufReader, BufWriter}; +use std::marker::{Send, Sync}; +use std::net::SocketAddr; +use std::sync::atomic::Ordering; +use std::sync::Arc; + +use async_trait::async_trait; +use log::*; +use portable_atomic::{AtomicBool, AtomicU16}; +use tokio::sync::{mpsc, oneshot, Mutex}; +use tokio::time::Duration; +use util::replay_detector::*; +use util::Conn; + +use crate::alert::*; +use crate::application_data::*; +use crate::cipher_suite::*; +use crate::config::*; +use crate::content::*; +use crate::curve::named_curve::NamedCurve; +use crate::error::*; +use crate::extension::extension_use_srtp::*; +use crate::flight::flight0::*; +use crate::flight::flight1::*; +use crate::flight::flight5::*; +use crate::flight::flight6::*; +use crate::flight::*; +use crate::fragment_buffer::*; +use crate::handshake::handshake_cache::*; +use crate::handshake::handshake_header::HandshakeHeader; +use crate::handshake::*; +use crate::handshaker::*; +use crate::record_layer::record_layer_header::*; +use crate::record_layer::*; +use crate::signature_hash_algorithm::parse_signature_schemes; +use crate::state::*; + +pub(crate) const INITIAL_TICKER_INTERVAL: Duration = Duration::from_secs(1); +pub(crate) const COOKIE_LENGTH: usize = 20; +pub(crate) const DEFAULT_NAMED_CURVE: NamedCurve = NamedCurve::X25519; +pub(crate) const INBOUND_BUFFER_SIZE: usize = 8192; +// Default replay protection window is specified by RFC 6347 Section 4.1.2.6 +pub(crate) const DEFAULT_REPLAY_PROTECTION_WINDOW: usize = 64; + +pub static INVALID_KEYING_LABELS: &[&str] = &[ + "client finished", + "server finished", + "master secret", + "key expansion", +]; + +type PacketSendRequest = (Vec, Option>>); + +struct ConnReaderContext { + is_client: bool, + replay_protection_window: usize, + replay_detector: Vec>, + decrypted_tx: mpsc::Sender>>, + encrypted_packets: Vec>, + fragment_buffer: FragmentBuffer, + cache: HandshakeCache, + cipher_suite: Arc>>>, + remote_epoch: Arc, + // use additional oneshot sender to mimic rendezvous channel behavior + handshake_tx: mpsc::Sender<(oneshot::Sender<()>, mpsc::Sender<()>)>, + handshake_done_rx: mpsc::Receiver<()>, + packet_tx: Arc>, +} + +// Conn represents a DTLS connection +pub struct DTLSConn { + conn: Arc, + pub(crate) cache: HandshakeCache, // caching of handshake messages for verifyData generation + decrypted_rx: Mutex>>>, // Decrypted Application Data or error, pull by calling `Read` + pub(crate) state: State, // Internal state + + handshake_completed_successfully: Arc, + connection_closed_by_user: bool, + // closeLock sync.Mutex + closed: AtomicBool, // *closer.Closer + //handshakeLoopsFinished sync.WaitGroup + + //readDeadline :deadline.Deadline, + //writeDeadline :deadline.Deadline, + + //log logging.LeveledLogger + /* + reading chan struct{} + handshakeRecv chan chan struct{} + cancelHandshaker func() + cancelHandshakeReader func() + */ + pub(crate) current_flight: Box, + pub(crate) flights: Option>, + pub(crate) cfg: HandshakeConfig, + pub(crate) retransmit: bool, + // use additional oneshot sender to mimic rendezvous channel behavior + pub(crate) handshake_rx: mpsc::Receiver<(oneshot::Sender<()>, mpsc::Sender<()>)>, + + pub(crate) packet_tx: Arc>, + pub(crate) handle_queue_tx: mpsc::Sender>, + pub(crate) handshake_done_tx: Option>, + + reader_close_tx: Mutex>>, +} + +type UtilResult = std::result::Result; + +#[async_trait] +impl Conn for DTLSConn { + async fn connect(&self, _addr: SocketAddr) -> UtilResult<()> { + Err(util::Error::Other("Not applicable".to_owned())) + } + + async fn recv(&self, buf: &mut [u8]) -> UtilResult { + self.read(buf, None).await.map_err(util::Error::from_std) + } + + async fn recv_from(&self, buf: &mut [u8]) -> UtilResult<(usize, SocketAddr)> { + if let Some(raddr) = self.conn.remote_addr() { + let n = self.read(buf, None).await.map_err(util::Error::from_std)?; + Ok((n, raddr)) + } else { + Err(util::Error::Other( + "No remote address is provided by underlying Conn".to_owned(), + )) + } + } + + async fn send(&self, buf: &[u8]) -> UtilResult { + self.write(buf, None).await.map_err(util::Error::from_std) + } + + async fn send_to(&self, _buf: &[u8], _target: SocketAddr) -> UtilResult { + Err(util::Error::Other("Not applicable".to_owned())) + } + + fn local_addr(&self) -> UtilResult { + self.conn.local_addr() + } + + fn remote_addr(&self) -> Option { + self.conn.remote_addr() + } + + async fn close(&self) -> UtilResult<()> { + self.close().await.map_err(util::Error::from_std) + } + + fn as_any(&self) -> &(dyn std::any::Any + Send + Sync) { + self + } +} + +impl DTLSConn { + pub async fn new( + conn: Arc, + mut config: Config, + is_client: bool, + initial_state: Option, + ) -> Result { + validate_config(is_client, &config)?; + + let local_cipher_suites: Vec = parse_cipher_suites( + &config.cipher_suites, + config.psk.is_none(), + config.psk.is_some(), + )? + .iter() + .map(|cs| cs.id()) + .collect(); + + let sigs: Vec = config.signature_schemes.iter().map(|x| *x as u16).collect(); + let local_signature_schemes = parse_signature_schemes(&sigs, config.insecure_hashes)?; + + let retransmit_interval = if config.flight_interval != Duration::from_secs(0) { + config.flight_interval + } else { + INITIAL_TICKER_INTERVAL + }; + + /* + loggerFactory := config.LoggerFactory + if loggerFactory == nil { + loggerFactory = logging.NewDefaultLoggerFactory() + } + + logger := loggerFactory.NewLogger("dtls") + */ + let maximum_transmission_unit = if config.mtu == 0 { + DEFAULT_MTU + } else { + config.mtu + }; + + let replay_protection_window = if config.replay_protection_window == 0 { + DEFAULT_REPLAY_PROTECTION_WINDOW + } else { + config.replay_protection_window + }; + + let mut server_name = config.server_name.clone(); + + // Use host from conn address when server_name is not provided + if is_client && server_name.is_empty() { + if let Some(remote_addr) = conn.remote_addr() { + server_name = remote_addr.ip().to_string(); + } else { + warn!("conn.remote_addr is empty, please set explicitly server_name in Config! Use default \"localhost\" as server_name now"); + "localhost".clone_into(&mut server_name); + } + } + + let cfg = HandshakeConfig { + local_psk_callback: config.psk.take(), + local_psk_identity_hint: config.psk_identity_hint.take(), + local_cipher_suites, + local_signature_schemes, + extended_master_secret: config.extended_master_secret, + local_srtp_protection_profiles: config.srtp_protection_profiles.clone(), + server_name, + client_auth: config.client_auth, + local_certificates: config.certificates.clone(), + insecure_skip_verify: config.insecure_skip_verify, + insecure_verification: config.insecure_verification, + verify_peer_certificate: config.verify_peer_certificate.take(), + client_cert_verifier: if config.client_auth as u8 + >= ClientAuthType::VerifyClientCertIfGiven as u8 + { + Some(Arc::new( + crate::certificate_verifier::CertificateVerifier::new(config.client_cas), + )) + } else { + None + }, + server_cert_verifier: Arc::new(crate::certificate_verifier::CertificateVerifier::new( + config.roots_cas, + )), + retransmit_interval, + //log: logger, + initial_epoch: 0, + ..Default::default() + }; + + let (state, flight, initial_fsm_state) = if let Some(state) = initial_state { + let flight = if is_client { + Box::new(Flight5 {}) as Box + } else { + Box::new(Flight6 {}) as Box + }; + + (state, flight, HandshakeState::Finished) + } else { + let flight = if is_client { + Box::new(Flight1 {}) as Box + } else { + Box::new(Flight0 {}) as Box + }; + + ( + State { + is_client, + ..Default::default() + }, + flight, + HandshakeState::Preparing, + ) + }; + + let (decrypted_tx, decrypted_rx) = mpsc::channel(1); + let (handshake_tx, handshake_rx) = mpsc::channel(1); + let (handshake_done_tx, handshake_done_rx) = mpsc::channel(1); + let (packet_tx, mut packet_rx) = mpsc::channel(1); + let (handle_queue_tx, mut handle_queue_rx) = mpsc::channel(1); + let (reader_close_tx, mut reader_close_rx) = mpsc::channel(1); + + let packet_tx = Arc::new(packet_tx); + let packet_tx2 = Arc::clone(&packet_tx); + let next_conn_rx = Arc::clone(&conn); + let next_conn_tx = Arc::clone(&conn); + let cache = HandshakeCache::new(); + let mut cache1 = cache.clone(); + let cache2 = cache.clone(); + let handshake_completed_successfully = Arc::new(AtomicBool::new(false)); + let handshake_completed_successfully2 = Arc::clone(&handshake_completed_successfully); + + let mut c = DTLSConn { + conn: Arc::clone(&conn), + cache, + decrypted_rx: Mutex::new(decrypted_rx), + state, + handshake_completed_successfully, + connection_closed_by_user: false, + closed: AtomicBool::new(false), + + current_flight: flight, + flights: None, + cfg, + retransmit: false, + handshake_rx, + packet_tx, + handle_queue_tx, + handshake_done_tx: Some(handshake_done_tx), + reader_close_tx: Mutex::new(Some(reader_close_tx)), + }; + + let cipher_suite1 = Arc::clone(&c.state.cipher_suite); + let sequence_number = Arc::clone(&c.state.local_sequence_number); + + tokio::spawn(async move { + loop { + let rx = packet_rx.recv().await; + if let Some(r) = rx { + let (pkt, result_tx) = r; + + let result = DTLSConn::handle_outgoing_packets( + &next_conn_tx, + pkt, + &mut cache1, + is_client, + &sequence_number, + &cipher_suite1, + maximum_transmission_unit, + ) + .await; + + if let Some(tx) = result_tx { + let _ = tx.send(result).await; + } + } else { + trace!("{}: handle_outgoing_packets exit", srv_cli_str(is_client)); + break; + } + } + }); + + let local_epoch = Arc::clone(&c.state.local_epoch); + let remote_epoch = Arc::clone(&c.state.remote_epoch); + let cipher_suite2 = Arc::clone(&c.state.cipher_suite); + + tokio::spawn(async move { + let mut buf = vec![0u8; INBOUND_BUFFER_SIZE]; + let mut ctx = ConnReaderContext { + is_client, + replay_protection_window, + replay_detector: vec![], + decrypted_tx, + encrypted_packets: vec![], + fragment_buffer: FragmentBuffer::new(), + cache: cache2, + cipher_suite: cipher_suite2, + remote_epoch, + handshake_tx, + handshake_done_rx, + packet_tx: packet_tx2, + }; + + //trace!("before enter read_and_buffer: {}] ", srv_cli_str(is_client)); + loop { + tokio::select! { + _ = reader_close_rx.recv() => { + trace!( + "{}: read_and_buffer exit", + srv_cli_str(ctx.is_client), + ); + break; + } + result = DTLSConn::read_and_buffer( + &mut ctx, + &next_conn_rx, + &mut handle_queue_rx, + &mut buf, + &local_epoch, + &handshake_completed_successfully2, + ) => { + if let Err(err) = result { + trace!( + "{}: read_and_buffer return err: {}", + srv_cli_str(is_client), + err + ); + if Error::ErrAlertFatalOrClose == err { + trace!( + "{}: read_and_buffer exit with {}", + srv_cli_str(ctx.is_client), + err + ); + + break; + } + } + } + } + } + }); + + // Do handshake + c.handshake(initial_fsm_state).await?; + + trace!("Handshake Completed"); + + Ok(c) + } + + // Read reads data from the connection. + pub async fn read(&self, p: &mut [u8], duration: Option) -> Result { + if !self.is_handshake_completed_successfully() { + return Err(Error::ErrHandshakeInProgress); + } + + let rx = { + let mut decrypted_rx = self.decrypted_rx.lock().await; + if let Some(d) = duration { + let timer = tokio::time::sleep(d); + tokio::pin!(timer); + + tokio::select! { + r = decrypted_rx.recv() => r, + _ = timer.as_mut() => return Err(Error::ErrDeadlineExceeded), + } + } else { + decrypted_rx.recv().await + } + }; + + if let Some(out) = rx { + match out { + Ok(val) => { + let n = val.len(); + if p.len() < n { + return Err(Error::ErrBufferTooSmall); + } + p[..n].copy_from_slice(&val); + Ok(n) + } + Err(err) => Err(err), + } + } else { + Err(Error::ErrAlertFatalOrClose) + } + } + + // Write writes len(p) bytes from p to the DTLS connection + pub async fn write(&self, p: &[u8], duration: Option) -> Result { + if self.is_connection_closed() { + return Err(Error::ErrConnClosed); + } + + if !self.is_handshake_completed_successfully() { + return Err(Error::ErrHandshakeInProgress); + } + + let pkts = vec![Packet { + record: RecordLayer::new( + PROTOCOL_VERSION1_2, + self.get_local_epoch(), + Content::ApplicationData(ApplicationData { data: p.to_vec() }), + ), + should_encrypt: true, + reset_local_sequence_number: false, + }]; + + if let Some(d) = duration { + let timer = tokio::time::sleep(d); + tokio::pin!(timer); + + tokio::select! { + result = self.write_packets(pkts) => { + result?; + } + _ = timer.as_mut() => return Err(Error::ErrDeadlineExceeded), + } + } else { + self.write_packets(pkts).await?; + } + + Ok(p.len()) + } + + // Close closes the connection. + pub async fn close(&self) -> Result<()> { + if self + .closed + .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) + .is_ok() + { + // Discard error from notify() to return non-error on the first user call of Close() + // even if the underlying connection is already closed. + self.notify(AlertLevel::Warning, AlertDescription::CloseNotify) + .await?; + + { + let mut reader_close_tx = self.reader_close_tx.lock().await; + reader_close_tx.take(); + } + self.conn.close().await?; + } + + Ok(()) + } + + /// connection_state returns basic DTLS details about the connection. + /// Note that this replaced the `Export` function of v1. + pub async fn connection_state(&self) -> State { + self.state.clone().await + } + + /// selected_srtpprotection_profile returns the selected SRTPProtectionProfile + pub fn selected_srtpprotection_profile(&self) -> SrtpProtectionProfile { + self.state.srtp_protection_profile + } + + pub(crate) async fn notify(&self, level: AlertLevel, desc: AlertDescription) -> Result<()> { + self.write_packets(vec![Packet { + record: RecordLayer::new( + PROTOCOL_VERSION1_2, + self.get_local_epoch(), + Content::Alert(Alert { + alert_level: level, + alert_description: desc, + }), + ), + should_encrypt: self.is_handshake_completed_successfully(), + reset_local_sequence_number: false, + }]) + .await + } + + pub(crate) async fn write_packets(&self, pkts: Vec) -> Result<()> { + let (tx, mut rx) = mpsc::channel(1); + + self.packet_tx.send((pkts, Some(tx))).await?; + + if let Some(result) = rx.recv().await { + result + } else { + Ok(()) + } + } + + async fn handle_outgoing_packets( + next_conn: &Arc, + mut pkts: Vec, + cache: &mut HandshakeCache, + is_client: bool, + local_sequence_number: &Arc>>, + cipher_suite: &Arc>>>, + maximum_transmission_unit: usize, + ) -> Result<()> { + let mut raw_packets = vec![]; + for p in &mut pkts { + if let Content::Handshake(h) = &p.record.content { + let mut handshake_raw = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(handshake_raw.as_mut()); + p.record.marshal(&mut writer)?; + } + trace!( + "Send [handshake:{}] -> {} (epoch: {}, seq: {})", + srv_cli_str(is_client), + h.handshake_header.handshake_type, + p.record.record_layer_header.epoch, + h.handshake_header.message_sequence + ); + cache + .push( + handshake_raw[RECORD_LAYER_HEADER_SIZE..].to_vec(), + p.record.record_layer_header.epoch, + h.handshake_header.message_sequence, + h.handshake_header.handshake_type, + is_client, + ) + .await; + + let raw_handshake_packets = DTLSConn::process_handshake_packet( + local_sequence_number, + cipher_suite, + maximum_transmission_unit, + p, + h, + ) + .await?; + raw_packets.extend_from_slice(&raw_handshake_packets); + } else { + /*if let Content::Alert(a) = &p.record.content { + if a.alert_description == AlertDescription::CloseNotify { + closed = true; + } + }*/ + + let raw_packet = + DTLSConn::process_packet(local_sequence_number, cipher_suite, p).await?; + raw_packets.push(raw_packet); + } + } + + if !raw_packets.is_empty() { + let compacted_raw_packets = + compact_raw_packets(&raw_packets, maximum_transmission_unit); + + for compacted_raw_packets in &compacted_raw_packets { + next_conn.send(compacted_raw_packets).await?; + } + } + + Ok(()) + } + + async fn process_packet( + local_sequence_number: &Arc>>, + cipher_suite: &Arc>>>, + p: &mut Packet, + ) -> Result> { + let epoch = p.record.record_layer_header.epoch as usize; + let seq = { + let mut lsn = local_sequence_number.lock().await; + while lsn.len() <= epoch { + lsn.push(0); + } + + lsn[epoch] += 1; + lsn[epoch] - 1 + }; + //trace!("{}: seq = {}", srv_cli_str(is_client), seq); + + if seq > MAX_SEQUENCE_NUMBER { + // RFC 6347 Section 4.1.0 + // The implementation must either abandon an association or rehandshake + // prior to allowing the sequence number to wrap. + return Err(Error::ErrSequenceNumberOverflow); + } + p.record.record_layer_header.sequence_number = seq; + + let mut raw_packet = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw_packet.as_mut()); + p.record.marshal(&mut writer)?; + } + + if p.should_encrypt { + let cipher_suite = cipher_suite.lock().await; + if let Some(cipher_suite) = &*cipher_suite { + raw_packet = cipher_suite.encrypt(&p.record.record_layer_header, &raw_packet)?; + } + } + + Ok(raw_packet) + } + + async fn process_handshake_packet( + local_sequence_number: &Arc>>, + cipher_suite: &Arc>>>, + maximum_transmission_unit: usize, + p: &Packet, + h: &Handshake, + ) -> Result>> { + let mut raw_packets = vec![]; + + let handshake_fragments = DTLSConn::fragment_handshake(maximum_transmission_unit, h)?; + + let epoch = p.record.record_layer_header.epoch as usize; + + let mut lsn = local_sequence_number.lock().await; + while lsn.len() <= epoch { + lsn.push(0); + } + + for handshake_fragment in &handshake_fragments { + let seq = { + lsn[epoch] += 1; + lsn[epoch] - 1 + }; + //trace!("seq = {}", seq); + if seq > MAX_SEQUENCE_NUMBER { + return Err(Error::ErrSequenceNumberOverflow); + } + + let record_layer_header = RecordLayerHeader { + protocol_version: p.record.record_layer_header.protocol_version, + content_type: p.record.record_layer_header.content_type, + content_len: handshake_fragment.len() as u16, + epoch: p.record.record_layer_header.epoch, + sequence_number: seq, + }; + + let mut record_layer_header_bytes = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(record_layer_header_bytes.as_mut()); + record_layer_header.marshal(&mut writer)?; + } + + //p.record.record_layer_header = record_layer_header; + + let mut raw_packet = vec![]; + raw_packet.extend_from_slice(&record_layer_header_bytes); + raw_packet.extend_from_slice(handshake_fragment); + if p.should_encrypt { + let cipher_suite = cipher_suite.lock().await; + if let Some(cipher_suite) = &*cipher_suite { + raw_packet = cipher_suite.encrypt(&record_layer_header, &raw_packet)?; + } + } + + raw_packets.push(raw_packet); + } + + Ok(raw_packets) + } + + fn fragment_handshake(maximum_transmission_unit: usize, h: &Handshake) -> Result>> { + let mut content = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(content.as_mut()); + h.handshake_message.marshal(&mut writer)?; + } + + let mut fragmented_handshakes = vec![]; + + let mut content_fragments = split_bytes(&content, maximum_transmission_unit); + if content_fragments.is_empty() { + content_fragments = vec![vec![]]; + } + + let mut offset = 0; + for content_fragment in &content_fragments { + let content_fragment_len = content_fragment.len(); + + let handshake_header_fragment = HandshakeHeader { + handshake_type: h.handshake_header.handshake_type, + length: h.handshake_header.length, + message_sequence: h.handshake_header.message_sequence, + fragment_offset: offset as u32, + fragment_length: content_fragment_len as u32, + }; + + offset += content_fragment_len; + + let mut handshake_header_fragment_raw = vec![]; + { + let mut writer = + BufWriter::<&mut Vec>::new(handshake_header_fragment_raw.as_mut()); + handshake_header_fragment.marshal(&mut writer)?; + } + + let mut fragmented_handshake = vec![]; + fragmented_handshake.extend_from_slice(&handshake_header_fragment_raw); + fragmented_handshake.extend_from_slice(content_fragment); + + fragmented_handshakes.push(fragmented_handshake); + } + + Ok(fragmented_handshakes) + } + + pub(crate) fn set_handshake_completed_successfully(&mut self) { + self.handshake_completed_successfully + .store(true, Ordering::SeqCst); + } + + pub(crate) fn is_handshake_completed_successfully(&self) -> bool { + self.handshake_completed_successfully.load(Ordering::SeqCst) + } + + async fn read_and_buffer( + ctx: &mut ConnReaderContext, + next_conn: &Arc, + handle_queue_rx: &mut mpsc::Receiver>, + buf: &mut [u8], + local_epoch: &Arc, + handshake_completed_successfully: &Arc, + ) -> Result<()> { + let n = next_conn.recv(buf).await?; + let pkts = unpack_datagram(&buf[..n])?; + let mut has_handshake = false; + for pkt in pkts { + let (hs, alert, mut err) = DTLSConn::handle_incoming_packet(ctx, pkt, true).await; + if let Some(alert) = alert { + let alert_err = ctx + .packet_tx + .send(( + vec![Packet { + record: RecordLayer::new( + PROTOCOL_VERSION1_2, + local_epoch.load(Ordering::SeqCst), + Content::Alert(Alert { + alert_level: alert.alert_level, + alert_description: alert.alert_description, + }), + ), + should_encrypt: handshake_completed_successfully.load(Ordering::SeqCst), + reset_local_sequence_number: false, + }], + None, + )) + .await; + + if let Err(alert_err) = alert_err { + if err.is_none() { + err = Some(Error::Other(alert_err.to_string())); + } + } + + if alert.alert_level == AlertLevel::Fatal + || alert.alert_description == AlertDescription::CloseNotify + { + return Err(Error::ErrAlertFatalOrClose); + } + } + + if let Some(err) = err { + return Err(err); + } + + if hs { + has_handshake = true + } + } + + if has_handshake { + let (done_tx, mut done_rx) = mpsc::channel(1); + let rendezvous_at_handshake = async { + let (rendezvous_tx, rendezvous_rx) = oneshot::channel(); + _ = ctx.handshake_tx.send((rendezvous_tx, done_tx)).await; + rendezvous_rx.await + }; + tokio::select! { + _ = rendezvous_at_handshake => { + let mut wait_done_rx = true; + while wait_done_rx{ + tokio::select!{ + _ = done_rx.recv() => { + // If the other party may retransmit the flight, + // we should respond even if it not a new message. + wait_done_rx = false; + } + done = handle_queue_rx.recv() => { + //trace!("recv handle_queue: {} ", srv_cli_str(ctx.is_client)); + + let pkts = ctx.encrypted_packets.drain(..).collect(); + DTLSConn::handle_queued_packets(ctx, local_epoch, handshake_completed_successfully, pkts).await?; + + drop(done); + } + } + } + } + _ = ctx.handshake_done_rx.recv() => {} + } + } + + Ok(()) + } + + async fn handle_queued_packets( + ctx: &mut ConnReaderContext, + local_epoch: &Arc, + handshake_completed_successfully: &Arc, + pkts: Vec>, + ) -> Result<()> { + for p in pkts { + let (_, alert, mut err) = DTLSConn::handle_incoming_packet(ctx, p, false).await; // don't re-enqueue + if let Some(alert) = alert { + let alert_err = ctx + .packet_tx + .send(( + vec![Packet { + record: RecordLayer::new( + PROTOCOL_VERSION1_2, + local_epoch.load(Ordering::SeqCst), + Content::Alert(Alert { + alert_level: alert.alert_level, + alert_description: alert.alert_description, + }), + ), + should_encrypt: handshake_completed_successfully.load(Ordering::SeqCst), + reset_local_sequence_number: false, + }], + None, + )) + .await; + + if let Err(alert_err) = alert_err { + if err.is_none() { + err = Some(Error::Other(alert_err.to_string())); + } + } + if alert.alert_level == AlertLevel::Fatal + || alert.alert_description == AlertDescription::CloseNotify + { + return Err(Error::ErrAlertFatalOrClose); + } + } + + if let Some(err) = err { + return Err(err); + } + } + + Ok(()) + } + + async fn handle_incoming_packet( + ctx: &mut ConnReaderContext, + mut pkt: Vec, + enqueue: bool, + ) -> (bool, Option, Option) { + let mut reader = BufReader::new(pkt.as_slice()); + let h = match RecordLayerHeader::unmarshal(&mut reader) { + Ok(h) => h, + Err(err) => { + // Decode error must be silently discarded + // [RFC6347 Section-4.1.2.7] + debug!( + "{}: discarded broken packet: {}", + srv_cli_str(ctx.is_client), + err + ); + return (false, None, None); + } + }; + + // Validate epoch + let epoch = ctx.remote_epoch.load(Ordering::SeqCst); + if h.epoch > epoch { + if h.epoch > epoch + 1 { + debug!( + "{}: discarded future packet (epoch: {}, seq: {})", + srv_cli_str(ctx.is_client), + h.epoch, + h.sequence_number, + ); + return (false, None, None); + } + if enqueue { + debug!( + "{}: received packet of next epoch, queuing packet", + srv_cli_str(ctx.is_client) + ); + ctx.encrypted_packets.push(pkt); + } + return (false, None, None); + } + + // Anti-replay protection + while ctx.replay_detector.len() <= h.epoch as usize { + ctx.replay_detector + .push(Box::new(SlidingWindowDetector::new( + ctx.replay_protection_window, + MAX_SEQUENCE_NUMBER, + ))); + } + + let ok = ctx.replay_detector[h.epoch as usize].check(h.sequence_number); + if !ok { + debug!( + "{}: discarded duplicated packet (epoch: {}, seq: {})", + srv_cli_str(ctx.is_client), + h.epoch, + h.sequence_number, + ); + return (false, None, None); + } + + // Decrypt + if h.epoch != 0 { + let invalid_cipher_suite = { + let cipher_suite = ctx.cipher_suite.lock().await; + if cipher_suite.is_none() { + true + } else if let Some(cipher_suite) = &*cipher_suite { + !cipher_suite.is_initialized() + } else { + false + } + }; + if invalid_cipher_suite { + if enqueue { + debug!( + "{}: handshake not finished, queuing packet", + srv_cli_str(ctx.is_client) + ); + ctx.encrypted_packets.push(pkt); + } + return (false, None, None); + } + + let cipher_suite = ctx.cipher_suite.lock().await; + if let Some(cipher_suite) = &*cipher_suite { + pkt = match cipher_suite.decrypt(&pkt) { + Ok(pkt) => pkt, + Err(err) => { + debug!("{}: decrypt failed: {}", srv_cli_str(ctx.is_client), err); + + // If we get an error for PSK we need to return an error. + if cipher_suite.is_psk() { + return ( + false, + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::UnknownPskIdentity, + }), + None, + ); + } else { + return (false, None, None); + } + } + }; + } + } + + let is_handshake = match ctx.fragment_buffer.push(&pkt) { + Ok(is_handshake) => is_handshake, + Err(err) => { + // Decode error must be silently discarded + // [RFC6347 Section-4.1.2.7] + debug!("{}: defragment failed: {}", srv_cli_str(ctx.is_client), err); + return (false, None, None); + } + }; + if is_handshake { + ctx.replay_detector[h.epoch as usize].accept(); + while let Ok((out, epoch)) = ctx.fragment_buffer.pop() { + //log::debug!("Extension Debug: out.len()={}", out.len()); + let mut reader = BufReader::new(out.as_slice()); + let raw_handshake = match Handshake::unmarshal(&mut reader) { + Ok(rh) => { + trace!( + "Recv [handshake:{}] -> {} (epoch: {}, seq: {})", + srv_cli_str(ctx.is_client), + rh.handshake_header.handshake_type, + h.epoch, + rh.handshake_header.message_sequence + ); + rh + } + Err(err) => { + debug!( + "{}: handshake parse failed: {}", + srv_cli_str(ctx.is_client), + err + ); + continue; + } + }; + + ctx.cache + .push( + out, + epoch, + raw_handshake.handshake_header.message_sequence, + raw_handshake.handshake_header.handshake_type, + !ctx.is_client, + ) + .await; + } + + return (true, None, None); + } + + let mut reader = BufReader::new(pkt.as_slice()); + let r = match RecordLayer::unmarshal(&mut reader) { + Ok(r) => r, + Err(err) => { + return ( + false, + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::DecodeError, + }), + Some(err), + ); + } + }; + + match r.content { + Content::Alert(mut a) => { + trace!("{}: <- {}", srv_cli_str(ctx.is_client), a); + if a.alert_description == AlertDescription::CloseNotify { + // Respond with a close_notify [RFC5246 Section 7.2.1] + a = Alert { + alert_level: AlertLevel::Warning, + alert_description: AlertDescription::CloseNotify, + }; + } + ctx.replay_detector[h.epoch as usize].accept(); + return ( + false, + Some(a), + Some(Error::Other(format!("Error of Alert {a}"))), + ); + } + Content::ChangeCipherSpec(_) => { + let invalid_cipher_suite = { + let cipher_suite = ctx.cipher_suite.lock().await; + if cipher_suite.is_none() { + true + } else if let Some(cipher_suite) = &*cipher_suite { + !cipher_suite.is_initialized() + } else { + false + } + }; + + if invalid_cipher_suite { + if enqueue { + debug!( + "{}: CipherSuite not initialized, queuing packet", + srv_cli_str(ctx.is_client) + ); + ctx.encrypted_packets.push(pkt); + } + return (false, None, None); + } + + let new_remote_epoch = h.epoch + 1; + trace!( + "{}: <- ChangeCipherSpec (epoch: {})", + srv_cli_str(ctx.is_client), + new_remote_epoch + ); + + if epoch + 1 == new_remote_epoch { + ctx.remote_epoch.store(new_remote_epoch, Ordering::SeqCst); + ctx.replay_detector[h.epoch as usize].accept(); + } + } + Content::ApplicationData(a) => { + if h.epoch == 0 { + return ( + false, + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::UnexpectedMessage, + }), + Some(Error::ErrApplicationDataEpochZero), + ); + } + + ctx.replay_detector[h.epoch as usize].accept(); + + let _ = ctx.decrypted_tx.send(Ok(a.data)).await; + //TODO + /*select { + case self.decrypted < - content.data: + case < -c.closed.Done(): + }*/ + } + _ => { + return ( + false, + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::UnexpectedMessage, + }), + Some(Error::ErrUnhandledContextType), + ); + } + }; + + (false, None, None) + } + + fn is_connection_closed(&self) -> bool { + self.closed.load(Ordering::SeqCst) + } + + pub(crate) fn set_local_epoch(&mut self, epoch: u16) { + self.state.local_epoch.store(epoch, Ordering::SeqCst); + } + + pub(crate) fn get_local_epoch(&self) -> u16 { + self.state.local_epoch.load(Ordering::SeqCst) + } +} + +fn compact_raw_packets(raw_packets: &[Vec], maximum_transmission_unit: usize) -> Vec> { + let mut combined_raw_packets = vec![]; + let mut current_combined_raw_packet = vec![]; + + for raw_packet in raw_packets { + if !current_combined_raw_packet.is_empty() + && current_combined_raw_packet.len() + raw_packet.len() >= maximum_transmission_unit + { + combined_raw_packets.push(current_combined_raw_packet); + current_combined_raw_packet = vec![]; + } + current_combined_raw_packet.extend_from_slice(raw_packet); + } + + combined_raw_packets.push(current_combined_raw_packet); + + combined_raw_packets +} + +fn split_bytes(bytes: &[u8], split_len: usize) -> Vec> { + let mut splits = vec![]; + let num_bytes = bytes.len(); + for i in (0..num_bytes).step_by(split_len) { + let mut j = i + split_len; + if j > num_bytes { + j = num_bytes; + } + + splits.push(bytes[i..j].to_vec()); + } + + splits +} diff --git a/vendor/dtls/src/content.rs b/vendor/dtls/src/content.rs new file mode 100644 index 00000000..c1fa2ebf --- /dev/null +++ b/vendor/dtls/src/content.rs @@ -0,0 +1,85 @@ +use std::io::{Read, Write}; + +use super::alert::*; +use super::application_data::*; +use super::change_cipher_spec::*; +use super::handshake::*; +use crate::error::*; + +/// ## Specifications +/// +/// * [RFC 4346 §6.2.1] +/// +/// [RFC 4346 §6.2.1]: https://tools.ietf.org/html/rfc4346#section-6.2.1 +#[derive(Default, Copy, Clone, PartialEq, Eq, Debug)] +pub enum ContentType { + ChangeCipherSpec = 20, + Alert = 21, + Handshake = 22, + ApplicationData = 23, + #[default] + Invalid, +} + +impl From for ContentType { + fn from(val: u8) -> Self { + match val { + 20 => ContentType::ChangeCipherSpec, + 21 => ContentType::Alert, + 22 => ContentType::Handshake, + 23 => ContentType::ApplicationData, + _ => ContentType::Invalid, + } + } +} + +#[derive(PartialEq, Debug, Clone)] +pub enum Content { + ChangeCipherSpec(ChangeCipherSpec), + Alert(Alert), + Handshake(Handshake), + ApplicationData(ApplicationData), +} + +impl Content { + pub fn content_type(&self) -> ContentType { + match self { + Content::ChangeCipherSpec(c) => c.content_type(), + Content::Alert(c) => c.content_type(), + Content::Handshake(c) => c.content_type(), + Content::ApplicationData(c) => c.content_type(), + } + } + + pub fn size(&self) -> usize { + match self { + Content::ChangeCipherSpec(c) => c.size(), + Content::Alert(c) => c.size(), + Content::Handshake(c) => c.size(), + Content::ApplicationData(c) => c.size(), + } + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + match self { + Content::ChangeCipherSpec(c) => c.marshal(writer), + Content::Alert(c) => c.marshal(writer), + Content::Handshake(c) => c.marshal(writer), + Content::ApplicationData(c) => c.marshal(writer), + } + } + + pub fn unmarshal(content_type: ContentType, reader: &mut R) -> Result { + match content_type { + ContentType::ChangeCipherSpec => Ok(Content::ChangeCipherSpec( + ChangeCipherSpec::unmarshal(reader)?, + )), + ContentType::Alert => Ok(Content::Alert(Alert::unmarshal(reader)?)), + ContentType::Handshake => Ok(Content::Handshake(Handshake::unmarshal(reader)?)), + ContentType::ApplicationData => Ok(Content::ApplicationData( + ApplicationData::unmarshal(reader)?, + )), + _ => Err(Error::ErrInvalidContentType), + } + } +} diff --git a/vendor/dtls/src/crypto/crypto_cbc.rs b/vendor/dtls/src/crypto/crypto_cbc.rs new file mode 100644 index 00000000..4c71a6c2 --- /dev/null +++ b/vendor/dtls/src/crypto/crypto_cbc.rs @@ -0,0 +1,136 @@ +// AES-CBC (Cipher Block Chaining) +// First historic block cipher for AES. +// CBC mode is insecure and must not be used. It’s been progressively deprecated and +// removed from SSL libraries. +// Introduced with TLS 1.0 year 2002. Superseded by GCM in TLS 1.2 year 2008. +// Removed in TLS 1.3 year 2018. +// RFC 3268 year 2002 https://tools.ietf.org/html/rfc3268 + +// https://github.com/RustCrypto/block-ciphers + +use aes::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit}; +use p256::elliptic_curve::subtle::ConstantTimeEq; +use rand::Rng; +use std::io::Cursor; +use std::ops::Not; + +use super::padding::DtlsPadding; +use crate::content::*; +use crate::error::*; +use crate::prf::*; +use crate::record_layer::record_layer_header::*; +type Aes256CbcEnc = cbc::Encryptor; +type Aes256CbcDec = cbc::Decryptor; + +// State needed to handle encrypted input/output +#[derive(Clone)] +pub struct CryptoCbc { + local_key: Vec, + remote_key: Vec, + write_mac: Vec, + read_mac: Vec, +} + +impl CryptoCbc { + const BLOCK_SIZE: usize = 16; + const MAC_SIZE: usize = 20; + + pub fn new( + local_key: &[u8], + local_mac: &[u8], + remote_key: &[u8], + remote_mac: &[u8], + ) -> Result { + Ok(CryptoCbc { + local_key: local_key.to_vec(), + write_mac: local_mac.to_vec(), + + remote_key: remote_key.to_vec(), + read_mac: remote_mac.to_vec(), + }) + } + + pub fn encrypt(&self, pkt_rlh: &RecordLayerHeader, raw: &[u8]) -> Result> { + let mut payload = raw[RECORD_LAYER_HEADER_SIZE..].to_vec(); + let raw = &raw[..RECORD_LAYER_HEADER_SIZE]; + + // Generate + Append MAC + let h = pkt_rlh; + + let mac = prf_mac( + h.epoch, + h.sequence_number, + h.content_type, + h.protocol_version, + &payload, + &self.write_mac, + )?; + payload.extend_from_slice(&mac); + + let mut iv: Vec = vec![0; Self::BLOCK_SIZE]; + rand::rng().fill(iv.as_mut_slice()); + + let write_cbc = Aes256CbcEnc::new_from_slices(&self.local_key, &iv)?; + let encrypted = write_cbc.encrypt_padded_vec_mut::(&payload); + + // Prepend unencrypte header with encrypted payload + let mut r = vec![]; + r.extend_from_slice(raw); + r.extend_from_slice(&iv); + r.extend_from_slice(&encrypted); + + let r_len = (r.len() - RECORD_LAYER_HEADER_SIZE) as u16; + r[RECORD_LAYER_HEADER_SIZE - 2..RECORD_LAYER_HEADER_SIZE] + .copy_from_slice(&r_len.to_be_bytes()); + + Ok(r) + } + + pub fn decrypt(&self, r: &[u8]) -> Result> { + let mut reader = Cursor::new(r); + let h = RecordLayerHeader::unmarshal(&mut reader)?; + if h.content_type == ContentType::ChangeCipherSpec { + // Nothing to encrypt with ChangeCipherSpec + return Ok(r.to_vec()); + } + + if r.len() < RECORD_LAYER_HEADER_SIZE + Self::BLOCK_SIZE { + return Err(Error::ErrInvalidPacketLength); + } + + let body = &r[RECORD_LAYER_HEADER_SIZE..]; + let iv = &body[0..Self::BLOCK_SIZE]; + let body = &body[Self::BLOCK_SIZE..]; + + if body.is_empty() || !body.len().is_multiple_of(Self::BLOCK_SIZE) { + return Err(Error::ErrInvalidPacketLength); + } + + let read_cbc = Aes256CbcDec::new_from_slices(&self.remote_key, iv)?; + + let decrypted = read_cbc + .decrypt_padded_vec_mut::(body) + .map_err(|_| Error::ErrInvalidPacketLength)?; + + let recv_mac = &decrypted[decrypted.len() - Self::MAC_SIZE..]; + let decrypted = &decrypted[0..decrypted.len() - Self::MAC_SIZE]; + let mac = prf_mac( + h.epoch, + h.sequence_number, + h.content_type, + h.protocol_version, + decrypted, + &self.read_mac, + )?; + + if recv_mac.ct_eq(&mac).not().into() { + return Err(Error::ErrInvalidMac); + } + + let mut d = Vec::with_capacity(RECORD_LAYER_HEADER_SIZE + decrypted.len()); + d.extend_from_slice(&r[..RECORD_LAYER_HEADER_SIZE]); + d.extend_from_slice(decrypted); + + Ok(d) + } +} diff --git a/vendor/dtls/src/crypto/crypto_ccm.rs b/vendor/dtls/src/crypto/crypto_ccm.rs new file mode 100644 index 00000000..071b4c54 --- /dev/null +++ b/vendor/dtls/src/crypto/crypto_ccm.rs @@ -0,0 +1,188 @@ +// AES-CCM (Counter with CBC-MAC) +// Alternative to GCM mode. +// Available in OpenSSL as of TLS 1.3 (2018), but disabled by default. +// Two AES computations per block, thus expected to be somewhat slower than AES-GCM. +// RFC 6655 year 2012 https://tools.ietf.org/html/rfc6655 +// Much lower adoption, probably because it came after GCM and offer no significant benefit. + +// https://github.com/RustCrypto/AEADs +// https://docs.rs/ccm/0.3.0/ccm/ Or https://crates.io/crates/aes-ccm? + +use std::io::Cursor; + +use aes::Aes128; +use ccm::aead::generic_array::GenericArray; +use ccm::aead::AeadInPlace; +use ccm::consts::{U12, U16, U8}; +use ccm::Ccm; +use ccm::KeyInit; +use rand::Rng; + +use super::*; +use crate::content::*; +use crate::error::*; +use crate::record_layer::record_layer_header::*; + +const CRYPTO_CCM_8_TAG_LENGTH: usize = 8; +const CRYPTO_CCM_TAG_LENGTH: usize = 16; +const CRYPTO_CCM_NONCE_LENGTH: usize = 12; + +type AesCcm8 = Ccm; +type AesCcm = Ccm; + +#[derive(Clone)] +pub enum CryptoCcmTagLen { + CryptoCcm8TagLength, + CryptoCcmTagLength, +} + +enum CryptoCcmType { + CryptoCcm8(AesCcm8), + CryptoCcm(AesCcm), +} + +// State needed to handle encrypted input/output +pub struct CryptoCcm { + local_ccm: CryptoCcmType, + remote_ccm: CryptoCcmType, + local_write_iv: Vec, + remote_write_iv: Vec, + // used by clone() + local_write_key: Vec, + remote_write_key: Vec, +} + +impl Clone for CryptoCcm { + fn clone(&self) -> Self { + match self.local_ccm { + CryptoCcmType::CryptoCcm(_) => Self::new( + &CryptoCcmTagLen::CryptoCcmTagLength, + &self.local_write_key, + &self.local_write_iv, + &self.remote_write_key, + &self.remote_write_iv, + ), + CryptoCcmType::CryptoCcm8(_) => Self::new( + &CryptoCcmTagLen::CryptoCcm8TagLength, + &self.local_write_key, + &self.local_write_iv, + &self.remote_write_key, + &self.remote_write_iv, + ), + } + } +} + +impl CryptoCcm { + pub fn new( + tag_len: &CryptoCcmTagLen, + local_key: &[u8], + local_write_iv: &[u8], + remote_key: &[u8], + remote_write_iv: &[u8], + ) -> Self { + let key = GenericArray::from_slice(local_key); + let local_ccm = match tag_len { + CryptoCcmTagLen::CryptoCcmTagLength => CryptoCcmType::CryptoCcm(AesCcm::new(key)), + CryptoCcmTagLen::CryptoCcm8TagLength => CryptoCcmType::CryptoCcm8(AesCcm8::new(key)), + }; + + let key = GenericArray::from_slice(remote_key); + let remote_ccm = match tag_len { + CryptoCcmTagLen::CryptoCcmTagLength => CryptoCcmType::CryptoCcm(AesCcm::new(key)), + CryptoCcmTagLen::CryptoCcm8TagLength => CryptoCcmType::CryptoCcm8(AesCcm8::new(key)), + }; + + CryptoCcm { + local_ccm, + local_write_key: local_key.to_vec(), + local_write_iv: local_write_iv.to_vec(), + remote_ccm, + remote_write_key: remote_key.to_vec(), + remote_write_iv: remote_write_iv.to_vec(), + } + } + + pub fn encrypt(&self, pkt_rlh: &RecordLayerHeader, raw: &[u8]) -> Result> { + let payload = &raw[RECORD_LAYER_HEADER_SIZE..]; + let raw = &raw[..RECORD_LAYER_HEADER_SIZE]; + + let mut nonce = vec![0u8; CRYPTO_CCM_NONCE_LENGTH]; + nonce[..4].copy_from_slice(&self.local_write_iv[..4]); + rand::rng().fill(&mut nonce[4..]); + let nonce = GenericArray::from_slice(&nonce); + + let additional_data = generate_aead_additional_data(pkt_rlh, payload.len()); + + let mut buffer: Vec = Vec::new(); + buffer.extend_from_slice(payload); + + match &self.local_ccm { + CryptoCcmType::CryptoCcm(ccm) => { + ccm.encrypt_in_place(nonce, &additional_data, &mut buffer) + .map_err(|e| Error::Other(e.to_string()))?; + } + CryptoCcmType::CryptoCcm8(ccm8) => { + ccm8.encrypt_in_place(nonce, &additional_data, &mut buffer) + .map_err(|e| Error::Other(e.to_string()))?; + } + } + + let mut r = Vec::with_capacity(raw.len() + nonce.len() + buffer.len()); + + r.extend_from_slice(raw); + r.extend_from_slice(&nonce[4..]); + r.extend_from_slice(&buffer); + + // Update recordLayer size to include explicit nonce + let r_len = (r.len() - RECORD_LAYER_HEADER_SIZE) as u16; + r[RECORD_LAYER_HEADER_SIZE - 2..RECORD_LAYER_HEADER_SIZE] + .copy_from_slice(&r_len.to_be_bytes()); + + Ok(r) + } + + pub fn decrypt(&self, r: &[u8]) -> Result> { + let mut reader = Cursor::new(r); + let h = RecordLayerHeader::unmarshal(&mut reader)?; + if h.content_type == ContentType::ChangeCipherSpec { + // Nothing to encrypt with ChangeCipherSpec + return Ok(r.to_vec()); + } + + if r.len() <= (RECORD_LAYER_HEADER_SIZE + 8) { + return Err(Error::ErrNotEnoughRoomForNonce); + } + + let mut nonce = vec![]; + nonce.extend_from_slice(&self.remote_write_iv[..4]); + nonce.extend_from_slice(&r[RECORD_LAYER_HEADER_SIZE..RECORD_LAYER_HEADER_SIZE + 8]); + let nonce = GenericArray::from_slice(&nonce); + + let out = &r[RECORD_LAYER_HEADER_SIZE + 8..]; + + let mut buffer: Vec = Vec::new(); + buffer.extend_from_slice(out); + + match &self.remote_ccm { + CryptoCcmType::CryptoCcm(ccm) => { + let additional_data = + generate_aead_additional_data(&h, out.len() - CRYPTO_CCM_TAG_LENGTH); + ccm.decrypt_in_place(nonce, &additional_data, &mut buffer) + .map_err(|e| Error::Other(e.to_string()))?; + } + CryptoCcmType::CryptoCcm8(ccm8) => { + let additional_data = + generate_aead_additional_data(&h, out.len() - CRYPTO_CCM_8_TAG_LENGTH); + ccm8.decrypt_in_place(nonce, &additional_data, &mut buffer) + .map_err(|e| Error::Other(e.to_string()))?; + } + } + + let mut d = Vec::with_capacity(RECORD_LAYER_HEADER_SIZE + buffer.len()); + d.extend_from_slice(&r[..RECORD_LAYER_HEADER_SIZE]); + d.extend_from_slice(&buffer); + + Ok(d) + } +} diff --git a/vendor/dtls/src/crypto/crypto_chacha20.rs b/vendor/dtls/src/crypto/crypto_chacha20.rs new file mode 100644 index 00000000..5ee5f3dc --- /dev/null +++ b/vendor/dtls/src/crypto/crypto_chacha20.rs @@ -0,0 +1,123 @@ +use std::io::Cursor; + +use chacha20poly1305::aead::generic_array::GenericArray; +use chacha20poly1305::aead::AeadInPlace; +use chacha20poly1305::{ChaCha20Poly1305, KeyInit}; + +use super::*; +use crate::content::*; +use crate::error::*; +use crate::record_layer::record_layer_header::*; // what about Aes256Gcm? + +const CRYPTO_CHACHA20_TAG_LENGTH: usize = 16; +const CRYPTO_CHACHA20_NONCE_LENGTH: usize = 12; + +// State needed to handle encrypted input/output +#[derive(Clone)] +pub struct CryptoChaCha20 { + local_cc: ChaCha20Poly1305, + remote_cc: ChaCha20Poly1305, + local_key: Vec, + remote_key: Vec, + local_write_iv: Vec, + remote_write_iv: Vec, +} + +fn noncegen(nonce: &mut [u8], epoch: u16, seqnum: u64) { + let epoch: u64 = epoch.into(); + let seqnum = (seqnum & 0xFFFFFFFFFFFF) | (epoch << 48); + for i in 0..8 { + nonce[i + 4] ^= ((seqnum >> (8 * (7 - i))) & 0xFF) as u8; + } +} + +impl CryptoChaCha20 { + pub fn new( + local_key: &[u8], + local_write_iv: &[u8], + remote_key: &[u8], + remote_write_iv: &[u8], + ) -> Self { + let key = GenericArray::from_slice(local_key); + let local_cc = ChaCha20Poly1305::new(key); + + let key = GenericArray::from_slice(remote_key); + let remote_cc = ChaCha20Poly1305::new(key); + + CryptoChaCha20 { + local_cc, + local_write_iv: local_write_iv.to_vec(), + remote_cc, + local_key: local_key.to_vec(), + remote_key: remote_key.to_vec(), + remote_write_iv: remote_write_iv.to_vec(), + } + } + + pub fn encrypt(&self, pkt_rlh: &RecordLayerHeader, raw: &[u8]) -> Result> { + let payload = &raw[RECORD_LAYER_HEADER_SIZE..]; + let raw = &raw[..RECORD_LAYER_HEADER_SIZE]; + + let mut nonce = vec![0u8; CRYPTO_CHACHA20_NONCE_LENGTH]; + nonce[..CRYPTO_CHACHA20_NONCE_LENGTH] + .copy_from_slice(&self.local_write_iv[..CRYPTO_CHACHA20_NONCE_LENGTH]); + + noncegen(&mut nonce[..], pkt_rlh.epoch, pkt_rlh.sequence_number); + let nonce = GenericArray::from_slice(&nonce); + + let additional_data = generate_aead_additional_data(pkt_rlh, payload.len()); + + let mut buffer: Vec = Vec::new(); + buffer.extend_from_slice(payload); + + let tag = self + .local_cc + .encrypt_in_place_detached(nonce, &additional_data, &mut buffer) + .map_err(|e| Error::Other(e.to_string()))?; + + let mut r = Vec::with_capacity(raw.len() + buffer.len() + tag.len()); + r.extend_from_slice(raw); + r.extend_from_slice(&buffer); + r.extend_from_slice(&tag); + + // Update recordLayer size to include explicit nonce + let r_len = (r.len() - RECORD_LAYER_HEADER_SIZE) as u16; + r[RECORD_LAYER_HEADER_SIZE - 2..RECORD_LAYER_HEADER_SIZE] + .copy_from_slice(&r_len.to_be_bytes()); + + Ok(r) + } + + pub fn decrypt(&self, r: &[u8]) -> Result> { + let mut reader = Cursor::new(r); + let h = RecordLayerHeader::unmarshal(&mut reader)?; + if h.content_type == ContentType::ChangeCipherSpec { + // Nothing to encrypt with ChangeCipherSpec + return Ok(r.to_vec()); + } + + let mut nonce = vec![]; + nonce.extend_from_slice(&self.remote_write_iv[..]); + + noncegen(&mut nonce[..], h.epoch, h.sequence_number); + let nonce = GenericArray::from_slice(&nonce); + + let out = &r[RECORD_LAYER_HEADER_SIZE..]; + + let additional_data = + generate_aead_additional_data(&h, out.len() - CRYPTO_CHACHA20_TAG_LENGTH); + + let mut buffer: Vec = Vec::new(); + buffer.extend_from_slice(out); + + self.remote_cc + .decrypt_in_place(nonce, &additional_data, &mut buffer) + .map_err(|e| Error::Other(e.to_string()))?; + + let mut d = Vec::with_capacity(RECORD_LAYER_HEADER_SIZE + buffer.len()); + d.extend_from_slice(&r[..RECORD_LAYER_HEADER_SIZE]); + d.extend_from_slice(&buffer); + + Ok(d) + } +} diff --git a/vendor/dtls/src/crypto/crypto_gcm.rs b/vendor/dtls/src/crypto/crypto_gcm.rs new file mode 100644 index 00000000..a692bd95 --- /dev/null +++ b/vendor/dtls/src/crypto/crypto_gcm.rs @@ -0,0 +1,119 @@ +// AES-GCM (Galois Counter Mode) +// The most widely used block cipher worldwide. +// Mandatory as of TLS 1.2 (2008) and used by default by most clients. +// RFC 5288 year 2008 https://tools.ietf.org/html/rfc5288 + +// https://github.com/RustCrypto/AEADs +// https://docs.rs/aes-gcm/0.8.0/aes_gcm/ + +use std::io::Cursor; + +use aes_gcm::aead::generic_array::GenericArray; +use aes_gcm::aead::AeadInPlace; +use aes_gcm::{Aes128Gcm, KeyInit}; +use rand::Rng; + +use super::*; +use crate::content::*; +use crate::error::*; +use crate::record_layer::record_layer_header::*; // what about Aes256Gcm? + +const CRYPTO_GCM_TAG_LENGTH: usize = 16; +const CRYPTO_GCM_NONCE_LENGTH: usize = 12; + +// State needed to handle encrypted input/output +#[derive(Clone)] +pub struct CryptoGcm { + local_gcm: Aes128Gcm, + remote_gcm: Aes128Gcm, + local_write_iv: Vec, + remote_write_iv: Vec, +} + +impl CryptoGcm { + pub fn new( + local_key: &[u8], + local_write_iv: &[u8], + remote_key: &[u8], + remote_write_iv: &[u8], + ) -> Self { + let key = GenericArray::from_slice(local_key); + let local_gcm = Aes128Gcm::new(key); + + let key = GenericArray::from_slice(remote_key); + let remote_gcm = Aes128Gcm::new(key); + + CryptoGcm { + local_gcm, + local_write_iv: local_write_iv.to_vec(), + remote_gcm, + remote_write_iv: remote_write_iv.to_vec(), + } + } + + pub fn encrypt(&self, pkt_rlh: &RecordLayerHeader, raw: &[u8]) -> Result> { + let payload = &raw[RECORD_LAYER_HEADER_SIZE..]; + let raw = &raw[..RECORD_LAYER_HEADER_SIZE]; + + let mut nonce = vec![0u8; CRYPTO_GCM_NONCE_LENGTH]; + nonce[..4].copy_from_slice(&self.local_write_iv[..4]); + rand::rng().fill(&mut nonce[4..]); + let nonce = GenericArray::from_slice(&nonce); + + let additional_data = generate_aead_additional_data(pkt_rlh, payload.len()); + + let mut buffer: Vec = Vec::new(); + buffer.extend_from_slice(payload); + + self.local_gcm + .encrypt_in_place(nonce, &additional_data, &mut buffer) + .map_err(|e| Error::Other(e.to_string()))?; + + let mut r = Vec::with_capacity(raw.len() + nonce.len() + buffer.len()); + r.extend_from_slice(raw); + r.extend_from_slice(&nonce[4..]); + r.extend_from_slice(&buffer); + + // Update recordLayer size to include explicit nonce + let r_len = (r.len() - RECORD_LAYER_HEADER_SIZE) as u16; + r[RECORD_LAYER_HEADER_SIZE - 2..RECORD_LAYER_HEADER_SIZE] + .copy_from_slice(&r_len.to_be_bytes()); + + Ok(r) + } + + pub fn decrypt(&self, r: &[u8]) -> Result> { + let mut reader = Cursor::new(r); + let h = RecordLayerHeader::unmarshal(&mut reader)?; + if h.content_type == ContentType::ChangeCipherSpec { + // Nothing to encrypt with ChangeCipherSpec + return Ok(r.to_vec()); + } + + if r.len() <= (RECORD_LAYER_HEADER_SIZE + 8) { + return Err(Error::ErrNotEnoughRoomForNonce); + } + + let mut nonce = vec![]; + nonce.extend_from_slice(&self.remote_write_iv[..4]); + nonce.extend_from_slice(&r[RECORD_LAYER_HEADER_SIZE..RECORD_LAYER_HEADER_SIZE + 8]); + let nonce = GenericArray::from_slice(&nonce); + + let out = &r[RECORD_LAYER_HEADER_SIZE + 8..]; + + let additional_data = generate_aead_additional_data(&h, out.len() - CRYPTO_GCM_TAG_LENGTH); + + let mut buffer: Vec = Vec::new(); + buffer.extend_from_slice(out); + + self.remote_gcm + .decrypt_in_place(nonce, &additional_data, &mut buffer) + .map_err(|e| Error::Other(e.to_string()))?; + + let mut d = Vec::with_capacity(RECORD_LAYER_HEADER_SIZE + buffer.len()); + d.extend_from_slice(&r[..RECORD_LAYER_HEADER_SIZE]); + d.extend_from_slice(&buffer); + + Ok(d) + } +} diff --git a/vendor/dtls/src/crypto/crypto_test.rs b/vendor/dtls/src/crypto/crypto_test.rs new file mode 100644 index 00000000..f06988bc --- /dev/null +++ b/vendor/dtls/src/crypto/crypto_test.rs @@ -0,0 +1,221 @@ +use std::io::Cursor; + +use x509_parser::pem::Pem; + +use super::crypto_ccm::*; +use super::*; +use crate::content::ContentType; +use crate::record_layer::record_layer_header::{ProtocolVersion, RECORD_LAYER_HEADER_SIZE}; + +const RAW_PRIVATE_KEY: &str = " +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAxIA2BrrnR2sIlATsp7aRBD/3krwZ7vt9dNeoDQAee0s6SuYP +6MBx/HPnAkwNvPS90R05a7pwRkoT6Ur4PfPhCVlUe8lV+0Eto3ZSEeHz3HdsqlM3 +bso67L7Dqrc7MdVstlKcgJi8yeAoGOIL9/igOv0XBFCeznm9nznx6mnsR5cugw+1 +ypXelaHmBCLV7r5SeVSh57+KhvZGbQ2fFpUaTPegRpJZXBNS8lSeWvtOv9d6N5UB +ROTAJodMZT5AfX0jB0QB9IT/0I96H6BSENH08NXOeXApMuLKvnAf361rS7cRAfRL +rWZqERMP4u6Cnk0Cnckc3WcW27kGGIbtwbqUIQIDAQABAoIBAGF7OVIdZp8Hejn0 +N3L8HvT8xtUEe9kS6ioM0lGgvX5s035Uo4/T6LhUx0VcdXRH9eLHnLTUyN4V4cra +ZkxVsE3zAvZl60G6E+oDyLMWZOP6Wu4kWlub9597A5atT7BpMIVCdmFVZFLB4SJ3 +AXkC3nplFAYP+Lh1rJxRIrIn2g+pEeBboWbYA++oDNuMQffDZaokTkJ8Bn1JZYh0 +xEXKY8Bi2Egd5NMeZa1UFO6y8tUbZfwgVs6Enq5uOgtfayq79vZwyjj1kd29MBUD +8g8byV053ZKxbUOiOuUts97eb+fN3DIDRTcT2c+lXt/4C54M1FclJAbtYRK/qwsl +pYWKQAECgYEA4ZUbqQnTo1ICvj81ifGrz+H4LKQqe92Hbf/W51D/Umk2kP702W22 +HP4CvrJRtALThJIG9m2TwUjl/WAuZIBrhSAbIvc3Fcoa2HjdRp+sO5U1ueDq7d/S +Z+PxRI8cbLbRpEdIaoR46qr/2uWZ943PHMv9h4VHPYn1w8b94hwD6vkCgYEA3v87 +mFLzyM9ercnEv9zHMRlMZFQhlcUGQZvfb8BuJYl/WogyT6vRrUuM0QXULNEPlrin +mBQTqc1nCYbgkFFsD2VVt1qIyiAJsB9MD1LNV6YuvE7T2KOSadmsA4fa9PUqbr71 +hf3lTTq+LeR09LebO7WgSGYY+5YKVOEGpYMR1GkCgYEAxPVQmk3HKHEhjgRYdaG5 +lp9A9ZE8uruYVJWtiHgzBTxx9TV2iST+fd/We7PsHFTfY3+wbpcMDBXfIVRKDVwH +BMwchXH9+Ztlxx34bYJaegd0SmA0Hw9ugWEHNgoSEmWpM1s9wir5/ELjc7dGsFtz +uzvsl9fpdLSxDYgAAdzeGtkCgYBAzKIgrVox7DBzB8KojhtD5ToRnXD0+H/M6OKQ +srZPKhlb0V/tTtxrIx0UUEFLlKSXA6mPw6XDHfDnD86JoV9pSeUSlrhRI+Ysy6tq +eIE7CwthpPZiaYXORHZ7wCqcK/HcpJjsCs9rFbrV0yE5S3FMdIbTAvgXg44VBB7O +UbwIoQKBgDuY8gSrA5/A747wjjmsdRWK4DMTMEV4eCW1BEP7Tg7Cxd5n3xPJiYhr +nhLGN+mMnVIcv2zEMS0/eNZr1j/0BtEdx+3IC6Eq+ONY0anZ4Irt57/5QeKgKn/L +JPhfPySIPG4UmwE4gW8t79vfOKxnUu2fDD1ZXUYopan6EckACNH/ +-----END RSA PRIVATE KEY----- +"; + +#[test] +fn test_generate_key_signature() -> Result<()> { + let reader = Cursor::new(RAW_PRIVATE_KEY.as_bytes()); + let pem = match Pem::read(reader) { + Ok((pem, _)) => pem, + Err(_) => return Err(Error::Other("Pem::read error".to_owned())), + }; + //let private_key = rsa::RSAPrivateKey::from_pkcs1(&pem.contents)?; + + let client_random = vec![ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, + ]; + let server_random = vec![ + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, + 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, + 0x8e, 0x8f, + ]; + let public_key = vec![ + 0x20, 0x9f, 0xd7, 0xad, 0x6d, 0xcf, 0xf4, 0x29, 0x8d, 0xd3, 0xf9, 0x6d, 0x5b, 0x1b, 0x2a, + 0xf9, 0x10, 0xa0, 0x53, 0x5b, 0x14, 0x88, 0xd7, 0xf8, 0xfa, 0xbb, 0x34, 0x9a, 0x98, 0x28, + 0x80, 0xb6, 0x15, + ]; + let expected_signature = vec![ + 0x6f, 0x47, 0x97, 0x85, 0xcc, 0x76, 0x50, 0x93, 0xbd, 0xe2, 0x6a, 0x69, 0x0b, 0xc3, 0x03, + 0xd1, 0xb7, 0xe4, 0xab, 0x88, 0x7b, 0xa6, 0x52, 0x80, 0xdf, 0xaa, 0x25, 0x7a, 0xdb, 0x29, + 0x32, 0xe4, 0xd8, 0x28, 0x28, 0xb3, 0xe8, 0x04, 0x3c, 0x38, 0x16, 0xfc, 0x78, 0xe9, 0x15, + 0x7b, 0xc5, 0xbd, 0x7d, 0xfc, 0xcd, 0x83, 0x00, 0x57, 0x4a, 0x3c, 0x23, 0x85, 0x75, 0x6b, + 0x37, 0xd5, 0x89, 0x72, 0x73, 0xf0, 0x44, 0x8c, 0x00, 0x70, 0x1f, 0x6e, 0xa2, 0x81, 0xd0, + 0x09, 0xc5, 0x20, 0x36, 0xab, 0x23, 0x09, 0x40, 0x1f, 0x4d, 0x45, 0x96, 0x62, 0xbb, 0x81, + 0xb0, 0x30, 0x72, 0xad, 0x3a, 0x0a, 0xac, 0x31, 0x63, 0x40, 0x52, 0x0a, 0x27, 0xf3, 0x34, + 0xde, 0x27, 0x7d, 0xb7, 0x54, 0xff, 0x0f, 0x9f, 0x5a, 0xfe, 0x07, 0x0f, 0x4e, 0x9f, 0x53, + 0x04, 0x34, 0x62, 0xf4, 0x30, 0x74, 0x83, 0x35, 0xfc, 0xe4, 0x7e, 0xbf, 0x5a, 0xc4, 0x52, + 0xd0, 0xea, 0xf9, 0x61, 0x4e, 0xf5, 0x1c, 0x0e, 0x58, 0x02, 0x71, 0xfb, 0x1f, 0x34, 0x55, + 0xe8, 0x36, 0x70, 0x3c, 0xc1, 0xcb, 0xc9, 0xb7, 0xbb, 0xb5, 0x1c, 0x44, 0x9a, 0x6d, 0x88, + 0x78, 0x98, 0xd4, 0x91, 0x2e, 0xeb, 0x98, 0x81, 0x23, 0x30, 0x73, 0x39, 0x43, 0xd5, 0xbb, + 0x70, 0x39, 0xba, 0x1f, 0xdb, 0x70, 0x9f, 0x91, 0x83, 0x56, 0xc2, 0xde, 0xed, 0x17, 0x6d, + 0x2c, 0x3e, 0x21, 0xea, 0x36, 0xb4, 0x91, 0xd8, 0x31, 0x05, 0x60, 0x90, 0xfd, 0xc6, 0x74, + 0xa9, 0x7b, 0x18, 0xfc, 0x1c, 0x6a, 0x1c, 0x6e, 0xec, 0xd3, 0xc1, 0xc0, 0x0d, 0x11, 0x25, + 0x48, 0x37, 0x3d, 0x45, 0x11, 0xa2, 0x31, 0x14, 0x0a, 0x66, 0x9f, 0xd8, 0xac, 0x74, 0xa2, + 0xcd, 0xc8, 0x79, 0xb3, 0x9e, 0xc6, 0x66, 0x25, 0xcf, 0x2c, 0x87, 0x5e, 0x5c, 0x36, 0x75, + 0x86, + ]; + + let signature = generate_key_signature( + &client_random, + &server_random, + &public_key, + NamedCurve::X25519, + &CryptoPrivateKey { + kind: CryptoPrivateKeyKind::Rsa256( + ring::rsa::KeyPair::from_der(&pem.contents) + .map_err(|e| Error::Other(e.to_string()))?, + ), + serialized_der: pem.contents.clone(), + }, //hashAlgorithmSHA256, + )?; + + assert_eq!( + signature, expected_signature, + "Signature generation failed \nexp {expected_signature:?} \nactual {signature:?} " + ); + + Ok(()) +} + +#[test] +fn test_ccm_encryption_and_decryption() -> Result<()> { + let key = vec![ + 0x18, 0x78, 0xac, 0xc2, 0x2a, 0xd8, 0xbd, 0xd8, 0xc6, 0x01, 0xa6, 0x17, 0x12, 0x6f, 0x63, + 0x54, + ]; + let iv = vec![0x0e, 0xb2, 0x09, 0x06]; + + let ccm = CryptoCcm::new(&CryptoCcmTagLen::CryptoCcmTagLength, &key, &iv, &key, &iv); + + let rlh = RecordLayerHeader { + content_type: ContentType::ApplicationData, + protocol_version: ProtocolVersion { + major: 0xfe, + minor: 0xff, + }, + epoch: 0, + sequence_number: 18, + content_len: 3, + }; + + let raw = vec![ + 0x17, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x03, 0xff, 0xaa, + 0xbb, + ]; + + let cipher_text = ccm.encrypt(&rlh, &raw)?; + + assert_eq!( + &cipher_text[RECORD_LAYER_HEADER_SIZE - 2..RECORD_LAYER_HEADER_SIZE], + [0, 27], + "RecordLayer size updating failed \nexp: {:?} \nactual {:?} ", + [0, 27], + &cipher_text[RECORD_LAYER_HEADER_SIZE - 2..RECORD_LAYER_HEADER_SIZE] + ); + + let plain_text = ccm.decrypt(&cipher_text)?; + + assert_eq!( + raw[RECORD_LAYER_HEADER_SIZE..], + plain_text[RECORD_LAYER_HEADER_SIZE..], + "Decryption failed \nexp: {:?} \nactual {:?} ", + &raw[RECORD_LAYER_HEADER_SIZE..], + &plain_text[RECORD_LAYER_HEADER_SIZE..] + ); + + Ok(()) +} + +#[test] +fn test_certificate_verify() -> Result<()> { + let plain_text: Vec = vec![ + 0x6f, 0x47, 0x97, 0x85, 0xcc, 0x76, 0x50, 0x93, 0xbd, 0xe2, 0x6a, 0x69, 0x0b, 0xc3, 0x03, + 0xd1, 0xb7, 0xe4, 0xab, 0x88, 0x7b, 0xa6, 0x52, 0x80, 0xdf, 0xaa, 0x25, 0x7a, 0xdb, 0x29, + 0x32, 0xe4, 0xd8, 0x28, 0x28, 0xb3, 0xe8, 0x04, 0x3c, 0x38, 0x16, 0xfc, 0x78, 0xe9, 0x15, + 0x7b, 0xc5, 0xbd, 0x7d, 0xfc, 0xcd, 0x83, 0x00, 0x57, 0x4a, 0x3c, 0x23, 0x85, 0x75, 0x6b, + 0x37, 0xd5, 0x89, 0x72, 0x73, 0xf0, 0x44, 0x8c, 0x00, 0x70, 0x1f, 0x6e, 0xa2, 0x81, 0xd0, + 0x09, 0xc5, 0x20, 0x36, 0xab, 0x23, 0x09, 0x40, 0x1f, 0x4d, 0x45, 0x96, 0x62, 0xbb, 0x81, + 0xb0, 0x30, 0x72, 0xad, 0x3a, 0x0a, 0xac, 0x31, 0x63, 0x40, 0x52, 0x0a, 0x27, 0xf3, 0x34, + 0xde, 0x27, 0x7d, 0xb7, 0x54, 0xff, 0x0f, 0x9f, 0x5a, 0xfe, 0x07, 0x0f, 0x4e, 0x9f, 0x53, + 0x04, 0x34, 0x62, 0xf4, 0x30, 0x74, 0x83, 0x35, 0xfc, 0xe4, 0x7e, 0xbf, 0x5a, 0xc4, 0x52, + 0xd0, 0xea, 0xf9, 0x61, 0x4e, 0xf5, 0x1c, 0x0e, 0x58, 0x02, 0x71, 0xfb, 0x1f, 0x34, 0x55, + 0xe8, 0x36, 0x70, 0x3c, 0xc1, 0xcb, 0xc9, 0xb7, 0xbb, 0xb5, 0x1c, 0x44, 0x9a, 0x6d, 0x88, + 0x78, 0x98, 0xd4, 0x91, 0x2e, 0xeb, 0x98, 0x81, 0x23, 0x30, 0x73, 0x39, 0x43, 0xd5, 0xbb, + 0x70, 0x39, 0xba, 0x1f, 0xdb, 0x70, 0x9f, 0x91, 0x83, 0x56, 0xc2, 0xde, 0xed, 0x17, 0x6d, + 0x2c, 0x3e, 0x21, 0xea, 0x36, 0xb4, 0x91, 0xd8, 0x31, 0x05, 0x60, 0x90, 0xfd, 0xc6, 0x74, + 0xa9, 0x7b, 0x18, 0xfc, 0x1c, 0x6a, 0x1c, 0x6e, 0xec, 0xd3, 0xc1, 0xc0, 0x0d, 0x11, 0x25, + 0x48, 0x37, 0x3d, 0x45, 0x11, 0xa2, 0x31, 0x14, 0x0a, 0x66, 0x9f, 0xd8, 0xac, 0x74, 0xa2, + 0xcd, 0xc8, 0x79, 0xb3, 0x9e, 0xc6, 0x66, 0x25, 0xcf, 0x2c, 0x87, 0x5e, 0x5c, 0x36, 0x75, + 0x86, + ]; + + //test ECDSA256 + let certificate_ecdsa256 = Certificate::generate_self_signed(vec!["localhost".to_owned()])?; + let cert_verify_ecdsa256 = + generate_certificate_verify(&plain_text, &certificate_ecdsa256.private_key)?; + verify_certificate_verify( + &plain_text, + &SignatureHashAlgorithm { + hash: HashAlgorithm::Sha256, + signature: SignatureAlgorithm::Ecdsa, + }, + &cert_verify_ecdsa256, + &certificate_ecdsa256 + .certificate + .iter() + .map(|x| x.as_ref().to_owned()) + .collect::>>(), + false, + )?; + + //test ED25519 + let certificate_ed25519 = Certificate::generate_self_signed_with_alg( + vec!["localhost".to_owned()], + &rcgen::PKCS_ED25519, + )?; + let cert_verify_ed25519 = + generate_certificate_verify(&plain_text, &certificate_ed25519.private_key)?; + verify_certificate_verify( + &plain_text, + &SignatureHashAlgorithm { + hash: HashAlgorithm::Sha256, + signature: SignatureAlgorithm::Ed25519, + }, + &cert_verify_ed25519, + &certificate_ed25519 + .certificate + .iter() + .map(|x| x.as_ref().to_owned()) + .collect::>>(), + false, + )?; + + Ok(()) +} diff --git a/vendor/dtls/src/crypto/mod.rs b/vendor/dtls/src/crypto/mod.rs new file mode 100644 index 00000000..8f99f707 --- /dev/null +++ b/vendor/dtls/src/crypto/mod.rs @@ -0,0 +1,514 @@ +#[cfg(test)] +mod crypto_test; + +pub mod crypto_cbc; +pub mod crypto_ccm; +pub mod crypto_chacha20; +pub mod crypto_gcm; +pub mod padding; + +use std::convert::TryFrom; +use std::sync::Arc; + +use der_parser::oid; +use der_parser::oid::Oid; + +use crate::certificate_verifier::CertificateVerifier; +use rustls_pki_types::CertificateDer; + +use rcgen::{generate_simple_self_signed, CertifiedKey, KeyPair}; +use ring::rand::SystemRandom; +use ring::signature::{EcdsaKeyPair, Ed25519KeyPair}; + +use crate::curve::named_curve::*; +use crate::error::*; +use crate::record_layer::record_layer_header::*; +use crate::signature_hash_algorithm::{HashAlgorithm, SignatureAlgorithm, SignatureHashAlgorithm}; + +/// A X.509 certificate(s) used to authenticate a DTLS connection. +#[derive(Clone, PartialEq, Debug)] +pub struct Certificate { + /// DER-encoded certificates. + pub certificate: Vec>, + /// Private key. + pub private_key: CryptoPrivateKey, +} + +impl Certificate { + /// Generate a self-signed certificate. + /// + /// See [`rcgen::generate_simple_self_signed`]. + pub fn generate_self_signed(subject_alt_names: impl Into>) -> Result { + let CertifiedKey { cert, key_pair } = + generate_simple_self_signed(subject_alt_names).unwrap(); + Ok(Certificate { + certificate: vec![cert.der().to_owned()], + private_key: CryptoPrivateKey::try_from(&key_pair)?, + }) + } + + /// Generate a self-signed certificate with the given algorithm. + /// + /// See [`rcgen::Certificate::from_params`]. + pub fn generate_self_signed_with_alg( + subject_alt_names: impl Into>, + alg: &'static rcgen::SignatureAlgorithm, + ) -> Result { + let params = rcgen::CertificateParams::new(subject_alt_names).unwrap(); + let key_pair = rcgen::KeyPair::generate_for(alg).unwrap(); + let cert = params.self_signed(&key_pair).unwrap(); + + Ok(Certificate { + certificate: vec![cert.der().to_owned()], + private_key: CryptoPrivateKey::try_from(&key_pair)?, + }) + } + + /// Parses a certificate from the ASCII PEM format. + #[cfg(feature = "pem")] + pub fn from_pem(pem_str: &str) -> Result { + let mut pems = pem::parse_many(pem_str).map_err(|e| Error::InvalidPEM(e.to_string()))?; + if pems.len() < 2 { + return Err(Error::InvalidPEM(format!( + "expected at least two PEM blocks, got {}", + pems.len() + ))); + } + if pems[0].tag() != "PRIVATE_KEY" { + return Err(Error::InvalidPEM(format!( + "invalid tag (expected: 'PRIVATE_KEY', got: '{}')", + pems[0].tag() + ))); + } + + let keypair = KeyPair::try_from(pems[0].contents()) + .map_err(|e| Error::InvalidPEM(format!("can't decode keypair: {e}")))?; + + let mut rustls_certs = Vec::new(); + for p in pems.drain(1..) { + if p.tag() != "CERTIFICATE" { + return Err(Error::InvalidPEM(format!( + "invalid tag (expected: 'CERTIFICATE', got: '{}')", + p.tag() + ))); + } + rustls_certs.push(CertificateDer::from(p.contents().to_vec())); + } + + Ok(Certificate { + certificate: rustls_certs, + private_key: CryptoPrivateKey::try_from(&keypair)?, + }) + } + + /// Serializes the certificate (including the private key) in PKCS#8 format in PEM. + #[cfg(feature = "pem")] + pub fn serialize_pem(&self) -> String { + let mut data = vec![pem::Pem::new( + "PRIVATE_KEY".to_string(), + self.private_key.serialized_der.clone(), + )]; + for rustls_cert in &self.certificate { + data.push(pem::Pem::new( + "CERTIFICATE".to_string(), + rustls_cert.as_ref(), + )); + } + pem::encode_many(&data) + } +} + +pub(crate) fn value_key_message( + client_random: &[u8], + server_random: &[u8], + public_key: &[u8], + named_curve: NamedCurve, +) -> Vec { + let mut server_ecdh_params = vec![0u8; 4]; + server_ecdh_params[0] = 3; // named curve + server_ecdh_params[1..3].copy_from_slice(&(named_curve as u16).to_be_bytes()); + server_ecdh_params[3] = public_key.len() as u8; + + let mut plaintext = vec![]; + plaintext.extend_from_slice(client_random); + plaintext.extend_from_slice(server_random); + plaintext.extend_from_slice(&server_ecdh_params); + plaintext.extend_from_slice(public_key); + + plaintext +} + +/// Either ED25519, ECDSA or RSA keypair. +#[derive(Debug)] +pub enum CryptoPrivateKeyKind { + Ed25519(Ed25519KeyPair), + Ecdsa256(EcdsaKeyPair), + Rsa256(ring::rsa::KeyPair), +} + +/// Private key. +#[derive(Debug)] +pub struct CryptoPrivateKey { + /// Keypair. + pub kind: CryptoPrivateKeyKind, + /// DER-encoded keypair. + pub serialized_der: Vec, +} + +impl PartialEq for CryptoPrivateKey { + fn eq(&self, other: &Self) -> bool { + if self.serialized_der != other.serialized_der { + return false; + } + + matches!( + (&self.kind, &other.kind), + ( + CryptoPrivateKeyKind::Rsa256(_), + CryptoPrivateKeyKind::Rsa256(_) + ) | ( + CryptoPrivateKeyKind::Ecdsa256(_), + CryptoPrivateKeyKind::Ecdsa256(_) + ) | ( + CryptoPrivateKeyKind::Ed25519(_), + CryptoPrivateKeyKind::Ed25519(_) + ) + ) + } +} + +impl Clone for CryptoPrivateKey { + fn clone(&self) -> Self { + match self.kind { + CryptoPrivateKeyKind::Ed25519(_) => CryptoPrivateKey { + kind: CryptoPrivateKeyKind::Ed25519( + Ed25519KeyPair::from_pkcs8_maybe_unchecked(&self.serialized_der).unwrap(), + ), + serialized_der: self.serialized_der.clone(), + }, + CryptoPrivateKeyKind::Ecdsa256(_) => CryptoPrivateKey { + kind: CryptoPrivateKeyKind::Ecdsa256( + EcdsaKeyPair::from_pkcs8( + &ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING, + &self.serialized_der, + &SystemRandom::new(), + ) + .unwrap(), + ), + serialized_der: self.serialized_der.clone(), + }, + CryptoPrivateKeyKind::Rsa256(_) => CryptoPrivateKey { + kind: CryptoPrivateKeyKind::Rsa256( + ring::rsa::KeyPair::from_pkcs8(&self.serialized_der).unwrap(), + ), + serialized_der: self.serialized_der.clone(), + }, + } + } +} + +impl TryFrom<&KeyPair> for CryptoPrivateKey { + type Error = Error; + + fn try_from(key_pair: &KeyPair) -> Result { + Self::from_key_pair(key_pair) + } +} + +impl CryptoPrivateKey { + pub fn from_key_pair(key_pair: &KeyPair) -> Result { + let serialized_der = key_pair.serialize_der(); + if key_pair.is_compatible(&rcgen::PKCS_ED25519) { + Ok(CryptoPrivateKey { + kind: CryptoPrivateKeyKind::Ed25519( + Ed25519KeyPair::from_pkcs8_maybe_unchecked(&serialized_der) + .map_err(|e| Error::Other(e.to_string()))?, + ), + serialized_der, + }) + } else if key_pair.is_compatible(&rcgen::PKCS_ECDSA_P256_SHA256) { + Ok(CryptoPrivateKey { + kind: CryptoPrivateKeyKind::Ecdsa256( + EcdsaKeyPair::from_pkcs8( + &ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING, + &serialized_der, + &SystemRandom::new(), + ) + .map_err(|e| Error::Other(e.to_string()))?, + ), + serialized_der, + }) + } else if key_pair.is_compatible(&rcgen::PKCS_RSA_SHA256) { + Ok(CryptoPrivateKey { + kind: CryptoPrivateKeyKind::Rsa256( + ring::rsa::KeyPair::from_pkcs8(&serialized_der) + .map_err(|e| Error::Other(e.to_string()))?, + ), + serialized_der, + }) + } else { + Err(Error::Other("Unsupported key_pair".to_owned())) + } + } +} + +// If the client provided a "signature_algorithms" extension, then all +// certificates provided by the server MUST be signed by a +// hash/signature algorithm pair that appears in that extension +// +// https://tools.ietf.org/html/rfc5246#section-7.4.2 +pub(crate) fn generate_key_signature( + client_random: &[u8], + server_random: &[u8], + public_key: &[u8], + named_curve: NamedCurve, + private_key: &CryptoPrivateKey, /*, hash_algorithm: HashAlgorithm*/ +) -> Result> { + let msg = value_key_message(client_random, server_random, public_key, named_curve); + let signature = match &private_key.kind { + CryptoPrivateKeyKind::Ed25519(kp) => kp.sign(&msg).as_ref().to_vec(), + CryptoPrivateKeyKind::Ecdsa256(kp) => { + let system_random = SystemRandom::new(); + kp.sign(&system_random, &msg) + .map_err(|e| Error::Other(e.to_string()))? + .as_ref() + .to_vec() + } + CryptoPrivateKeyKind::Rsa256(kp) => { + let system_random = SystemRandom::new(); + let mut signature = vec![0; kp.public().modulus_len()]; + kp.sign( + &ring::signature::RSA_PKCS1_SHA256, + &system_random, + &msg, + &mut signature, + ) + .map_err(|e| Error::Other(e.to_string()))?; + + signature + } + }; + + Ok(signature) +} + +// add OID_ED25519 which is not defined in x509_parser +pub const OID_ED25519: Oid<'static> = oid!(1.3.101 .112); +pub const OID_ECDSA: Oid<'static> = oid!(1.2.840 .10045 .2 .1); + +fn verify_signature( + message: &[u8], + hash_algorithm: &SignatureHashAlgorithm, + remote_key_signature: &[u8], + raw_certificates: &[Vec], + insecure_verification: bool, +) -> Result<()> { + if raw_certificates.is_empty() { + return Err(Error::ErrLengthMismatch); + } + + let (_, certificate) = x509_parser::parse_x509_certificate(&raw_certificates[0]) + .map_err(|e| Error::Other(e.to_string()))?; + + let verify_alg: &dyn ring::signature::VerificationAlgorithm = match hash_algorithm.signature { + SignatureAlgorithm::Ed25519 => &ring::signature::ED25519, + SignatureAlgorithm::Ecdsa if hash_algorithm.hash == HashAlgorithm::Sha256 => { + &ring::signature::ECDSA_P256_SHA256_ASN1 + } + SignatureAlgorithm::Ecdsa if hash_algorithm.hash == HashAlgorithm::Sha384 => { + &ring::signature::ECDSA_P384_SHA384_ASN1 + } + SignatureAlgorithm::Rsa if hash_algorithm.hash == HashAlgorithm::Sha1 => { + &ring::signature::RSA_PKCS1_1024_8192_SHA1_FOR_LEGACY_USE_ONLY + } + SignatureAlgorithm::Rsa if (hash_algorithm.hash == HashAlgorithm::Sha256) => { + if remote_key_signature.len() < 256 && insecure_verification { + &ring::signature::RSA_PKCS1_1024_8192_SHA256_FOR_LEGACY_USE_ONLY + } else { + &ring::signature::RSA_PKCS1_2048_8192_SHA256 + } + } + SignatureAlgorithm::Rsa if hash_algorithm.hash == HashAlgorithm::Sha384 => { + &ring::signature::RSA_PKCS1_2048_8192_SHA384 + } + SignatureAlgorithm::Rsa if hash_algorithm.hash == HashAlgorithm::Sha512 => { + if remote_key_signature.len() < 256 && insecure_verification { + &ring::signature::RSA_PKCS1_1024_8192_SHA512_FOR_LEGACY_USE_ONLY + } else { + &ring::signature::RSA_PKCS1_2048_8192_SHA512 + } + } + _ => return Err(Error::ErrKeySignatureVerifyUnimplemented), + }; + + log::trace!("Picked an algorithm {verify_alg:?}"); + + let public_key = ring::signature::UnparsedPublicKey::new( + verify_alg, + certificate + .tbs_certificate + .subject_pki + .subject_public_key + .data, + ); + + public_key + .verify(message, remote_key_signature) + .map_err(|e| Error::Other(e.to_string()))?; + + Ok(()) +} + +pub(crate) fn verify_key_signature( + message: &[u8], + hash_algorithm: &SignatureHashAlgorithm, + remote_key_signature: &[u8], + raw_certificates: &[Vec], + insecure_verification: bool, +) -> Result<()> { + verify_signature( + message, + hash_algorithm, + remote_key_signature, + raw_certificates, + insecure_verification, + ) +} + +// If the server has sent a CertificateRequest message, the client MUST send the Certificate +// message. The ClientKeyExchange message is now sent, and the content +// of that message will depend on the public key algorithm selected +// between the ClientHello and the ServerHello. If the client has sent +// a certificate with signing ability, a digitally-signed +// CertificateVerify message is sent to explicitly verify possession of +// the private key in the certificate. +// https://tools.ietf.org/html/rfc5246#section-7.3 +pub(crate) fn generate_certificate_verify( + handshake_bodies: &[u8], + private_key: &CryptoPrivateKey, /*, hashAlgorithm hashAlgorithm*/ +) -> Result> { + let signature = match &private_key.kind { + CryptoPrivateKeyKind::Ed25519(kp) => kp.sign(handshake_bodies).as_ref().to_vec(), + CryptoPrivateKeyKind::Ecdsa256(kp) => { + let system_random = SystemRandom::new(); + kp.sign(&system_random, handshake_bodies) + .map_err(|e| Error::Other(e.to_string()))? + .as_ref() + .to_vec() + } + CryptoPrivateKeyKind::Rsa256(kp) => { + let system_random = SystemRandom::new(); + let mut signature = vec![0; kp.public().modulus_len()]; + kp.sign( + &ring::signature::RSA_PKCS1_SHA256, + &system_random, + handshake_bodies, + &mut signature, + ) + .map_err(|e| Error::Other(e.to_string()))?; + + signature + } + }; + + Ok(signature) +} + +pub(crate) fn verify_certificate_verify( + handshake_bodies: &[u8], + hash_algorithm: &SignatureHashAlgorithm, + remote_key_signature: &[u8], + raw_certificates: &[Vec], + insecure_verification: bool, +) -> Result<()> { + verify_signature( + handshake_bodies, + hash_algorithm, + remote_key_signature, + raw_certificates, + insecure_verification, + ) +} + +pub(crate) fn load_certs(raw_certificates: &[Vec]) -> Result>> { + if raw_certificates.is_empty() { + return Err(Error::ErrLengthMismatch); + } + + let mut certs = vec![]; + for raw_cert in raw_certificates { + let cert = CertificateDer::from(raw_cert.to_vec()); + certs.push(cert); + } + + Ok(certs) +} + +pub(crate) fn verify_client_cert( + raw_certificates: &[Vec], + cert_verifier: &Arc, +) -> Result>> { + let chains = load_certs(raw_certificates)?; + + let (end_entity, intermediates) = chains + .split_first() + .ok_or(Error::ErrClientCertificateRequired)?; + + match cert_verifier.verify_client_cert(end_entity, intermediates) { + Ok(_) => {} + Err(err) => return Err(Error::Other(err.to_string())), + }; + + Ok(chains) +} + +pub(crate) fn verify_server_cert( + raw_certificates: &[Vec], + cert_verifier: &Arc, + server_name: &str, +) -> Result>> { + let chains = load_certs(raw_certificates)?; + + let (end_entity, intermediates) = chains + .split_first() + .ok_or(Error::ErrServerMustHaveCertificate)?; + match cert_verifier.verify_server_cert(end_entity, intermediates, server_name) { + Ok(_) => {} + Err(err) => return Err(Error::Other(err.to_string())), + }; + + Ok(chains) +} + +pub(crate) fn generate_aead_additional_data(h: &RecordLayerHeader, payload_len: usize) -> Vec { + let mut additional_data = vec![0u8; 13]; + // SequenceNumber MUST be set first + // we only want uint48, clobbering an extra 2 (using uint64, rust doesn't have uint48) + additional_data[..8].copy_from_slice(&h.sequence_number.to_be_bytes()); + additional_data[..2].copy_from_slice(&h.epoch.to_be_bytes()); + additional_data[8] = h.content_type as u8; + additional_data[9] = h.protocol_version.major; + additional_data[10] = h.protocol_version.minor; + additional_data[11..].copy_from_slice(&(payload_len as u16).to_be_bytes()); + + additional_data +} + +#[cfg(test)] +mod test { + #[cfg(feature = "pem")] + use super::*; + + #[cfg(feature = "pem")] + #[test] + fn test_certificate_serialize_pem_and_from_pem() -> crate::error::Result<()> { + let cert = Certificate::generate_self_signed(vec!["webrtc.rs".to_owned()])?; + + let pem = cert.serialize_pem(); + let loaded_cert = Certificate::from_pem(&pem)?; + + assert_eq!(loaded_cert, cert); + + Ok(()) + } +} diff --git a/vendor/dtls/src/crypto/padding.rs b/vendor/dtls/src/crypto/padding.rs new file mode 100644 index 00000000..c10871a4 --- /dev/null +++ b/vendor/dtls/src/crypto/padding.rs @@ -0,0 +1,122 @@ +use cbc::cipher::block_padding::{PadType, RawPadding, UnpadError}; +use core::panic; + +pub enum DtlsPadding {} +/// Reference: RFC5246, 6.2.3.2 +impl RawPadding for DtlsPadding { + const TYPE: PadType = PadType::Reversible; + + fn raw_pad(block: &mut [u8], pos: usize) { + if pos >= block.len() { + panic!("`pos` is bigger or equal to block size"); + } + + let padding_length = block.len() - pos - 1; + if padding_length > 255 { + panic!("block size is too big for DTLS"); + } + + set(&mut block[pos..], padding_length as u8); + } + + fn raw_unpad(data: &[u8]) -> Result<&[u8], UnpadError> { + let padding_length = data.last().copied().unwrap_or(1) as usize; + if padding_length + 1 > data.len() { + return Err(UnpadError); + } + + let padding_begin = data.len() - padding_length - 1; + + if data[padding_begin..data.len() - 1] + .iter() + .any(|&byte| byte as usize != padding_length) + { + return Err(UnpadError); + } + + Ok(&data[0..padding_begin]) + } +} + +/// Sets all bytes in `dst` equal to `value` +#[inline(always)] +fn set(dst: &mut [u8], value: u8) { + // SAFETY: we overwrite valid memory behind `dst` + // note: loop is not used here because it produces + // unnecessary branch which tests for zero-length slices + unsafe { + core::ptr::write_bytes(dst.as_mut_ptr(), value, dst.len()); + } +} + +#[cfg(test)] +pub mod tests { + use rand::Rng; + + use super::*; + + #[test] + fn padding_length_is_amount_of_bytes_excluding_the_padding_length_itself() -> Result<(), ()> { + for original_length in 0..128 { + for padding_length in 0..(256 - original_length) { + let mut block = vec![0; original_length + padding_length + 1]; + rand::rng().fill(&mut block[0..original_length]); + let original = block[0..original_length].to_vec(); + DtlsPadding::raw_pad(&mut block, original_length); + + for byte in block[original_length..].iter() { + assert_eq!(*byte as usize, padding_length); + } + assert_eq!(block[0..original_length], original); + } + } + + Ok(()) + } + + #[test] + #[should_panic] + fn full_block_is_padding_error() { + for original_length in 0..256 { + let mut block = vec![0; original_length]; + DtlsPadding::raw_pad(&mut block, original_length); + } + } + + #[test] + #[should_panic] + fn padding_length_bigger_than_255_is_a_pad_error() { + let padding_length = 256; + for original_length in 0..128 { + let mut block = vec![0; original_length + padding_length + 1]; + DtlsPadding::raw_pad(&mut block, original_length); + } + } + + #[test] + fn empty_block_is_unpadding_error() { + let r = DtlsPadding::raw_unpad(&[]); + assert!(r.is_err()); + } + + #[test] + fn padding_too_big_for_block_is_unpadding_error() { + let r = DtlsPadding::raw_unpad(&[1]); + assert!(r.is_err()); + } + + #[test] + fn one_of_the_padding_bytes_with_value_different_than_padding_length_is_unpadding_error() { + for padding_length in 0..16 { + for invalid_byte in 0..padding_length { + let mut block = vec![0; padding_length + 1]; + DtlsPadding::raw_pad(&mut block, 0); + + assert_eq!(DtlsPadding::raw_unpad(&block).ok(), Some(&[][..])); + block[invalid_byte] = (padding_length - 1) as u8; + let r = DtlsPadding::raw_unpad(&block); + assert!(r.is_err()); + } + } + } +} diff --git a/vendor/dtls/src/curve/mod.rs b/vendor/dtls/src/curve/mod.rs new file mode 100644 index 00000000..dd48d5ff --- /dev/null +++ b/vendor/dtls/src/curve/mod.rs @@ -0,0 +1,17 @@ +pub mod named_curve; + +// https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-10 +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum EllipticCurveType { + NamedCurve = 0x03, + Unsupported, +} + +impl From for EllipticCurveType { + fn from(val: u8) -> Self { + match val { + 0x03 => EllipticCurveType::NamedCurve, + _ => EllipticCurveType::Unsupported, + } + } +} diff --git a/vendor/dtls/src/curve/named_curve.rs b/vendor/dtls/src/curve/named_curve.rs new file mode 100644 index 00000000..6d7d97f8 --- /dev/null +++ b/vendor/dtls/src/curve/named_curve.rs @@ -0,0 +1,83 @@ +use rand_core::OsRng; // requires 'getrandom' feature + +use crate::error::*; + +// https://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-8 +#[repr(u16)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum NamedCurve { + Unsupported = 0x0000, + P256 = 0x0017, + P384 = 0x0018, + X25519 = 0x001d, +} + +impl From for NamedCurve { + fn from(val: u16) -> Self { + match val { + 0x0017 => NamedCurve::P256, + 0x0018 => NamedCurve::P384, + 0x001d => NamedCurve::X25519, + _ => NamedCurve::Unsupported, + } + } +} + +pub(crate) enum NamedCurvePrivateKey { + EphemeralSecretP256(p256::ecdh::EphemeralSecret), + EphemeralSecretP384(p384::ecdh::EphemeralSecret), + StaticSecretX25519(x25519_dalek::StaticSecret), +} + +pub struct NamedCurveKeypair { + pub(crate) curve: NamedCurve, + pub(crate) public_key: Vec, + pub(crate) private_key: NamedCurvePrivateKey, +} + +fn elliptic_curve_keypair(curve: NamedCurve) -> Result { + let (public_key, private_key) = match curve { + NamedCurve::P256 => { + let secret_key = p256::ecdh::EphemeralSecret::random(&mut OsRng); + let public_key = p256::EncodedPoint::from(secret_key.public_key()); + ( + public_key.as_bytes().to_vec(), + NamedCurvePrivateKey::EphemeralSecretP256(secret_key), + ) + } + NamedCurve::P384 => { + let secret_key = p384::ecdh::EphemeralSecret::random(&mut OsRng); + let public_key = p384::EncodedPoint::from(secret_key.public_key()); + ( + public_key.as_bytes().to_vec(), + NamedCurvePrivateKey::EphemeralSecretP384(secret_key), + ) + } + NamedCurve::X25519 => { + let secret_key = x25519_dalek::StaticSecret::random_from_rng(OsRng); + let public_key = x25519_dalek::PublicKey::from(&secret_key); + ( + public_key.as_bytes().to_vec(), + NamedCurvePrivateKey::StaticSecretX25519(secret_key), + ) + } + _ => return Err(Error::ErrInvalidNamedCurve), + }; + + Ok(NamedCurveKeypair { + curve, + public_key, + private_key, + }) +} + +impl NamedCurve { + pub fn generate_keypair(&self) -> Result { + match *self { + NamedCurve::X25519 => elliptic_curve_keypair(NamedCurve::X25519), + NamedCurve::P256 => elliptic_curve_keypair(NamedCurve::P256), + NamedCurve::P384 => elliptic_curve_keypair(NamedCurve::P384), + _ => Err(Error::ErrInvalidNamedCurve), + } + } +} diff --git a/vendor/dtls/src/error.rs b/vendor/dtls/src/error.rs new file mode 100644 index 00000000..a90d9fa8 --- /dev/null +++ b/vendor/dtls/src/error.rs @@ -0,0 +1,223 @@ +use std::io; +use std::string::FromUtf8Error; + +use thiserror::Error; +use tokio::sync::mpsc::error::SendError as MpscSendError; +use util::KeyingMaterialExporterError; + +pub type Result = std::result::Result; + +#[derive(Debug, Error, PartialEq)] +#[non_exhaustive] +pub enum Error { + #[error("conn is closed")] + ErrConnClosed, + #[error("read/write timeout")] + ErrDeadlineExceeded, + #[error("buffer is too small")] + ErrBufferTooSmall, + #[error("context is not supported for export_keying_material")] + ErrContextUnsupported, + #[error("packet is too short")] + ErrDtlspacketInvalidLength, + #[error("handshake is in progress")] + ErrHandshakeInProgress, + #[error("invalid content type")] + ErrInvalidContentType, + #[error("invalid mac")] + ErrInvalidMac, + #[error("packet length and declared length do not match")] + ErrInvalidPacketLength, + #[error("export_keying_material can not be used with a reserved label")] + ErrReservedExportKeyingMaterial, + #[error("client sent certificate verify but we have no certificate to verify")] + ErrCertificateVerifyNoCertificate, + #[error("client+server do not support any shared cipher suites")] + ErrCipherSuiteNoIntersection, + #[error("server hello can not be created without a cipher suite")] + ErrCipherSuiteUnset, + #[error("client sent certificate but did not verify it")] + ErrClientCertificateNotVerified, + #[error("server required client verification, but got none")] + ErrClientCertificateRequired, + #[error("server responded with SRTP Profile we do not support")] + ErrClientNoMatchingSrtpProfile, + #[error("client required Extended Master Secret extension, but server does not support it")] + ErrClientRequiredButNoServerEms, + #[error("server hello can not be created without a compression method")] + ErrCompressionMethodUnset, + #[error("client+server cookie does not match")] + ErrCookieMismatch, + #[error("cookie must not be longer then 255 bytes")] + ErrCookieTooLong, + #[error("PSK Identity Hint provided but PSK is nil")] + ErrIdentityNoPsk, + #[error("no certificate provided")] + ErrInvalidCertificate, + #[error("cipher spec invalid")] + ErrInvalidCipherSpec, + #[error("invalid or unknown cipher suite")] + ErrInvalidCipherSuite, + #[error("unable to determine if ClientKeyExchange is a public key or PSK Identity")] + ErrInvalidClientKeyExchange, + #[error("invalid or unknown compression method")] + ErrInvalidCompressionMethod, + #[error("ECDSA signature contained zero or negative values")] + ErrInvalidEcdsasignature, + #[error("invalid or unknown elliptic curve type")] + ErrInvalidEllipticCurveType, + #[error("invalid extension type")] + ErrInvalidExtensionType, + #[error("invalid hash algorithm")] + ErrInvalidHashAlgorithm, + #[error("invalid named curve")] + ErrInvalidNamedCurve, + #[error("invalid private key type")] + ErrInvalidPrivateKey, + #[error("named curve and private key type does not match")] + ErrNamedCurveAndPrivateKeyMismatch, + #[error("invalid server name format")] + ErrInvalidSniFormat, + #[error("invalid signature algorithm")] + ErrInvalidSignatureAlgorithm, + #[error("expected and actual key signature do not match")] + ErrKeySignatureMismatch, + #[error("Conn can not be created with a nil nextConn")] + ErrNilNextConn, + #[error("connection can not be created, no CipherSuites satisfy this Config")] + ErrNoAvailableCipherSuites, + #[error("connection can not be created, no SignatureScheme satisfy this Config")] + ErrNoAvailableSignatureSchemes, + #[error("no certificates configured")] + ErrNoCertificates, + #[error("no config provided")] + ErrNoConfigProvided, + #[error("client requested zero or more elliptic curves that are not supported by the server")] + ErrNoSupportedEllipticCurves, + #[error("unsupported protocol version")] + ErrUnsupportedProtocolVersion, + #[error("Certificate and PSK provided")] + ErrPskAndCertificate, + #[error("PSK and PSK Identity Hint must both be set for client")] + ErrPskAndIdentityMustBeSetForClient, + #[error("SRTP support was requested but server did not respond with use_srtp extension")] + ErrRequestedButNoSrtpExtension, + #[error("Certificate is mandatory for server")] + ErrServerMustHaveCertificate, + #[error("client requested SRTP but we have no matching profiles")] + ErrServerNoMatchingSrtpProfile, + #[error( + "server requires the Extended Master Secret extension, but the client does not support it" + )] + ErrServerRequiredButNoClientEms, + #[error("expected and actual verify data does not match")] + ErrVerifyDataMismatch, + #[error("handshake message unset, unable to marshal")] + ErrHandshakeMessageUnset, + #[error("invalid flight number")] + ErrInvalidFlight, + #[error("unable to generate key signature, unimplemented")] + ErrKeySignatureGenerateUnimplemented, + #[error("unable to verify key signature, unimplemented")] + ErrKeySignatureVerifyUnimplemented, + #[error("data length and declared length do not match")] + ErrLengthMismatch, + #[error("buffer not long enough to contain nonce")] + ErrNotEnoughRoomForNonce, + #[error("feature has not been implemented yet")] + ErrNotImplemented, + #[error("sequence number overflow")] + ErrSequenceNumberOverflow, + #[error("unable to marshal fragmented handshakes")] + ErrUnableToMarshalFragmented, + #[error("invalid state machine transition")] + ErrInvalidFsmTransition, + #[error("ApplicationData with epoch of 0")] + ErrApplicationDataEpochZero, + #[error("unhandled contentType")] + ErrUnhandledContextType, + #[error("context canceled")] + ErrContextCanceled, + #[error("empty fragment")] + ErrEmptyFragment, + #[error("Alert is Fatal or Close Notify")] + ErrAlertFatalOrClose, + + #[error( + "Fragment buffer overflow. New size {new_size} is greater than specified max {max_size}" + )] + ErrFragmentBufferOverflow { new_size: usize, max_size: usize }, + + #[error("{0}")] + Io(#[source] IoError), + #[error("{0}")] + Util(#[from] util::Error), + #[error("utf8: {0}")] + Utf8(#[from] FromUtf8Error), + #[error("{0}")] + Sec1(#[source] sec1::Error), + #[error("{0}")] + Aes(#[from] aes::cipher::InvalidLength), + #[error("{0}")] + P256(#[source] P256Error), + #[error("{0}")] + RcGen(#[from] rcgen::Error), + #[error("mpsc send: {0}")] + MpscSend(String), + #[error("keying material: {0}")] + KeyingMaterial(#[from] KeyingMaterialExporterError), + + /// Error parsing a given PEM string. + #[error("invalid PEM: {0}")] + InvalidPEM(String), + + #[allow(non_camel_case_types)] + #[error("{0}")] + Other(String), +} + +#[derive(Debug, Error)] +#[error("io error: {0}")] +pub struct IoError(#[from] pub io::Error); + +// Workaround for wanting PartialEq for io::Error. +impl PartialEq for IoError { + fn eq(&self, other: &Self) -> bool { + self.0.kind() == other.0.kind() + } +} + +impl From for Error { + fn from(e: io::Error) -> Self { + Error::Io(IoError(e)) + } +} + +impl From for Error { + fn from(e: sec1::Error) -> Self { + Error::Sec1(e) + } +} + +#[derive(Debug, Error)] +#[error("{0}")] +pub struct P256Error(#[source] p256::elliptic_curve::Error); + +impl PartialEq for P256Error { + fn eq(&self, _: &Self) -> bool { + false + } +} + +impl From for Error { + fn from(e: p256::elliptic_curve::Error) -> Self { + Error::P256(P256Error(e)) + } +} + +// Because Tokio SendError is parameterized, we sadly lose the backtrace. +impl From> for Error { + fn from(e: MpscSendError) -> Self { + Error::MpscSend(e.to_string()) + } +} diff --git a/vendor/dtls/src/extension/extension_server_name.rs b/vendor/dtls/src/extension/extension_server_name.rs new file mode 100644 index 00000000..92fd259b --- /dev/null +++ b/vendor/dtls/src/extension/extension_server_name.rs @@ -0,0 +1,56 @@ +#[cfg(test)] +mod extension_server_name_test; + +use std::io::{Read, Write}; + +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; + +use super::*; + +const EXTENSION_SERVER_NAME_TYPE_DNSHOST_NAME: u8 = 0; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ExtensionServerName { + pub(crate) server_name: String, +} + +impl ExtensionServerName { + pub fn extension_value(&self) -> ExtensionValue { + ExtensionValue::ServerName + } + + pub fn size(&self) -> usize { + //TODO: check how to do cryptobyte? + 2 + 2 + 1 + 2 + self.server_name.len() + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + //TODO: check how to do cryptobyte? + writer.write_u16::(2 + 1 + 2 + self.server_name.len() as u16)?; + writer.write_u16::(1 + 2 + self.server_name.len() as u16)?; + writer.write_u8(EXTENSION_SERVER_NAME_TYPE_DNSHOST_NAME)?; + writer.write_u16::(self.server_name.len() as u16)?; + writer.write_all(self.server_name.as_bytes())?; + + Ok(writer.flush()?) + } + + pub fn unmarshal(reader: &mut R) -> Result { + //TODO: check how to do cryptobyte? + let _ = reader.read_u16::()? as usize; + let _ = reader.read_u16::()? as usize; + + let name_type = reader.read_u8()?; + if name_type != EXTENSION_SERVER_NAME_TYPE_DNSHOST_NAME { + return Err(Error::ErrInvalidSniFormat); + } + + let buf_len = reader.read_u16::()? as usize; + let mut buf: Vec = vec![0u8; buf_len]; + reader.read_exact(&mut buf)?; + + let server_name = String::from_utf8(buf)?; + + Ok(ExtensionServerName { server_name }) + } +} diff --git a/vendor/dtls/src/extension/extension_server_name/extension_server_name_test.rs b/vendor/dtls/src/extension/extension_server_name/extension_server_name_test.rs new file mode 100644 index 00000000..4cfb7a1a --- /dev/null +++ b/vendor/dtls/src/extension/extension_server_name/extension_server_name_test.rs @@ -0,0 +1,26 @@ +use std::io::{BufReader, BufWriter}; + +use super::*; + +#[test] +fn test_extension_server_name() -> Result<()> { + let extension = ExtensionServerName { + server_name: "test.domain".to_owned(), + }; + + let mut raw = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw.as_mut()); + extension.marshal(&mut writer)?; + } + + let mut reader = BufReader::new(raw.as_slice()); + let new_extension = ExtensionServerName::unmarshal(&mut reader)?; + + assert_eq!( + new_extension, extension, + "extensionServerName marshal: got {new_extension:?} expected {extension:?}", + ); + + Ok(()) +} diff --git a/vendor/dtls/src/extension/extension_supported_elliptic_curves.rs b/vendor/dtls/src/extension/extension_supported_elliptic_curves.rs new file mode 100644 index 00000000..64fe8084 --- /dev/null +++ b/vendor/dtls/src/extension/extension_supported_elliptic_curves.rs @@ -0,0 +1,50 @@ +#[cfg(test)] +mod extension_supported_elliptic_curves_test; + +use super::*; +use crate::curve::named_curve::*; + +const EXTENSION_SUPPORTED_GROUPS_HEADER_SIZE: usize = 6; + +/// ## Specifications +/// +/// * [RFC 8422 §5.1.1] +/// +/// [RFC 8422 §5.1.1]: https://tools.ietf.org/html/rfc8422#section-5.1.1 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ExtensionSupportedEllipticCurves { + pub elliptic_curves: Vec, +} + +impl ExtensionSupportedEllipticCurves { + pub fn extension_value(&self) -> ExtensionValue { + ExtensionValue::SupportedEllipticCurves + } + + pub fn size(&self) -> usize { + 2 + 2 + self.elliptic_curves.len() * 2 + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + writer.write_u16::(2 + 2 * self.elliptic_curves.len() as u16)?; + writer.write_u16::(2 * self.elliptic_curves.len() as u16)?; + for v in &self.elliptic_curves { + writer.write_u16::(*v as u16)?; + } + + Ok(writer.flush()?) + } + + pub fn unmarshal(reader: &mut R) -> Result { + let _ = reader.read_u16::()?; + + let group_count = reader.read_u16::()? as usize / 2; + let mut elliptic_curves = vec![]; + for _ in 0..group_count { + let elliptic_curve = reader.read_u16::()?.into(); + elliptic_curves.push(elliptic_curve); + } + + Ok(ExtensionSupportedEllipticCurves { elliptic_curves }) + } +} diff --git a/vendor/dtls/src/extension/extension_supported_elliptic_curves/extension_supported_elliptic_curves_test.rs b/vendor/dtls/src/extension/extension_supported_elliptic_curves/extension_supported_elliptic_curves_test.rs new file mode 100644 index 00000000..2bcf70c1 --- /dev/null +++ b/vendor/dtls/src/extension/extension_supported_elliptic_curves/extension_supported_elliptic_curves_test.rs @@ -0,0 +1,32 @@ +use std::io::{BufReader, BufWriter}; + +use super::*; + +#[test] +fn test_extension_supported_groups() -> Result<()> { + let raw_supported_groups = vec![0x0, 0x4, 0x0, 0x2, 0x0, 0x1d]; // 0x0, 0xa, + let parsed_supported_groups = ExtensionSupportedEllipticCurves { + elliptic_curves: vec![NamedCurve::X25519], + }; + + let mut raw = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw.as_mut()); + parsed_supported_groups.marshal(&mut writer)?; + } + + assert_eq!( + raw, raw_supported_groups, + "extensionSupportedGroups marshal: got {raw:?}, want {raw_supported_groups:?}" + ); + + let mut reader = BufReader::new(raw.as_slice()); + let new_supported_groups = ExtensionSupportedEllipticCurves::unmarshal(&mut reader)?; + + assert_eq!( + new_supported_groups, parsed_supported_groups, + "extensionSupportedGroups unmarshal: got {new_supported_groups:?}, want {parsed_supported_groups:?}" + ); + + Ok(()) +} diff --git a/vendor/dtls/src/extension/extension_supported_point_formats.rs b/vendor/dtls/src/extension/extension_supported_point_formats.rs new file mode 100644 index 00000000..60e5df94 --- /dev/null +++ b/vendor/dtls/src/extension/extension_supported_point_formats.rs @@ -0,0 +1,53 @@ +#[cfg(test)] +mod extension_supported_point_formats_test; + +use super::*; + +const EXTENSION_SUPPORTED_POINT_FORMATS_SIZE: usize = 5; + +pub type EllipticCurvePointFormat = u8; + +pub const ELLIPTIC_CURVE_POINT_FORMAT_UNCOMPRESSED: EllipticCurvePointFormat = 0; + +/// ## Specifications +/// +/// * [RFC 4492 §5.1.2] +/// +/// [RFC 4492 §5.1.2]: https://tools.ietf.org/html/rfc4492#section-5.1.2 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ExtensionSupportedPointFormats { + pub(crate) point_formats: Vec, +} + +impl ExtensionSupportedPointFormats { + pub fn extension_value(&self) -> ExtensionValue { + ExtensionValue::SupportedPointFormats + } + + pub fn size(&self) -> usize { + 2 + 1 + self.point_formats.len() + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + writer.write_u16::(1 + self.point_formats.len() as u16)?; + writer.write_u8(self.point_formats.len() as u8)?; + for v in &self.point_formats { + writer.write_u8(*v)?; + } + + Ok(writer.flush()?) + } + + pub fn unmarshal(reader: &mut R) -> Result { + let _ = reader.read_u16::()?; + + let point_format_count = reader.read_u8()? as usize; + let mut point_formats = vec![]; + for _ in 0..point_format_count { + let point_format = reader.read_u8()?; + point_formats.push(point_format); + } + + Ok(ExtensionSupportedPointFormats { point_formats }) + } +} diff --git a/vendor/dtls/src/extension/extension_supported_point_formats/extension_supported_point_formats_test.rs b/vendor/dtls/src/extension/extension_supported_point_formats/extension_supported_point_formats_test.rs new file mode 100644 index 00000000..e624a98a --- /dev/null +++ b/vendor/dtls/src/extension/extension_supported_point_formats/extension_supported_point_formats_test.rs @@ -0,0 +1,33 @@ +use std::io::{BufReader, BufWriter}; + +use super::*; + +#[test] +fn test_extension_supported_point_formats() -> Result<()> { + let raw_extension_supported_point_formats = vec![0x00, 0x02, 0x01, 0x00]; // 0x00, 0x0b, + let parsed_extension_supported_point_formats = ExtensionSupportedPointFormats { + point_formats: vec![ELLIPTIC_CURVE_POINT_FORMAT_UNCOMPRESSED], + }; + + let mut raw = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw.as_mut()); + parsed_extension_supported_point_formats.marshal(&mut writer)?; + } + + assert_eq!( + raw, raw_extension_supported_point_formats, + "extensionSupportedPointFormats marshal: got {raw:?}, want {raw_extension_supported_point_formats:?}" + ); + + let mut reader = BufReader::new(raw.as_slice()); + let new_extension_supported_point_formats = + ExtensionSupportedPointFormats::unmarshal(&mut reader)?; + + assert_eq!( + new_extension_supported_point_formats, parsed_extension_supported_point_formats, + "extensionSupportedPointFormats unmarshal: got {new_extension_supported_point_formats:?}, want {parsed_extension_supported_point_formats:?}" + ); + + Ok(()) +} diff --git a/vendor/dtls/src/extension/extension_supported_signature_algorithms.rs b/vendor/dtls/src/extension/extension_supported_signature_algorithms.rs new file mode 100644 index 00000000..24ca158d --- /dev/null +++ b/vendor/dtls/src/extension/extension_supported_signature_algorithms.rs @@ -0,0 +1,54 @@ +#[cfg(test)] +mod extension_supported_signature_algorithms_test; + +use super::*; +use crate::signature_hash_algorithm::*; + +const EXTENSION_SUPPORTED_SIGNATURE_ALGORITHMS_HEADER_SIZE: usize = 6; + +/// ## Specifications +/// +/// * [RFC 5246 §7.4.1.4.1] +/// +/// [RFC 5246 §7.4.1.4.1]: https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ExtensionSupportedSignatureAlgorithms { + pub(crate) signature_hash_algorithms: Vec, +} + +impl ExtensionSupportedSignatureAlgorithms { + pub fn extension_value(&self) -> ExtensionValue { + ExtensionValue::SupportedSignatureAlgorithms + } + + pub fn size(&self) -> usize { + 2 + 2 + self.signature_hash_algorithms.len() * 2 + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + writer.write_u16::(2 + 2 * self.signature_hash_algorithms.len() as u16)?; + writer.write_u16::(2 * self.signature_hash_algorithms.len() as u16)?; + for v in &self.signature_hash_algorithms { + writer.write_u8(v.hash as u8)?; + writer.write_u8(v.signature as u8)?; + } + + Ok(writer.flush()?) + } + + pub fn unmarshal(reader: &mut R) -> Result { + let _ = reader.read_u16::()?; + + let algorithm_count = reader.read_u16::()? as usize / 2; + let mut signature_hash_algorithms = vec![]; + for _ in 0..algorithm_count { + let hash = reader.read_u8()?.into(); + let signature = reader.read_u8()?.into(); + signature_hash_algorithms.push(SignatureHashAlgorithm { hash, signature }); + } + + Ok(ExtensionSupportedSignatureAlgorithms { + signature_hash_algorithms, + }) + } +} diff --git a/vendor/dtls/src/extension/extension_supported_signature_algorithms/extension_supported_signature_algorithms_test.rs b/vendor/dtls/src/extension/extension_supported_signature_algorithms/extension_supported_signature_algorithms_test.rs new file mode 100644 index 00000000..e531fee0 --- /dev/null +++ b/vendor/dtls/src/extension/extension_supported_signature_algorithms/extension_supported_signature_algorithms_test.rs @@ -0,0 +1,48 @@ +use std::io::{BufReader, BufWriter}; + +use super::*; + +#[test] +fn test_extension_supported_signature_algorithms() -> Result<()> { + let raw_extension_supported_signature_algorithms = + vec![0x00, 0x08, 0x00, 0x06, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03]; //0x00, 0x0d, + let parsed_extension_supported_signature_algorithms = ExtensionSupportedSignatureAlgorithms { + signature_hash_algorithms: vec![ + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha256, + signature: SignatureAlgorithm::Ecdsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha384, + signature: SignatureAlgorithm::Ecdsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha512, + signature: SignatureAlgorithm::Ecdsa, + }, + ], + }; + + let mut raw = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw.as_mut()); + parsed_extension_supported_signature_algorithms.marshal(&mut writer)?; + } + + assert_eq!( + raw, raw_extension_supported_signature_algorithms, + "extensionSupportedSignatureAlgorithms marshal: got {raw:?}, want {raw_extension_supported_signature_algorithms:?}" + ); + + let mut reader = BufReader::new(raw.as_slice()); + let new_extension_supported_signature_algorithms = + ExtensionSupportedSignatureAlgorithms::unmarshal(&mut reader)?; + + assert_eq!( + new_extension_supported_signature_algorithms, + parsed_extension_supported_signature_algorithms, + "extensionSupportedSignatureAlgorithms unmarshal: got {new_extension_supported_signature_algorithms:?}, want {parsed_extension_supported_signature_algorithms:?}" + ); + + Ok(()) +} diff --git a/vendor/dtls/src/extension/extension_use_extended_master_secret.rs b/vendor/dtls/src/extension/extension_use_extended_master_secret.rs new file mode 100644 index 00000000..d6a1d0a4 --- /dev/null +++ b/vendor/dtls/src/extension/extension_use_extended_master_secret.rs @@ -0,0 +1,39 @@ +#[cfg(test)] +mod extension_use_extended_master_secret_test; + +use super::*; + +const EXTENSION_USE_EXTENDED_MASTER_SECRET_HEADER_SIZE: usize = 4; + +/// ## Specifications +/// +/// * [RFC 8422] +/// +/// [RFC 8422]: https://tools.ietf.org/html/rfc8422 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ExtensionUseExtendedMasterSecret { + pub(crate) supported: bool, +} + +impl ExtensionUseExtendedMasterSecret { + pub fn extension_value(&self) -> ExtensionValue { + ExtensionValue::UseExtendedMasterSecret + } + + pub fn size(&self) -> usize { + 2 + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + // length + writer.write_u16::(0)?; + + Ok(writer.flush()?) + } + + pub fn unmarshal(reader: &mut R) -> Result { + let _ = reader.read_u16::()?; + + Ok(ExtensionUseExtendedMasterSecret { supported: true }) + } +} diff --git a/vendor/dtls/src/extension/extension_use_extended_master_secret/extension_use_extended_master_secret_test.rs b/vendor/dtls/src/extension/extension_use_extended_master_secret/extension_use_extended_master_secret_test.rs new file mode 100644 index 00000000..d2b0dd42 --- /dev/null +++ b/vendor/dtls/src/extension/extension_use_extended_master_secret/extension_use_extended_master_secret_test.rs @@ -0,0 +1,32 @@ +use std::io::{BufReader, BufWriter}; + +use super::*; + +#[test] +fn test_extension_use_extended_master_secret() -> Result<()> { + let raw_extension_use_extended_master_secret = vec![0x00, 0x00]; + let parsed_extension_use_extended_master_secret = + ExtensionUseExtendedMasterSecret { supported: true }; + + let mut raw = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw.as_mut()); + parsed_extension_use_extended_master_secret.marshal(&mut writer)?; + } + + assert_eq!( + raw, raw_extension_use_extended_master_secret, + "extension_use_extended_master_secret marshal: got {raw:?}, want {raw_extension_use_extended_master_secret:?}" + ); + + let mut reader = BufReader::new(raw.as_slice()); + let new_extension_use_extended_master_secret = + ExtensionUseExtendedMasterSecret::unmarshal(&mut reader)?; + + assert_eq!( + new_extension_use_extended_master_secret, parsed_extension_use_extended_master_secret, + "extension_use_extended_master_secret unmarshal: got {new_extension_use_extended_master_secret:?}, want {parsed_extension_use_extended_master_secret:?}" + ); + + Ok(()) +} diff --git a/vendor/dtls/src/extension/extension_use_srtp.rs b/vendor/dtls/src/extension/extension_use_srtp.rs new file mode 100644 index 00000000..5b2dd8b0 --- /dev/null +++ b/vendor/dtls/src/extension/extension_use_srtp.rs @@ -0,0 +1,88 @@ +#[cfg(test)] +mod extension_use_srtp_test; + +use super::*; + +// SRTPProtectionProfile defines the parameters and options that are in effect for the SRTP processing +/// ## Specifications +/// +/// * [RFC 5764 §4.1.2] +/// +/// [RFC 5764 §4.1.2]: https://tools.ietf.org/html/rfc5764#section-4.1.2 +#[allow(non_camel_case_types)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum SrtpProtectionProfile { + Srtp_Aes128_Cm_Hmac_Sha1_80 = 0x0001, + Srtp_Aes128_Cm_Hmac_Sha1_32 = 0x0002, + Srtp_Aead_Aes_128_Gcm = 0x0007, + Srtp_Aead_Aes_256_Gcm = 0x0008, + Unsupported, +} + +impl From for SrtpProtectionProfile { + fn from(val: u16) -> Self { + match val { + 0x0001 => SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_80, + 0x0002 => SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_32, + 0x0007 => SrtpProtectionProfile::Srtp_Aead_Aes_128_Gcm, + 0x0008 => SrtpProtectionProfile::Srtp_Aead_Aes_256_Gcm, + _ => SrtpProtectionProfile::Unsupported, + } + } +} + +const EXTENSION_USE_SRTPHEADER_SIZE: usize = 6; + +/// ## Specifications +/// +/// * [RFC 8422] +/// +/// [RFC 8422]: https://tools.ietf.org/html/rfc8422 +#[allow(non_camel_case_types)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ExtensionUseSrtp { + pub(crate) protection_profiles: Vec, +} + +impl ExtensionUseSrtp { + pub fn extension_value(&self) -> ExtensionValue { + ExtensionValue::UseSrtp + } + + pub fn size(&self) -> usize { + 2 + 2 + self.protection_profiles.len() * 2 + 1 + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + writer.write_u16::( + 2 + /* MKI Length */ 1 + 2 * self.protection_profiles.len() as u16, + )?; + writer.write_u16::(2 * self.protection_profiles.len() as u16)?; + for v in &self.protection_profiles { + writer.write_u16::(*v as u16)?; + } + + /* MKI Length */ + writer.write_u8(0x00)?; + + Ok(writer.flush()?) + } + + pub fn unmarshal(reader: &mut R) -> Result { + let _ = reader.read_u16::()?; + + let profile_count = reader.read_u16::()? as usize / 2; + let mut protection_profiles = vec![]; + for _ in 0..profile_count { + let protection_profile = reader.read_u16::()?.into(); + protection_profiles.push(protection_profile); + } + + /* MKI Length */ + let _ = reader.read_u8()?; + + Ok(ExtensionUseSrtp { + protection_profiles, + }) + } +} diff --git a/vendor/dtls/src/extension/extension_use_srtp/extension_use_srtp_test.rs b/vendor/dtls/src/extension/extension_use_srtp/extension_use_srtp_test.rs new file mode 100644 index 00000000..c58ba56a --- /dev/null +++ b/vendor/dtls/src/extension/extension_use_srtp/extension_use_srtp_test.rs @@ -0,0 +1,32 @@ +use std::io::{BufReader, BufWriter}; + +use super::*; + +#[test] +fn test_extension_use_srtp() -> Result<()> { + let raw_use_srtp = vec![0x00, 0x05, 0x00, 0x02, 0x00, 0x01, 0x00]; //0x00, 0x0e, + let parsed_use_srtp = ExtensionUseSrtp { + protection_profiles: vec![SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_80], + }; + + let mut raw = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw.as_mut()); + parsed_use_srtp.marshal(&mut writer)?; + } + + assert_eq!( + raw, raw_use_srtp, + "extensionUseSRTP marshal: got {raw:?}, want {raw_use_srtp:?}" + ); + + let mut reader = BufReader::new(raw.as_slice()); + let new_use_srtp = ExtensionUseSrtp::unmarshal(&mut reader)?; + + assert_eq!( + new_use_srtp, parsed_use_srtp, + "extensionUseSRTP unmarshal: got {new_use_srtp:?}, want {parsed_use_srtp:?}" + ); + + Ok(()) +} diff --git a/vendor/dtls/src/extension/mod.rs b/vendor/dtls/src/extension/mod.rs new file mode 100644 index 00000000..20300381 --- /dev/null +++ b/vendor/dtls/src/extension/mod.rs @@ -0,0 +1,130 @@ +pub mod extension_server_name; +pub mod extension_supported_elliptic_curves; +pub mod extension_supported_point_formats; +pub mod extension_supported_signature_algorithms; +pub mod extension_use_extended_master_secret; +pub mod extension_use_srtp; +pub mod renegotiation_info; + +use std::io::{Read, Write}; + +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use extension_server_name::*; +use extension_supported_elliptic_curves::*; +use extension_supported_point_formats::*; +use extension_supported_signature_algorithms::*; +use extension_use_extended_master_secret::*; +use extension_use_srtp::*; + +use crate::error::*; +use crate::extension::renegotiation_info::ExtensionRenegotiationInfo; + +// https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ExtensionValue { + ServerName = 0, + SupportedEllipticCurves = 10, + SupportedPointFormats = 11, + SupportedSignatureAlgorithms = 13, + UseSrtp = 14, + UseExtendedMasterSecret = 23, + RenegotiationInfo = 65281, + Unsupported, +} + +impl From for ExtensionValue { + fn from(val: u16) -> Self { + match val { + 0 => ExtensionValue::ServerName, + 10 => ExtensionValue::SupportedEllipticCurves, + 11 => ExtensionValue::SupportedPointFormats, + 13 => ExtensionValue::SupportedSignatureAlgorithms, + 14 => ExtensionValue::UseSrtp, + 23 => ExtensionValue::UseExtendedMasterSecret, + 65281 => ExtensionValue::RenegotiationInfo, + _ => ExtensionValue::Unsupported, + } + } +} + +#[derive(PartialEq, Eq, Debug, Clone)] +pub enum Extension { + ServerName(ExtensionServerName), + SupportedEllipticCurves(ExtensionSupportedEllipticCurves), + SupportedPointFormats(ExtensionSupportedPointFormats), + SupportedSignatureAlgorithms(ExtensionSupportedSignatureAlgorithms), + UseSrtp(ExtensionUseSrtp), + UseExtendedMasterSecret(ExtensionUseExtendedMasterSecret), + RenegotiationInfo(ExtensionRenegotiationInfo), +} + +impl Extension { + pub fn extension_value(&self) -> ExtensionValue { + match self { + Extension::ServerName(ext) => ext.extension_value(), + Extension::SupportedEllipticCurves(ext) => ext.extension_value(), + Extension::SupportedPointFormats(ext) => ext.extension_value(), + Extension::SupportedSignatureAlgorithms(ext) => ext.extension_value(), + Extension::UseSrtp(ext) => ext.extension_value(), + Extension::UseExtendedMasterSecret(ext) => ext.extension_value(), + Extension::RenegotiationInfo(ext) => ext.extension_value(), + } + } + + pub fn size(&self) -> usize { + let mut len = 2; + + len += match self { + Extension::ServerName(ext) => ext.size(), + Extension::SupportedEllipticCurves(ext) => ext.size(), + Extension::SupportedPointFormats(ext) => ext.size(), + Extension::SupportedSignatureAlgorithms(ext) => ext.size(), + Extension::UseSrtp(ext) => ext.size(), + Extension::UseExtendedMasterSecret(ext) => ext.size(), + Extension::RenegotiationInfo(ext) => ext.size(), + }; + + len + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + writer.write_u16::(self.extension_value() as u16)?; + match self { + Extension::ServerName(ext) => ext.marshal(writer), + Extension::SupportedEllipticCurves(ext) => ext.marshal(writer), + Extension::SupportedPointFormats(ext) => ext.marshal(writer), + Extension::SupportedSignatureAlgorithms(ext) => ext.marshal(writer), + Extension::UseSrtp(ext) => ext.marshal(writer), + Extension::UseExtendedMasterSecret(ext) => ext.marshal(writer), + Extension::RenegotiationInfo(ext) => ext.marshal(writer), + } + } + + pub fn unmarshal(reader: &mut R) -> Result { + let extension_value: ExtensionValue = reader.read_u16::()?.into(); + match extension_value { + ExtensionValue::ServerName => Ok(Extension::ServerName( + ExtensionServerName::unmarshal(reader)?, + )), + ExtensionValue::SupportedEllipticCurves => Ok(Extension::SupportedEllipticCurves( + ExtensionSupportedEllipticCurves::unmarshal(reader)?, + )), + ExtensionValue::SupportedPointFormats => Ok(Extension::SupportedPointFormats( + ExtensionSupportedPointFormats::unmarshal(reader)?, + )), + ExtensionValue::SupportedSignatureAlgorithms => { + Ok(Extension::SupportedSignatureAlgorithms( + ExtensionSupportedSignatureAlgorithms::unmarshal(reader)?, + )) + } + ExtensionValue::UseSrtp => Ok(Extension::UseSrtp(ExtensionUseSrtp::unmarshal(reader)?)), + ExtensionValue::UseExtendedMasterSecret => Ok(Extension::UseExtendedMasterSecret( + ExtensionUseExtendedMasterSecret::unmarshal(reader)?, + )), + ExtensionValue::RenegotiationInfo => Ok(Extension::RenegotiationInfo( + ExtensionRenegotiationInfo::unmarshal(reader)?, + )), + _ => Err(Error::ErrInvalidExtensionType), + } + } +} diff --git a/vendor/dtls/src/extension/renegotiation_info.rs b/vendor/dtls/src/extension/renegotiation_info.rs new file mode 100644 index 00000000..80ebbbec --- /dev/null +++ b/vendor/dtls/src/extension/renegotiation_info.rs @@ -0,0 +1,53 @@ +#[cfg(test)] +mod renegotiation_info_test; + +use super::*; +use crate::error::Error::ErrInvalidPacketLength; + +const RENEGOTIATION_INFO_HEADER_SIZE: usize = 5; + +/// RenegotiationInfo allows a Client/Server to +/// communicate their renegotiation support +/// +/// ## Specifications +/// +/// * [RFC 5746] +/// +/// [RFC 5746]: https://tools.ietf.org/html/rfc5746 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ExtensionRenegotiationInfo { + pub(crate) renegotiated_connection: u8, +} + +impl ExtensionRenegotiationInfo { + // TypeValue returns the extension TypeValue + pub fn extension_value(&self) -> ExtensionValue { + ExtensionValue::RenegotiationInfo + } + + pub fn size(&self) -> usize { + 3 + } + + /// marshal encodes the extension + pub fn marshal(&self, writer: &mut W) -> Result<()> { + writer.write_u16::(1)?; //length + writer.write_u8(self.renegotiated_connection)?; + + Ok(writer.flush()?) + } + + /// Unmarshal populates the extension from encoded data + pub fn unmarshal(reader: &mut R) -> Result { + let l = reader.read_u16::()?; //length + if l != 1 { + return Err(ErrInvalidPacketLength); + } + + let renegotiated_connection = reader.read_u8()?; + + Ok(ExtensionRenegotiationInfo { + renegotiated_connection, + }) + } +} diff --git a/vendor/dtls/src/extension/renegotiation_info/renegotiation_info_test.rs b/vendor/dtls/src/extension/renegotiation_info/renegotiation_info_test.rs new file mode 100644 index 00000000..5266a94c --- /dev/null +++ b/vendor/dtls/src/extension/renegotiation_info/renegotiation_info_test.rs @@ -0,0 +1,26 @@ +use std::io::{BufReader, BufWriter}; + +use super::*; + +#[test] +fn test_renegotiation_info() -> Result<()> { + let extension = ExtensionRenegotiationInfo { + renegotiated_connection: 0, + }; + + let mut raw = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw.as_mut()); + extension.marshal(&mut writer)?; + } + + let mut reader = BufReader::new(raw.as_slice()); + let new_extension = ExtensionRenegotiationInfo::unmarshal(&mut reader)?; + + assert_eq!( + new_extension.renegotiated_connection, + extension.renegotiated_connection + ); + + Ok(()) +} diff --git a/vendor/dtls/src/flight/flight0.rs b/vendor/dtls/src/flight/flight0.rs new file mode 100644 index 00000000..55805daa --- /dev/null +++ b/vendor/dtls/src/flight/flight0.rs @@ -0,0 +1,209 @@ +use std::fmt; +use std::sync::atomic::Ordering; + +use async_trait::async_trait; +use rand::Rng; + +use super::flight2::*; +use super::*; +use crate::config::*; +use crate::conn::*; +use crate::curve::named_curve::NamedCurve; +use crate::error::Error; +use crate::extension::*; +use crate::handshake::*; +use crate::record_layer::record_layer_header::*; +use crate::*; + +#[derive(Debug, PartialEq)] +pub(crate) struct Flight0; + +impl fmt::Display for Flight0 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Flight 0") + } +} + +#[async_trait] +impl Flight for Flight0 { + async fn parse( + &self, + _tx: &mut mpsc::Sender>, + state: &mut State, + cache: &HandshakeCache, + cfg: &HandshakeConfig, + ) -> Result, (Option, Option)> { + let (seq, msgs) = match cache + .full_pull_map( + 0, + &[HandshakeCachePullRule { + typ: HandshakeType::ClientHello, + epoch: cfg.initial_epoch, + is_client: true, + optional: false, + }], + ) + .await + { + Ok((seq, msgs)) => (seq, msgs), + Err(_) => return Err((None, None)), + }; + + state.handshake_recv_sequence = seq; + + if let Some(message) = msgs.get(&HandshakeType::ClientHello) { + // Validate type + let client_hello = match message { + HandshakeMessage::ClientHello(client_hello) => client_hello, + _ => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + None, + )) + } + }; + + if client_hello.version != PROTOCOL_VERSION1_2 { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::ProtocolVersion, + }), + Some(Error::ErrUnsupportedProtocolVersion), + )); + } + + state.remote_random = client_hello.random.clone(); + + if let Ok(id) = + find_matching_cipher_suite(&client_hello.cipher_suites, &cfg.local_cipher_suites) + { + if let Ok(cipher_suite) = cipher_suite_for_id(id) { + log::debug!( + "[handshake:{}] use cipher suite: {}", + srv_cli_str(state.is_client), + cipher_suite.to_string() + ); + let mut cs = state.cipher_suite.lock().await; + *cs = Some(cipher_suite); + } + } else { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InsufficientSecurity, + }), + Some(Error::ErrCipherSuiteNoIntersection), + )); + } + + for extension in &client_hello.extensions { + match extension { + Extension::SupportedEllipticCurves(e) => { + if e.elliptic_curves.is_empty() { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InsufficientSecurity, + }), + Some(Error::ErrNoSupportedEllipticCurves), + )); + } + for curve in e.elliptic_curves.iter() { + if curve != &NamedCurve::Unsupported { + state.named_curve = *curve; + break; + } + } + } + Extension::UseSrtp(e) => { + if let Ok(profile) = find_matching_srtp_profile( + &e.protection_profiles, + &cfg.local_srtp_protection_profiles, + ) { + state.srtp_protection_profile = profile; + } else { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InsufficientSecurity, + }), + Some(Error::ErrServerNoMatchingSrtpProfile), + )); + } + } + Extension::UseExtendedMasterSecret(_) => { + if cfg.extended_master_secret != ExtendedMasterSecretType::Disable { + state.extended_master_secret = true; + } + } + Extension::ServerName(e) => { + state.server_name.clone_from(&e.server_name); // remote server name + } + _ => {} + } + } + + if cfg.extended_master_secret == ExtendedMasterSecretType::Require + && !state.extended_master_secret + { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InsufficientSecurity, + }), + Some(Error::ErrServerRequiredButNoClientEms), + )); + } + + if state.local_keypair.is_none() { + state.local_keypair = match state.named_curve.generate_keypair() { + Ok(local_keypar) => Some(local_keypar), + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::IllegalParameter, + }), + Some(err), + )) + } + }; + } + + Ok(Box::new(Flight2 {})) + } else { + Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + None, + )) + } + } + + async fn generate( + &self, + state: &mut State, + _cache: &HandshakeCache, + _cfg: &HandshakeConfig, + ) -> Result, (Option, Option)> { + // Initialize + state.cookie = vec![0; COOKIE_LENGTH]; + rand::rng().fill(state.cookie.as_mut_slice()); + + //TODO: figure out difference between golang's atom store and rust atom store + let zero_epoch = 0; + state.local_epoch.store(zero_epoch, Ordering::SeqCst); + state.remote_epoch.store(zero_epoch, Ordering::SeqCst); + + state.named_curve = DEFAULT_NAMED_CURVE; + state.local_random.populate(); + + Ok(vec![]) + } +} diff --git a/vendor/dtls/src/flight/flight1.rs b/vendor/dtls/src/flight/flight1.rs new file mode 100644 index 00000000..0cb37e62 --- /dev/null +++ b/vendor/dtls/src/flight/flight1.rs @@ -0,0 +1,193 @@ +use std::fmt; +use std::sync::atomic::Ordering; + +use async_trait::async_trait; + +use super::flight3::*; +use super::*; +use crate::compression_methods::*; +use crate::config::*; +use crate::conn::*; +use crate::content::*; +use crate::curve::named_curve::*; +use crate::error::Error; +use crate::extension::extension_server_name::*; +use crate::extension::extension_supported_elliptic_curves::*; +use crate::extension::extension_supported_point_formats::*; +use crate::extension::extension_supported_signature_algorithms::*; +use crate::extension::extension_use_extended_master_secret::*; +use crate::extension::extension_use_srtp::*; +use crate::extension::renegotiation_info::ExtensionRenegotiationInfo; +use crate::extension::*; +use crate::handshake::handshake_message_client_hello::*; +use crate::handshake::*; +use crate::record_layer::record_layer_header::*; +use crate::record_layer::*; + +#[derive(Debug, PartialEq)] +pub(crate) struct Flight1; + +impl fmt::Display for Flight1 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Flight 1") + } +} + +#[async_trait] +impl Flight for Flight1 { + async fn parse( + &self, + tx: &mut mpsc::Sender>, + state: &mut State, + cache: &HandshakeCache, + cfg: &HandshakeConfig, + ) -> Result, (Option, Option)> { + // HelloVerifyRequest can be skipped by the server, + // so allow ServerHello during flight1 also + let (seq, msgs) = match cache + .full_pull_map( + state.handshake_recv_sequence, + &[ + HandshakeCachePullRule { + typ: HandshakeType::HelloVerifyRequest, + epoch: cfg.initial_epoch, + is_client: false, + optional: true, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerHello, + epoch: cfg.initial_epoch, + is_client: false, + optional: true, + }, + ], + ) + .await + { + // No valid message received. Keep reading + Ok((seq, msgs)) => (seq, msgs), + Err(_) => return Err((None, None)), + }; + + if msgs.contains_key(&HandshakeType::ServerHello) { + // Flight1 and flight2 were skipped. + // Parse as flight3. + let flight3 = Flight3 {}; + return flight3.parse(tx, state, cache, cfg).await; + } + + if let Some(message) = msgs.get(&HandshakeType::HelloVerifyRequest) { + // DTLS 1.2 clients must not assume that the server will use the protocol version + // specified in HelloVerifyRequest message. RFC 6347 Section 4.2.1 + let h = match message { + HandshakeMessage::HelloVerifyRequest(h) => h, + _ => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + None, + )) + } + }; + + if h.version != PROTOCOL_VERSION1_0 && h.version != PROTOCOL_VERSION1_2 { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::ProtocolVersion, + }), + Some(Error::ErrUnsupportedProtocolVersion), + )); + } + + state.cookie.clone_from(&h.cookie); + state.handshake_recv_sequence = seq; + Ok(Box::new(Flight3 {})) + } else { + Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + None, + )) + } + } + + async fn generate( + &self, + state: &mut State, + _cache: &HandshakeCache, + cfg: &HandshakeConfig, + ) -> Result, (Option, Option)> { + let zero_epoch = 0; + state.local_epoch.store(zero_epoch, Ordering::SeqCst); + state.remote_epoch.store(zero_epoch, Ordering::SeqCst); + + state.named_curve = DEFAULT_NAMED_CURVE; + state.cookie = vec![]; + state.local_random.populate(); + + let mut extensions = vec![ + Extension::SupportedSignatureAlgorithms(ExtensionSupportedSignatureAlgorithms { + signature_hash_algorithms: cfg.local_signature_schemes.clone(), + }), + Extension::RenegotiationInfo(ExtensionRenegotiationInfo { + renegotiated_connection: 0, + }), + ]; + + if cfg.local_psk_callback.is_none() { + extensions.extend_from_slice(&[ + Extension::SupportedEllipticCurves(ExtensionSupportedEllipticCurves { + elliptic_curves: vec![NamedCurve::P256, NamedCurve::X25519, NamedCurve::P384], + }), + Extension::SupportedPointFormats(ExtensionSupportedPointFormats { + point_formats: vec![ELLIPTIC_CURVE_POINT_FORMAT_UNCOMPRESSED], + }), + ]); + } + + if !cfg.local_srtp_protection_profiles.is_empty() { + extensions.push(Extension::UseSrtp(ExtensionUseSrtp { + protection_profiles: cfg.local_srtp_protection_profiles.clone(), + })); + } + + if cfg.extended_master_secret == ExtendedMasterSecretType::Request + || cfg.extended_master_secret == ExtendedMasterSecretType::Require + { + extensions.push(Extension::UseExtendedMasterSecret( + ExtensionUseExtendedMasterSecret { supported: true }, + )); + } + + if !cfg.server_name.is_empty() { + extensions.push(Extension::ServerName(ExtensionServerName { + server_name: cfg.server_name.clone(), + })); + } + + Ok(vec![Packet { + record: RecordLayer::new( + PROTOCOL_VERSION1_2, + 0, + Content::Handshake(Handshake::new(HandshakeMessage::ClientHello( + HandshakeMessageClientHello { + version: PROTOCOL_VERSION1_2, + random: state.local_random.clone(), + cookie: state.cookie.clone(), + + cipher_suites: cfg.local_cipher_suites.clone(), + compression_methods: default_compression_methods(), + extensions, + }, + ))), + ), + should_encrypt: false, + reset_local_sequence_number: false, + }]) + } +} diff --git a/vendor/dtls/src/flight/flight2.rs b/vendor/dtls/src/flight/flight2.rs new file mode 100644 index 00000000..9703c9aa --- /dev/null +++ b/vendor/dtls/src/flight/flight2.rs @@ -0,0 +1,131 @@ +use std::fmt; + +use async_trait::async_trait; + +use super::flight0::*; +use super::flight4::*; +use super::*; +use crate::content::*; +use crate::error::Error; +use crate::handshake::handshake_message_hello_verify_request::*; +use crate::handshake::*; +use crate::record_layer::record_layer_header::*; + +#[derive(Debug, PartialEq)] +pub(crate) struct Flight2; + +impl fmt::Display for Flight2 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Flight 2") + } +} + +#[async_trait] +impl Flight for Flight2 { + fn has_retransmit(&self) -> bool { + false + } + + async fn parse( + &self, + tx: &mut mpsc::Sender>, + state: &mut State, + cache: &HandshakeCache, + cfg: &HandshakeConfig, + ) -> Result, (Option, Option)> { + let (seq, msgs) = match cache + .full_pull_map( + state.handshake_recv_sequence, + &[HandshakeCachePullRule { + typ: HandshakeType::ClientHello, + epoch: cfg.initial_epoch, + is_client: true, + optional: false, + }], + ) + .await + { + // No valid message received. Keep reading + Ok((seq, msgs)) => (seq, msgs), + + // Client may retransmit the first ClientHello when HelloVerifyRequest is dropped. + // Parse as flight 0 in this case. + Err(_) => return Flight0 {}.parse(tx, state, cache, cfg).await, + }; + + state.handshake_recv_sequence = seq; + + if let Some(message) = msgs.get(&HandshakeType::ClientHello) { + // Validate type + let client_hello = match message { + HandshakeMessage::ClientHello(client_hello) => client_hello, + _ => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + None, + )) + } + }; + + if client_hello.version != PROTOCOL_VERSION1_2 { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::ProtocolVersion, + }), + Some(Error::ErrUnsupportedProtocolVersion), + )); + } + + if client_hello.cookie.is_empty() { + return Err((None, None)); + } + + if state.cookie != client_hello.cookie { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::AccessDenied, + }), + Some(Error::ErrCookieMismatch), + )); + } + + Ok(Box::new(Flight4 {})) + } else { + Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + None, + )) + } + } + + async fn generate( + &self, + state: &mut State, + _cache: &HandshakeCache, + _cfg: &HandshakeConfig, + ) -> Result, (Option, Option)> { + state.handshake_send_sequence = 0; + Ok(vec![Packet { + record: RecordLayer::new( + PROTOCOL_VERSION1_2, + 0, + Content::Handshake(Handshake::new(HandshakeMessage::HelloVerifyRequest( + HandshakeMessageHelloVerifyRequest { + version: PROTOCOL_VERSION1_2, + cookie: state.cookie.clone(), + }, + ))), + ), + should_encrypt: false, + reset_local_sequence_number: false, + }]) + } +} diff --git a/vendor/dtls/src/flight/flight3.rs b/vendor/dtls/src/flight/flight3.rs new file mode 100644 index 00000000..a4e2725c --- /dev/null +++ b/vendor/dtls/src/flight/flight3.rs @@ -0,0 +1,470 @@ +use std::fmt; + +use async_trait::async_trait; +use log::*; + +use super::flight5::*; +use super::*; +use crate::cipher_suite::cipher_suite_for_id; +use crate::compression_methods::*; +use crate::config::*; +use crate::content::*; +use crate::curve::named_curve::*; +use crate::error::Error; +use crate::extension::extension_server_name::*; +use crate::extension::extension_supported_elliptic_curves::*; +use crate::extension::extension_supported_point_formats::*; +use crate::extension::extension_supported_signature_algorithms::*; +use crate::extension::extension_use_extended_master_secret::*; +use crate::extension::extension_use_srtp::*; +use crate::extension::renegotiation_info::ExtensionRenegotiationInfo; +use crate::extension::*; +use crate::handshake::handshake_message_client_hello::*; +use crate::handshake::handshake_message_server_key_exchange::*; +use crate::handshake::*; +use crate::prf::{prf_pre_master_secret, prf_psk_pre_master_secret}; +use crate::record_layer::record_layer_header::*; +use crate::record_layer::*; +use crate::{find_matching_cipher_suite, find_matching_srtp_profile}; + +#[derive(Debug, PartialEq)] +pub(crate) struct Flight3; + +impl fmt::Display for Flight3 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Flight 3") + } +} + +#[async_trait] +impl Flight for Flight3 { + async fn parse( + &self, + _tx: &mut mpsc::Sender>, + state: &mut State, + cache: &HandshakeCache, + cfg: &HandshakeConfig, + ) -> Result, (Option, Option)> { + // Clients may receive multiple HelloVerifyRequest messages with different cookies. + // Clients SHOULD handle this by sending a new ClientHello with a cookie in response + // to the new HelloVerifyRequest. RFC 6347 Section 4.2.1 + if let Ok((seq, msgs)) = cache + .full_pull_map( + state.handshake_recv_sequence, + &[HandshakeCachePullRule { + typ: HandshakeType::HelloVerifyRequest, + epoch: cfg.initial_epoch, + is_client: false, + optional: true, + }], + ) + .await + { + if let Some(message) = msgs.get(&HandshakeType::HelloVerifyRequest) { + // DTLS 1.2 clients must not assume that the server will use the protocol version + // specified in HelloVerifyRequest message. RFC 6347 Section 4.2.1 + let h = match message { + HandshakeMessage::HelloVerifyRequest(h) => h, + _ => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + None, + )) + } + }; + + // DTLS 1.2 clients must not assume that the server will use the protocol version + // specified in HelloVerifyRequest message. RFC 6347 Section 4.2.1 + if h.version != PROTOCOL_VERSION1_0 && h.version != PROTOCOL_VERSION1_2 { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::ProtocolVersion, + }), + Some(Error::ErrUnsupportedProtocolVersion), + )); + } + + state.cookie.clone_from(&h.cookie); + state.handshake_recv_sequence = seq; + return Ok(Box::new(Flight3 {}) as Box); + } + } + + let result = if cfg.local_psk_callback.is_some() { + cache + .full_pull_map( + state.handshake_recv_sequence, + &[ + HandshakeCachePullRule { + typ: HandshakeType::ServerHello, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerKeyExchange, + epoch: cfg.initial_epoch, + is_client: false, + optional: true, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerHelloDone, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + ], + ) + .await + } else { + cache + .full_pull_map( + state.handshake_recv_sequence, + &[ + HandshakeCachePullRule { + typ: HandshakeType::ServerHello, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::Certificate, + epoch: cfg.initial_epoch, + is_client: false, + optional: true, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerKeyExchange, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::CertificateRequest, + epoch: cfg.initial_epoch, + is_client: false, + optional: true, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerHelloDone, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + ], + ) + .await + }; + + let (seq, msgs) = match result { + Ok((seq, msgs)) => (seq, msgs), + Err(_) => return Err((None, None)), + }; + + state.handshake_recv_sequence = seq; + + if let Some(message) = msgs.get(&HandshakeType::ServerHello) { + let h = match message { + HandshakeMessage::ServerHello(h) => h, + _ => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + None, + )) + } + }; + + if h.version != PROTOCOL_VERSION1_2 { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::ProtocolVersion, + }), + Some(Error::ErrUnsupportedProtocolVersion), + )); + } + + for extension in &h.extensions { + match extension { + Extension::UseSrtp(e) => { + let profile = match find_matching_srtp_profile( + &e.protection_profiles, + &cfg.local_srtp_protection_profiles, + ) { + Ok(profile) => profile, + Err(_) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::IllegalParameter, + }), + Some(Error::ErrClientNoMatchingSrtpProfile), + )) + } + }; + state.srtp_protection_profile = profile; + } + Extension::UseExtendedMasterSecret(_) => { + if cfg.extended_master_secret != ExtendedMasterSecretType::Disable { + state.extended_master_secret = true; + } + } + _ => {} + }; + } + + if cfg.extended_master_secret == ExtendedMasterSecretType::Require + && !state.extended_master_secret + { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InsufficientSecurity, + }), + Some(Error::ErrClientRequiredButNoServerEms), + )); + } + if !cfg.local_srtp_protection_profiles.is_empty() + && state.srtp_protection_profile == SrtpProtectionProfile::Unsupported + { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InsufficientSecurity, + }), + Some(Error::ErrRequestedButNoSrtpExtension), + )); + } + if find_matching_cipher_suite(&[h.cipher_suite], &cfg.local_cipher_suites).is_err() { + debug!( + "[handshake:{}] use cipher suite: {}", + srv_cli_str(state.is_client), + h.cipher_suite + ); + + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InsufficientSecurity, + }), + Some(Error::ErrCipherSuiteNoIntersection), + )); + } + + let cipher_suite = match cipher_suite_for_id(h.cipher_suite) { + Ok(cipher_suite) => cipher_suite, + Err(_) => { + debug!( + "[handshake:{}] use cipher suite: {}", + srv_cli_str(state.is_client), + h.cipher_suite + ); + + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InsufficientSecurity, + }), + Some(Error::ErrInvalidCipherSuite), + )); + } + }; + + trace!( + "[handshake:{}] use cipher suite: {}", + srv_cli_str(state.is_client), + cipher_suite.to_string() + ); + { + let mut cs = state.cipher_suite.lock().await; + *cs = Some(cipher_suite); + } + state.remote_random = h.random.clone(); + } + + if let Some(message) = msgs.get(&HandshakeType::Certificate) { + let h = match message { + HandshakeMessage::Certificate(h) => h, + _ => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + None, + )) + } + }; + state.peer_certificates.clone_from(&h.certificate); + } + + if let Some(message) = msgs.get(&HandshakeType::ServerKeyExchange) { + let h = match message { + HandshakeMessage::ServerKeyExchange(h) => h, + _ => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + None, + )) + } + }; + + if let Err((alert, err)) = handle_server_key_exchange(state, cfg, h).await { + return Err((alert, err)); + } + } + + if let Some(message) = msgs.get(&HandshakeType::CertificateRequest) { + match message { + HandshakeMessage::CertificateRequest(_) => {} + _ => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + None, + )) + } + }; + state.remote_requested_certificate = true; + } + + Ok(Box::new(Flight5 {}) as Box) + } + + async fn generate( + &self, + state: &mut State, + _cache: &HandshakeCache, + cfg: &HandshakeConfig, + ) -> Result, (Option, Option)> { + let mut extensions = vec![ + Extension::SupportedSignatureAlgorithms(ExtensionSupportedSignatureAlgorithms { + signature_hash_algorithms: cfg.local_signature_schemes.clone(), + }), + Extension::RenegotiationInfo(ExtensionRenegotiationInfo { + renegotiated_connection: 0, + }), + ]; + + if cfg.local_psk_callback.is_none() { + extensions.extend_from_slice(&[ + Extension::SupportedEllipticCurves(ExtensionSupportedEllipticCurves { + elliptic_curves: vec![NamedCurve::P256, NamedCurve::X25519, NamedCurve::P384], + }), + Extension::SupportedPointFormats(ExtensionSupportedPointFormats { + point_formats: vec![ELLIPTIC_CURVE_POINT_FORMAT_UNCOMPRESSED], + }), + ]); + } + + if !cfg.local_srtp_protection_profiles.is_empty() { + extensions.push(Extension::UseSrtp(ExtensionUseSrtp { + protection_profiles: cfg.local_srtp_protection_profiles.clone(), + })); + } + + if cfg.extended_master_secret == ExtendedMasterSecretType::Request + || cfg.extended_master_secret == ExtendedMasterSecretType::Require + { + extensions.push(Extension::UseExtendedMasterSecret( + ExtensionUseExtendedMasterSecret { supported: true }, + )); + } + + if !cfg.server_name.is_empty() { + extensions.push(Extension::ServerName(ExtensionServerName { + server_name: cfg.server_name.clone(), + })); + } + + Ok(vec![Packet { + record: RecordLayer::new( + PROTOCOL_VERSION1_2, + 0, + Content::Handshake(Handshake::new(HandshakeMessage::ClientHello( + HandshakeMessageClientHello { + version: PROTOCOL_VERSION1_2, + random: state.local_random.clone(), + cookie: state.cookie.clone(), + + cipher_suites: cfg.local_cipher_suites.clone(), + compression_methods: default_compression_methods(), + extensions, + }, + ))), + ), + should_encrypt: false, + reset_local_sequence_number: false, + }]) + } +} + +pub(crate) async fn handle_server_key_exchange( + state: &mut State, + cfg: &HandshakeConfig, + h: &HandshakeMessageServerKeyExchange, +) -> Result<(), (Option, Option)> { + if let Some(local_psk_callback) = &cfg.local_psk_callback { + let psk = match local_psk_callback(&h.identity_hint).await { + Ok(psk) => psk, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + Some(err), + )) + } + }; + + state.identity_hint.clone_from(&h.identity_hint); + state.pre_master_secret = prf_psk_pre_master_secret(&psk); + } else { + let local_keypair = match h.named_curve.generate_keypair() { + Ok(local_keypair) => local_keypair, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + Some(err), + )) + } + }; + + state.pre_master_secret = match prf_pre_master_secret( + &h.public_key, + &local_keypair.private_key, + local_keypair.curve, + ) { + Ok(pre_master_secret) => pre_master_secret, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + Some(err), + )) + } + }; + + state.local_keypair = Some(local_keypair); + } + + Ok(()) +} diff --git a/vendor/dtls/src/flight/flight4.rs b/vendor/dtls/src/flight/flight4.rs new file mode 100644 index 00000000..b93e78e4 --- /dev/null +++ b/vendor/dtls/src/flight/flight4.rs @@ -0,0 +1,853 @@ +use std::fmt; +use std::io::BufWriter; + +use async_trait::async_trait; +use log::*; + +use super::flight6::*; +use super::*; +use crate::cipher_suite::*; +use crate::client_certificate_type::*; +use crate::compression_methods::*; +use crate::config::*; +use crate::content::*; +use crate::crypto::*; +use crate::curve::named_curve::*; +use crate::curve::*; +use crate::error::Error; +use crate::extension::extension_supported_elliptic_curves::*; +use crate::extension::extension_supported_point_formats::*; +use crate::extension::extension_use_extended_master_secret::*; +use crate::extension::extension_use_srtp::*; +use crate::extension::renegotiation_info::ExtensionRenegotiationInfo; +use crate::extension::*; +use crate::handshake::handshake_message_certificate::*; +use crate::handshake::handshake_message_certificate_request::*; +use crate::handshake::handshake_message_server_hello::*; +use crate::handshake::handshake_message_server_hello_done::*; +use crate::handshake::handshake_message_server_key_exchange::*; +use crate::handshake::*; +use crate::prf::*; +use crate::record_layer::record_layer_header::*; +use crate::record_layer::*; +use crate::signature_hash_algorithm::*; + +#[derive(Debug, PartialEq)] +pub(crate) struct Flight4; + +impl fmt::Display for Flight4 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Flight 4") + } +} + +#[async_trait] +impl Flight for Flight4 { + async fn parse( + &self, + tx: &mut mpsc::Sender>, + state: &mut State, + cache: &HandshakeCache, + cfg: &HandshakeConfig, + ) -> Result, (Option, Option)> { + let (seq, msgs) = match cache + .full_pull_map( + state.handshake_recv_sequence, + &[ + HandshakeCachePullRule { + typ: HandshakeType::Certificate, + epoch: cfg.initial_epoch, + is_client: true, + optional: true, + }, + HandshakeCachePullRule { + typ: HandshakeType::ClientKeyExchange, + epoch: cfg.initial_epoch, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::CertificateVerify, + epoch: cfg.initial_epoch, + is_client: true, + optional: true, + }, + ], + ) + .await + { + Ok((seq, msgs)) => (seq, msgs), + Err(_) => return Err((None, None)), + }; + + let client_key_exchange = if let Some(HandshakeMessage::ClientKeyExchange(h)) = + msgs.get(&HandshakeType::ClientKeyExchange) + { + h + } else { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + None, + )); + }; + + if let Some(message) = msgs.get(&HandshakeType::Certificate) { + let h = match message { + HandshakeMessage::Certificate(h) => h, + _ => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + None, + )) + } + }; + + state.peer_certificates.clone_from(&h.certificate); + trace!( + "[handshake] PeerCertificates4 {}", + state.peer_certificates.len() + ); + } + + if let Some(message) = msgs.get(&HandshakeType::CertificateVerify) { + let h = match message { + HandshakeMessage::CertificateVerify(h) => h, + _ => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + None, + )) + } + }; + + if state.peer_certificates.is_empty() { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::NoCertificate, + }), + Some(Error::ErrCertificateVerifyNoCertificate), + )); + } + + let plain_text = cache + .pull_and_merge(&[ + HandshakeCachePullRule { + typ: HandshakeType::ClientHello, + epoch: cfg.initial_epoch, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerHello, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::Certificate, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerKeyExchange, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::CertificateRequest, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerHelloDone, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::Certificate, + epoch: cfg.initial_epoch, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ClientKeyExchange, + epoch: cfg.initial_epoch, + is_client: true, + optional: false, + }, + ]) + .await; + + // Verify that the pair of hash algorithm and signature is listed. + let mut valid_signature_scheme = false; + for ss in &cfg.local_signature_schemes { + if ss.hash == h.algorithm.hash && ss.signature == h.algorithm.signature { + valid_signature_scheme = true; + break; + } + } + if !valid_signature_scheme { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InsufficientSecurity, + }), + Some(Error::ErrNoAvailableSignatureSchemes), + )); + } + + if let Err(err) = verify_certificate_verify( + &plain_text, + &h.algorithm, + &h.signature, + &state.peer_certificates, + cfg.insecure_verification, + ) { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::BadCertificate, + }), + Some(err), + )); + } + + let mut chains = vec![]; + let mut verified = false; + if cfg.client_auth as u8 >= ClientAuthType::VerifyClientCertIfGiven as u8 { + if let Some(client_cert_verifier) = &cfg.client_cert_verifier { + chains = + match verify_client_cert(&state.peer_certificates, client_cert_verifier) { + Ok(chains) => chains, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::BadCertificate, + }), + Some(err), + )) + } + }; + } else { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::BadCertificate, + }), + Some(Error::ErrInvalidCertificate), + )); + } + + verified = true + } + if let Some(verify_peer_certificate) = &cfg.verify_peer_certificate { + if let Err(err) = verify_peer_certificate(&state.peer_certificates, &chains) { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::BadCertificate, + }), + Some(err), + )); + } + } + state.peer_certificates_verified = verified + } else if !state.peer_certificates.is_empty() { + // A certificate was received, but we haven't seen a CertificateVerify + // keep reading until we receive one + return Err((None, None)); + } + + { + let mut cipher_suite = state.cipher_suite.lock().await; + if let Some(cipher_suite) = &mut *cipher_suite { + if !cipher_suite.is_initialized() { + let mut server_random = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(server_random.as_mut()); + let _ = state.local_random.marshal(&mut writer); + } + let mut client_random = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(client_random.as_mut()); + let _ = state.remote_random.marshal(&mut writer); + } + + let mut pre_master_secret = vec![]; + if let Some(local_psk_callback) = &cfg.local_psk_callback { + let psk = match local_psk_callback(&client_key_exchange.identity_hint).await + { + Ok(psk) => psk, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + Some(err), + )) + } + }; + + state + .identity_hint + .clone_from(&client_key_exchange.identity_hint); + pre_master_secret = prf_psk_pre_master_secret(&psk); + } else if let Some(local_keypair) = &state.local_keypair { + pre_master_secret = match prf_pre_master_secret( + &client_key_exchange.public_key, + &local_keypair.private_key, + local_keypair.curve, + ) { + Ok(pre_master_secret) => pre_master_secret, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::IllegalParameter, + }), + Some(err), + )) + } + }; + } + + if state.extended_master_secret { + let hf = cipher_suite.hash_func(); + let session_hash = + match cache.session_hash(hf, cfg.initial_epoch, &[]).await { + Ok(s) => s, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + Some(err), + )) + } + }; + + state.master_secret = match prf_extended_master_secret( + &pre_master_secret, + &session_hash, + cipher_suite.hash_func(), + ) { + Ok(ms) => ms, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + Some(err), + )) + } + }; + } else { + state.master_secret = match prf_master_secret( + &pre_master_secret, + &client_random, + &server_random, + cipher_suite.hash_func(), + ) { + Ok(ms) => ms, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + Some(err), + )) + } + }; + } + + if let Err(err) = cipher_suite.init( + &state.master_secret, + &client_random, + &server_random, + false, + ) { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + Some(err), + )); + } + } + } + } + + // Now, encrypted packets can be handled + let (done_tx, mut done_rx) = mpsc::channel(1); + if let Err(err) = tx.send(done_tx).await { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + Some(Error::Other(err.to_string())), + )); + } + + done_rx.recv().await; + + let (seq, msgs) = match cache + .full_pull_map( + seq, + &[HandshakeCachePullRule { + typ: HandshakeType::Finished, + epoch: cfg.initial_epoch + 1, + is_client: true, + optional: false, + }], + ) + .await + { + Ok((seq, msgs)) => (seq, msgs), + // No valid message received. Keep reading + Err(_) => return Err((None, None)), + }; + + state.handshake_recv_sequence = seq; + + if let Some(HandshakeMessage::Finished(h)) = msgs.get(&HandshakeType::Finished) { + h + } else { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + None, + )); + }; + + match cfg.client_auth { + ClientAuthType::RequireAnyClientCert => { + trace!( + "{} peer_certificates.len() {}", + srv_cli_str(state.is_client), + state.peer_certificates.len(), + ); + if state.peer_certificates.is_empty() { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::NoCertificate, + }), + Some(Error::ErrClientCertificateRequired), + )); + } + } + ClientAuthType::VerifyClientCertIfGiven => { + if !state.peer_certificates.is_empty() && !state.peer_certificates_verified { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::BadCertificate, + }), + Some(Error::ErrClientCertificateNotVerified), + )); + } + } + ClientAuthType::RequireAndVerifyClientCert => { + if state.peer_certificates.is_empty() { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::NoCertificate, + }), + Some(Error::ErrClientCertificateRequired), + )); + } + if !state.peer_certificates_verified { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::BadCertificate, + }), + Some(Error::ErrClientCertificateNotVerified), + )); + } + } + ClientAuthType::NoClientCert | ClientAuthType::RequestClientCert => { + return Ok(Box::new(Flight6 {}) as Box); + } + } + + Ok(Box::new(Flight6 {}) as Box) + } + + async fn generate( + &self, + state: &mut State, + _cache: &HandshakeCache, + cfg: &HandshakeConfig, + ) -> Result, (Option, Option)> { + let mut extensions = vec![Extension::RenegotiationInfo(ExtensionRenegotiationInfo { + renegotiated_connection: 0, + })]; + if (cfg.extended_master_secret == ExtendedMasterSecretType::Request + || cfg.extended_master_secret == ExtendedMasterSecretType::Require) + && state.extended_master_secret + { + extensions.push(Extension::UseExtendedMasterSecret( + ExtensionUseExtendedMasterSecret { supported: true }, + )); + } + + if state.srtp_protection_profile != SrtpProtectionProfile::Unsupported { + extensions.push(Extension::UseSrtp(ExtensionUseSrtp { + protection_profiles: vec![state.srtp_protection_profile], + })); + } + + if cfg.local_psk_callback.is_none() { + extensions.extend_from_slice(&[ + Extension::SupportedEllipticCurves(ExtensionSupportedEllipticCurves { + elliptic_curves: vec![NamedCurve::P256, NamedCurve::X25519, NamedCurve::P384], + }), + Extension::SupportedPointFormats(ExtensionSupportedPointFormats { + point_formats: vec![ELLIPTIC_CURVE_POINT_FORMAT_UNCOMPRESSED], + }), + ]); + } + + let mut pkts = vec![Packet { + record: RecordLayer::new( + PROTOCOL_VERSION1_2, + 0, + Content::Handshake(Handshake::new(HandshakeMessage::ServerHello( + HandshakeMessageServerHello { + version: PROTOCOL_VERSION1_2, + random: state.local_random.clone(), + cipher_suite: { + let cipher_suite = state.cipher_suite.lock().await; + if let Some(cipher_suite) = &*cipher_suite { + cipher_suite.id() + } else { + CipherSuiteId::Unsupported + } + }, + compression_method: default_compression_methods().ids[0], + extensions, + }, + ))), + ), + should_encrypt: false, + reset_local_sequence_number: false, + }]; + + if cfg.local_psk_callback.is_none() { + let certificate = match cfg.get_certificate(&cfg.server_name) { + Ok(cert) => cert, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::HandshakeFailure, + }), + Some(err), + )) + } + }; + + pkts.push(Packet { + record: RecordLayer::new( + PROTOCOL_VERSION1_2, + 0, + Content::Handshake(Handshake::new(HandshakeMessage::Certificate( + HandshakeMessageCertificate { + certificate: certificate + .certificate + .iter() + .map(|x| x.as_ref().to_owned()) + .collect(), + }, + ))), + ), + should_encrypt: false, + reset_local_sequence_number: false, + }); + + let mut server_random = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(server_random.as_mut()); + let _ = state.local_random.marshal(&mut writer); + } + let mut client_random = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(client_random.as_mut()); + let _ = state.remote_random.marshal(&mut writer); + } + + // Find compatible signature scheme + let signature_hash_algo = match select_signature_scheme( + &cfg.local_signature_schemes, + &certificate.private_key, + ) { + Ok(s) => s, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InsufficientSecurity, + }), + Some(err), + )) + } + }; + + if let Some(local_keypair) = &state.local_keypair { + let signature = match generate_key_signature( + &client_random, + &server_random, + &local_keypair.public_key, + state.named_curve, + &certificate.private_key, /*, signature_hash_algo.hash*/ + ) { + Ok(s) => s, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + Some(err), + )) + } + }; + + state.local_key_signature = signature; + + pkts.push(Packet { + record: RecordLayer::new( + PROTOCOL_VERSION1_2, + 0, + Content::Handshake(Handshake::new(HandshakeMessage::ServerKeyExchange( + HandshakeMessageServerKeyExchange { + identity_hint: vec![], + elliptic_curve_type: EllipticCurveType::NamedCurve, + named_curve: state.named_curve, + public_key: local_keypair.public_key.clone(), + algorithm: SignatureHashAlgorithm { + hash: signature_hash_algo.hash, + signature: signature_hash_algo.signature, + }, + signature: state.local_key_signature.clone(), + }, + ))), + ), + should_encrypt: false, + reset_local_sequence_number: false, + }); + } + + if cfg.client_auth as u8 > ClientAuthType::NoClientCert as u8 { + pkts.push(Packet { + record: RecordLayer::new( + PROTOCOL_VERSION1_2, + 0, + Content::Handshake(Handshake::new(HandshakeMessage::CertificateRequest( + HandshakeMessageCertificateRequest { + certificate_types: vec![ + ClientCertificateType::RsaSign, + ClientCertificateType::EcdsaSign, + ], + signature_hash_algorithms: cfg.local_signature_schemes.clone(), + }, + ))), + ), + should_encrypt: false, + reset_local_sequence_number: false, + }); + } + } else if let Some(local_psk_identity_hint) = &cfg.local_psk_identity_hint { + // To help the client in selecting which identity to use, the server + // can provide a "PSK identity hint" in the ServerKeyExchange message. + // If no hint is provided, the ServerKeyExchange message is omitted. + // + // https://tools.ietf.org/html/rfc4279#section-2 + pkts.push(Packet { + record: RecordLayer::new( + PROTOCOL_VERSION1_2, + 0, + Content::Handshake(Handshake::new(HandshakeMessage::ServerKeyExchange( + HandshakeMessageServerKeyExchange { + identity_hint: local_psk_identity_hint.clone(), + elliptic_curve_type: EllipticCurveType::Unsupported, + named_curve: NamedCurve::Unsupported, + public_key: vec![], + algorithm: SignatureHashAlgorithm { + hash: HashAlgorithm::Unsupported, + signature: SignatureAlgorithm::Unsupported, + }, + signature: vec![], + }, + ))), + ), + should_encrypt: false, + reset_local_sequence_number: false, + }); + } + + pkts.push(Packet { + record: RecordLayer::new( + PROTOCOL_VERSION1_2, + 0, + Content::Handshake(Handshake::new(HandshakeMessage::ServerHelloDone( + HandshakeMessageServerHelloDone {}, + ))), + ), + should_encrypt: false, + reset_local_sequence_number: false, + }); + + Ok(pkts) + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use tokio::sync::Mutex; + + use super::*; + use crate::error::Result; + + struct MockCipherSuite {} + + impl CipherSuite for MockCipherSuite { + fn to_string(&self) -> String { + "MockCipherSuite".into() + } + fn id(&self) -> CipherSuiteId { + unimplemented!(); + } + fn certificate_type(&self) -> ClientCertificateType { + unimplemented!(); + } + fn hash_func(&self) -> CipherSuiteHash { + unimplemented!(); + } + fn is_psk(&self) -> bool { + false + } + fn is_initialized(&self) -> bool { + panic!("is_initialized called with Certificate but not CertificateVerify"); + } + + // Generate the internal encryption state + fn init( + &mut self, + _master_secret: &[u8], + _client_random: &[u8], + _server_random: &[u8], + _is_client: bool, + ) -> Result<()> { + unimplemented!(); + } + + fn encrypt(&self, _pkt_rlh: &RecordLayerHeader, _raw: &[u8]) -> Result> { + unimplemented!(); + } + fn decrypt(&self, _input: &[u8]) -> Result> { + unimplemented!(); + } + } + + // Assert that if a client sends a certificate they must also send a `CertificateVerify` + // message. The `Flight4` must not interact with the `cipher_suite` if the `CertificateVerify` + // is missing. + #[tokio::test] + async fn test_flight4_process_certificateverify() { + let mut state = State { + cipher_suite: Arc::new(Mutex::new(Some(Box::new(MockCipherSuite {})))), + ..Default::default() + }; + + let raw_certificate = vec![ + 0x0b, 0x00, 0x01, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x9b, 0x00, 0x01, + 0x98, 0x00, 0x01, 0x95, 0x30, 0x82, 0x01, 0x91, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, + 0x02, 0x01, 0x02, 0x02, 0x11, 0x01, 0x65, 0x03, 0x3f, 0x4d, 0x0b, 0x9a, 0x62, 0x91, + 0xdb, 0x4d, 0x28, 0x2c, 0x1f, 0xd6, 0x73, 0x32, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, + 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x00, 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x32, + 0x30, 0x35, 0x31, 0x35, 0x31, 0x38, 0x34, 0x33, 0x35, 0x35, 0x5a, 0x17, 0x0d, 0x32, + 0x32, 0x30, 0x36, 0x31, 0x35, 0x31, 0x38, 0x34, 0x33, 0x35, 0x35, 0x5a, 0x30, 0x00, + 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, + 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0xc3, + 0xb7, 0x13, 0x1a, 0x0a, 0xfc, 0xd0, 0x82, 0xf8, 0x94, 0x5e, 0xc0, 0x77, 0x07, 0x81, + 0x28, 0xc9, 0xcb, 0x08, 0x84, 0x50, 0x6b, 0xf0, 0x22, 0xe8, 0x79, 0xb9, 0x15, 0x33, + 0xc4, 0x56, 0xa1, 0xd3, 0x1b, 0x24, 0xe3, 0x61, 0xbd, 0x4d, 0x65, 0x80, 0x6b, 0x5d, + 0x96, 0x48, 0xa2, 0x44, 0x9e, 0xce, 0xe8, 0x65, 0xd6, 0x3c, 0xe0, 0x9b, 0x6b, 0xa1, + 0x36, 0x34, 0xb2, 0x39, 0xe2, 0x03, 0x00, 0xa3, 0x81, 0x92, 0x30, 0x81, 0x8f, 0x30, + 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x02, + 0xa4, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x16, 0x30, 0x14, 0x06, 0x08, + 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, + 0x05, 0x07, 0x03, 0x01, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, + 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, + 0x04, 0x16, 0x04, 0x14, 0xb1, 0x1a, 0xe3, 0xeb, 0x6f, 0x7c, 0xc3, 0x8f, 0xba, 0x6f, + 0x1c, 0xe8, 0xf0, 0x23, 0x08, 0x50, 0x8d, 0x3c, 0xea, 0x31, 0x30, 0x2e, 0x06, 0x03, + 0x55, 0x1d, 0x11, 0x01, 0x01, 0xff, 0x04, 0x24, 0x30, 0x22, 0x82, 0x20, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, + 0x03, 0x47, 0x00, 0x30, 0x44, 0x02, 0x20, 0x06, 0x31, 0x43, 0xac, 0x03, 0x45, 0x79, + 0x3c, 0xd7, 0x5f, 0x6e, 0x6a, 0xf8, 0x0e, 0xfd, 0x35, 0x49, 0xee, 0x1b, 0xbc, 0x47, + 0xce, 0xe3, 0x39, 0xec, 0xe4, 0x62, 0xe1, 0x30, 0x1a, 0xa1, 0x89, 0x02, 0x20, 0x35, + 0xcd, 0x7a, 0x15, 0x68, 0x09, 0x50, 0x49, 0x9e, 0x3e, 0x05, 0xd7, 0xc2, 0x69, 0x3f, + 0x9c, 0x0c, 0x98, 0x92, 0x65, 0xec, 0xae, 0x44, 0xfe, 0xe5, 0x68, 0xb8, 0x09, 0x78, + 0x7f, 0x6b, 0x77, + ]; + + let raw_client_key_exchange = vec![ + 0x10, 0x00, 0x00, 0x21, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x20, 0x96, + 0xed, 0x0c, 0xee, 0xf3, 0x11, 0xb1, 0x9d, 0x8b, 0x1c, 0x02, 0x7f, 0x06, 0x7c, 0x57, + 0x7a, 0x14, 0xa6, 0x41, 0xde, 0x63, 0x57, 0x9e, 0xcd, 0x34, 0x54, 0xba, 0x37, 0x4d, + 0x34, 0x15, 0x18, + ]; + + let mut cache = HandshakeCache::new(); + cache + .push(raw_certificate, 0, 0, HandshakeType::Certificate, true) + .await; + cache + .push( + raw_client_key_exchange, + 0, + 1, + HandshakeType::ClientKeyExchange, + true, + ) + .await; + + let cfg = HandshakeConfig::default(); + + let (mut tx, _rx) = mpsc::channel::>(1); + + let f = Flight4 {}; + let res = f.parse(&mut tx, &mut state, &cache, &cfg).await; + assert!(res.is_err()); + } +} diff --git a/vendor/dtls/src/flight/flight5.rs b/vendor/dtls/src/flight/flight5.rs new file mode 100644 index 00000000..c794680f --- /dev/null +++ b/vendor/dtls/src/flight/flight5.rs @@ -0,0 +1,779 @@ +use std::fmt; +use std::io::{BufReader, BufWriter}; + +use async_trait::async_trait; + +use super::flight3::*; +use super::*; +use crate::change_cipher_spec::ChangeCipherSpec; +use crate::content::*; +use crate::crypto::*; +use crate::curve::named_curve::*; +use crate::curve::*; +use crate::error::Error; +use crate::handshake::handshake_message_certificate::*; +use crate::handshake::handshake_message_certificate_verify::*; +use crate::handshake::handshake_message_client_key_exchange::*; +use crate::handshake::handshake_message_finished::*; +use crate::handshake::handshake_message_server_key_exchange::*; +use crate::handshake::*; +use crate::prf::*; +use crate::record_layer::record_layer_header::*; +use crate::record_layer::*; +use crate::signature_hash_algorithm::*; + +#[derive(Debug, PartialEq)] +pub(crate) struct Flight5; + +impl fmt::Display for Flight5 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Flight 5") + } +} + +#[async_trait] +impl Flight for Flight5 { + fn is_last_recv_flight(&self) -> bool { + true + } + + async fn parse( + &self, + _tx: &mut mpsc::Sender>, + state: &mut State, + cache: &HandshakeCache, + cfg: &HandshakeConfig, + ) -> Result, (Option, Option)> { + let (_seq, msgs) = match cache + .full_pull_map( + state.handshake_recv_sequence, + &[HandshakeCachePullRule { + typ: HandshakeType::Finished, + epoch: cfg.initial_epoch + 1, + is_client: false, + optional: false, + }], + ) + .await + { + Ok((seq, msgs)) => (seq, msgs), + Err(_) => return Err((None, None)), + }; + + let finished = + if let Some(HandshakeMessage::Finished(h)) = msgs.get(&HandshakeType::Finished) { + h + } else { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + None, + )); + }; + + let plain_text = cache + .pull_and_merge(&[ + HandshakeCachePullRule { + typ: HandshakeType::ClientHello, + epoch: cfg.initial_epoch, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerHello, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::Certificate, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerKeyExchange, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::CertificateRequest, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerHelloDone, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::Certificate, + epoch: cfg.initial_epoch, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ClientKeyExchange, + epoch: cfg.initial_epoch, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::CertificateVerify, + epoch: cfg.initial_epoch, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::Finished, + epoch: cfg.initial_epoch + 1, + is_client: true, + optional: false, + }, + ]) + .await; + + { + let cipher_suite = state.cipher_suite.lock().await; + if let Some(cipher_suite) = &*cipher_suite { + let expected_verify_data = match prf_verify_data_server( + &state.master_secret, + &plain_text, + cipher_suite.hash_func(), + ) { + Ok(d) => d, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InsufficientSecurity, + }), + Some(err), + )) + } + }; + + if expected_verify_data != finished.verify_data { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::HandshakeFailure, + }), + Some(Error::ErrVerifyDataMismatch), + )); + } + } + } + + Ok(Box::new(Flight5 {})) + } + + async fn generate( + &self, + state: &mut State, + cache: &HandshakeCache, + cfg: &HandshakeConfig, + ) -> Result, (Option, Option)> { + let certificate = if !cfg.local_certificates.is_empty() { + let cert = match cfg.get_certificate(&cfg.server_name) { + Ok(cert) => cert, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::HandshakeFailure, + }), + Some(err), + )) + } + }; + Some(cert) + } else { + None + }; + + let mut pkts = vec![]; + + if state.remote_requested_certificate { + pkts.push(Packet { + record: RecordLayer::new( + PROTOCOL_VERSION1_2, + 0, + Content::Handshake(Handshake::new(HandshakeMessage::Certificate( + HandshakeMessageCertificate { + certificate: if let Some(cert) = &certificate { + cert.certificate + .iter() + .map(|x| x.as_ref().to_owned()) + .collect() + } else { + vec![] + }, + }, + ))), + ), + should_encrypt: false, + reset_local_sequence_number: false, + }); + } + + let mut client_key_exchange = HandshakeMessageClientKeyExchange { + identity_hint: vec![], + public_key: vec![], + }; + if cfg.local_psk_callback.is_none() { + if let Some(local_keypair) = &state.local_keypair { + client_key_exchange + .public_key + .clone_from(&local_keypair.public_key); + } + } else if let Some(local_psk_identity_hint) = &cfg.local_psk_identity_hint { + client_key_exchange + .identity_hint + .clone_from(local_psk_identity_hint); + } + + pkts.push(Packet { + record: RecordLayer::new( + PROTOCOL_VERSION1_2, + 0, + Content::Handshake(Handshake::new(HandshakeMessage::ClientKeyExchange( + client_key_exchange, + ))), + ), + should_encrypt: false, + reset_local_sequence_number: false, + }); + + let server_key_exchange_data = cache + .pull_and_merge(&[HandshakeCachePullRule { + typ: HandshakeType::ServerKeyExchange, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }]) + .await; + + let mut server_key_exchange = HandshakeMessageServerKeyExchange { + identity_hint: vec![], + elliptic_curve_type: EllipticCurveType::Unsupported, + named_curve: NamedCurve::Unsupported, + public_key: vec![], + algorithm: SignatureHashAlgorithm { + hash: HashAlgorithm::Unsupported, + signature: SignatureAlgorithm::Unsupported, + }, + signature: vec![], + }; + + // handshakeMessageServerKeyExchange is optional for PSK + if server_key_exchange_data.is_empty() { + if let Err((alert, err)) = + handle_server_key_exchange(state, cfg, &server_key_exchange).await + { + return Err((alert, err)); + } + } else { + let mut reader = BufReader::new(server_key_exchange_data.as_slice()); + let raw_handshake = match Handshake::unmarshal(&mut reader) { + Ok(h) => h, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::UnexpectedMessage, + }), + Some(err), + )) + } + }; + + match raw_handshake.handshake_message { + HandshakeMessage::ServerKeyExchange(h) => server_key_exchange = h, + _ => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::UnexpectedMessage, + }), + Some(Error::ErrInvalidContentType), + )) + } + }; + } + + // Append not-yet-sent packets + let mut merged = vec![]; + let mut seq_pred = state.handshake_send_sequence as u16; + for p in &mut pkts { + let h = match &mut p.record.content { + Content::Handshake(h) => h, + _ => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + Some(Error::ErrInvalidContentType), + )) + } + }; + h.handshake_header.message_sequence = seq_pred; + seq_pred += 1; + + let mut raw = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw.as_mut()); + if let Err(err) = h.marshal(&mut writer) { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + Some(err), + )); + } + } + + merged.extend_from_slice(&raw); + } + + if let Err((alert, err)) = + initialize_cipher_suite(state, cache, cfg, &server_key_exchange, &merged).await + { + return Err((alert, err)); + } + + // If the client has sent a certificate with signing ability, a digitally-signed + // CertificateVerify message is sent to explicitly verify possession of the + // private key in the certificate. + if state.remote_requested_certificate && !cfg.local_certificates.is_empty() { + let mut plain_text = cache + .pull_and_merge(&[ + HandshakeCachePullRule { + typ: HandshakeType::ClientHello, + epoch: cfg.initial_epoch, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerHello, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::Certificate, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerKeyExchange, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::CertificateRequest, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerHelloDone, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::Certificate, + epoch: cfg.initial_epoch, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ClientKeyExchange, + epoch: cfg.initial_epoch, + is_client: true, + optional: false, + }, + ]) + .await; + + plain_text.extend_from_slice(&merged); + + // Find compatible signature scheme + let signature_hash_algo = match select_signature_scheme( + &cfg.local_signature_schemes, + &certificate.as_ref().unwrap().private_key, + ) { + Ok(s) => s, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InsufficientSecurity, + }), + Some(err), + )) + } + }; + + let cert_verify = match generate_certificate_verify( + &plain_text, + &certificate.as_ref().unwrap().private_key, /*, signature_hash_algo.hash*/ + ) { + Ok(cert) => cert, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + Some(err), + )) + } + }; + state.local_certificates_verify = cert_verify; + + let mut p = Packet { + record: RecordLayer::new( + PROTOCOL_VERSION1_2, + 0, + Content::Handshake(Handshake::new(HandshakeMessage::CertificateVerify( + HandshakeMessageCertificateVerify { + algorithm: signature_hash_algo, + signature: state.local_certificates_verify.clone(), + }, + ))), + ), + should_encrypt: false, + reset_local_sequence_number: false, + }; + + let h = match &mut p.record.content { + Content::Handshake(h) => h, + _ => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + Some(Error::ErrInvalidContentType), + )) + } + }; + h.handshake_header.message_sequence = seq_pred; + + // seqPred++ // this is the last use of seqPred + + let mut raw = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw.as_mut()); + if let Err(err) = h.marshal(&mut writer) { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + Some(err), + )); + } + } + merged.extend_from_slice(&raw); + + pkts.push(p); + } + + pkts.push(Packet { + record: RecordLayer::new( + PROTOCOL_VERSION1_2, + 0, + Content::ChangeCipherSpec(ChangeCipherSpec {}), + ), + should_encrypt: false, + reset_local_sequence_number: false, + }); + + if state.local_verify_data.is_empty() { + let mut plain_text = cache + .pull_and_merge(&[ + HandshakeCachePullRule { + typ: HandshakeType::ClientHello, + epoch: cfg.initial_epoch, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerHello, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::Certificate, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerKeyExchange, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::CertificateRequest, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerHelloDone, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::Certificate, + epoch: cfg.initial_epoch, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ClientKeyExchange, + epoch: cfg.initial_epoch, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::CertificateVerify, + epoch: cfg.initial_epoch, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::Finished, + epoch: cfg.initial_epoch + 1, + is_client: true, + optional: false, + }, + ]) + .await; + + plain_text.extend_from_slice(&merged); + + let cipher_suite = state.cipher_suite.lock().await; + if let Some(cipher_suite) = &*cipher_suite { + state.local_verify_data = match prf_verify_data_client( + &state.master_secret, + &plain_text, + cipher_suite.hash_func(), + ) { + Ok(data) => data, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + Some(err), + )) + } + }; + } + } + + pkts.push(Packet { + record: RecordLayer::new( + PROTOCOL_VERSION1_2, + 1, + Content::Handshake(Handshake::new(HandshakeMessage::Finished( + HandshakeMessageFinished { + verify_data: state.local_verify_data.clone(), + }, + ))), + ), + should_encrypt: true, + reset_local_sequence_number: true, + }); + + Ok(pkts) + } +} +async fn initialize_cipher_suite( + state: &mut State, + cache: &HandshakeCache, + cfg: &HandshakeConfig, + h: &HandshakeMessageServerKeyExchange, + sending_plain_text: &[u8], +) -> Result<(), (Option, Option)> { + let mut cipher_suite = state.cipher_suite.lock().await; + + if let Some(cipher_suite) = &*cipher_suite { + if cipher_suite.is_initialized() { + return Ok(()); + } + } + + let mut client_random = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(client_random.as_mut()); + let _ = state.local_random.marshal(&mut writer); + } + let mut server_random = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(server_random.as_mut()); + let _ = state.remote_random.marshal(&mut writer); + } + + if let Some(cipher_suite) = &*cipher_suite { + if state.extended_master_secret { + let session_hash = match cache + .session_hash( + cipher_suite.hash_func(), + cfg.initial_epoch, + sending_plain_text, + ) + .await + { + Ok(s) => s, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + Some(err), + )) + } + }; + + state.master_secret = match prf_extended_master_secret( + &state.pre_master_secret, + &session_hash, + cipher_suite.hash_func(), + ) { + Ok(m) => m, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::IllegalParameter, + }), + Some(err), + )) + } + }; + } else { + state.master_secret = match prf_master_secret( + &state.pre_master_secret, + &client_random, + &server_random, + cipher_suite.hash_func(), + ) { + Ok(m) => m, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + Some(err), + )) + } + }; + } + } + + if cfg.local_psk_callback.is_none() { + // Verify that the pair of hash algorithm and signiture is listed. + let mut valid_signature_scheme = false; + for ss in &cfg.local_signature_schemes { + if ss.hash == h.algorithm.hash && ss.signature == h.algorithm.signature { + valid_signature_scheme = true; + break; + } + } + if !valid_signature_scheme { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InsufficientSecurity, + }), + Some(Error::ErrNoAvailableSignatureSchemes), + )); + } + + let expected_msg = + value_key_message(&client_random, &server_random, &h.public_key, h.named_curve); + if let Err(err) = verify_key_signature( + &expected_msg, + &h.algorithm, + &h.signature, + &state.peer_certificates, + cfg.insecure_verification, + ) { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::BadCertificate, + }), + Some(err), + )); + } + + let mut chains = vec![]; + if !cfg.insecure_skip_verify { + chains = match verify_server_cert( + &state.peer_certificates, + &cfg.server_cert_verifier, + &cfg.server_name, + ) { + Ok(chains) => chains, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::BadCertificate, + }), + Some(err), + )) + } + } + } + if let Some(verify_peer_certificate) = &cfg.verify_peer_certificate { + if let Err(err) = verify_peer_certificate(&state.peer_certificates, &chains) { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::BadCertificate, + }), + Some(err), + )); + } + } + } + + if let Some(cipher_suite) = &mut *cipher_suite { + if let Err(err) = + cipher_suite.init(&state.master_secret, &client_random, &server_random, true) + { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + Some(err), + )); + } + } + + Ok(()) +} diff --git a/vendor/dtls/src/flight/flight6.rs b/vendor/dtls/src/flight/flight6.rs new file mode 100644 index 00000000..1e9b0036 --- /dev/null +++ b/vendor/dtls/src/flight/flight6.rs @@ -0,0 +1,190 @@ +use std::fmt; + +use async_trait::async_trait; + +use super::*; +use crate::change_cipher_spec::*; +use crate::content::*; +use crate::handshake::handshake_message_finished::*; +use crate::handshake::*; +use crate::prf::*; +use crate::record_layer::record_layer_header::*; + +#[derive(Debug, PartialEq)] +pub(crate) struct Flight6; + +impl fmt::Display for Flight6 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Flight 6") + } +} + +#[async_trait] +impl Flight for Flight6 { + fn is_last_send_flight(&self) -> bool { + true + } + + async fn parse( + &self, + _tx: &mut mpsc::Sender>, + state: &mut State, + cache: &HandshakeCache, + cfg: &HandshakeConfig, + ) -> Result, (Option, Option)> { + let (_, msgs) = match cache + .full_pull_map( + state.handshake_recv_sequence - 1, + &[HandshakeCachePullRule { + typ: HandshakeType::Finished, + epoch: cfg.initial_epoch + 1, + is_client: true, + optional: false, + }], + ) + .await + { + Ok((seq, msgs)) => (seq, msgs), + // No valid message received. Keep reading + Err(_) => return Err((None, None)), + }; + + if let Some(message) = msgs.get(&HandshakeType::Finished) { + match message { + HandshakeMessage::Finished(_) => {} + _ => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + None, + )) + } + }; + } + + // Other party retransmitted the last flight. + Ok(Box::new(Flight6 {})) + } + + async fn generate( + &self, + state: &mut State, + cache: &HandshakeCache, + cfg: &HandshakeConfig, + ) -> Result, (Option, Option)> { + let mut pkts = vec![Packet { + record: RecordLayer::new( + PROTOCOL_VERSION1_2, + 0, + Content::ChangeCipherSpec(ChangeCipherSpec {}), + ), + should_encrypt: false, + reset_local_sequence_number: false, + }]; + + if state.local_verify_data.is_empty() { + let plain_text = cache + .pull_and_merge(&[ + HandshakeCachePullRule { + typ: HandshakeType::ClientHello, + epoch: cfg.initial_epoch, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerHello, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::Certificate, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerKeyExchange, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::CertificateRequest, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerHelloDone, + epoch: cfg.initial_epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::Certificate, + epoch: cfg.initial_epoch, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ClientKeyExchange, + epoch: cfg.initial_epoch, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::CertificateVerify, + epoch: cfg.initial_epoch, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::Finished, + epoch: cfg.initial_epoch + 1, + is_client: true, + optional: false, + }, + ]) + .await; + + let cipher_suite = state.cipher_suite.lock().await; + if let Some(cipher_suite) = &*cipher_suite { + state.local_verify_data = match prf_verify_data_server( + &state.master_secret, + &plain_text, + cipher_suite.hash_func(), + ) { + Ok(data) => data, + Err(err) => { + return Err(( + Some(Alert { + alert_level: AlertLevel::Fatal, + alert_description: AlertDescription::InternalError, + }), + Some(err), + )) + } + }; + } + } + + pkts.push(Packet { + record: RecordLayer::new( + PROTOCOL_VERSION1_2, + 1, + Content::Handshake(Handshake::new(HandshakeMessage::Finished( + HandshakeMessageFinished { + verify_data: state.local_verify_data.clone(), + }, + ))), + ), + should_encrypt: true, + reset_local_sequence_number: true, + }); + + Ok(pkts) + } +} diff --git a/vendor/dtls/src/flight/mod.rs b/vendor/dtls/src/flight/mod.rs new file mode 100644 index 00000000..8e6b41e6 --- /dev/null +++ b/vendor/dtls/src/flight/mod.rs @@ -0,0 +1,87 @@ +pub(crate) mod flight0; +pub(crate) mod flight1; +pub(crate) mod flight2; +pub(crate) mod flight3; +pub(crate) mod flight4; +pub(crate) mod flight5; +pub(crate) mod flight6; + +use std::fmt; + +use async_trait::async_trait; +use tokio::sync::mpsc; + +use crate::alert::*; +use crate::error::Error; +use crate::handshake::handshake_cache::*; +use crate::handshaker::*; +use crate::record_layer::*; +use crate::state::*; + +/* + DTLS messages are grouped into a series of message flights, according + to the diagrams below. Although each Flight of messages may consist + of a number of messages, they should be viewed as monolithic for the + purpose of timeout and retransmission. + https://tools.ietf.org/html/rfc4347#section-4.2.4 + Client Server + ------ ------ + Waiting Flight 0 + + ClientHello --------> Flight 1 + + <------- HelloVerifyRequest Flight 2 + + ClientHello --------> Flight 3 + + ServerHello \ + Certificate* \ + ServerKeyExchange* Flight 4 + CertificateRequest* / + <-------- ServerHelloDone / + + Certificate* \ + ClientKeyExchange \ + CertificateVerify* Flight 5 + [ChangeCipherSpec] / + Finished --------> / + + [ChangeCipherSpec] \ Flight 6 + <-------- Finished / + +*/ + +#[derive(Clone, Debug)] +pub(crate) struct Packet { + pub(crate) record: RecordLayer, + pub(crate) should_encrypt: bool, + pub(crate) reset_local_sequence_number: bool, +} + +#[async_trait] +pub(crate) trait Flight: fmt::Display + fmt::Debug { + fn is_last_send_flight(&self) -> bool { + false + } + fn is_last_recv_flight(&self) -> bool { + false + } + fn has_retransmit(&self) -> bool { + true + } + + async fn parse( + &self, + tx: &mut mpsc::Sender>, + state: &mut State, + cache: &HandshakeCache, + cfg: &HandshakeConfig, + ) -> Result, (Option, Option)>; + + async fn generate( + &self, + state: &mut State, + cache: &HandshakeCache, + cfg: &HandshakeConfig, + ) -> Result, (Option, Option)>; +} diff --git a/vendor/dtls/src/fragment_buffer/fragment_buffer_test.rs b/vendor/dtls/src/fragment_buffer/fragment_buffer_test.rs new file mode 100644 index 00000000..0e909080 --- /dev/null +++ b/vendor/dtls/src/fragment_buffer/fragment_buffer_test.rs @@ -0,0 +1,174 @@ +use super::*; + +#[test] +fn test_fragment_buffer() -> Result<()> { + let tests = vec![ + ( + "Single Fragment", + vec![vec![ + 0x16, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0xff, 0x00, + ]], + vec![vec![ + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0xff, + 0x00, + ]], + 0, + ), + ( + "Single Fragment Epoch 3", + vec![vec![ + 0x16, 0xfe, 0xff, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0xff, 0x00, + ]], + vec![vec![ + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0xff, + 0x00, + ]], + 3, + ), + ( + "Multiple Fragments", + vec![ + vec![ + 0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, + 0x0b, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, + 0x01, 0x02, 0x03, 0x04, + ], + vec![ + 0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, + 0x0b, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x05, 0x05, + 0x06, 0x07, 0x08, 0x09, + ], + vec![ + 0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, + 0x0b, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x05, 0x0A, + 0x0B, 0x0C, 0x0D, 0x0E, + ], + ], + vec![vec![ + 0x0b, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + ]], + 0, + ), + ( + "Multiple Unordered Fragments", + vec![ + vec![ + 0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, + 0x0b, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, + 0x01, 0x02, 0x03, 0x04, + ], + vec![ + 0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, + 0x0b, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x05, 0x0A, + 0x0B, 0x0C, 0x0D, 0x0E, + ], + vec![ + 0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x81, + 0x0b, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x05, 0x05, + 0x06, 0x07, 0x08, 0x09, + ], + ], + vec![vec![ + 0x0b, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + ]], + 0, + ), + ( + "Multiple Handshakes in Single Fragment", + vec![vec![ + 0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x30, /* record header */ + 0x03, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfe, 0xff, + 0x01, 0x01, /*handshake msg 1*/ + 0x03, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfe, 0xff, + 0x01, 0x01, /*handshake msg 2*/ + 0x03, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfe, 0xff, + 0x01, 0x01, /*handshake msg 3*/ + ]], + vec![ + vec![ + 0x03, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfe, + 0xff, 0x01, 0x01, + ], + vec![ + 0x03, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfe, + 0xff, 0x01, 0x01, + ], + vec![ + 0x03, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfe, + 0xff, 0x01, 0x01, + ], + ], + 0, + ), + // Ensure zero length fragments don't cause an infinite recursive loop which in turn causes + // a stack overflow. + ( + "Zero length fragment", + vec![vec![ + 0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]], + vec![vec![ + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + ]], + 0, + ), + ]; + + for (name, inputs, expects, expected_epoch) in tests { + let mut fragment_buffer = FragmentBuffer::new(); + for frag in inputs { + let status = fragment_buffer.push(&frag)?; + assert!( + status, + "fragment_buffer didn't accept fragments for '{name}'" + ); + } + + for expected in expects { + let (out, epoch) = fragment_buffer.pop()?; + assert_eq!( + out, expected, + "fragment_buffer '{name}' push/pop: got {out:?}, want {expected:?}" + ); + + assert_eq!( + epoch, expected_epoch, + "fragment_buffer returned wrong epoch: got {epoch}, want {expected_epoch}" + ); + } + + let result = fragment_buffer.pop(); + assert!( + result.is_err(), + "fragment_buffer popped single buffer multiple times for '{name}'" + ); + } + + Ok(()) +} + +#[test] +fn test_fragment_buffer_overflow() -> Result<()> { + let mut fragment_buffer = FragmentBuffer::new(); + + fragment_buffer.push(&[ + 0x16, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x03, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0xff, 0x00, + ])?; + + let big_buffer = vec![0; 2_000_000]; + let result = fragment_buffer.push(&big_buffer); + + assert!( + result.is_err(), + "Pushing a buffer of size 2MB should have caused FragmentBuffer::push to return an error" + ); + + Ok(()) +} diff --git a/vendor/dtls/src/fragment_buffer/mod.rs b/vendor/dtls/src/fragment_buffer/mod.rs new file mode 100644 index 00000000..d375bc2c --- /dev/null +++ b/vendor/dtls/src/fragment_buffer/mod.rs @@ -0,0 +1,160 @@ +#[cfg(test)] +mod fragment_buffer_test; + +use std::collections::HashMap; +use std::io::{BufWriter, Cursor}; + +use crate::content::*; +use crate::error::*; +use crate::handshake::handshake_header::*; +use crate::record_layer::record_layer_header::*; + +// 2 mb max buffer size +const FRAGMENT_BUFFER_MAX_SIZE: usize = 2_000_000; + +pub(crate) struct Fragment { + record_layer_header: RecordLayerHeader, + handshake_header: HandshakeHeader, + data: Vec, +} + +pub(crate) struct FragmentBuffer { + // map of MessageSequenceNumbers that hold slices of fragments + cache: HashMap>, + + current_message_sequence_number: u16, +} + +impl FragmentBuffer { + pub fn new() -> Self { + FragmentBuffer { + cache: HashMap::new(), + current_message_sequence_number: 0, + } + } + + // Attempts to push a DTLS packet to the FragmentBuffer + // when it returns true it means the FragmentBuffer has inserted and the buffer shouldn't be handled + // when an error returns it is fatal, and the DTLS connection should be stopped + pub fn push(&mut self, mut buf: &[u8]) -> Result { + let current_size = self.size(); + if current_size + buf.len() >= FRAGMENT_BUFFER_MAX_SIZE { + return Err(Error::ErrFragmentBufferOverflow { + new_size: current_size + buf.len(), + max_size: FRAGMENT_BUFFER_MAX_SIZE, + }); + } + + let mut reader = Cursor::new(buf); + let record_layer_header = RecordLayerHeader::unmarshal(&mut reader)?; + + // Fragment isn't a handshake, we don't need to handle it + if record_layer_header.content_type != ContentType::Handshake { + return Ok(false); + } + + buf = &buf[RECORD_LAYER_HEADER_SIZE..]; + while !buf.is_empty() { + let mut reader = Cursor::new(buf); + let handshake_header = HandshakeHeader::unmarshal(&mut reader)?; + + self.cache + .entry(handshake_header.message_sequence) + .or_default(); + + // end index should be the length of handshake header but if the handshake + // was fragmented, we should keep them all + let mut end = HANDSHAKE_HEADER_LENGTH + handshake_header.length as usize; + if end > buf.len() { + end = buf.len(); + } + + // Discard all headers, when rebuilding the packet we will re-build + let data = buf[HANDSHAKE_HEADER_LENGTH..end].to_vec(); + + if let Some(x) = self.cache.get_mut(&handshake_header.message_sequence) { + x.push(Fragment { + record_layer_header, + handshake_header, + data, + }); + } + buf = &buf[end..]; + } + + Ok(true) + } + + pub fn pop(&mut self) -> Result<(Vec, u16)> { + let seq_num = self.current_message_sequence_number; + if !self.cache.contains_key(&seq_num) { + return Err(Error::ErrEmptyFragment); + } + + let (content, epoch) = if let Some(frags) = self.cache.get_mut(&seq_num) { + let mut raw_message = vec![]; + // Recursively collect up + if !append_message(0, frags, &mut raw_message) { + return Err(Error::ErrEmptyFragment); + } + + let mut first_header = frags[0].handshake_header; + first_header.fragment_offset = 0; + first_header.fragment_length = first_header.length; + + let mut raw_header = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw_header.as_mut()); + if first_header.marshal(&mut writer).is_err() { + return Err(Error::ErrEmptyFragment); + } + } + + let message_epoch = frags[0].record_layer_header.epoch; + + raw_header.extend_from_slice(&raw_message); + + (raw_header, message_epoch) + } else { + return Err(Error::ErrEmptyFragment); + }; + + self.cache.remove(&seq_num); + self.current_message_sequence_number += 1; + + Ok((content, epoch)) + } + + fn size(&self) -> usize { + self.cache + .values() + .map(|fragment| fragment.iter().map(|f| f.data.len()).sum::()) + .sum() + } +} + +fn append_message(target_offset: u32, frags: &[Fragment], raw_message: &mut Vec) -> bool { + for f in frags { + if f.handshake_header.fragment_offset == target_offset { + let fragment_end = + f.handshake_header.fragment_offset + f.handshake_header.fragment_length; + + // NB: Order here is important, the `f.handshake_header.fragment_length != 0` + // MUST come before the recursive call. + if fragment_end != f.handshake_header.length + && f.handshake_header.fragment_length != 0 + && !append_message(fragment_end, frags, raw_message) + { + return false; + } + + let mut message = vec![]; + message.extend_from_slice(&f.data); + message.extend_from_slice(raw_message); + *raw_message = message; + return true; + } + } + + false +} diff --git a/vendor/dtls/src/handshake/handshake_cache.rs b/vendor/dtls/src/handshake/handshake_cache.rs new file mode 100644 index 00000000..38667bb2 --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_cache.rs @@ -0,0 +1,241 @@ +#[cfg(test)] +mod handshake_cache_test; + +use std::collections::HashMap; +use std::io::BufReader; +use std::sync::Arc; + +use sha2::{Digest, Sha256}; +use tokio::sync::Mutex; + +use crate::cipher_suite::*; +use crate::handshake::*; + +#[derive(Clone, Debug)] +pub(crate) struct HandshakeCacheItem { + typ: HandshakeType, + is_client: bool, + epoch: u16, + message_sequence: u16, + data: Vec, +} + +#[derive(Copy, Clone, Debug)] +pub(crate) struct HandshakeCachePullRule { + pub(crate) typ: HandshakeType, + pub(crate) epoch: u16, + pub(crate) is_client: bool, + pub(crate) optional: bool, +} + +#[derive(Clone)] +pub(crate) struct HandshakeCache { + cache: Arc>>, +} + +impl HandshakeCache { + pub(crate) fn new() -> Self { + HandshakeCache { + cache: Arc::new(Mutex::new(vec![])), + } + } + + pub(crate) async fn push( + &mut self, + data: Vec, + epoch: u16, + message_sequence: u16, + typ: HandshakeType, + is_client: bool, + ) -> bool { + let mut cache = self.cache.lock().await; + + for i in &*cache { + if i.message_sequence == message_sequence && i.is_client == is_client { + return false; + } + } + + cache.push(HandshakeCacheItem { + typ, + is_client, + epoch, + message_sequence, + data, + }); + + true + } + + // returns a list handshakes that match the requested rules + // the list will contain null entries for rules that can't be satisfied + // multiple entries may match a rule, but only the last match is returned (ie ClientHello with cookies) + pub(crate) async fn pull(&self, rules: &[HandshakeCachePullRule]) -> Vec { + let cache = self.cache.lock().await; + + let mut out = vec![]; + for r in rules { + let mut item: Option = None; + for c in &*cache { + if c.typ == r.typ && c.is_client == r.is_client && c.epoch == r.epoch { + if let Some(x) = &item { + if x.message_sequence < c.message_sequence { + item = Some(c.clone()); + } + } else { + item = Some(c.clone()); + } + } + } + + if let Some(c) = item { + out.push(c); + } + } + + out + } + + // full_pull_map pulls all handshakes between rules[0] to rules[len(rules)-1] as map. + pub(crate) async fn full_pull_map( + &self, + start_seq: isize, + rules: &[HandshakeCachePullRule], + ) -> Result<(isize, HashMap)> { + let cache = self.cache.lock().await; + + let mut ci = HashMap::new(); + for r in rules { + let mut item: Option = None; + for c in &*cache { + if c.typ == r.typ && c.is_client == r.is_client && c.epoch == r.epoch { + if let Some(x) = &item { + if x.message_sequence < c.message_sequence { + item = Some(c.clone()); + } + } else { + item = Some(c.clone()); + } + } + } + if !r.optional && item.is_none() { + // Missing mandatory message. + return Err(Error::Other("Missing mandatory message".to_owned())); + } + + if let Some(c) = item { + ci.insert(r.typ, c); + } + } + + let mut out = HashMap::new(); + let mut seq = start_seq; + for r in rules { + let t = r.typ; + if let Some(i) = ci.get(&t) { + let mut reader = BufReader::new(i.data.as_slice()); + let raw_handshake = Handshake::unmarshal(&mut reader)?; + if seq as u16 != raw_handshake.handshake_header.message_sequence { + // There is a gap. Some messages are not arrived. + return Err(Error::Other( + "There is a gap. Some messages are not arrived.".to_owned(), + )); + } + seq += 1; + out.insert(t, raw_handshake.handshake_message); + } + } + + Ok((seq, out)) + } + + // pull_and_merge calls pull and then merges the results, ignoring any null entries + pub(crate) async fn pull_and_merge(&self, rules: &[HandshakeCachePullRule]) -> Vec { + let mut merged = vec![]; + + for p in &self.pull(rules).await { + merged.extend_from_slice(&p.data); + } + + merged + } + + // session_hash returns the session hash for Extended Master Secret support + // https://tools.ietf.org/html/draft-ietf-tls-session-hash-06#section-4 + pub(crate) async fn session_hash( + &self, + hf: CipherSuiteHash, + epoch: u16, + additional: &[u8], + ) -> Result> { + let mut merged = vec![]; + + // Order defined by https://tools.ietf.org/html/rfc5246#section-7.3 + let handshake_buffer = self + .pull(&[ + HandshakeCachePullRule { + typ: HandshakeType::ClientHello, + epoch, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerHello, + epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::Certificate, + epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerKeyExchange, + epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::CertificateRequest, + epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ServerHelloDone, + epoch, + is_client: false, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::Certificate, + epoch, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: HandshakeType::ClientKeyExchange, + epoch, + is_client: true, + optional: false, + }, + ]) + .await; + + for p in &handshake_buffer { + merged.extend_from_slice(&p.data); + } + + merged.extend_from_slice(additional); + + let mut hasher = match hf { + CipherSuiteHash::Sha256 => Sha256::new(), + }; + hasher.update(&merged); + let result = hasher.finalize(); + + Ok(result.as_slice().to_vec()) + } +} diff --git a/vendor/dtls/src/handshake/handshake_cache/handshake_cache_test.rs b/vendor/dtls/src/handshake/handshake_cache/handshake_cache_test.rs new file mode 100644 index 00000000..b17391b1 --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_cache/handshake_cache_test.rs @@ -0,0 +1,658 @@ +use super::*; + +#[tokio::test] +async fn test_handshake_cache_single_push() -> Result<()> { + let tests = vec![ + ( + "Single Push", + vec![HandshakeCacheItem { + typ: 0.into(), + is_client: true, + epoch: 0, + message_sequence: 0, + data: vec![0x00], + }], + vec![HandshakeCachePullRule { + typ: 0.into(), + epoch: 0, + is_client: true, + optional: false, + }], + vec![0x00], + ), + ( + "Multi Push", + vec![ + HandshakeCacheItem { + typ: 0.into(), + is_client: true, + epoch: 0, + message_sequence: 0, + data: vec![0x00], + }, + HandshakeCacheItem { + typ: 1.into(), + is_client: true, + epoch: 0, + message_sequence: 1, + data: vec![0x01], + }, + HandshakeCacheItem { + typ: 2.into(), + is_client: true, + epoch: 0, + message_sequence: 2, + data: vec![0x02], + }, + ], + vec![ + HandshakeCachePullRule { + typ: 0.into(), + epoch: 0, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: 1.into(), + epoch: 0, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: 2.into(), + epoch: 0, + is_client: true, + optional: false, + }, + ], + vec![0x00, 0x01, 0x02], + ), + ( + "Multi Push, Rules set order", + vec![ + HandshakeCacheItem { + typ: 2.into(), + is_client: true, + epoch: 0, + message_sequence: 2, + data: vec![0x02], + }, + HandshakeCacheItem { + typ: 0.into(), + is_client: true, + epoch: 0, + message_sequence: 0, + data: vec![0x00], + }, + HandshakeCacheItem { + typ: 1.into(), + is_client: true, + epoch: 0, + message_sequence: 1, + data: vec![0x01], + }, + ], + vec![ + HandshakeCachePullRule { + typ: 0.into(), + epoch: 0, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: 1.into(), + epoch: 0, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: 2.into(), + epoch: 0, + is_client: true, + optional: false, + }, + ], + vec![0x00, 0x01, 0x02], + ), + ( + "Multi Push, Dupe Seqnum", + vec![ + HandshakeCacheItem { + typ: 0.into(), + is_client: true, + epoch: 0, + message_sequence: 0, + data: vec![0x00], + }, + HandshakeCacheItem { + typ: 1.into(), + is_client: true, + epoch: 0, + message_sequence: 1, + data: vec![0x01], + }, + HandshakeCacheItem { + typ: 1.into(), + is_client: true, + epoch: 0, + message_sequence: 1, + data: vec![0x01], + }, + ], + vec![ + HandshakeCachePullRule { + typ: 0.into(), + epoch: 0, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: 1.into(), + epoch: 0, + is_client: true, + optional: false, + }, + ], + vec![0x00, 0x01], + ), + ( + "Multi Push, Dupe Seqnum Client/Server", + vec![ + HandshakeCacheItem { + typ: 0.into(), + is_client: true, + epoch: 0, + message_sequence: 0, + data: vec![0x00], + }, + HandshakeCacheItem { + typ: 1.into(), + is_client: true, + epoch: 0, + message_sequence: 1, + data: vec![0x01], + }, + HandshakeCacheItem { + typ: 1.into(), + is_client: false, + epoch: 0, + message_sequence: 1, + data: vec![0x02], + }, + ], + vec![ + HandshakeCachePullRule { + typ: 0.into(), + epoch: 0, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: 1.into(), + epoch: 0, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: 1.into(), + epoch: 0, + is_client: false, + optional: false, + }, + ], + vec![0x00, 0x01, 0x02], + ), + ( + "Multi Push, Dupe Seqnum with Unique HandshakeType", + vec![ + HandshakeCacheItem { + typ: 1.into(), + is_client: true, + epoch: 0, + message_sequence: 0, + data: vec![0x00], + }, + HandshakeCacheItem { + typ: 2.into(), + is_client: true, + epoch: 0, + message_sequence: 1, + data: vec![0x01], + }, + HandshakeCacheItem { + typ: 3.into(), + is_client: false, + epoch: 0, + message_sequence: 0, + data: vec![0x02], + }, + ], + vec![ + HandshakeCachePullRule { + typ: 1.into(), + epoch: 0, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: 2.into(), + epoch: 0, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: 3.into(), + epoch: 0, + is_client: false, + optional: false, + }, + ], + vec![0x00, 0x01, 0x02], + ), + ( + "Multi Push, Wrong epoch", + vec![ + HandshakeCacheItem { + typ: 1.into(), + is_client: true, + epoch: 0, + message_sequence: 0, + data: vec![0x00], + }, + HandshakeCacheItem { + typ: 2.into(), + is_client: true, + epoch: 1, + message_sequence: 1, + data: vec![0x01], + }, + HandshakeCacheItem { + typ: 2.into(), + is_client: true, + epoch: 0, + message_sequence: 2, + data: vec![0x11], + }, + HandshakeCacheItem { + typ: 3.into(), + is_client: false, + epoch: 0, + message_sequence: 0, + data: vec![0x02], + }, + HandshakeCacheItem { + typ: 3.into(), + is_client: false, + epoch: 1, + message_sequence: 0, + data: vec![0x12], + }, + HandshakeCacheItem { + typ: 3.into(), + is_client: false, + epoch: 2, + message_sequence: 0, + data: vec![0x12], + }, + ], + vec![ + HandshakeCachePullRule { + typ: 1.into(), + epoch: 0, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: 2.into(), + epoch: 1, + is_client: true, + optional: false, + }, + HandshakeCachePullRule { + typ: 3.into(), + epoch: 0, + is_client: false, + optional: false, + }, + ], + vec![0x00, 0x01, 0x02], + ), + ]; + + for (name, inputs, rules, expected) in tests { + let mut h = HandshakeCache::new(); + for i in inputs { + h.push(i.data, i.epoch, i.message_sequence, i.typ, i.is_client) + .await; + } + let verify_data = h.pull_and_merge(&rules).await; + assert_eq!( + verify_data, expected, + "handshakeCache '{name}' exp:{expected:?} actual {verify_data:?}", + ); + } + + Ok(()) +} + +#[tokio::test] +async fn test_handshake_cache_session_hash() -> Result<()> { + let tests = vec![ + ( + "Standard Handshake", + vec![ + HandshakeCacheItem { + typ: HandshakeType::ClientHello, + is_client: true, + epoch: 0, + message_sequence: 0, + data: vec![0x00], + }, + HandshakeCacheItem { + typ: HandshakeType::ServerHello, + is_client: false, + epoch: 0, + message_sequence: 1, + data: vec![0x01], + }, + HandshakeCacheItem { + typ: HandshakeType::Certificate, + is_client: false, + epoch: 0, + message_sequence: 2, + data: vec![0x02], + }, + HandshakeCacheItem { + typ: HandshakeType::ServerKeyExchange, + is_client: false, + epoch: 0, + message_sequence: 3, + data: vec![0x03], + }, + HandshakeCacheItem { + typ: HandshakeType::ServerHelloDone, + is_client: false, + epoch: 0, + message_sequence: 4, + data: vec![0x04], + }, + HandshakeCacheItem { + typ: HandshakeType::ClientKeyExchange, + is_client: true, + epoch: 0, + message_sequence: 5, + data: vec![0x05], + }, + ], + vec![ + 0x17, 0xe8, 0x8d, 0xb1, 0x87, 0xaf, 0xd6, 0x2c, 0x16, 0xe5, 0xde, 0xbf, 0x3e, 0x65, + 0x27, 0xcd, 0x00, 0x6b, 0xc0, 0x12, 0xbc, 0x90, 0xb5, 0x1a, 0x81, 0x0c, 0xd8, 0x0c, + 0x2d, 0x51, 0x1f, 0x43, + ], + ), + ( + "Handshake With Client Cert Request", + vec![ + HandshakeCacheItem { + typ: HandshakeType::ClientHello, + is_client: true, + epoch: 0, + message_sequence: 0, + data: vec![0x00], + }, + HandshakeCacheItem { + typ: HandshakeType::ServerHello, + is_client: false, + epoch: 0, + message_sequence: 1, + data: vec![0x01], + }, + HandshakeCacheItem { + typ: HandshakeType::Certificate, + is_client: false, + epoch: 0, + message_sequence: 2, + data: vec![0x02], + }, + HandshakeCacheItem { + typ: HandshakeType::ServerKeyExchange, + is_client: false, + epoch: 0, + message_sequence: 3, + data: vec![0x03], + }, + HandshakeCacheItem { + typ: HandshakeType::CertificateRequest, + is_client: false, + epoch: 0, + message_sequence: 4, + data: vec![0x04], + }, + HandshakeCacheItem { + typ: HandshakeType::ServerHelloDone, + is_client: false, + epoch: 0, + message_sequence: 5, + data: vec![0x05], + }, + HandshakeCacheItem { + typ: HandshakeType::ClientKeyExchange, + is_client: true, + epoch: 0, + message_sequence: 6, + data: vec![0x06], + }, + ], + vec![ + 0x57, 0x35, 0x5a, 0xc3, 0x30, 0x3c, 0x14, 0x8f, 0x11, 0xae, 0xf7, 0xcb, 0x17, 0x94, + 0x56, 0xb9, 0x23, 0x2c, 0xde, 0x33, 0xa8, 0x18, 0xdf, 0xda, 0x2c, 0x2f, 0xcb, 0x93, + 0x25, 0x74, 0x9a, 0x6b, + ], + ), + ( + "Handshake Ignores after ClientKeyExchange", + vec![ + HandshakeCacheItem { + typ: HandshakeType::ClientHello, + is_client: true, + epoch: 0, + message_sequence: 0, + data: vec![0x00], + }, + HandshakeCacheItem { + typ: HandshakeType::ServerHello, + is_client: false, + epoch: 0, + message_sequence: 1, + data: vec![0x01], + }, + HandshakeCacheItem { + typ: HandshakeType::Certificate, + is_client: false, + epoch: 0, + message_sequence: 2, + data: vec![0x02], + }, + HandshakeCacheItem { + typ: HandshakeType::ServerKeyExchange, + is_client: false, + epoch: 0, + message_sequence: 3, + data: vec![0x03], + }, + HandshakeCacheItem { + typ: HandshakeType::CertificateRequest, + is_client: false, + epoch: 0, + message_sequence: 4, + data: vec![0x04], + }, + HandshakeCacheItem { + typ: HandshakeType::ServerHelloDone, + is_client: false, + epoch: 0, + message_sequence: 5, + data: vec![0x05], + }, + HandshakeCacheItem { + typ: HandshakeType::ClientKeyExchange, + is_client: true, + epoch: 0, + message_sequence: 6, + data: vec![0x06], + }, + HandshakeCacheItem { + typ: HandshakeType::CertificateVerify, + is_client: true, + epoch: 0, + message_sequence: 7, + data: vec![0x07], + }, + HandshakeCacheItem { + typ: HandshakeType::Finished, + is_client: true, + epoch: 1, + message_sequence: 7, + data: vec![0x08], + }, + HandshakeCacheItem { + typ: HandshakeType::Finished, + is_client: false, + epoch: 1, + message_sequence: 7, + data: vec![0x09], + }, + ], + vec![ + 0x57, 0x35, 0x5a, 0xc3, 0x30, 0x3c, 0x14, 0x8f, 0x11, 0xae, 0xf7, 0xcb, 0x17, 0x94, + 0x56, 0xb9, 0x23, 0x2c, 0xde, 0x33, 0xa8, 0x18, 0xdf, 0xda, 0x2c, 0x2f, 0xcb, 0x93, + 0x25, 0x74, 0x9a, 0x6b, + ], + ), + ( + "Handshake Ignores wrong epoch", + vec![ + HandshakeCacheItem { + typ: HandshakeType::ClientHello, + is_client: true, + epoch: 0, + message_sequence: 0, + data: vec![0x00], + }, + HandshakeCacheItem { + typ: HandshakeType::ServerHello, + is_client: false, + epoch: 0, + message_sequence: 1, + data: vec![0x01], + }, + HandshakeCacheItem { + typ: HandshakeType::Certificate, + is_client: false, + epoch: 0, + message_sequence: 2, + data: vec![0x02], + }, + HandshakeCacheItem { + typ: HandshakeType::ServerKeyExchange, + is_client: false, + epoch: 0, + message_sequence: 3, + data: vec![0x03], + }, + HandshakeCacheItem { + typ: HandshakeType::CertificateRequest, + is_client: false, + epoch: 0, + message_sequence: 4, + data: vec![0x04], + }, + HandshakeCacheItem { + typ: HandshakeType::ServerHelloDone, + is_client: false, + epoch: 0, + message_sequence: 5, + data: vec![0x05], + }, + HandshakeCacheItem { + typ: HandshakeType::ClientKeyExchange, + is_client: true, + epoch: 0, + message_sequence: 6, + data: vec![0x06], + }, + HandshakeCacheItem { + typ: HandshakeType::CertificateVerify, + is_client: true, + epoch: 0, + message_sequence: 7, + data: vec![0x07], + }, + HandshakeCacheItem { + typ: HandshakeType::Finished, + is_client: true, + epoch: 0, + message_sequence: 7, + data: vec![0xf0], + }, + HandshakeCacheItem { + typ: HandshakeType::Finished, + is_client: false, + epoch: 0, + message_sequence: 7, + data: vec![0xf1], + }, + HandshakeCacheItem { + typ: HandshakeType::Finished, + is_client: true, + epoch: 1, + message_sequence: 7, + data: vec![0x08], + }, + HandshakeCacheItem { + typ: HandshakeType::Finished, + is_client: false, + epoch: 1, + message_sequence: 7, + data: vec![0x09], + }, + HandshakeCacheItem { + typ: HandshakeType::Finished, + is_client: true, + epoch: 0, + message_sequence: 7, + data: vec![0xf0], + }, + HandshakeCacheItem { + typ: HandshakeType::Finished, + is_client: false, + epoch: 0, + message_sequence: 7, + data: vec![0xf1], + }, + ], + vec![ + 0x57, 0x35, 0x5a, 0xc3, 0x30, 0x3c, 0x14, 0x8f, 0x11, 0xae, 0xf7, 0xcb, 0x17, 0x94, + 0x56, 0xb9, 0x23, 0x2c, 0xde, 0x33, 0xa8, 0x18, 0xdf, 0xda, 0x2c, 0x2f, 0xcb, 0x93, + 0x25, 0x74, 0x9a, 0x6b, + ], + ), + ]; + + for (name, inputs, expected) in tests { + let mut h = HandshakeCache::new(); + for i in inputs { + h.push(i.data, i.epoch, i.message_sequence, i.typ, i.is_client) + .await; + } + + let verify_data = h.session_hash(CipherSuiteHash::Sha256, 0, &[]).await?; + + assert_eq!( + verify_data, expected, + "handshakeCacheSessionHassh '{name}' exp: {expected:?} actual {verify_data:?}" + ); + } + + Ok(()) +} diff --git a/vendor/dtls/src/handshake/handshake_header.rs b/vendor/dtls/src/handshake/handshake_header.rs new file mode 100644 index 00000000..70c7610a --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_header.rs @@ -0,0 +1,50 @@ +use std::io::{Read, Write}; + +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; + +use super::*; + +// msg_len for Handshake messages assumes an extra 12 bytes for +// sequence, Fragment and version information +pub(crate) const HANDSHAKE_HEADER_LENGTH: usize = 12; + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] +pub struct HandshakeHeader { + pub(crate) handshake_type: HandshakeType, + pub(crate) length: u32, // uint24 in spec + pub(crate) message_sequence: u16, + pub(crate) fragment_offset: u32, // uint24 in spec + pub(crate) fragment_length: u32, // uint24 in spec +} + +impl HandshakeHeader { + pub fn size(&self) -> usize { + 1 + 3 + 2 + 3 + 3 + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + writer.write_u8(self.handshake_type as u8)?; + writer.write_u24::(self.length)?; + writer.write_u16::(self.message_sequence)?; + writer.write_u24::(self.fragment_offset)?; + writer.write_u24::(self.fragment_length)?; + + Ok(writer.flush()?) + } + + pub fn unmarshal(reader: &mut R) -> Result { + let handshake_type = reader.read_u8()?.into(); + let length = reader.read_u24::()?; + let message_sequence = reader.read_u16::()?; + let fragment_offset = reader.read_u24::()?; + let fragment_length = reader.read_u24::()?; + + Ok(HandshakeHeader { + handshake_type, + length, + message_sequence, + fragment_offset, + fragment_length, + }) + } +} diff --git a/vendor/dtls/src/handshake/handshake_message_certificate.rs b/vendor/dtls/src/handshake/handshake_message_certificate.rs new file mode 100644 index 00000000..c4e0034e --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_message_certificate.rs @@ -0,0 +1,70 @@ +use std::io::{Read, Write}; + +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; + +use super::*; + +#[cfg(test)] +mod handshake_message_certificate_test; + +const HANDSHAKE_MESSAGE_CERTIFICATE_LENGTH_FIELD_SIZE: usize = 3; + +#[derive(PartialEq, Eq, Debug, Clone)] +pub struct HandshakeMessageCertificate { + pub(crate) certificate: Vec>, +} + +impl HandshakeMessageCertificate { + pub fn handshake_type(&self) -> HandshakeType { + HandshakeType::Certificate + } + + pub fn size(&self) -> usize { + let mut len = 3; + + for r in &self.certificate { + len += HANDSHAKE_MESSAGE_CERTIFICATE_LENGTH_FIELD_SIZE + r.len(); + } + + len + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + let mut payload_size = 0; + for r in &self.certificate { + payload_size += HANDSHAKE_MESSAGE_CERTIFICATE_LENGTH_FIELD_SIZE + r.len(); + } + + // Total Payload Size + writer.write_u24::(payload_size as u32)?; + + for r in &self.certificate { + // Certificate Length + writer.write_u24::(r.len() as u32)?; + + // Certificate body + writer.write_all(r)?; + } + + Ok(writer.flush()?) + } + + pub fn unmarshal(reader: &mut R) -> Result { + let mut certificate: Vec> = vec![]; + + let payload_size = reader.read_u24::()? as usize; + let mut offset = 0; + while offset < payload_size { + let certificate_len = reader.read_u24::()? as usize; + offset += HANDSHAKE_MESSAGE_CERTIFICATE_LENGTH_FIELD_SIZE; + + let mut buf = vec![0; certificate_len]; + reader.read_exact(&mut buf)?; + offset += certificate_len; + + certificate.push(buf); + } + + Ok(HandshakeMessageCertificate { certificate }) + } +} diff --git a/vendor/dtls/src/handshake/handshake_message_certificate/handshake_message_certificate_test.rs b/vendor/dtls/src/handshake/handshake_message_certificate/handshake_message_certificate_test.rs new file mode 100644 index 00000000..582d16df --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_message_certificate/handshake_message_certificate_test.rs @@ -0,0 +1,79 @@ +use std::io::{BufReader, BufWriter}; + +use super::*; + +#[test] +fn test_handshake_message_certificate() -> Result<()> { + let raw_certificate = vec![ + 0x00, 0x01, 0x8c, 0x00, 0x01, 0x89, 0x30, 0x82, 0x01, 0x85, 0x30, 0x82, 0x01, 0x2b, 0x02, + 0x14, 0x7d, 0x00, 0xcf, 0x07, 0xfc, 0xe2, 0xb6, 0xb8, 0x3f, 0x72, 0xeb, 0x11, 0x36, 0x1b, + 0xf6, 0x39, 0xf1, 0x3c, 0x33, 0x41, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, + 0x04, 0x03, 0x02, 0x30, 0x45, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, + 0x02, 0x41, 0x55, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x53, + 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, + 0x55, 0x04, 0x0a, 0x0c, 0x18, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57, + 0x69, 0x64, 0x67, 0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20, 0x4c, 0x74, 0x64, 0x30, + 0x1e, 0x17, 0x0d, 0x31, 0x38, 0x31, 0x30, 0x32, 0x35, 0x30, 0x38, 0x35, 0x31, 0x31, 0x32, + 0x5a, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x30, 0x32, 0x35, 0x30, 0x38, 0x35, 0x31, 0x31, 0x32, + 0x5a, 0x30, 0x45, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x41, + 0x55, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x53, 0x6f, 0x6d, + 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, + 0x0a, 0x0c, 0x18, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57, 0x69, 0x64, + 0x67, 0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20, 0x4c, 0x74, 0x64, 0x30, 0x59, 0x30, + 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, + 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0xf9, 0xb1, 0x62, 0xd6, 0x07, 0xae, + 0xc3, 0x36, 0x34, 0xf5, 0xa3, 0x09, 0x39, 0x86, 0xe7, 0x3b, 0x59, 0xf7, 0x4a, 0x1d, 0xf4, + 0x97, 0x4f, 0x91, 0x40, 0x56, 0x1b, 0x3d, 0x6c, 0x5a, 0x38, 0x10, 0x15, 0x58, 0xf5, 0xa4, + 0xcc, 0xdf, 0xd5, 0xf5, 0x4a, 0x35, 0x40, 0x0f, 0x9f, 0x54, 0xb7, 0xe9, 0xe2, 0xae, 0x63, + 0x83, 0x6a, 0x4c, 0xfc, 0xc2, 0x5f, 0x78, 0xa0, 0xbb, 0x46, 0x54, 0xa4, 0xda, 0x30, 0x0a, + 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45, + 0x02, 0x20, 0x47, 0x1a, 0x5f, 0x58, 0x2a, 0x74, 0x33, 0x6d, 0xed, 0xac, 0x37, 0x21, 0xfa, + 0x76, 0x5a, 0x4d, 0x78, 0x68, 0x1a, 0xdd, 0x80, 0xa4, 0xd4, 0xb7, 0x7f, 0x7d, 0x78, 0xb3, + 0xfb, 0xf3, 0x95, 0xfb, 0x02, 0x21, 0x00, 0xc0, 0x73, 0x30, 0xda, 0x2b, 0xc0, 0x0c, 0x9e, + 0xb2, 0x25, 0x0d, 0x46, 0xb0, 0xbc, 0x66, 0x7f, 0x71, 0x66, 0xbf, 0x16, 0xb3, 0x80, 0x78, + 0xd0, 0x0c, 0xef, 0xcc, 0xf5, 0xc1, 0x15, 0x0f, 0x58, + ]; + + let mut reader = BufReader::new(raw_certificate.as_slice()); + let c = HandshakeMessageCertificate::unmarshal(&mut reader)?; + //TODO: add x509 parse + // certificate, err := x509.ParseCertificate(c.certificate[0]) + // if err != nil { + // t.Error(err) + // } + // copyCertificatePrivateMembers(certificate, parsedCertificate) + // if !reflect.DeepEqual(certificate, parsedCertificate) { + // t.Errorf("handshakeMessageCertificate unmarshal: got %#v, want %#v", c, parsedCertificate) + // } + + let mut raw = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw.as_mut()); + c.marshal(&mut writer)?; + } + assert_eq!( + raw, raw_certificate, + "handshakeMessageCertificate marshal: got {raw:?}, want {raw_certificate:?}" + ); + + Ok(()) +} + +#[test] +fn test_empty_handshake_message_certificate() -> Result<()> { + let raw_certificate = vec![0x00, 0x00, 0x00]; + + let expected_certificate = HandshakeMessageCertificate { + certificate: vec![], + }; + + let mut reader = BufReader::new(raw_certificate.as_slice()); + let c = HandshakeMessageCertificate::unmarshal(&mut reader)?; + + assert_eq!( + c, expected_certificate, + "handshakeMessageCertificate unmarshal: got {c:?}, want {expected_certificate:?}", + ); + + Ok(()) +} diff --git a/vendor/dtls/src/handshake/handshake_message_certificate_request.rs b/vendor/dtls/src/handshake/handshake_message_certificate_request.rs new file mode 100644 index 00000000..f48cecbb --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_message_certificate_request.rs @@ -0,0 +1,77 @@ +#[cfg(test)] +mod handshake_message_certificate_request_test; + +use std::io::{Read, Write}; + +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; + +use super::*; +use crate::client_certificate_type::*; +use crate::signature_hash_algorithm::*; + +/* +A non-anonymous server can optionally request a certificate from +the client, if appropriate for the selected cipher suite. This +message, if sent, will immediately follow the ServerKeyExchange +message (if it is sent; otherwise, this message follows the +server's Certificate message). +*/ +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct HandshakeMessageCertificateRequest { + pub(crate) certificate_types: Vec, + pub(crate) signature_hash_algorithms: Vec, +} + +const HANDSHAKE_MESSAGE_CERTIFICATE_REQUEST_MIN_LENGTH: usize = 5; + +impl HandshakeMessageCertificateRequest { + pub fn handshake_type(&self) -> HandshakeType { + HandshakeType::CertificateRequest + } + + pub fn size(&self) -> usize { + 1 + self.certificate_types.len() + 2 + self.signature_hash_algorithms.len() * 2 + 2 + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + writer.write_u8(self.certificate_types.len() as u8)?; + for v in &self.certificate_types { + writer.write_u8(*v as u8)?; + } + + writer.write_u16::(2 * self.signature_hash_algorithms.len() as u16)?; + for v in &self.signature_hash_algorithms { + writer.write_u8(v.hash as u8)?; + writer.write_u8(v.signature as u8)?; + } + + writer.write_all(&[0x00, 0x00])?; // Distinguished Names Length + + Ok(writer.flush()?) + } + + pub fn unmarshal(reader: &mut R) -> Result { + let certificate_types_length = reader.read_u8()?; + + let mut certificate_types = vec![]; + for _ in 0..certificate_types_length { + let cert_type = reader.read_u8()?.into(); + certificate_types.push(cert_type); + } + + let signature_hash_algorithms_length = reader.read_u16::()?; + + let mut signature_hash_algorithms = vec![]; + for _ in (0..signature_hash_algorithms_length).step_by(2) { + let hash = reader.read_u8()?.into(); + let signature = reader.read_u8()?.into(); + + signature_hash_algorithms.push(SignatureHashAlgorithm { hash, signature }); + } + + Ok(HandshakeMessageCertificateRequest { + certificate_types, + signature_hash_algorithms, + }) + } +} diff --git a/vendor/dtls/src/handshake/handshake_message_certificate_request/handshake_message_certificate_request_test.rs b/vendor/dtls/src/handshake/handshake_message_certificate_request/handshake_message_certificate_request_test.rs new file mode 100644 index 00000000..f261cde5 --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_message_certificate_request/handshake_message_certificate_request_test.rs @@ -0,0 +1,64 @@ +use std::io::{BufReader, BufWriter}; + +use super::*; +use crate::signature_hash_algorithm::*; + +#[test] +fn test_handshake_message_certificate_request() -> Result<()> { + let raw_certificate_request = vec![ + 0x02, 0x01, 0x40, 0x00, 0x0C, 0x04, 0x03, 0x04, 0x01, 0x05, 0x03, 0x05, 0x01, 0x06, 0x01, + 0x02, 0x01, 0x00, 0x00, + ]; + + let parsed_certificate_request = HandshakeMessageCertificateRequest { + certificate_types: vec![ + ClientCertificateType::RsaSign, + ClientCertificateType::EcdsaSign, + ], + signature_hash_algorithms: vec![ + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha256, + signature: SignatureAlgorithm::Ecdsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha256, + signature: SignatureAlgorithm::Rsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha384, + signature: SignatureAlgorithm::Ecdsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha384, + signature: SignatureAlgorithm::Rsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha512, + signature: SignatureAlgorithm::Rsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha1, + signature: SignatureAlgorithm::Rsa, + }, + ], + }; + + let mut reader = BufReader::new(raw_certificate_request.as_slice()); + let c = HandshakeMessageCertificateRequest::unmarshal(&mut reader)?; + assert_eq!( + c, parsed_certificate_request, + "parsedCertificateRequest unmarshal: got {c:?}, want {parsed_certificate_request:?}" + ); + + let mut raw = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw.as_mut()); + c.marshal(&mut writer)?; + } + assert_eq!( + raw, raw_certificate_request, + "parsedCertificateRequest marshal: got {raw:?}, want {raw_certificate_request:?}" + ); + + Ok(()) +} diff --git a/vendor/dtls/src/handshake/handshake_message_certificate_verify.rs b/vendor/dtls/src/handshake/handshake_message_certificate_verify.rs new file mode 100644 index 00000000..4b535ce1 --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_message_certificate_verify.rs @@ -0,0 +1,52 @@ +#[cfg(test)] +mod handshake_message_certificate_verify_test; + +use std::io::{Read, Write}; + +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; + +use super::*; +use crate::signature_hash_algorithm::*; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct HandshakeMessageCertificateVerify { + pub(crate) algorithm: SignatureHashAlgorithm, + pub(crate) signature: Vec, +} + +const HANDSHAKE_MESSAGE_CERTIFICATE_VERIFY_MIN_LENGTH: usize = 4; + +impl HandshakeMessageCertificateVerify { + pub fn handshake_type(&self) -> HandshakeType { + HandshakeType::CertificateVerify + } + + pub fn size(&self) -> usize { + 1 + 1 + 2 + self.signature.len() + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + writer.write_u8(self.algorithm.hash as u8)?; + writer.write_u8(self.algorithm.signature as u8)?; + writer.write_u16::(self.signature.len() as u16)?; + writer.write_all(&self.signature)?; + + Ok(writer.flush()?) + } + + pub fn unmarshal(reader: &mut R) -> Result { + let hash_algorithm = reader.read_u8()?.into(); + let signature_algorithm = reader.read_u8()?.into(); + let signature_length = reader.read_u16::()? as usize; + let mut signature = vec![0; signature_length]; + reader.read_exact(&mut signature)?; + + Ok(HandshakeMessageCertificateVerify { + algorithm: SignatureHashAlgorithm { + hash: hash_algorithm, + signature: signature_algorithm, + }, + signature, + }) + } +} diff --git a/vendor/dtls/src/handshake/handshake_message_certificate_verify/handshake_message_certificate_verify_test.rs b/vendor/dtls/src/handshake/handshake_message_certificate_verify/handshake_message_certificate_verify_test.rs new file mode 100644 index 00000000..b52cebc5 --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_message_certificate_verify/handshake_message_certificate_verify_test.rs @@ -0,0 +1,40 @@ +use std::io::{BufReader, BufWriter}; + +use super::*; + +#[test] +fn test_handshake_message_certificate_request() -> Result<()> { + let raw_certificate_verify = vec![ + 0x04, 0x03, 0x00, 0x47, 0x30, 0x45, 0x02, 0x20, 0x6b, 0x63, 0x17, 0xad, 0xbe, 0xb7, 0x7b, + 0x0f, 0x86, 0x73, 0x39, 0x1e, 0xba, 0xb3, 0x50, 0x9c, 0xce, 0x9c, 0xe4, 0x8b, 0xe5, 0x13, + 0x07, 0x59, 0x18, 0x1f, 0xe5, 0xa0, 0x2b, 0xca, 0xa6, 0xad, 0x02, 0x21, 0x00, 0xd3, 0xb5, + 0x01, 0xbe, 0x87, 0x6c, 0x04, 0xa1, 0xdc, 0x28, 0xaa, 0x5f, 0xf7, 0x1e, 0x9c, 0xc0, 0x1e, + 0x00, 0x2c, 0xe5, 0x94, 0xbb, 0x03, 0x0e, 0xf1, 0xcb, 0x28, 0x22, 0x33, 0x23, 0x88, 0xad, + ]; + let parsed_certificate_verify = HandshakeMessageCertificateVerify { + algorithm: SignatureHashAlgorithm { + hash: raw_certificate_verify[0].into(), + signature: raw_certificate_verify[1].into(), + }, + signature: raw_certificate_verify[4..].to_vec(), + }; + + let mut reader = BufReader::new(raw_certificate_verify.as_slice()); + let c = HandshakeMessageCertificateVerify::unmarshal(&mut reader)?; + assert_eq!( + c, parsed_certificate_verify, + "handshakeMessageCertificate unmarshal: got {c:?}, want {parsed_certificate_verify:?}" + ); + + let mut raw = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw.as_mut()); + c.marshal(&mut writer)?; + } + assert_eq!( + raw, raw_certificate_verify, + "handshakeMessageCertificateVerify marshal: got {raw:?}, want {raw_certificate_verify:?}" + ); + + Ok(()) +} diff --git a/vendor/dtls/src/handshake/handshake_message_client_hello.rs b/vendor/dtls/src/handshake/handshake_message_client_hello.rs new file mode 100644 index 00000000..f98340e0 --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_message_client_hello.rs @@ -0,0 +1,196 @@ +#[cfg(test)] +mod handshake_message_client_hello_test; + +use std::fmt; +use std::io::{BufReader, BufWriter}; + +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; + +use super::handshake_random::*; +use super::*; +use crate::cipher_suite::*; +use crate::compression_methods::*; +use crate::extension::*; +use crate::record_layer::record_layer_header::*; + +/* +When a client first connects to a server it is required to send +the client hello as its first message. The client can also send a +client hello in response to a hello request or on its own +initiative in order to renegotiate the security parameters in an +existing connection. +*/ +#[derive(Clone)] +pub struct HandshakeMessageClientHello { + pub(crate) version: ProtocolVersion, + pub(crate) random: HandshakeRandom, + pub(crate) cookie: Vec, + + pub(crate) cipher_suites: Vec, + pub(crate) compression_methods: CompressionMethods, + pub(crate) extensions: Vec, +} + +impl PartialEq for HandshakeMessageClientHello { + fn eq(&self, other: &Self) -> bool { + if !(self.version == other.version + && self.random == other.random + && self.cookie == other.cookie + && self.compression_methods == other.compression_methods + && self.extensions == other.extensions + && self.cipher_suites.len() == other.cipher_suites.len()) + { + return false; + } + + for i in 0..self.cipher_suites.len() { + if self.cipher_suites[i] != other.cipher_suites[i] { + return false; + } + } + + true + } +} + +impl fmt::Debug for HandshakeMessageClientHello { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut cipher_suites_str = String::new(); + for cipher_suite in &self.cipher_suites { + cipher_suites_str += &cipher_suite.to_string(); + cipher_suites_str += " "; + } + let s = [ + format!("version: {:?} random: {:?}", self.version, self.random), + format!("cookie: {:?}", self.cookie), + format!("cipher_suites: {cipher_suites_str:?}"), + format!("compression_methods: {:?}", self.compression_methods), + format!("extensions: {:?}", self.extensions), + ]; + write!(f, "{}", s.join(" ")) + } +} + +const HANDSHAKE_MESSAGE_CLIENT_HELLO_VARIABLE_WIDTH_START: usize = 34; + +impl HandshakeMessageClientHello { + pub fn handshake_type(&self) -> HandshakeType { + HandshakeType::ClientHello + } + + pub fn size(&self) -> usize { + let mut len = 0; + + len += 2; // version.major+minor + len += self.random.size(); + + // SessionID + len += 1; + + len += 1 + self.cookie.len(); + + len += 2 + 2 * self.cipher_suites.len(); + + len += self.compression_methods.size(); + + len += 2; + for extension in &self.extensions { + len += extension.size(); + } + + len + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + if self.cookie.len() > 255 { + return Err(Error::ErrCookieTooLong); + } + + writer.write_u8(self.version.major)?; + writer.write_u8(self.version.minor)?; + self.random.marshal(writer)?; + + // SessionID + writer.write_u8(0x00)?; + + writer.write_u8(self.cookie.len() as u8)?; + writer.write_all(&self.cookie)?; + + writer.write_u16::(2 * self.cipher_suites.len() as u16)?; + for cipher_suite in &self.cipher_suites { + writer.write_u16::(*cipher_suite as u16)?; + } + + self.compression_methods.marshal(writer)?; + + let mut extension_buffer = vec![]; + { + let mut extension_writer = BufWriter::<&mut Vec>::new(extension_buffer.as_mut()); + for extension in &self.extensions { + extension.marshal(&mut extension_writer)?; + } + } + + writer.write_u16::(extension_buffer.len() as u16)?; + writer.write_all(&extension_buffer)?; + + Ok(writer.flush()?) + } + + pub fn unmarshal(reader: &mut R) -> Result { + let major = reader.read_u8()?; + let minor = reader.read_u8()?; + let random = HandshakeRandom::unmarshal(reader)?; + + // Session ID + reader.read_u8()?; + + let cookie_len = reader.read_u8()? as usize; + let mut cookie = vec![0; cookie_len]; + reader.read_exact(&mut cookie)?; + + let cipher_suites_len = reader.read_u16::()? as usize / 2; + let mut cipher_suites = vec![]; + for _ in 0..cipher_suites_len { + let id: CipherSuiteId = reader.read_u16::()?.into(); + //let cipher_suite = cipher_suite_for_id(id)?; + cipher_suites.push(id); + } + + let compression_methods = CompressionMethods::unmarshal(reader)?; + let mut extensions = vec![]; + + let extension_buffer_len = reader.read_u16::()? as usize; + let mut extension_buffer = vec![0u8; extension_buffer_len]; + reader.read_exact(&mut extension_buffer)?; + + let mut offset = 0; + while offset < extension_buffer_len { + let mut extension_reader = BufReader::new(&extension_buffer[offset..]); + if let Ok(extension) = Extension::unmarshal(&mut extension_reader) { + extensions.push(extension); + } else { + log::warn!( + "Unsupported Extension Type {} {}", + extension_buffer[offset], + extension_buffer[offset + 1] + ); + } + + let extension_len = + u16::from_be_bytes([extension_buffer[offset + 2], extension_buffer[offset + 3]]) + as usize; + offset += 4 + extension_len; + } + + Ok(HandshakeMessageClientHello { + version: ProtocolVersion { major, minor }, + random, + cookie, + + cipher_suites, + compression_methods, + extensions, + }) + } +} diff --git a/vendor/dtls/src/handshake/handshake_message_client_hello/handshake_message_client_hello_test.rs b/vendor/dtls/src/handshake/handshake_message_client_hello/handshake_message_client_hello_test.rs new file mode 100644 index 00000000..ba426031 --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_message_client_hello/handshake_message_client_hello_test.rs @@ -0,0 +1,75 @@ +use std::io::{BufReader, BufWriter}; +use std::time::{Duration, SystemTime}; + +use super::*; +use crate::curve::named_curve::*; +use crate::extension::extension_supported_elliptic_curves::*; + +#[test] +fn test_handshake_message_client_hello() -> Result<()> { + let raw_client_hello = vec![ + 0xfe, 0xfd, 0xb6, 0x2f, 0xce, 0x5c, 0x42, 0x54, 0xff, 0x86, 0xe1, 0x24, 0x41, 0x91, 0x42, + 0x62, 0x15, 0xad, 0x16, 0xc9, 0x15, 0x8d, 0x95, 0x71, 0x8a, 0xbb, 0x22, 0xd7, 0x47, 0xec, + 0xd8, 0x3d, 0xdc, 0x4b, 0x00, 0x14, 0xe6, 0x14, 0x3a, 0x1b, 0x04, 0xea, 0x9e, 0x7a, 0x14, + 0xd6, 0x6c, 0x57, 0xd0, 0x0e, 0x32, 0x85, 0x76, 0x18, 0xde, 0xd8, 0x00, 0x04, 0xc0, 0x2b, + 0xc0, 0x0a, 0x01, 0x00, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x04, 0x00, 0x02, 0x00, 0x1d, + ]; + + let gmt_unix_time = if let Some(unix_time) = + SystemTime::UNIX_EPOCH.checked_add(Duration::new(3056586332u64, 0)) + { + unix_time + } else { + SystemTime::UNIX_EPOCH + }; + let parsed_client_hello = HandshakeMessageClientHello { + version: ProtocolVersion { + major: 0xFE, + minor: 0xFD, + }, + random: HandshakeRandom { + gmt_unix_time, + random_bytes: [ + 0x42, 0x54, 0xff, 0x86, 0xe1, 0x24, 0x41, 0x91, 0x42, 0x62, 0x15, 0xad, 0x16, 0xc9, + 0x15, 0x8d, 0x95, 0x71, 0x8a, 0xbb, 0x22, 0xd7, 0x47, 0xec, 0xd8, 0x3d, 0xdc, 0x4b, + ], + }, + cookie: vec![ + 0xe6, 0x14, 0x3a, 0x1b, 0x04, 0xea, 0x9e, 0x7a, 0x14, 0xd6, 0x6c, 0x57, 0xd0, 0x0e, + 0x32, 0x85, 0x76, 0x18, 0xde, 0xd8, + ], + cipher_suites: vec![ + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256, + CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_256_Cbc_Sha, + //Box::::default(), + //Box::::default(), + ], + compression_methods: CompressionMethods { + ids: vec![CompressionMethodId::Null], + }, + extensions: vec![Extension::SupportedEllipticCurves( + ExtensionSupportedEllipticCurves { + elliptic_curves: vec![NamedCurve::X25519], + }, + )], + }; + + let mut reader = BufReader::new(raw_client_hello.as_slice()); + let c = HandshakeMessageClientHello::unmarshal(&mut reader)?; + assert_eq!( + c, parsed_client_hello, + "handshakeMessageClientHello unmarshal: got {c:?}, want {parsed_client_hello:?}" + ); + + let mut raw = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw.as_mut()); + c.marshal(&mut writer)?; + } + assert_eq!( + raw, raw_client_hello, + "handshakeMessageClientHello marshal: got {raw:?}, want {raw_client_hello:?}" + ); + + Ok(()) +} diff --git a/vendor/dtls/src/handshake/handshake_message_client_key_exchange.rs b/vendor/dtls/src/handshake/handshake_message_client_key_exchange.rs new file mode 100644 index 00000000..4c027ff8 --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_message_client_key_exchange.rs @@ -0,0 +1,70 @@ +#[cfg(test)] +mod handshake_message_client_key_exchange_test; + +use std::io::{Read, Write}; + +use byteorder::{BigEndian, WriteBytesExt}; + +use super::*; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct HandshakeMessageClientKeyExchange { + pub(crate) identity_hint: Vec, + pub(crate) public_key: Vec, +} + +impl HandshakeMessageClientKeyExchange { + pub fn handshake_type(&self) -> HandshakeType { + HandshakeType::ClientKeyExchange + } + + pub fn size(&self) -> usize { + if !self.public_key.is_empty() { + 1 + self.public_key.len() + } else { + 2 + self.identity_hint.len() + } + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + if (!self.identity_hint.is_empty() && !self.public_key.is_empty()) + || (self.identity_hint.is_empty() && self.public_key.is_empty()) + { + return Err(Error::ErrInvalidClientKeyExchange); + } + + if !self.public_key.is_empty() { + writer.write_u8(self.public_key.len() as u8)?; + writer.write_all(&self.public_key)?; + } else { + writer.write_u16::(self.identity_hint.len() as u16)?; + writer.write_all(&self.identity_hint)?; + } + + Ok(writer.flush()?) + } + + pub fn unmarshal(reader: &mut R) -> Result { + let mut data = vec![]; + reader.read_to_end(&mut data)?; + + // If parsed as PSK return early and only populate PSK Identity Hint + let psk_length = ((data[0] as u16) << 8) | data[1] as u16; + if data.len() == psk_length as usize + 2 { + return Ok(HandshakeMessageClientKeyExchange { + identity_hint: data[2..].to_vec(), + public_key: vec![], + }); + } + + let public_key_length = data[0] as usize; + if data.len() != public_key_length + 1 { + return Err(Error::ErrBufferTooSmall); + } + + Ok(HandshakeMessageClientKeyExchange { + identity_hint: vec![], + public_key: data[1..].to_vec(), + }) + } +} diff --git a/vendor/dtls/src/handshake/handshake_message_client_key_exchange/handshake_message_client_key_exchange_test.rs b/vendor/dtls/src/handshake/handshake_message_client_key_exchange/handshake_message_client_key_exchange_test.rs new file mode 100644 index 00000000..0b9d6c17 --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_message_client_key_exchange/handshake_message_client_key_exchange_test.rs @@ -0,0 +1,35 @@ +use std::io::{BufReader, BufWriter}; + +use super::*; + +#[test] +fn test_handshake_message_client_key_exchange() -> Result<()> { + let raw_client_key_exchange = vec![ + 0x20, 0x26, 0x78, 0x4a, 0x78, 0x70, 0xc1, 0xf9, 0x71, 0xea, 0x50, 0x4a, 0xb5, 0xbb, 0x00, + 0x76, 0x02, 0x05, 0xda, 0xf7, 0xd0, 0x3f, 0xe3, 0xf7, 0x4e, 0x8a, 0x14, 0x6f, 0xb7, 0xe0, + 0xc0, 0xff, 0x54, + ]; + let parsed_client_key_exchange = HandshakeMessageClientKeyExchange { + identity_hint: vec![], + public_key: raw_client_key_exchange[1..].to_vec(), + }; + + let mut reader = BufReader::new(raw_client_key_exchange.as_slice()); + let c = HandshakeMessageClientKeyExchange::unmarshal(&mut reader)?; + assert_eq!( + c, parsed_client_key_exchange, + "parsedCertificateRequest unmarshal: got {c:?}, want {parsed_client_key_exchange:?}" + ); + + let mut raw = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw.as_mut()); + c.marshal(&mut writer)?; + } + assert_eq!( + raw, raw_client_key_exchange, + "handshakeMessageClientKeyExchange marshal: got {raw:?}, want {raw_client_key_exchange:?}" + ); + + Ok(()) +} diff --git a/vendor/dtls/src/handshake/handshake_message_finished.rs b/vendor/dtls/src/handshake/handshake_message_finished.rs new file mode 100644 index 00000000..d20feb6e --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_message_finished.rs @@ -0,0 +1,34 @@ +#[cfg(test)] +mod handshake_message_finished_test; + +use std::io::{Read, Write}; + +use super::*; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct HandshakeMessageFinished { + pub(crate) verify_data: Vec, +} + +impl HandshakeMessageFinished { + pub fn handshake_type(&self) -> HandshakeType { + HandshakeType::Finished + } + + pub fn size(&self) -> usize { + self.verify_data.len() + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + writer.write_all(&self.verify_data)?; + + Ok(writer.flush()?) + } + + pub fn unmarshal(reader: &mut R) -> Result { + let mut verify_data: Vec = vec![]; + reader.read_to_end(&mut verify_data)?; + + Ok(HandshakeMessageFinished { verify_data }) + } +} diff --git a/vendor/dtls/src/handshake/handshake_message_finished/handshake_message_finished_test.rs b/vendor/dtls/src/handshake/handshake_message_finished/handshake_message_finished_test.rs new file mode 100644 index 00000000..7980ee90 --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_message_finished/handshake_message_finished_test.rs @@ -0,0 +1,32 @@ +use std::io::{BufReader, BufWriter}; + +use super::*; + +#[test] +fn test_handshake_message_finished() -> Result<()> { + let raw_finished = vec![ + 0x01, 0x01, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + ]; + let parsed_finished = HandshakeMessageFinished { + verify_data: raw_finished.clone(), + }; + + let mut reader = BufReader::new(raw_finished.as_slice()); + let c = HandshakeMessageFinished::unmarshal(&mut reader)?; + assert_eq!( + c, parsed_finished, + "handshakeMessageFinished unmarshal: got {c:?}, want {parsed_finished:?}" + ); + + let mut raw = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw.as_mut()); + c.marshal(&mut writer)?; + } + assert_eq!( + raw, raw_finished, + "handshakeMessageFinished marshal: got {raw:?}, want {raw_finished:?}" + ); + + Ok(()) +} diff --git a/vendor/dtls/src/handshake/handshake_message_hello_verify_request.rs b/vendor/dtls/src/handshake/handshake_message_hello_verify_request.rs new file mode 100644 index 00000000..4e8eb99a --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_message_hello_verify_request.rs @@ -0,0 +1,75 @@ +#[cfg(test)] +mod handshake_message_hello_verify_request_test; + +use std::io::{Read, Write}; + +use byteorder::{ReadBytesExt, WriteBytesExt}; + +use super::*; +use crate::record_layer::record_layer_header::*; + +/* + The definition of HelloVerifyRequest is as follows: + + struct { + ProtocolVersion server_version; + opaque cookie<0..2^8-1>; + } HelloVerifyRequest; + + The HelloVerifyRequest message type is hello_verify_request(3). + + When the client sends its ClientHello message to the server, the server + MAY respond with a HelloVerifyRequest message. This message contains + a stateless cookie generated using the technique of [PHOTURIS]. The + client MUST retransmit the ClientHello with the cookie added. +*/ +/// ## Specifications +/// +/// * [RFC 6347 §4.2.1] +/// +/// [RFC 6347 §4.2.1]: https://tools.ietf.org/html/rfc6347#section-4.2.1 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct HandshakeMessageHelloVerifyRequest { + pub(crate) version: ProtocolVersion, + pub(crate) cookie: Vec, +} + +impl HandshakeMessageHelloVerifyRequest { + pub fn handshake_type(&self) -> HandshakeType { + HandshakeType::HelloVerifyRequest + } + + pub fn size(&self) -> usize { + 1 + 1 + 1 + self.cookie.len() + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + if self.cookie.len() > 255 { + return Err(Error::ErrCookieTooLong); + } + + writer.write_u8(self.version.major)?; + writer.write_u8(self.version.minor)?; + writer.write_u8(self.cookie.len() as u8)?; + writer.write_all(&self.cookie)?; + + Ok(writer.flush()?) + } + + pub fn unmarshal(reader: &mut R) -> Result { + let major = reader.read_u8()?; + let minor = reader.read_u8()?; + let cookie_length = reader.read_u8()?; + let mut cookie = vec![]; + reader.read_to_end(&mut cookie)?; + + if cookie.len() < cookie_length as usize { + return Err(Error::ErrBufferTooSmall); + } + + Ok(HandshakeMessageHelloVerifyRequest { + version: ProtocolVersion { major, minor }, + cookie, + }) + } +} diff --git a/vendor/dtls/src/handshake/handshake_message_hello_verify_request/handshake_message_hello_verify_request_test.rs b/vendor/dtls/src/handshake/handshake_message_hello_verify_request/handshake_message_hello_verify_request_test.rs new file mode 100644 index 00000000..5a25ce00 --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_message_hello_verify_request/handshake_message_hello_verify_request_test.rs @@ -0,0 +1,40 @@ +use std::io::{BufReader, BufWriter}; + +use super::*; + +#[test] +fn test_handshake_message_hello_verify_request() -> Result<()> { + let raw_hello_verify_request = vec![ + 0xfe, 0xff, 0x14, 0x25, 0xfb, 0xee, 0xb3, 0x7c, 0x95, 0xcf, 0x00, 0xeb, 0xad, 0xe2, 0xef, + 0xc7, 0xfd, 0xbb, 0xed, 0xf7, 0x1f, 0x6c, 0xcd, + ]; + let parsed_hello_verify_request = HandshakeMessageHelloVerifyRequest { + version: ProtocolVersion { + major: 0xFE, + minor: 0xFF, + }, + cookie: vec![ + 0x25, 0xfb, 0xee, 0xb3, 0x7c, 0x95, 0xcf, 0x00, 0xeb, 0xad, 0xe2, 0xef, 0xc7, 0xfd, + 0xbb, 0xed, 0xf7, 0x1f, 0x6c, 0xcd, + ], + }; + + let mut reader = BufReader::new(raw_hello_verify_request.as_slice()); + let c = HandshakeMessageHelloVerifyRequest::unmarshal(&mut reader)?; + assert_eq!( + c, parsed_hello_verify_request, + "parsed_hello_verify_request unmarshal: got {c:?}, want {parsed_hello_verify_request:?}" + ); + + let mut raw = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw.as_mut()); + c.marshal(&mut writer)?; + } + assert_eq!( + raw, raw_hello_verify_request, + "parsed_hello_verify_request marshal: got {raw:?}, want {raw_hello_verify_request:?}" + ); + + Ok(()) +} diff --git a/vendor/dtls/src/handshake/handshake_message_server_hello.rs b/vendor/dtls/src/handshake/handshake_message_server_hello.rs new file mode 100644 index 00000000..6be32e45 --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_message_server_hello.rs @@ -0,0 +1,151 @@ +#[cfg(test)] +mod handshake_message_server_hello_test; + +use std::fmt; +use std::io::{BufReader, BufWriter}; + +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; + +use super::handshake_random::*; +use super::*; +use crate::cipher_suite::*; +use crate::compression_methods::*; +use crate::extension::*; +use crate::record_layer::record_layer_header::*; + +/* +The server will send this message in response to a ClientHello +message when it was able to find an acceptable set of algorithms. +If it cannot find such a match, it will respond with a handshake +failure alert. +https://tools.ietf.org/html/rfc5246#section-7.4.1.3 +*/ +#[derive(Clone)] +pub struct HandshakeMessageServerHello { + pub(crate) version: ProtocolVersion, + pub(crate) random: HandshakeRandom, + + pub(crate) cipher_suite: CipherSuiteId, + pub(crate) compression_method: CompressionMethodId, + pub(crate) extensions: Vec, +} + +impl PartialEq for HandshakeMessageServerHello { + fn eq(&self, other: &Self) -> bool { + self.version == other.version + && self.random == other.random + && self.compression_method == other.compression_method + && self.extensions == other.extensions + && self.cipher_suite == other.cipher_suite + } +} + +impl fmt::Debug for HandshakeMessageServerHello { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = [ + format!("version: {:?} random: {:?}", self.version, self.random), + format!("cipher_suites: {:?}", self.cipher_suite), + format!("compression_method: {:?}", self.compression_method), + format!("extensions: {:?}", self.extensions), + ]; + write!(f, "{}", s.join(" ")) + } +} + +impl HandshakeMessageServerHello { + pub fn handshake_type(&self) -> HandshakeType { + HandshakeType::ServerHello + } + + pub fn size(&self) -> usize { + let mut len = 2 + self.random.size(); + + // SessionID + len += 1; + + len += 2; + + len += 1; + + len += 2; + for extension in &self.extensions { + len += extension.size(); + } + + len + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + writer.write_u8(self.version.major)?; + writer.write_u8(self.version.minor)?; + self.random.marshal(writer)?; + + // SessionID + writer.write_u8(0x00)?; + + writer.write_u16::(self.cipher_suite as u16)?; + + writer.write_u8(self.compression_method as u8)?; + + let mut extension_buffer = vec![]; + { + let mut extension_writer = BufWriter::<&mut Vec>::new(extension_buffer.as_mut()); + for extension in &self.extensions { + extension.marshal(&mut extension_writer)?; + } + } + + writer.write_u16::(extension_buffer.len() as u16)?; + writer.write_all(&extension_buffer)?; + + Ok(writer.flush()?) + } + + pub fn unmarshal(reader: &mut R) -> Result { + let major = reader.read_u8()?; + let minor = reader.read_u8()?; + let random = HandshakeRandom::unmarshal(reader)?; + + // Session ID + let session_id_len = reader.read_u8()? as usize; + let mut session_id_buffer = vec![0u8; session_id_len]; + reader.read_exact(&mut session_id_buffer)?; + + let cipher_suite: CipherSuiteId = reader.read_u16::()?.into(); + + let compression_method = reader.read_u8()?.into(); + let mut extensions = vec![]; + + let extension_buffer_len = reader.read_u16::()? as usize; + let mut extension_buffer = vec![0u8; extension_buffer_len]; + reader.read_exact(&mut extension_buffer)?; + + let mut offset = 0; + while offset < extension_buffer_len { + let mut extension_reader = BufReader::new(&extension_buffer[offset..]); + if let Ok(extension) = Extension::unmarshal(&mut extension_reader) { + extensions.push(extension); + } else { + log::warn!( + "Unsupported Extension Type {} {}", + extension_buffer[offset], + extension_buffer[offset + 1] + ); + } + + let extension_len = + u16::from_be_bytes([extension_buffer[offset + 2], extension_buffer[offset + 3]]) + as usize; + offset += 4 + extension_len; + } + + Ok(HandshakeMessageServerHello { + version: ProtocolVersion { major, minor }, + random, + + cipher_suite, + compression_method, + extensions, + }) + } +} diff --git a/vendor/dtls/src/handshake/handshake_message_server_hello/handshake_message_server_hello_test.rs b/vendor/dtls/src/handshake/handshake_message_server_hello/handshake_message_server_hello_test.rs new file mode 100644 index 00000000..ba906e06 --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_message_server_hello/handshake_message_server_hello_test.rs @@ -0,0 +1,56 @@ +use std::io::{BufReader, BufWriter}; +use std::time::{Duration, SystemTime}; + +use super::*; + +#[test] +fn test_handshake_message_server_hello() -> Result<()> { + let raw_server_hello = vec![ + 0xfe, 0xfd, 0x21, 0x63, 0x32, 0x21, 0x81, 0x0e, 0x98, 0x6c, 0x85, 0x3d, 0xa4, 0x39, 0xaf, + 0x5f, 0xd6, 0x5c, 0xcc, 0x20, 0x7f, 0x7c, 0x78, 0xf1, 0x5f, 0x7e, 0x1c, 0xb7, 0xa1, 0x1e, + 0xcf, 0x63, 0x84, 0x28, 0x00, 0xc0, 0x2b, 0x00, 0x00, 0x00, + ]; + + let gmt_unix_time = if let Some(unix_time) = + SystemTime::UNIX_EPOCH.checked_add(Duration::new(560149025u64, 0)) + { + unix_time + } else { + SystemTime::UNIX_EPOCH + }; + let parsed_server_hello = HandshakeMessageServerHello { + version: ProtocolVersion { + major: 0xFE, + minor: 0xFD, + }, + random: HandshakeRandom { + gmt_unix_time, + random_bytes: [ + 0x81, 0x0e, 0x98, 0x6c, 0x85, 0x3d, 0xa4, 0x39, 0xaf, 0x5f, 0xd6, 0x5c, 0xcc, 0x20, + 0x7f, 0x7c, 0x78, 0xf1, 0x5f, 0x7e, 0x1c, 0xb7, 0xa1, 0x1e, 0xcf, 0x63, 0x84, 0x28, + ], + }, + cipher_suite: CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256, + compression_method: CompressionMethodId::Null, + extensions: vec![], + }; + + let mut reader = BufReader::new(raw_server_hello.as_slice()); + let c = HandshakeMessageServerHello::unmarshal(&mut reader)?; + assert_eq!( + c, parsed_server_hello, + "handshakeMessageServerHello unmarshal: got {c:?}, want {parsed_server_hello:?}" + ); + + let mut raw = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw.as_mut()); + c.marshal(&mut writer)?; + } + assert_eq!( + raw, raw_server_hello, + "handshakeMessageServerHello marshal: got {raw:?}, want {raw_server_hello:?}" + ); + + Ok(()) +} diff --git a/vendor/dtls/src/handshake/handshake_message_server_hello_done.rs b/vendor/dtls/src/handshake/handshake_message_server_hello_done.rs new file mode 100644 index 00000000..bae58f21 --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_message_server_hello_done.rs @@ -0,0 +1,27 @@ +#[cfg(test)] +mod handshake_message_server_hello_done_test; + +use std::io::{Read, Write}; + +use super::*; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct HandshakeMessageServerHelloDone; + +impl HandshakeMessageServerHelloDone { + pub fn handshake_type(&self) -> HandshakeType { + HandshakeType::ServerHelloDone + } + + pub fn size(&self) -> usize { + 0 + } + + pub fn marshal(&self, _writer: &mut W) -> Result<()> { + Ok(()) + } + + pub fn unmarshal(_reader: &mut R) -> Result { + Ok(HandshakeMessageServerHelloDone {}) + } +} diff --git a/vendor/dtls/src/handshake/handshake_message_server_hello_done/handshake_message_server_hello_done_test.rs b/vendor/dtls/src/handshake/handshake_message_server_hello_done/handshake_message_server_hello_done_test.rs new file mode 100644 index 00000000..b9d3b835 --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_message_server_hello_done/handshake_message_server_hello_done_test.rs @@ -0,0 +1,28 @@ +use std::io::{BufReader, BufWriter}; + +use super::*; + +#[test] +fn test_handshake_message_server_hello_done() -> Result<()> { + let raw_server_hello_done = vec![]; + let parsed_server_hello_done = HandshakeMessageServerHelloDone {}; + + let mut reader = BufReader::new(raw_server_hello_done.as_slice()); + let c = HandshakeMessageServerHelloDone::unmarshal(&mut reader)?; + assert_eq!( + c, parsed_server_hello_done, + "handshakeMessageServerHelloDone unmarshal: got {c:?}, want {parsed_server_hello_done:?}" + ); + + let mut raw = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw.as_mut()); + c.marshal(&mut writer)?; + } + assert_eq!( + raw, raw_server_hello_done, + "handshakeMessageServerHelloDone marshal: got {raw:?}, want {raw_server_hello_done:?}" + ); + + Ok(()) +} diff --git a/vendor/dtls/src/handshake/handshake_message_server_key_exchange.rs b/vendor/dtls/src/handshake/handshake_message_server_key_exchange.rs new file mode 100644 index 00000000..e8438427 --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_message_server_key_exchange.rs @@ -0,0 +1,133 @@ +#[cfg(test)] +mod handshake_message_server_key_exchange_test; + +use std::io::{Read, Write}; + +use byteorder::{BigEndian, WriteBytesExt}; + +use super::*; +use crate::curve::named_curve::*; +use crate::curve::*; +use crate::signature_hash_algorithm::*; + +// Structure supports ECDH and PSK +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct HandshakeMessageServerKeyExchange { + pub(crate) identity_hint: Vec, + + pub(crate) elliptic_curve_type: EllipticCurveType, + pub(crate) named_curve: NamedCurve, + pub(crate) public_key: Vec, + pub(crate) algorithm: SignatureHashAlgorithm, + pub(crate) signature: Vec, +} + +impl HandshakeMessageServerKeyExchange { + pub fn handshake_type(&self) -> HandshakeType { + HandshakeType::ServerKeyExchange + } + + pub fn size(&self) -> usize { + if !self.identity_hint.is_empty() { + 2 + self.identity_hint.len() + } else { + 1 + 2 + 1 + self.public_key.len() + 2 + 2 + self.signature.len() + } + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + if !self.identity_hint.is_empty() { + writer.write_u16::(self.identity_hint.len() as u16)?; + writer.write_all(&self.identity_hint)?; + return Ok(writer.flush()?); + } + + writer.write_u8(self.elliptic_curve_type as u8)?; + writer.write_u16::(self.named_curve as u16)?; + + writer.write_u8(self.public_key.len() as u8)?; + writer.write_all(&self.public_key)?; + + writer.write_u8(self.algorithm.hash as u8)?; + writer.write_u8(self.algorithm.signature as u8)?; + + writer.write_u16::(self.signature.len() as u16)?; + writer.write_all(&self.signature)?; + + Ok(writer.flush()?) + } + + pub fn unmarshal(reader: &mut R) -> Result { + let mut data = vec![]; + reader.read_to_end(&mut data)?; + + // If parsed as PSK return early and only populate PSK Identity Hint + let psk_length = ((data[0] as u16) << 8) | data[1] as u16; + if data.len() == psk_length as usize + 2 { + return Ok(HandshakeMessageServerKeyExchange { + identity_hint: data[2..].to_vec(), + + elliptic_curve_type: EllipticCurveType::Unsupported, + named_curve: NamedCurve::Unsupported, + public_key: vec![], + algorithm: SignatureHashAlgorithm { + hash: HashAlgorithm::Unsupported, + signature: SignatureAlgorithm::Unsupported, + }, + signature: vec![], + }); + } + + let elliptic_curve_type = data[0].into(); + if data[1..].len() < 2 { + return Err(Error::ErrBufferTooSmall); + } + + let named_curve = (((data[1] as u16) << 8) | data[2] as u16).into(); + if data.len() < 4 { + return Err(Error::ErrBufferTooSmall); + } + + let public_key_length = data[3] as usize; + let mut offset = 4 + public_key_length; + if data.len() < offset { + return Err(Error::ErrBufferTooSmall); + } + let public_key = data[4..offset].to_vec(); + if data.len() <= offset { + return Err(Error::ErrBufferTooSmall); + } + + let hash_algorithm = data[offset].into(); + offset += 1; + if data.len() <= offset { + return Err(Error::ErrBufferTooSmall); + } + + let signature_algorithm = data[offset].into(); + offset += 1; + if data.len() < offset + 2 { + return Err(Error::ErrBufferTooSmall); + } + + let signature_length = (((data[offset] as u16) << 8) | data[offset + 1] as u16) as usize; + offset += 2; + if data.len() < offset + signature_length { + return Err(Error::ErrBufferTooSmall); + } + let signature = data[offset..offset + signature_length].to_vec(); + + Ok(HandshakeMessageServerKeyExchange { + identity_hint: vec![], + + elliptic_curve_type, + named_curve, + public_key, + algorithm: SignatureHashAlgorithm { + hash: hash_algorithm, + signature: signature_algorithm, + }, + signature, + }) + } +} diff --git a/vendor/dtls/src/handshake/handshake_message_server_key_exchange/handshake_message_server_key_exchange_test.rs b/vendor/dtls/src/handshake/handshake_message_server_key_exchange/handshake_message_server_key_exchange_test.rs new file mode 100644 index 00000000..4fd7adf5 --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_message_server_key_exchange/handshake_message_server_key_exchange_test.rs @@ -0,0 +1,50 @@ +use std::io::{BufReader, BufWriter}; + +use super::*; + +#[test] +fn test_handshake_message_server_key_exchange() -> Result<()> { + let raw_server_key_exchange = vec![ + 0x03, 0x00, 0x1d, 0x41, 0x04, 0x0c, 0xb9, 0xa3, 0xb9, 0x90, 0x71, 0x35, 0x4a, 0x08, 0x66, + 0xaf, 0xd6, 0x88, 0x58, 0x29, 0x69, 0x98, 0xf1, 0x87, 0x0f, 0xb5, 0xa8, 0xcd, 0x92, 0xf6, + 0x2b, 0x08, 0x0c, 0xd4, 0x16, 0x5b, 0xcc, 0x81, 0xf2, 0x58, 0x91, 0x8e, 0x62, 0xdf, 0xc1, + 0xec, 0x72, 0xe8, 0x47, 0x24, 0x42, 0x96, 0xb8, 0x7b, 0xee, 0xe7, 0x0d, 0xdc, 0x44, 0xec, + 0xf3, 0x97, 0x6b, 0x1b, 0x45, 0x28, 0xac, 0x3f, 0x35, 0x02, 0x03, 0x00, 0x47, 0x30, 0x45, + 0x02, 0x21, 0x00, 0xb2, 0x0b, 0x22, 0x95, 0x3d, 0x56, 0x57, 0x6a, 0x3f, 0x85, 0x30, 0x6f, + 0x55, 0xc3, 0xf4, 0x24, 0x1b, 0x21, 0x07, 0xe5, 0xdf, 0xba, 0x24, 0x02, 0x68, 0x95, 0x1f, + 0x6e, 0x13, 0xbd, 0x9f, 0xaa, 0x02, 0x20, 0x49, 0x9c, 0x9d, 0xdf, 0x84, 0x60, 0x33, 0x27, + 0x96, 0x9e, 0x58, 0x6d, 0x72, 0x13, 0xe7, 0x3a, 0xe8, 0xdf, 0x43, 0x75, 0xc7, 0xb9, 0x37, + 0x6e, 0x90, 0xe5, 0x3b, 0x81, 0xd4, 0xda, 0x68, 0xcd, + ]; + let parsed_server_key_exchange = HandshakeMessageServerKeyExchange { + identity_hint: vec![], + elliptic_curve_type: EllipticCurveType::NamedCurve, + named_curve: NamedCurve::X25519, + public_key: raw_server_key_exchange[4..69].to_vec(), + algorithm: SignatureHashAlgorithm { + hash: HashAlgorithm::Sha1, + signature: SignatureAlgorithm::Ecdsa, + }, + + signature: raw_server_key_exchange[73..144].to_vec(), + }; + + let mut reader = BufReader::new(raw_server_key_exchange.as_slice()); + let c = HandshakeMessageServerKeyExchange::unmarshal(&mut reader)?; + assert_eq!( + c, parsed_server_key_exchange, + "handshakeMessageServerKeyExchange unmarshal: got {c:?}, want {parsed_server_key_exchange:?}" + ); + + let mut raw = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw.as_mut()); + c.marshal(&mut writer)?; + } + assert_eq!( + raw, raw_server_key_exchange, + "handshakeMessageServerKeyExchange marshal: got {raw:?}, want {raw_server_key_exchange:?}" + ); + + Ok(()) +} diff --git a/vendor/dtls/src/handshake/handshake_random.rs b/vendor/dtls/src/handshake/handshake_random.rs new file mode 100644 index 00000000..505ab06e --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_random.rs @@ -0,0 +1,71 @@ +use std::io::{self, Read, Write}; +use std::time::{Duration, SystemTime}; + +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use rand::Rng; + +pub const RANDOM_BYTES_LENGTH: usize = 28; +pub const HANDSHAKE_RANDOM_LENGTH: usize = RANDOM_BYTES_LENGTH + 4; + +/// ## Specifications +/// +/// * [RFC 4346 §7.4.1.2] +/// +/// [RFC 4346 §7.4.1.2]: https://tools.ietf.org/html/rfc4346#section-7.4.1.2 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct HandshakeRandom { + pub gmt_unix_time: SystemTime, + pub random_bytes: [u8; RANDOM_BYTES_LENGTH], +} + +impl Default for HandshakeRandom { + fn default() -> Self { + HandshakeRandom { + gmt_unix_time: SystemTime::UNIX_EPOCH, + random_bytes: [0u8; RANDOM_BYTES_LENGTH], + } + } +} + +impl HandshakeRandom { + pub fn size(&self) -> usize { + 4 + RANDOM_BYTES_LENGTH + } + + pub fn marshal(&self, writer: &mut W) -> io::Result<()> { + let secs = match self.gmt_unix_time.duration_since(SystemTime::UNIX_EPOCH) { + Ok(d) => d.as_secs() as u32, + Err(_) => 0, + }; + writer.write_u32::(secs)?; + writer.write_all(&self.random_bytes)?; + + writer.flush() + } + + pub fn unmarshal(reader: &mut R) -> io::Result { + let secs = reader.read_u32::()?; + let gmt_unix_time = if let Some(unix_time) = + SystemTime::UNIX_EPOCH.checked_add(Duration::new(secs as u64, 0)) + { + unix_time + } else { + SystemTime::UNIX_EPOCH + }; + + let mut random_bytes = [0u8; RANDOM_BYTES_LENGTH]; + reader.read_exact(&mut random_bytes)?; + + Ok(HandshakeRandom { + gmt_unix_time, + random_bytes, + }) + } + + // populate fills the HandshakeRandom with random values + // may be called multiple times + pub fn populate(&mut self) { + self.gmt_unix_time = SystemTime::now(); + rand::rng().fill(&mut self.random_bytes); + } +} diff --git a/vendor/dtls/src/handshake/handshake_test.rs b/vendor/dtls/src/handshake/handshake_test.rs new file mode 100644 index 00000000..a61da45c --- /dev/null +++ b/vendor/dtls/src/handshake/handshake_test.rs @@ -0,0 +1,70 @@ +use std::io::{BufReader, BufWriter}; +use std::time::{Duration, SystemTime}; + +use super::*; +use crate::compression_methods::*; +use crate::handshake::handshake_message_client_hello::*; +use crate::handshake::handshake_random::HandshakeRandom; +use crate::record_layer::record_layer_header::ProtocolVersion; + +#[test] +fn test_handshake_message() -> Result<()> { + let raw_handshake_message = vec![ + 0x01, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0xfe, 0xfd, 0xb6, + 0x2f, 0xce, 0x5c, 0x42, 0x54, 0xff, 0x86, 0xe1, 0x24, 0x41, 0x91, 0x42, 0x62, 0x15, 0xad, + 0x16, 0xc9, 0x15, 0x8d, 0x95, 0x71, 0x8a, 0xbb, 0x22, 0xd7, 0x47, 0xec, 0xd8, 0x3d, 0xdc, + 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let parsed_handshake = Handshake { + handshake_header: HandshakeHeader { + handshake_type: HandshakeType::ClientHello, + length: 0x29, + message_sequence: 0, + fragment_offset: 0, + fragment_length: 0x29, + }, + handshake_message: HandshakeMessage::ClientHello(HandshakeMessageClientHello { + version: ProtocolVersion { + major: 0xFE, + minor: 0xFD, + }, + random: HandshakeRandom { + gmt_unix_time: if let Some(unix_time) = + SystemTime::UNIX_EPOCH.checked_add(Duration::new(3056586332u64, 0)) + { + unix_time + } else { + SystemTime::UNIX_EPOCH + }, + random_bytes: [ + 0x42, 0x54, 0xff, 0x86, 0xe1, 0x24, 0x41, 0x91, 0x42, 0x62, 0x15, 0xad, 0x16, + 0xc9, 0x15, 0x8d, 0x95, 0x71, 0x8a, 0xbb, 0x22, 0xd7, 0x47, 0xec, 0xd8, 0x3d, + 0xdc, 0x4b, + ], + }, + cookie: vec![], + cipher_suites: vec![], + compression_methods: CompressionMethods { ids: vec![] }, + extensions: vec![], + }), + }; + + let mut reader = BufReader::new(raw_handshake_message.as_slice()); + let h = Handshake::unmarshal(&mut reader)?; + assert_eq!( + h, parsed_handshake, + "handshakeMessageClientHello unmarshal: got {h:?}, want {parsed_handshake:?}" + ); + + let mut raw = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(raw.as_mut()); + h.marshal(&mut writer)?; + } + assert_eq!( + raw, raw_handshake_message, + "handshakeMessageClientHello marshal: got {raw:?}, want {raw_handshake_message:?}" + ); + + Ok(()) +} diff --git a/vendor/dtls/src/handshake/mod.rs b/vendor/dtls/src/handshake/mod.rs new file mode 100644 index 00000000..0b0b3bff --- /dev/null +++ b/vendor/dtls/src/handshake/mod.rs @@ -0,0 +1,242 @@ +pub mod handshake_cache; +pub mod handshake_header; +pub mod handshake_message_certificate; +pub mod handshake_message_certificate_request; +pub mod handshake_message_certificate_verify; +pub mod handshake_message_client_hello; +pub mod handshake_message_client_key_exchange; +pub mod handshake_message_finished; +pub mod handshake_message_hello_verify_request; +pub mod handshake_message_server_hello; +pub mod handshake_message_server_hello_done; +pub mod handshake_message_server_key_exchange; +pub mod handshake_random; + +#[cfg(test)] +mod handshake_test; + +use std::fmt; +use std::io::{Read, Write}; + +use handshake_header::*; +use handshake_message_certificate::*; +use handshake_message_certificate_request::*; +use handshake_message_certificate_verify::*; +use handshake_message_client_hello::*; +use handshake_message_client_key_exchange::*; +use handshake_message_finished::*; +use handshake_message_hello_verify_request::*; +use handshake_message_server_hello::*; +use handshake_message_server_hello_done::*; +use handshake_message_server_key_exchange::*; + +use super::content::*; +use super::error::*; + +/// ## Specifications +/// +/// * [RFC 5246 §7.4] +/// +/// [RFC 5246 §7.4]: https://tools.ietf.org/html/rfc5246#section-7.4 +#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum HandshakeType { + HelloRequest = 0, + ClientHello = 1, + ServerHello = 2, + HelloVerifyRequest = 3, + Certificate = 11, + ServerKeyExchange = 12, + CertificateRequest = 13, + ServerHelloDone = 14, + CertificateVerify = 15, + ClientKeyExchange = 16, + Finished = 20, + #[default] + Invalid, +} + +impl fmt::Display for HandshakeType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + HandshakeType::HelloRequest => write!(f, "HelloRequest"), + HandshakeType::ClientHello => write!(f, "ClientHello"), + HandshakeType::ServerHello => write!(f, "ServerHello"), + HandshakeType::HelloVerifyRequest => write!(f, "HelloVerifyRequest"), + HandshakeType::Certificate => write!(f, "Certificate"), + HandshakeType::ServerKeyExchange => write!(f, "ServerKeyExchange"), + HandshakeType::CertificateRequest => write!(f, "CertificateRequest"), + HandshakeType::ServerHelloDone => write!(f, "ServerHelloDone"), + HandshakeType::CertificateVerify => write!(f, "CertificateVerify"), + HandshakeType::ClientKeyExchange => write!(f, "ClientKeyExchange"), + HandshakeType::Finished => write!(f, "Finished"), + HandshakeType::Invalid => write!(f, "Invalid"), + } + } +} + +impl From for HandshakeType { + fn from(val: u8) -> Self { + match val { + 0 => HandshakeType::HelloRequest, + 1 => HandshakeType::ClientHello, + 2 => HandshakeType::ServerHello, + 3 => HandshakeType::HelloVerifyRequest, + 11 => HandshakeType::Certificate, + 12 => HandshakeType::ServerKeyExchange, + 13 => HandshakeType::CertificateRequest, + 14 => HandshakeType::ServerHelloDone, + 15 => HandshakeType::CertificateVerify, + 16 => HandshakeType::ClientKeyExchange, + 20 => HandshakeType::Finished, + _ => HandshakeType::Invalid, + } + } +} + +#[derive(PartialEq, Debug, Clone)] +pub enum HandshakeMessage { + //HelloRequest(errNotImplemented), + ClientHello(HandshakeMessageClientHello), + ServerHello(HandshakeMessageServerHello), + HelloVerifyRequest(HandshakeMessageHelloVerifyRequest), + Certificate(HandshakeMessageCertificate), + ServerKeyExchange(HandshakeMessageServerKeyExchange), + CertificateRequest(HandshakeMessageCertificateRequest), + ServerHelloDone(HandshakeMessageServerHelloDone), + CertificateVerify(HandshakeMessageCertificateVerify), + ClientKeyExchange(HandshakeMessageClientKeyExchange), + Finished(HandshakeMessageFinished), +} + +impl HandshakeMessage { + pub fn handshake_type(&self) -> HandshakeType { + match self { + HandshakeMessage::ClientHello(msg) => msg.handshake_type(), + HandshakeMessage::ServerHello(msg) => msg.handshake_type(), + HandshakeMessage::HelloVerifyRequest(msg) => msg.handshake_type(), + HandshakeMessage::Certificate(msg) => msg.handshake_type(), + HandshakeMessage::ServerKeyExchange(msg) => msg.handshake_type(), + HandshakeMessage::CertificateRequest(msg) => msg.handshake_type(), + HandshakeMessage::ServerHelloDone(msg) => msg.handshake_type(), + HandshakeMessage::CertificateVerify(msg) => msg.handshake_type(), + HandshakeMessage::ClientKeyExchange(msg) => msg.handshake_type(), + HandshakeMessage::Finished(msg) => msg.handshake_type(), + } + } + + pub fn size(&self) -> usize { + match self { + HandshakeMessage::ClientHello(msg) => msg.size(), + HandshakeMessage::ServerHello(msg) => msg.size(), + HandshakeMessage::HelloVerifyRequest(msg) => msg.size(), + HandshakeMessage::Certificate(msg) => msg.size(), + HandshakeMessage::ServerKeyExchange(msg) => msg.size(), + HandshakeMessage::CertificateRequest(msg) => msg.size(), + HandshakeMessage::ServerHelloDone(msg) => msg.size(), + HandshakeMessage::CertificateVerify(msg) => msg.size(), + HandshakeMessage::ClientKeyExchange(msg) => msg.size(), + HandshakeMessage::Finished(msg) => msg.size(), + } + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + match self { + HandshakeMessage::ClientHello(msg) => msg.marshal(writer)?, + HandshakeMessage::ServerHello(msg) => msg.marshal(writer)?, + HandshakeMessage::HelloVerifyRequest(msg) => msg.marshal(writer)?, + HandshakeMessage::Certificate(msg) => msg.marshal(writer)?, + HandshakeMessage::ServerKeyExchange(msg) => msg.marshal(writer)?, + HandshakeMessage::CertificateRequest(msg) => msg.marshal(writer)?, + HandshakeMessage::ServerHelloDone(msg) => msg.marshal(writer)?, + HandshakeMessage::CertificateVerify(msg) => msg.marshal(writer)?, + HandshakeMessage::ClientKeyExchange(msg) => msg.marshal(writer)?, + HandshakeMessage::Finished(msg) => msg.marshal(writer)?, + } + + Ok(()) + } +} + +// The handshake protocol is responsible for selecting a cipher spec and +// generating a master secret, which together comprise the primary +// cryptographic parameters associated with a secure session. The +// handshake protocol can also optionally authenticate parties who have +// certificates signed by a trusted certificate authority. +// https://tools.ietf.org/html/rfc5246#section-7.3 +#[derive(PartialEq, Debug, Clone)] +pub struct Handshake { + pub(crate) handshake_header: HandshakeHeader, + pub(crate) handshake_message: HandshakeMessage, +} + +impl Handshake { + pub fn new(handshake_message: HandshakeMessage) -> Self { + Handshake { + handshake_header: HandshakeHeader { + handshake_type: handshake_message.handshake_type(), + length: handshake_message.size() as u32, + message_sequence: 0, + fragment_offset: 0, + fragment_length: handshake_message.size() as u32, + }, + handshake_message, + } + } + + pub fn content_type(&self) -> ContentType { + ContentType::Handshake + } + + pub fn size(&self) -> usize { + self.handshake_header.size() + self.handshake_message.size() + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + self.handshake_header.marshal(writer)?; + self.handshake_message.marshal(writer)?; + Ok(()) + } + + pub fn unmarshal(reader: &mut R) -> Result { + let handshake_header = HandshakeHeader::unmarshal(reader)?; + + let handshake_message = match handshake_header.handshake_type { + HandshakeType::ClientHello => { + HandshakeMessage::ClientHello(HandshakeMessageClientHello::unmarshal(reader)?) + } + HandshakeType::ServerHello => { + HandshakeMessage::ServerHello(HandshakeMessageServerHello::unmarshal(reader)?) + } + HandshakeType::HelloVerifyRequest => HandshakeMessage::HelloVerifyRequest( + HandshakeMessageHelloVerifyRequest::unmarshal(reader)?, + ), + HandshakeType::Certificate => { + HandshakeMessage::Certificate(HandshakeMessageCertificate::unmarshal(reader)?) + } + HandshakeType::ServerKeyExchange => HandshakeMessage::ServerKeyExchange( + HandshakeMessageServerKeyExchange::unmarshal(reader)?, + ), + HandshakeType::CertificateRequest => HandshakeMessage::CertificateRequest( + HandshakeMessageCertificateRequest::unmarshal(reader)?, + ), + HandshakeType::ServerHelloDone => HandshakeMessage::ServerHelloDone( + HandshakeMessageServerHelloDone::unmarshal(reader)?, + ), + HandshakeType::CertificateVerify => HandshakeMessage::CertificateVerify( + HandshakeMessageCertificateVerify::unmarshal(reader)?, + ), + HandshakeType::ClientKeyExchange => HandshakeMessage::ClientKeyExchange( + HandshakeMessageClientKeyExchange::unmarshal(reader)?, + ), + HandshakeType::Finished => { + HandshakeMessage::Finished(HandshakeMessageFinished::unmarshal(reader)?) + } + _ => return Err(Error::ErrNotImplemented), + }; + + Ok(Handshake { + handshake_header, + handshake_message, + }) + } +} diff --git a/vendor/dtls/src/handshaker.rs b/vendor/dtls/src/handshaker.rs new file mode 100644 index 00000000..b80ba4ac --- /dev/null +++ b/vendor/dtls/src/handshaker.rs @@ -0,0 +1,425 @@ +use std::collections::HashMap; +use std::fmt; +use std::sync::Arc; + +use log::*; + +use crate::cipher_suite::*; +use crate::config::*; +use crate::conn::*; +use crate::content::*; +use crate::crypto::*; +use crate::error::*; +use crate::extension::extension_use_srtp::*; +use crate::signature_hash_algorithm::*; + +use crate::certificate_verifier::{CertificateVerifier, RootCertStore}; +use rustls_pki_types::CertificateDer; + +//use std::io::BufWriter; + +// [RFC6347 Section-4.2.4] +// +-----------+ +// +---> | PREPARING | <--------------------+ +// | +-----------+ | +// | | | +// | | Buffer next flight | +// | | | +// | \|/ | +// | +-----------+ | +// | | SENDING |<------------------+ | Send +// | +-----------+ | | HelloRequest +// Receive | | | | +// next | | Send flight | | or +// flight | +--------+ | | +// | | | Set retransmit timer | | Receive +// | | \|/ | | HelloRequest +// | | +-----------+ | | Send +// +--)--| WAITING |-------------------+ | ClientHello +// | | +-----------+ Timer expires | | +// | | | | | +// | | +------------------------+ | +// Receive | | Send Read retransmit | +// last | | last | +// flight | | flight | +// | | | +// \|/\|/ | +// +-----------+ | +// | FINISHED | -------------------------------+ +// +-----------+ +// | /|\ +// | | +// +---+ +// Read retransmit +// Retransmit last flight + +#[derive(Copy, Clone, PartialEq)] +pub(crate) enum HandshakeState { + Errored, + Preparing, + Sending, + Waiting, + Finished, +} + +impl fmt::Display for HandshakeState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + HandshakeState::Errored => write!(f, "Errored"), + HandshakeState::Preparing => write!(f, "Preparing"), + HandshakeState::Sending => write!(f, "Sending"), + HandshakeState::Waiting => write!(f, "Waiting"), + HandshakeState::Finished => write!(f, "Finished"), + } + } +} + +pub(crate) type VerifyPeerCertificateFn = + Arc], &[CertificateDer<'static>]) -> Result<()>) + Send + Sync>; + +pub(crate) struct HandshakeConfig { + pub(crate) local_psk_callback: Option, + pub(crate) local_psk_identity_hint: Option>, + pub(crate) local_cipher_suites: Vec, // Available CipherSuites + pub(crate) local_signature_schemes: Vec, // Available signature schemes + pub(crate) extended_master_secret: ExtendedMasterSecretType, // Policy for the Extended Master Support extension + pub(crate) local_srtp_protection_profiles: Vec, // Available SRTPProtectionProfiles, if empty no SRTP support + pub(crate) server_name: String, + pub(crate) client_auth: ClientAuthType, // If we are a client should we request a client certificate + pub(crate) local_certificates: Vec, + pub(crate) name_to_certificate: HashMap, + pub(crate) insecure_skip_verify: bool, + pub(crate) insecure_verification: bool, + pub(crate) verify_peer_certificate: Option, + pub(crate) server_cert_verifier: Arc, + pub(crate) client_cert_verifier: Option>, + pub(crate) retransmit_interval: tokio::time::Duration, + pub(crate) initial_epoch: u16, + //log logging.LeveledLogger + //mu sync.Mutex +} + +pub fn gen_self_signed_root_cert() -> RootCertStore { + let mut certs = RootCertStore::empty(); + certs + .add( + rcgen::generate_simple_self_signed(vec![]) + .unwrap() + .cert + .der() + .to_owned(), + ) + .unwrap(); + certs +} + +impl Default for HandshakeConfig { + fn default() -> Self { + HandshakeConfig { + local_psk_callback: None, + local_psk_identity_hint: None, + local_cipher_suites: vec![], + local_signature_schemes: vec![], + extended_master_secret: ExtendedMasterSecretType::Disable, + local_srtp_protection_profiles: vec![], + server_name: String::new(), + client_auth: ClientAuthType::NoClientCert, + local_certificates: vec![], + name_to_certificate: HashMap::new(), + insecure_skip_verify: false, + insecure_verification: false, + verify_peer_certificate: None, + server_cert_verifier: Arc::new(CertificateVerifier::new(RootCertStore::empty())), + client_cert_verifier: None, + retransmit_interval: tokio::time::Duration::from_secs(0), + initial_epoch: 0, + } + } +} + +impl HandshakeConfig { + pub(crate) fn get_certificate(&self, server_name: &str) -> Result { + //TODO + /*if self.name_to_certificate.is_empty() { + let mut name_to_certificate = HashMap::new(); + for cert in &self.local_certificates { + if let Ok((_rem, x509_cert)) = x509_parser::parse_x509_der(&cert.certificate) { + if let Some(a) = x509_cert.tbs_certificate.subject.iter_common_name().next() { + let common_name = match a.attr_value.as_str() { + Ok(cn) => cn.to_lowercase(), + Err(err) => return Err(Error::new(err.to_string())), + }; + name_to_certificate.insert(common_name, cert.clone()); + } + if let Some((_, sans)) = x509_cert.tbs_certificate.subject_alternative_name() { + for gn in &sans.general_names { + match gn { + x509_parser::extensions::GeneralName::DNSName(san) => { + let san = san.to_lowercase(); + name_to_certificate.insert(san, cert.clone()); + } + _ => {} + } + } + } + } else { + continue; + } + } + self.name_to_certificate = name_to_certificate; + }*/ + + if self.local_certificates.is_empty() { + return Err(Error::ErrNoCertificates); + } + + if self.local_certificates.len() == 1 { + // There's only one choice, so no point doing any work. + return Ok(self.local_certificates[0].clone()); + } + + if server_name.is_empty() { + return Ok(self.local_certificates[0].clone()); + } + + let lower = server_name.to_lowercase(); + let name = lower.trim_end_matches('.'); + + if let Some(cert) = self.name_to_certificate.get(name) { + return Ok(cert.clone()); + } + + // try replacing labels in the name with wildcards until we get a + // match. + let mut labels: Vec<&str> = name.split_terminator('.').collect(); + for i in 0..labels.len() { + labels[i] = "*"; + let candidate = labels.join("."); + if let Some(cert) = self.name_to_certificate.get(&candidate) { + return Ok(cert.clone()); + } + } + + // If nothing matches, return the first certificate. + Ok(self.local_certificates[0].clone()) + } +} + +pub(crate) fn srv_cli_str(is_client: bool) -> String { + if is_client { + return "client".to_owned(); + } + "server".to_owned() +} + +impl DTLSConn { + pub(crate) async fn handshake(&mut self, mut state: HandshakeState) -> Result<()> { + loop { + trace!( + "[handshake:{}] {}: {}", + srv_cli_str(self.state.is_client), + self.current_flight, + state + ); + + if state == HandshakeState::Finished && !self.is_handshake_completed_successfully() { + self.set_handshake_completed_successfully(); + self.handshake_done_tx.take(); // drop it by take + return Ok(()); + } + + state = match state { + HandshakeState::Preparing => self.prepare().await?, + HandshakeState::Sending => self.send().await?, + HandshakeState::Waiting => self.wait().await?, + HandshakeState::Finished => self.finish().await?, + _ => return Err(Error::ErrInvalidFsmTransition), + }; + } + } + + async fn prepare(&mut self) -> Result { + self.flights = None; + + // Prepare flights + self.retransmit = self.current_flight.has_retransmit(); + + let result = self + .current_flight + .generate(&mut self.state, &self.cache, &self.cfg) + .await; + + match result { + Err((a, mut err)) => { + if let Some(a) = a { + let alert_err = self.notify(a.alert_level, a.alert_description).await; + + if let Err(alert_err) = alert_err { + if err.is_some() { + err = Some(alert_err); + } + } + } + if let Some(err) = err { + return Err(err); + } + } + Ok(pkts) => { + /*if !pkts.is_empty() { + let mut s = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(s.as_mut()); + pkts[0].record.content.marshal(&mut writer)?; + } + trace!( + "[handshake:{}] {}: {:?}", + srv_cli_str(self.state.is_client), + self.current_flight.to_string(), + s, + ); + }*/ + self.flights = Some(pkts) + } + }; + + let epoch = self.cfg.initial_epoch; + let mut next_epoch = epoch; + if let Some(pkts) = &mut self.flights { + for p in pkts { + p.record.record_layer_header.epoch += epoch; + if p.record.record_layer_header.epoch > next_epoch { + next_epoch = p.record.record_layer_header.epoch; + } + if let Content::Handshake(h) = &mut p.record.content { + h.handshake_header.message_sequence = self.state.handshake_send_sequence as u16; + self.state.handshake_send_sequence += 1; + } + } + } + if epoch != next_epoch { + trace!( + "[handshake:{}] -> changeCipherSpec (epoch: {})", + srv_cli_str(self.state.is_client), + next_epoch + ); + self.set_local_epoch(next_epoch); + } + + Ok(HandshakeState::Sending) + } + async fn send(&mut self) -> Result { + // Send flights + if let Some(pkts) = self.flights.clone() { + self.write_packets(pkts).await?; + } + + if self.current_flight.is_last_send_flight() { + Ok(HandshakeState::Finished) + } else { + Ok(HandshakeState::Waiting) + } + } + async fn wait(&mut self) -> Result { + let retransmit_timer = tokio::time::sleep(self.cfg.retransmit_interval); + tokio::pin!(retransmit_timer); + + loop { + tokio::select! { + done_senders = self.handshake_rx.recv() =>{ + if done_senders.is_none() { + trace!("[handshake:{}] {} handshake_tx is dropped", srv_cli_str(self.state.is_client), self.current_flight); + return Err(Error::ErrAlertFatalOrClose); + } else if let Some((rendezvous_tx, done_tx)) = done_senders { + rendezvous_tx.send(()).ok(); + //trace!("[handshake:{}] {} received handshake_rx", srv_cli_str(self.state.is_client), self.current_flight); + let result = self.current_flight.parse(&mut self.handle_queue_tx, &mut self.state, &self.cache, &self.cfg).await; + drop(done_tx); + match result { + Err((alert, mut err)) => { + trace!("[handshake:{}] {} result alert:{:?}, err:{:?}", + srv_cli_str(self.state.is_client), + self.current_flight, + alert, + err); + + if let Some(alert) = alert { + let alert_err = self.notify(alert.alert_level, alert.alert_description).await; + + if let Err(alert_err) = alert_err { + if err.is_some() { + err = Some(alert_err); + } + } + } + if let Some(err) = err { + return Err(err); + } + } + Ok(next_flight) => { + trace!("[handshake:{}] {} -> {}", srv_cli_str(self.state.is_client), self.current_flight, next_flight); + if next_flight.is_last_recv_flight() && self.current_flight.to_string() == next_flight.to_string() { + return Ok(HandshakeState::Finished); + } + self.current_flight = next_flight; + return Ok(HandshakeState::Preparing); + } + }; + } + } + + _ = retransmit_timer.as_mut() =>{ + trace!("[handshake:{}] {} retransmit_timer", srv_cli_str(self.state.is_client), self.current_flight); + + if !self.retransmit { + return Ok(HandshakeState::Waiting); + } + return Ok(HandshakeState::Sending); + } + + /*_ = self.done_rx.recv() => { + return Err(Error::new("done_rx recv".to_owned())); + }*/ + } + } + } + async fn finish(&mut self) -> Result { + let retransmit_timer = tokio::time::sleep(self.cfg.retransmit_interval); + + tokio::select! { + done = self.handshake_rx.recv() =>{ + if done.is_none() { + trace!("[handshake:{}] {} handshake_tx is dropped", srv_cli_str(self.state.is_client), self.current_flight); + return Err(Error::ErrAlertFatalOrClose); + } + let result = self.current_flight.parse(&mut self.handle_queue_tx, &mut self.state, &self.cache, &self.cfg).await; + drop(done); + match result { + Err((alert, mut err)) => { + if let Some(alert) = alert { + let alert_err = self.notify(alert.alert_level, alert.alert_description).await; + if let Err(alert_err) = alert_err { + if err.is_some() { + err = Some(alert_err); + } + } + } + if let Some(err) = err { + return Err(err); + } + } + Ok(_) => { + retransmit_timer.await; + // Retransmit last flight + return Ok(HandshakeState::Sending); + } + }; + } + + /*_ = self.done_rx.recv() => { + return Err(Error::new("done_rx recv".to_owned())); + }*/ + } + + Ok(HandshakeState::Finished) + } +} diff --git a/vendor/dtls/src/lib.rs b/vendor/dtls/src/lib.rs new file mode 100644 index 00000000..46c3cb49 --- /dev/null +++ b/vendor/dtls/src/lib.rs @@ -0,0 +1,58 @@ +#![warn(rust_2018_idioms)] +#![allow(dead_code)] + +pub mod alert; +pub mod application_data; +pub mod certificate_verifier; +pub mod change_cipher_spec; +pub mod cipher_suite; +pub mod client_certificate_type; +pub mod compression_methods; +pub mod config; +pub mod conn; +pub mod content; +pub mod crypto; +pub mod curve; +mod error; +pub mod extension; +pub mod flight; +pub mod fragment_buffer; +pub mod handshake; +pub mod handshaker; +pub mod listener; +pub mod prf; +pub mod record_layer; +pub mod signature_hash_algorithm; +pub mod state; + +use cipher_suite::*; +pub use error::Error; +use extension::extension_use_srtp::SrtpProtectionProfile; + +pub(crate) fn find_matching_srtp_profile( + a: &[SrtpProtectionProfile], + b: &[SrtpProtectionProfile], +) -> Result { + for a_profile in a { + for b_profile in b { + if a_profile == b_profile { + return Ok(*a_profile); + } + } + } + Err(()) +} + +pub(crate) fn find_matching_cipher_suite( + a: &[CipherSuiteId], + b: &[CipherSuiteId], +) -> Result { + for a_suite in a { + for b_suite in b { + if a_suite == b_suite { + return Ok(*a_suite); + } + } + } + Err(()) +} diff --git a/vendor/dtls/src/listener.rs b/vendor/dtls/src/listener.rs new file mode 100644 index 00000000..0dc6257c --- /dev/null +++ b/vendor/dtls/src/listener.rs @@ -0,0 +1,95 @@ +use std::future::Future; +use std::io::BufReader; +use std::net::SocketAddr; +use std::pin::Pin; +use std::sync::Arc; + +use async_trait::async_trait; +use tokio::net::ToSocketAddrs; +use util::conn::conn_udp_listener::*; +use util::conn::*; + +use crate::config::*; +use crate::conn::DTLSConn; +use crate::content::ContentType; +use crate::error::Result; +use crate::record_layer::record_layer_header::RecordLayerHeader; +use crate::record_layer::unpack_datagram; + +/// Listen creates a DTLS listener +pub async fn listen(laddr: A, config: Config) -> Result { + validate_config(false, &config)?; + + let mut lc = ListenConfig { + accept_filter: Some(Box::new( + |packet: &[u8]| -> Pin + Send + 'static>> { + let pkts = match unpack_datagram(packet) { + Ok(pkts) => { + if pkts.is_empty() { + return Box::pin(async { false }); + } + pkts + } + Err(_) => return Box::pin(async { false }), + }; + + let mut reader = BufReader::new(pkts[0].as_slice()); + match RecordLayerHeader::unmarshal(&mut reader) { + Ok(h) => { + let content_type = h.content_type; + Box::pin(async move { content_type == ContentType::Handshake }) + } + Err(_) => Box::pin(async { false }), + } + }, + )), + ..Default::default() + }; + + let parent = Arc::new(lc.listen(laddr).await?); + Ok(DTLSListener { parent, config }) +} + +/// DTLSListener represents a DTLS listener +pub struct DTLSListener { + parent: Arc, + config: Config, +} + +impl DTLSListener { + /// creates a DTLS listener which accepts connections from an inner Listener. + pub fn new(parent: Arc, config: Config) -> Result { + validate_config(false, &config)?; + + Ok(DTLSListener { parent, config }) + } +} + +type UtilResult = std::result::Result; + +#[async_trait] +impl Listener for DTLSListener { + /// Accept waits for and returns the next connection to the listener. + /// You have to either close or read on all connection that are created. + /// Connection handshake will timeout using ConnectContextMaker in the Config. + /// If you want to specify the timeout duration, set ConnectContextMaker. + async fn accept(&self) -> UtilResult<(Arc, SocketAddr)> { + let (conn, raddr) = self.parent.accept().await?; + let dtls_conn = DTLSConn::new(conn, self.config.clone(), false, None) + .await + .map_err(util::Error::from_std)?; + Ok((Arc::new(dtls_conn), raddr)) + } + + /// Close closes the listener. + /// Any blocked Accept operations will be unblocked and return errors. + /// Already Accepted connections are not closed. + async fn close(&self) -> UtilResult<()> { + self.parent.close().await + } + + /// Addr returns the listener's network address. + async fn addr(&self) -> UtilResult { + self.parent.addr().await + } +} diff --git a/vendor/dtls/src/prf/mod.rs b/vendor/dtls/src/prf/mod.rs new file mode 100644 index 00000000..e38cd454 --- /dev/null +++ b/vendor/dtls/src/prf/mod.rs @@ -0,0 +1,315 @@ +#[cfg(test)] +mod prf_test; + +use std::convert::TryInto; +use std::fmt; + +use hmac::{Hmac, Mac}; +use sha1::Sha1; +use sha2::{Digest, Sha256}; + +type HmacSha256 = Hmac; +type HmacSha1 = Hmac; + +use crate::cipher_suite::CipherSuiteHash; +use crate::content::ContentType; +use crate::curve::named_curve::*; +use crate::error::*; +use crate::record_layer::record_layer_header::ProtocolVersion; + +pub(crate) const PRF_MASTER_SECRET_LABEL: &str = "master secret"; +pub(crate) const PRF_EXTENDED_MASTER_SECRET_LABEL: &str = "extended master secret"; +pub(crate) const PRF_KEY_EXPANSION_LABEL: &str = "key expansion"; +pub(crate) const PRF_VERIFY_DATA_CLIENT_LABEL: &str = "client finished"; +pub(crate) const PRF_VERIFY_DATA_SERVER_LABEL: &str = "server finished"; + +#[derive(PartialEq, Debug, Clone)] +pub(crate) struct EncryptionKeys { + pub(crate) master_secret: Vec, + pub(crate) client_mac_key: Vec, + pub(crate) server_mac_key: Vec, + pub(crate) client_write_key: Vec, + pub(crate) server_write_key: Vec, + pub(crate) client_write_iv: Vec, + pub(crate) server_write_iv: Vec, +} + +impl fmt::Display for EncryptionKeys { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut out = "EncryptionKeys:\n".to_string(); + + out += format!("- master_secret: {:?}\n", self.master_secret).as_str(); + out += format!("- client_mackey: {:?}\n", self.client_mac_key).as_str(); + out += format!("- server_mackey: {:?}\n", self.server_mac_key).as_str(); + out += format!("- client_write_key: {:?}\n", self.client_write_key).as_str(); + out += format!("- server_write_key: {:?}\n", self.server_write_key).as_str(); + out += format!("- client_write_iv: {:?}\n", self.client_write_iv).as_str(); + out += format!("- server_write_iv: {:?}\n", self.server_write_iv).as_str(); + + write!(f, "{out}") + } +} + +// The premaster secret is formed as follows: if the PSK is N octets +// long, concatenate a uint16 with the value N, N zero octets, a second +// uint16 with the value N, and the PSK itself. +// +// https://tools.ietf.org/html/rfc4279#section-2 +pub(crate) fn prf_psk_pre_master_secret(psk: &[u8]) -> Vec { + let psk_len = psk.len(); + + let mut out = vec![0u8; 2 + psk_len + 2]; + + out.extend_from_slice(psk); + let be = (psk_len as u16).to_be_bytes(); + out[..2].copy_from_slice(&be); + out[2 + psk_len..2 + psk_len + 2].copy_from_slice(&be); + + out +} + +pub(crate) fn prf_pre_master_secret( + public_key: &[u8], + private_key: &NamedCurvePrivateKey, + curve: NamedCurve, +) -> Result> { + match curve { + NamedCurve::P256 => elliptic_curve_pre_master_secret(public_key, private_key, curve), + NamedCurve::P384 => elliptic_curve_pre_master_secret(public_key, private_key, curve), + NamedCurve::X25519 => elliptic_curve_pre_master_secret(public_key, private_key, curve), + _ => Err(Error::ErrInvalidNamedCurve), + } +} + +fn elliptic_curve_pre_master_secret( + public_key: &[u8], + private_key: &NamedCurvePrivateKey, + curve: NamedCurve, +) -> Result> { + match curve { + NamedCurve::P256 => { + let pub_key = p256::EncodedPoint::from_bytes(public_key)?; + let public = p256::PublicKey::from_sec1_bytes(pub_key.as_ref())?; + if let NamedCurvePrivateKey::EphemeralSecretP256(secret) = private_key { + return Ok(secret.diffie_hellman(&public).raw_secret_bytes().to_vec()); + } + } + NamedCurve::P384 => { + let pub_key = p384::EncodedPoint::from_bytes(public_key)?; + let public = p384::PublicKey::from_sec1_bytes(pub_key.as_ref())?; + if let NamedCurvePrivateKey::EphemeralSecretP384(secret) = private_key { + return Ok(secret.diffie_hellman(&public).raw_secret_bytes().to_vec()); + } + } + NamedCurve::X25519 => { + if public_key.len() != 32 { + return Err(Error::Other("Public key is not 32 len".into())); + } + let pub_key: [u8; 32] = public_key.try_into().unwrap(); + let public = x25519_dalek::PublicKey::from(pub_key); + if let NamedCurvePrivateKey::StaticSecretX25519(secret) = private_key { + return Ok(secret.diffie_hellman(&public).as_bytes().to_vec()); + } + } + _ => return Err(Error::ErrInvalidNamedCurve), + } + Err(Error::ErrNamedCurveAndPrivateKeyMismatch) +} + +// This PRF with the SHA-256 hash function is used for all cipher suites +// defined in this document and in TLS documents published prior to this +// document when TLS 1.2 is negotiated. New cipher suites MUST explicitly +// specify a PRF and, in general, SHOULD use the TLS PRF with SHA-256 or a +// stronger standard hash function. +// +// P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) + +// HMAC_hash(secret, A(2) + seed) + +// HMAC_hash(secret, A(3) + seed) + ... +// +// A() is defined as: +// +// A(0) = seed +// A(i) = HMAC_hash(secret, A(i-1)) +// +// P_hash can be iterated as many times as necessary to produce the +// required quantity of data. For example, if P_SHA256 is being used to +// create 80 bytes of data, it will have to be iterated three times +// (through A(3)), creating 96 bytes of output data; the last 16 bytes +// of the final iteration will then be discarded, leaving 80 bytes of +// output data. +// +// https://tools.ietf.org/html/rfc4346w +fn hmac_sha(h: CipherSuiteHash, key: &[u8], data: &[u8]) -> Result> { + let mut mac = match h { + CipherSuiteHash::Sha256 => { + HmacSha256::new_from_slice(key).map_err(|e| Error::Other(e.to_string()))? + } + }; + mac.update(data); + let result = mac.finalize(); + let code_bytes = result.into_bytes(); + Ok(code_bytes.to_vec()) +} + +pub(crate) fn prf_p_hash( + secret: &[u8], + seed: &[u8], + requested_length: usize, + h: CipherSuiteHash, +) -> Result> { + let mut last_round = seed.to_vec(); + let mut out = vec![]; + + let iterations = ((requested_length as f64) / (h.size() as f64)).ceil() as usize; + for _ in 0..iterations { + last_round = hmac_sha(h, secret, &last_round)?; + + let mut last_round_seed = last_round.clone(); + last_round_seed.extend_from_slice(seed); + let with_secret = hmac_sha(h, secret, &last_round_seed)?; + + out.extend_from_slice(&with_secret); + } + + Ok(out[..requested_length].to_vec()) +} + +pub(crate) fn prf_extended_master_secret( + pre_master_secret: &[u8], + session_hash: &[u8], + h: CipherSuiteHash, +) -> Result> { + let mut seed = PRF_EXTENDED_MASTER_SECRET_LABEL.as_bytes().to_vec(); + seed.extend_from_slice(session_hash); + prf_p_hash(pre_master_secret, &seed, 48, h) +} + +pub(crate) fn prf_master_secret( + pre_master_secret: &[u8], + client_random: &[u8], + server_random: &[u8], + h: CipherSuiteHash, +) -> Result> { + let mut seed = PRF_MASTER_SECRET_LABEL.as_bytes().to_vec(); + seed.extend_from_slice(client_random); + seed.extend_from_slice(server_random); + prf_p_hash(pre_master_secret, &seed, 48, h) +} + +pub(crate) fn prf_encryption_keys( + master_secret: &[u8], + client_random: &[u8], + server_random: &[u8], + prf_mac_len: usize, + prf_key_len: usize, + prf_iv_len: usize, + h: CipherSuiteHash, +) -> Result { + let mut seed = PRF_KEY_EXPANSION_LABEL.as_bytes().to_vec(); + seed.extend_from_slice(server_random); + seed.extend_from_slice(client_random); + + let material = prf_p_hash( + master_secret, + &seed, + (2 * prf_mac_len) + (2 * prf_key_len) + (2 * prf_iv_len), + h, + )?; + let mut key_material = &material[..]; + + let client_mac_key = key_material[..prf_mac_len].to_vec(); + key_material = &key_material[prf_mac_len..]; + + let server_mac_key = key_material[..prf_mac_len].to_vec(); + key_material = &key_material[prf_mac_len..]; + + let client_write_key = key_material[..prf_key_len].to_vec(); + key_material = &key_material[prf_key_len..]; + + let server_write_key = key_material[..prf_key_len].to_vec(); + key_material = &key_material[prf_key_len..]; + + let client_write_iv = key_material[..prf_iv_len].to_vec(); + key_material = &key_material[prf_iv_len..]; + + let server_write_iv = key_material[..prf_iv_len].to_vec(); + + Ok(EncryptionKeys { + master_secret: master_secret.to_vec(), + client_mac_key, + server_mac_key, + client_write_key, + server_write_key, + client_write_iv, + server_write_iv, + }) +} + +pub(crate) fn prf_verify_data( + master_secret: &[u8], + handshake_bodies: &[u8], + label: &str, + h: CipherSuiteHash, +) -> Result> { + let mut hasher = match h { + CipherSuiteHash::Sha256 => Sha256::new(), + }; + hasher.update(handshake_bodies); + let result = hasher.finalize(); + let mut seed = label.as_bytes().to_vec(); + seed.extend_from_slice(&result); + + prf_p_hash(master_secret, &seed, 12, h) +} + +pub(crate) fn prf_verify_data_client( + master_secret: &[u8], + handshake_bodies: &[u8], + h: CipherSuiteHash, +) -> Result> { + prf_verify_data( + master_secret, + handshake_bodies, + PRF_VERIFY_DATA_CLIENT_LABEL, + h, + ) +} + +pub(crate) fn prf_verify_data_server( + master_secret: &[u8], + handshake_bodies: &[u8], + h: CipherSuiteHash, +) -> Result> { + prf_verify_data( + master_secret, + handshake_bodies, + PRF_VERIFY_DATA_SERVER_LABEL, + h, + ) +} + +// compute the MAC using HMAC-SHA1 +pub(crate) fn prf_mac( + epoch: u16, + sequence_number: u64, + content_type: ContentType, + protocol_version: ProtocolVersion, + payload: &[u8], + key: &[u8], +) -> Result> { + let mut hmac = HmacSha1::new_from_slice(key).map_err(|e| Error::Other(e.to_string()))?; + + let mut msg = vec![0u8; 13]; + msg[..2].copy_from_slice(&epoch.to_be_bytes()); + msg[2..8].copy_from_slice(&sequence_number.to_be_bytes()[2..]); + msg[8] = content_type as u8; + msg[9] = protocol_version.major; + msg[10] = protocol_version.minor; + msg[11..].copy_from_slice(&(payload.len() as u16).to_be_bytes()); + + hmac.update(&msg); + hmac.update(payload); + let result = hmac.finalize(); + + Ok(result.into_bytes().to_vec()) +} diff --git a/vendor/dtls/src/prf/prf_test.rs b/vendor/dtls/src/prf/prf_test.rs new file mode 100644 index 00000000..33d5241f --- /dev/null +++ b/vendor/dtls/src/prf/prf_test.rs @@ -0,0 +1,261 @@ +use super::*; +use crate::cipher_suite::CipherSuiteHash; + +#[test] +fn test_pre_master_secret() -> Result<()> { + let private_key: [u8; 32] = [ + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, + 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, + 0x3e, 0x3f, + ]; + let private_key = + NamedCurvePrivateKey::StaticSecretX25519(x25519_dalek::StaticSecret::from(private_key)); + let public_key = [ + 0x9f, 0xd7, 0xad, 0x6d, 0xcf, 0xf4, 0x29, 0x8d, 0xd3, 0xf9, 0x6d, 0x5b, 0x1b, 0x2a, 0xf9, + 0x10, 0xa0, 0x53, 0x5b, 0x14, 0x88, 0xd7, 0xf8, 0xfa, 0xbb, 0x34, 0x9a, 0x98, 0x28, 0x80, + 0xb6, 0x15, + ]; + + let expected_pre_master_secret = vec![ + 0xdf, 0x4a, 0x29, 0x1b, 0xaa, 0x1e, 0xb7, 0xcf, 0xa6, 0x93, 0x4b, 0x29, 0xb4, 0x74, 0xba, + 0xad, 0x26, 0x97, 0xe2, 0x9f, 0x1f, 0x92, 0x0d, 0xcc, 0x77, 0xc8, 0xa0, 0xa0, 0x88, 0x44, + 0x76, 0x24, + ]; + + let pre_master_secret = prf_pre_master_secret(&public_key, &private_key, NamedCurve::X25519)?; + + assert_eq!( + expected_pre_master_secret, pre_master_secret, + "PremasterSecret exp: {expected_pre_master_secret:?} actual: {pre_master_secret:?}" + ); + + Ok(()) +} + +#[test] +fn test_master_secret() -> Result<()> { + let pre_master_secret = vec![ + 0xdf, 0x4a, 0x29, 0x1b, 0xaa, 0x1e, 0xb7, 0xcf, 0xa6, 0x93, 0x4b, 0x29, 0xb4, 0x74, 0xba, + 0xad, 0x26, 0x97, 0xe2, 0x9f, 0x1f, 0x92, 0x0d, 0xcc, 0x77, 0xc8, 0xa0, 0xa0, 0x88, 0x44, + 0x76, 0x24, + ]; + let client_random = vec![ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, + ]; + let server_random = vec![ + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, + 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, + 0x8e, 0x8f, + ]; + let expected_master_secret = vec![ + 0x91, 0x6a, 0xbf, 0x9d, 0xa5, 0x59, 0x73, 0xe1, 0x36, 0x14, 0xae, 0x0a, 0x3f, 0x5d, 0x3f, + 0x37, 0xb0, 0x23, 0xba, 0x12, 0x9a, 0xee, 0x02, 0xcc, 0x91, 0x34, 0x33, 0x81, 0x27, 0xcd, + 0x70, 0x49, 0x78, 0x1c, 0x8e, 0x19, 0xfc, 0x1e, 0xb2, 0xa7, 0x38, 0x7a, 0xc0, 0x6a, 0xe2, + 0x37, 0x34, 0x4c, + ]; + + let master_secret = prf_master_secret( + &pre_master_secret, + &client_random, + &server_random, + CipherSuiteHash::Sha256, + )?; + + assert_eq!( + expected_master_secret, master_secret, + "master_secret exp: {expected_master_secret:?} actual: {master_secret:?}" + ); + + Ok(()) +} + +#[test] +fn test_encryption_keys() -> Result<()> { + let client_random = vec![ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, + ]; + let server_random = vec![ + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, + 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, + 0x8e, 0x8f, + ]; + let master_secret = vec![ + 0x91, 0x6a, 0xbf, 0x9d, 0xa5, 0x59, 0x73, 0xe1, 0x36, 0x14, 0xae, 0x0a, 0x3f, 0x5d, 0x3f, + 0x37, 0xb0, 0x23, 0xba, 0x12, 0x9a, 0xee, 0x02, 0xcc, 0x91, 0x34, 0x33, 0x81, 0x27, 0xcd, + 0x70, 0x49, 0x78, 0x1c, 0x8e, 0x19, 0xfc, 0x1e, 0xb2, 0xa7, 0x38, 0x7a, 0xc0, 0x6a, 0xe2, + 0x37, 0x34, 0x4c, + ]; + + let expected_encryption_keys = EncryptionKeys { + master_secret: master_secret.clone(), + client_mac_key: vec![], + server_mac_key: vec![], + client_write_key: vec![ + 0x1b, 0x7d, 0x11, 0x7c, 0x7d, 0x5f, 0x69, 0x0b, 0xc2, 0x63, 0xca, 0xe8, 0xef, 0x60, + 0xaf, 0x0f, + ], + server_write_key: vec![ + 0x18, 0x78, 0xac, 0xc2, 0x2a, 0xd8, 0xbd, 0xd8, 0xc6, 0x01, 0xa6, 0x17, 0x12, 0x6f, + 0x63, 0x54, + ], + client_write_iv: vec![0x0e, 0xb2, 0x09, 0x06], + server_write_iv: vec![0xf7, 0x81, 0xfa, 0xd2], + }; + + let keys = prf_encryption_keys( + &master_secret, + &client_random, + &server_random, + 0, + 16, + 4, + CipherSuiteHash::Sha256, + )?; + + assert_eq!( + expected_encryption_keys, keys, + "master_secret exp: {expected_encryption_keys:?} actual: {keys:?}", + ); + + Ok(()) +} + +#[test] +fn test_verify_data() -> Result<()> { + let client_hello = vec![ + 0x01, 0x00, 0x00, 0xa1, 0x03, 0x03, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x00, 0x00, 0x20, 0xcc, 0xa8, 0xcc, 0xa9, + 0xc0, 0x2f, 0xc0, 0x30, 0xc0, 0x2b, 0xc0, 0x2c, 0xc0, 0x13, 0xc0, 0x09, 0xc0, 0x14, 0xc0, + 0x0a, 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35, 0xc0, 0x12, 0x00, 0x0a, 0x01, 0x00, + 0x00, 0x58, 0x00, 0x00, 0x00, 0x18, 0x00, 0x16, 0x00, 0x00, 0x13, 0x65, 0x78, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x2e, 0x75, 0x6c, 0x66, 0x68, 0x65, 0x69, 0x6d, 0x2e, 0x6e, 0x65, 0x74, + 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, + 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, + 0x0d, 0x00, 0x12, 0x00, 0x10, 0x04, 0x01, 0x04, 0x03, 0x05, 0x01, 0x05, 0x03, 0x06, 0x01, + 0x06, 0x03, 0x02, 0x01, 0x02, 0x03, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x12, 0x00, 0x00, + ]; + let server_hello = vec![ + 0x02, 0x00, 0x00, 0x2d, 0x03, 0x03, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x00, 0xc0, 0x13, 0x00, 0x00, 0x05, 0xff, + 0x01, 0x00, 0x01, 0x00, + ]; + let server_certificate = vec![ + 0x0b, 0x00, 0x03, 0x2b, 0x00, 0x03, 0x28, 0x00, 0x03, 0x25, 0x30, 0x82, 0x03, 0x21, 0x30, + 0x82, 0x02, 0x09, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x08, 0x15, 0x5a, 0x92, 0xad, 0xc2, + 0x04, 0x8f, 0x90, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x0b, 0x05, 0x00, 0x30, 0x22, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, + 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0a, 0x45, + 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x38, + 0x31, 0x30, 0x30, 0x35, 0x30, 0x31, 0x33, 0x38, 0x31, 0x37, 0x5a, 0x17, 0x0d, 0x31, 0x39, + 0x31, 0x30, 0x30, 0x35, 0x30, 0x31, 0x33, 0x38, 0x31, 0x37, 0x5a, 0x30, 0x2b, 0x31, 0x0b, + 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x1c, 0x30, 0x1a, + 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x13, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, + 0x75, 0x6c, 0x66, 0x68, 0x65, 0x69, 0x6d, 0x2e, 0x6e, 0x65, 0x74, 0x30, 0x82, 0x01, 0x22, + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, + 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xc4, + 0x80, 0x36, 0x06, 0xba, 0xe7, 0x47, 0x6b, 0x08, 0x94, 0x04, 0xec, 0xa7, 0xb6, 0x91, 0x04, + 0x3f, 0xf7, 0x92, 0xbc, 0x19, 0xee, 0xfb, 0x7d, 0x74, 0xd7, 0xa8, 0x0d, 0x00, 0x1e, 0x7b, + 0x4b, 0x3a, 0x4a, 0xe6, 0x0f, 0xe8, 0xc0, 0x71, 0xfc, 0x73, 0xe7, 0x02, 0x4c, 0x0d, 0xbc, + 0xf4, 0xbd, 0xd1, 0x1d, 0x39, 0x6b, 0xba, 0x70, 0x46, 0x4a, 0x13, 0xe9, 0x4a, 0xf8, 0x3d, + 0xf3, 0xe1, 0x09, 0x59, 0x54, 0x7b, 0xc9, 0x55, 0xfb, 0x41, 0x2d, 0xa3, 0x76, 0x52, 0x11, + 0xe1, 0xf3, 0xdc, 0x77, 0x6c, 0xaa, 0x53, 0x37, 0x6e, 0xca, 0x3a, 0xec, 0xbe, 0xc3, 0xaa, + 0xb7, 0x3b, 0x31, 0xd5, 0x6c, 0xb6, 0x52, 0x9c, 0x80, 0x98, 0xbc, 0xc9, 0xe0, 0x28, 0x18, + 0xe2, 0x0b, 0xf7, 0xf8, 0xa0, 0x3a, 0xfd, 0x17, 0x04, 0x50, 0x9e, 0xce, 0x79, 0xbd, 0x9f, + 0x39, 0xf1, 0xea, 0x69, 0xec, 0x47, 0x97, 0x2e, 0x83, 0x0f, 0xb5, 0xca, 0x95, 0xde, 0x95, + 0xa1, 0xe6, 0x04, 0x22, 0xd5, 0xee, 0xbe, 0x52, 0x79, 0x54, 0xa1, 0xe7, 0xbf, 0x8a, 0x86, + 0xf6, 0x46, 0x6d, 0x0d, 0x9f, 0x16, 0x95, 0x1a, 0x4c, 0xf7, 0xa0, 0x46, 0x92, 0x59, 0x5c, + 0x13, 0x52, 0xf2, 0x54, 0x9e, 0x5a, 0xfb, 0x4e, 0xbf, 0xd7, 0x7a, 0x37, 0x95, 0x01, 0x44, + 0xe4, 0xc0, 0x26, 0x87, 0x4c, 0x65, 0x3e, 0x40, 0x7d, 0x7d, 0x23, 0x07, 0x44, 0x01, 0xf4, + 0x84, 0xff, 0xd0, 0x8f, 0x7a, 0x1f, 0xa0, 0x52, 0x10, 0xd1, 0xf4, 0xf0, 0xd5, 0xce, 0x79, + 0x70, 0x29, 0x32, 0xe2, 0xca, 0xbe, 0x70, 0x1f, 0xdf, 0xad, 0x6b, 0x4b, 0xb7, 0x11, 0x01, + 0xf4, 0x4b, 0xad, 0x66, 0x6a, 0x11, 0x13, 0x0f, 0xe2, 0xee, 0x82, 0x9e, 0x4d, 0x02, 0x9d, + 0xc9, 0x1c, 0xdd, 0x67, 0x16, 0xdb, 0xb9, 0x06, 0x18, 0x86, 0xed, 0xc1, 0xba, 0x94, 0x21, + 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x52, 0x30, 0x50, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, + 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x05, 0xa0, 0x30, 0x1d, 0x06, 0x03, 0x55, + 0x1d, 0x25, 0x04, 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, + 0x02, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x30, 0x1f, 0x06, 0x03, + 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x89, 0x4f, 0xde, 0x5b, 0xcc, 0x69, + 0xe2, 0x52, 0xcf, 0x3e, 0xa3, 0x00, 0xdf, 0xb1, 0x97, 0xb8, 0x1d, 0xe1, 0xc1, 0x46, 0x30, + 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, + 0x82, 0x01, 0x01, 0x00, 0x59, 0x16, 0x45, 0xa6, 0x9a, 0x2e, 0x37, 0x79, 0xe4, 0xf6, 0xdd, + 0x27, 0x1a, 0xba, 0x1c, 0x0b, 0xfd, 0x6c, 0xd7, 0x55, 0x99, 0xb5, 0xe7, 0xc3, 0x6e, 0x53, + 0x3e, 0xff, 0x36, 0x59, 0x08, 0x43, 0x24, 0xc9, 0xe7, 0xa5, 0x04, 0x07, 0x9d, 0x39, 0xe0, + 0xd4, 0x29, 0x87, 0xff, 0xe3, 0xeb, 0xdd, 0x09, 0xc1, 0xcf, 0x1d, 0x91, 0x44, 0x55, 0x87, + 0x0b, 0x57, 0x1d, 0xd1, 0x9b, 0xdf, 0x1d, 0x24, 0xf8, 0xbb, 0x9a, 0x11, 0xfe, 0x80, 0xfd, + 0x59, 0x2b, 0xa0, 0x39, 0x8c, 0xde, 0x11, 0xe2, 0x65, 0x1e, 0x61, 0x8c, 0xe5, 0x98, 0xfa, + 0x96, 0xe5, 0x37, 0x2e, 0xef, 0x3d, 0x24, 0x8a, 0xfd, 0xe1, 0x74, 0x63, 0xeb, 0xbf, 0xab, + 0xb8, 0xe4, 0xd1, 0xab, 0x50, 0x2a, 0x54, 0xec, 0x00, 0x64, 0xe9, 0x2f, 0x78, 0x19, 0x66, + 0x0d, 0x3f, 0x27, 0xcf, 0x20, 0x9e, 0x66, 0x7f, 0xce, 0x5a, 0xe2, 0xe4, 0xac, 0x99, 0xc7, + 0xc9, 0x38, 0x18, 0xf8, 0xb2, 0x51, 0x07, 0x22, 0xdf, 0xed, 0x97, 0xf3, 0x2e, 0x3e, 0x93, + 0x49, 0xd4, 0xc6, 0x6c, 0x9e, 0xa6, 0x39, 0x6d, 0x74, 0x44, 0x62, 0xa0, 0x6b, 0x42, 0xc6, + 0xd5, 0xba, 0x68, 0x8e, 0xac, 0x3a, 0x01, 0x7b, 0xdd, 0xfc, 0x8e, 0x2c, 0xfc, 0xad, 0x27, + 0xcb, 0x69, 0xd3, 0xcc, 0xdc, 0xa2, 0x80, 0x41, 0x44, 0x65, 0xd3, 0xae, 0x34, 0x8c, 0xe0, + 0xf3, 0x4a, 0xb2, 0xfb, 0x9c, 0x61, 0x83, 0x71, 0x31, 0x2b, 0x19, 0x10, 0x41, 0x64, 0x1c, + 0x23, 0x7f, 0x11, 0xa5, 0xd6, 0x5c, 0x84, 0x4f, 0x04, 0x04, 0x84, 0x99, 0x38, 0x71, 0x2b, + 0x95, 0x9e, 0xd6, 0x85, 0xbc, 0x5c, 0x5d, 0xd6, 0x45, 0xed, 0x19, 0x90, 0x94, 0x73, 0x40, + 0x29, 0x26, 0xdc, 0xb4, 0x0e, 0x34, 0x69, 0xa1, 0x59, 0x41, 0xe8, 0xe2, 0xcc, 0xa8, 0x4b, + 0xb6, 0x08, 0x46, 0x36, 0xa0, + ]; + let server_key_exchange = vec![ + 0x0c, 0x00, 0x01, 0x28, 0x03, 0x00, 0x1d, 0x20, 0x9f, 0xd7, 0xad, 0x6d, 0xcf, 0xf4, 0x29, + 0x8d, 0xd3, 0xf9, 0x6d, 0x5b, 0x1b, 0x2a, 0xf9, 0x10, 0xa0, 0x53, 0x5b, 0x14, 0x88, 0xd7, + 0xf8, 0xfa, 0xbb, 0x34, 0x9a, 0x98, 0x28, 0x80, 0xb6, 0x15, 0x04, 0x01, 0x01, 0x00, 0x04, + 0x02, 0xb6, 0x61, 0xf7, 0xc1, 0x91, 0xee, 0x59, 0xbe, 0x45, 0x37, 0x66, 0x39, 0xbd, 0xc3, + 0xd4, 0xbb, 0x81, 0xe1, 0x15, 0xca, 0x73, 0xc8, 0x34, 0x8b, 0x52, 0x5b, 0x0d, 0x23, 0x38, + 0xaa, 0x14, 0x46, 0x67, 0xed, 0x94, 0x31, 0x02, 0x14, 0x12, 0xcd, 0x9b, 0x84, 0x4c, 0xba, + 0x29, 0x93, 0x4a, 0xaa, 0xcc, 0xe8, 0x73, 0x41, 0x4e, 0xc1, 0x1c, 0xb0, 0x2e, 0x27, 0x2d, + 0x0a, 0xd8, 0x1f, 0x76, 0x7d, 0x33, 0x07, 0x67, 0x21, 0xf1, 0x3b, 0xf3, 0x60, 0x20, 0xcf, + 0x0b, 0x1f, 0xd0, 0xec, 0xb0, 0x78, 0xde, 0x11, 0x28, 0xbe, 0xba, 0x09, 0x49, 0xeb, 0xec, + 0xe1, 0xa1, 0xf9, 0x6e, 0x20, 0x9d, 0xc3, 0x6e, 0x4f, 0xff, 0xd3, 0x6b, 0x67, 0x3a, 0x7d, + 0xdc, 0x15, 0x97, 0xad, 0x44, 0x08, 0xe4, 0x85, 0xc4, 0xad, 0xb2, 0xc8, 0x73, 0x84, 0x12, + 0x49, 0x37, 0x25, 0x23, 0x80, 0x9e, 0x43, 0x12, 0xd0, 0xc7, 0xb3, 0x52, 0x2e, 0xf9, 0x83, + 0xca, 0xc1, 0xe0, 0x39, 0x35, 0xff, 0x13, 0xa8, 0xe9, 0x6b, 0xa6, 0x81, 0xa6, 0x2e, 0x40, + 0xd3, 0xe7, 0x0a, 0x7f, 0xf3, 0x58, 0x66, 0xd3, 0xd9, 0x99, 0x3f, 0x9e, 0x26, 0xa6, 0x34, + 0xc8, 0x1b, 0x4e, 0x71, 0x38, 0x0f, 0xcd, 0xd6, 0xf4, 0xe8, 0x35, 0xf7, 0x5a, 0x64, 0x09, + 0xc7, 0xdc, 0x2c, 0x07, 0x41, 0x0e, 0x6f, 0x87, 0x85, 0x8c, 0x7b, 0x94, 0xc0, 0x1c, 0x2e, + 0x32, 0xf2, 0x91, 0x76, 0x9e, 0xac, 0xca, 0x71, 0x64, 0x3b, 0x8b, 0x98, 0xa9, 0x63, 0xdf, + 0x0a, 0x32, 0x9b, 0xea, 0x4e, 0xd6, 0x39, 0x7e, 0x8c, 0xd0, 0x1a, 0x11, 0x0a, 0xb3, 0x61, + 0xac, 0x5b, 0xad, 0x1c, 0xcd, 0x84, 0x0a, 0x6c, 0x8a, 0x6e, 0xaa, 0x00, 0x1a, 0x9d, 0x7d, + 0x87, 0xdc, 0x33, 0x18, 0x64, 0x35, 0x71, 0x22, 0x6c, 0x4d, 0xd2, 0xc2, 0xac, 0x41, 0xfb, + ]; + let server_hello_done = vec![0x0e, 0x00, 0x00, 0x00]; + let client_key_exchange = vec![ + 0x10, 0x00, 0x00, 0x21, 0x20, 0x35, 0x80, 0x72, 0xd6, 0x36, 0x58, 0x80, 0xd1, 0xae, 0xea, + 0x32, 0x9a, 0xdf, 0x91, 0x21, 0x38, 0x38, 0x51, 0xed, 0x21, 0xa2, 0x8e, 0x3b, 0x75, 0xe9, + 0x65, 0xd0, 0xd2, 0xcd, 0x16, 0x62, 0x54, + ]; + + let mut final_msg = vec![]; + final_msg.extend_from_slice(&client_hello); + final_msg.extend_from_slice(&server_hello); + final_msg.extend_from_slice(&server_certificate); + final_msg.extend_from_slice(&server_key_exchange); + final_msg.extend_from_slice(&server_hello_done); + final_msg.extend_from_slice(&client_key_exchange); + + let master_secret = vec![ + 0x91, 0x6a, 0xbf, 0x9d, 0xa5, 0x59, 0x73, 0xe1, 0x36, 0x14, 0xae, 0x0a, 0x3f, 0x5d, 0x3f, + 0x37, 0xb0, 0x23, 0xba, 0x12, 0x9a, 0xee, 0x02, 0xcc, 0x91, 0x34, 0x33, 0x81, 0x27, 0xcd, + 0x70, 0x49, 0x78, 0x1c, 0x8e, 0x19, 0xfc, 0x1e, 0xb2, 0xa7, 0x38, 0x7a, 0xc0, 0x6a, 0xe2, + 0x37, 0x34, 0x4c, + ]; + + let expected_verify_data = vec![ + 0xcf, 0x91, 0x96, 0x26, 0xf1, 0x36, 0x0c, 0x53, 0x6a, 0xaa, 0xd7, 0x3a, + ]; + + let verify_data = prf_verify_data_client(&master_secret, &final_msg, CipherSuiteHash::Sha256)?; + + assert_eq!( + expected_verify_data, verify_data, + "verify_data exp: {expected_verify_data:?} actual: {verify_data:?}" + ); + + Ok(()) +} diff --git a/vendor/dtls/src/record_layer/mod.rs b/vendor/dtls/src/record_layer/mod.rs new file mode 100644 index 00000000..b7cead16 --- /dev/null +++ b/vendor/dtls/src/record_layer/mod.rs @@ -0,0 +1,111 @@ +pub mod record_layer_header; + +#[cfg(test)] +mod record_layer_test; + +use std::io::{Read, Write}; + +use record_layer_header::*; + +use super::content::*; +use super::error::*; +use crate::alert::Alert; +use crate::application_data::ApplicationData; +use crate::change_cipher_spec::ChangeCipherSpec; +use crate::handshake::Handshake; + +/* + The TLS Record Layer which handles all data transport. + The record layer is assumed to sit directly on top of some + reliable transport such as TCP. The record layer can carry four types of content: + + 1. Handshake messages—used for algorithm negotiation and key establishment. + 2. ChangeCipherSpec messages—really part of the handshake but technically a separate kind of message. + 3. Alert messages—used to signal that errors have occurred + 4. Application layer data + + The DTLS record layer is extremely similar to that of TLS 1.1. The + only change is the inclusion of an explicit sequence number in the + record. This sequence number allows the recipient to correctly + verify the TLS MAC. +*/ +/// ## Specifications +/// +/// * [RFC 4347 §4.1] +/// +/// [RFC 4347 §4.1]: https://tools.ietf.org/html/rfc4347#section-4.1 +#[derive(Debug, Clone, PartialEq)] +pub struct RecordLayer { + pub record_layer_header: RecordLayerHeader, + pub content: Content, +} + +impl RecordLayer { + pub fn new(protocol_version: ProtocolVersion, epoch: u16, content: Content) -> Self { + RecordLayer { + record_layer_header: RecordLayerHeader { + content_type: content.content_type(), + protocol_version, + epoch, + sequence_number: 0, + content_len: content.size() as u16, + }, + content, + } + } + + pub fn marshal(&self, writer: &mut W) -> Result<()> { + self.record_layer_header.marshal(writer)?; + self.content.marshal(writer)?; + Ok(()) + } + + pub fn unmarshal(reader: &mut R) -> Result { + let record_layer_header = RecordLayerHeader::unmarshal(reader)?; + let content = match record_layer_header.content_type { + ContentType::Alert => Content::Alert(Alert::unmarshal(reader)?), + ContentType::ApplicationData => { + Content::ApplicationData(ApplicationData::unmarshal(reader)?) + } + ContentType::ChangeCipherSpec => { + Content::ChangeCipherSpec(ChangeCipherSpec::unmarshal(reader)?) + } + ContentType::Handshake => Content::Handshake(Handshake::unmarshal(reader)?), + _ => return Err(Error::Other("Invalid Content Type".to_owned())), + }; + + Ok(RecordLayer { + record_layer_header, + content, + }) + } +} + +// Note that as with TLS, multiple handshake messages may be placed in +// the same DTLS record, provided that there is room and that they are +// part of the same flight. Thus, there are two acceptable ways to pack +// two DTLS messages into the same datagram: in the same record or in +// separate records. +// https://tools.ietf.org/html/rfc6347#section-4.2.3 +pub(crate) fn unpack_datagram(buf: &[u8]) -> Result>> { + let mut out = vec![]; + + let mut offset = 0; + while buf.len() != offset { + if buf.len() - offset <= RECORD_LAYER_HEADER_SIZE { + return Err(Error::ErrInvalidPacketLength); + } + + let pkt_len = RECORD_LAYER_HEADER_SIZE + + (((buf[offset + RECORD_LAYER_HEADER_SIZE - 2] as usize) << 8) + | buf[offset + RECORD_LAYER_HEADER_SIZE - 1] as usize); + if offset + pkt_len > buf.len() { + return Err(Error::ErrInvalidPacketLength); + } + + out.push(buf[offset..offset + pkt_len].to_vec()); + offset += pkt_len + } + + Ok(out) +} diff --git a/vendor/dtls/src/record_layer/record_layer_header.rs b/vendor/dtls/src/record_layer/record_layer_header.rs new file mode 100644 index 00000000..e10d9ea4 --- /dev/null +++ b/vendor/dtls/src/record_layer/record_layer_header.rs @@ -0,0 +1,94 @@ +use std::io::{Read, Write}; + +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; + +use crate::content::*; +use crate::error::*; + +pub const RECORD_LAYER_HEADER_SIZE: usize = 13; +pub const MAX_SEQUENCE_NUMBER: u64 = 0x0000FFFFFFFFFFFF; + +pub const DTLS1_2MAJOR: u8 = 0xfe; +pub const DTLS1_2MINOR: u8 = 0xfd; + +pub const DTLS1_0MAJOR: u8 = 0xfe; +pub const DTLS1_0MINOR: u8 = 0xff; + +// VERSION_DTLS12 is the DTLS version in the same style as +// VersionTLSXX from crypto/tls +pub const VERSION_DTLS12: u16 = 0xfefd; + +pub const PROTOCOL_VERSION1_0: ProtocolVersion = ProtocolVersion { + major: DTLS1_0MAJOR, + minor: DTLS1_0MINOR, +}; +pub const PROTOCOL_VERSION1_2: ProtocolVersion = ProtocolVersion { + major: DTLS1_2MAJOR, + minor: DTLS1_2MINOR, +}; + +/// ## Specifications +/// +/// * [RFC 4346 §6.2.1] +/// +/// [RFC 4346 §6.2.1]: https://tools.ietf.org/html/rfc4346#section-6.2.1 +#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] +pub struct ProtocolVersion { + pub major: u8, + pub minor: u8, +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] +pub struct RecordLayerHeader { + pub content_type: ContentType, + pub protocol_version: ProtocolVersion, + pub epoch: u16, + pub sequence_number: u64, // uint48 in spec + pub content_len: u16, +} + +impl RecordLayerHeader { + pub fn marshal(&self, writer: &mut W) -> Result<()> { + if self.sequence_number > MAX_SEQUENCE_NUMBER { + return Err(Error::ErrSequenceNumberOverflow); + } + + writer.write_u8(self.content_type as u8)?; + writer.write_u8(self.protocol_version.major)?; + writer.write_u8(self.protocol_version.minor)?; + writer.write_u16::(self.epoch)?; + + let be: [u8; 8] = self.sequence_number.to_be_bytes(); + writer.write_all(&be[2..])?; // uint48 in spec + + writer.write_u16::(self.content_len)?; + + Ok(writer.flush()?) + } + + pub fn unmarshal(reader: &mut R) -> Result { + let content_type = reader.read_u8()?.into(); + let major = reader.read_u8()?; + let minor = reader.read_u8()?; + let epoch = reader.read_u16::()?; + + // SequenceNumber is stored as uint48, make into uint64 + let mut be: [u8; 8] = [0u8; 8]; + reader.read_exact(&mut be[2..])?; + let sequence_number = u64::from_be_bytes(be); + + let protocol_version = ProtocolVersion { major, minor }; + if protocol_version != PROTOCOL_VERSION1_0 && protocol_version != PROTOCOL_VERSION1_2 { + return Err(Error::ErrUnsupportedProtocolVersion); + } + let content_len = reader.read_u16::()?; + + Ok(RecordLayerHeader { + content_type, + protocol_version, + epoch, + sequence_number, + content_len, + }) + } +} diff --git a/vendor/dtls/src/record_layer/record_layer_test.rs b/vendor/dtls/src/record_layer/record_layer_test.rs new file mode 100644 index 00000000..3e644e50 --- /dev/null +++ b/vendor/dtls/src/record_layer/record_layer_test.rs @@ -0,0 +1,118 @@ +use std::io::{BufReader, BufWriter}; + +use super::record_layer_header::*; +use super::*; +use crate::change_cipher_spec::ChangeCipherSpec; + +#[test] +fn test_udp_decode() -> Result<()> { + let tests = vec![ + ( + "Change Cipher Spec, single packet", + vec![ + 0x14, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x01, 0x01, + ], + vec![vec![ + 0x14, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x01, 0x01, + ]], + None, + ), + ( + "Change Cipher Spec, multi packet", + vec![ + 0x14, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x01, 0x01, + 0x14, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x01, 0x01, + ], + vec![ + vec![ + 0x14, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x01, + 0x01, + ], + vec![ + 0x14, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x01, + 0x01, + ], + ], + None, + ), + ( + "Invalid packet length", + vec![0x14, 0xfe], + vec![], + Some(Error::ErrInvalidPacketLength), + ), + ( + "Packet declared invalid length", + vec![ + 0x14, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0xFF, 0x01, + ], + vec![], + Some(Error::ErrInvalidPacketLength), + ), + ]; + + for (name, data, wanted, wanted_err) in tests { + let dtls_pkts = unpack_datagram(&data); + if let Some(err) = wanted_err { + if let Err(dtls) = dtls_pkts { + assert_eq!(err.to_string(), dtls.to_string()); + } else { + panic!("something wrong for {name} when wanted_err is Some"); + } + } else if let Ok(pkts) = dtls_pkts { + assert_eq!( + wanted, pkts, + "{name} UDP decode: got {pkts:?}, want {wanted:?}", + ); + } else { + panic!("something wrong for {name} when wanted_err is None"); + } + } + + Ok(()) +} + +#[test] +fn test_record_layer_round_trip() -> Result<()> { + let tests = vec![( + "Change Cipher Spec, single packet", + vec![ + 0x14, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x01, 0x01, + ], + RecordLayer { + record_layer_header: RecordLayerHeader { + content_type: ContentType::ChangeCipherSpec, + protocol_version: ProtocolVersion { + major: 0xfe, + minor: 0xff, + }, + epoch: 0, + sequence_number: 18, + content_len: 1, + }, + content: Content::ChangeCipherSpec(ChangeCipherSpec {}), + }, + )]; + + for (name, data, want) in tests { + let mut reader = BufReader::new(data.as_slice()); + let r = RecordLayer::unmarshal(&mut reader)?; + + assert_eq!( + want, r, + "{name} recordLayer.unmarshal: got {r:?}, want {want:?}" + ); + + let mut data2 = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(data2.as_mut()); + r.marshal(&mut writer)?; + } + assert_eq!( + data, data2, + "{name} recordLayer.marshal: got {data2:?}, want {data:?}" + ); + } + + Ok(()) +} diff --git a/vendor/dtls/src/signature_hash_algorithm/mod.rs b/vendor/dtls/src/signature_hash_algorithm/mod.rs new file mode 100644 index 00000000..41e5dab3 --- /dev/null +++ b/vendor/dtls/src/signature_hash_algorithm/mod.rs @@ -0,0 +1,215 @@ +#[cfg(test)] +mod signature_hash_algorithm_test; + +use std::fmt; + +use crate::crypto::*; +use crate::error::*; + +// HashAlgorithm is used to indicate the hash algorithm used +// https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-18 +// Supported hash hash algorithms +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum HashAlgorithm { + Md2 = 0, // Blacklisted + Md5 = 1, // Blacklisted + Sha1 = 2, // Blacklisted + Sha224 = 3, + Sha256 = 4, + Sha384 = 5, + Sha512 = 6, + Ed25519 = 8, + Unsupported, +} + +impl From for HashAlgorithm { + fn from(val: u8) -> Self { + match val { + 0 => HashAlgorithm::Md2, + 1 => HashAlgorithm::Md5, + 2 => HashAlgorithm::Sha1, + 3 => HashAlgorithm::Sha224, + 4 => HashAlgorithm::Sha256, + 5 => HashAlgorithm::Sha384, + 6 => HashAlgorithm::Sha512, + 8 => HashAlgorithm::Ed25519, + _ => HashAlgorithm::Unsupported, + } + } +} + +impl fmt::Display for HashAlgorithm { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + HashAlgorithm::Md2 => write!(f, "md2"), + HashAlgorithm::Md5 => write!(f, "md5"), // [RFC3279] + HashAlgorithm::Sha1 => write!(f, "sha-1"), // [RFC3279] + HashAlgorithm::Sha224 => write!(f, "sha-224"), // [RFC4055] + HashAlgorithm::Sha256 => write!(f, "sha-256"), // [RFC4055] + HashAlgorithm::Sha384 => write!(f, "sha-384"), // [RFC4055] + HashAlgorithm::Sha512 => write!(f, "sha-512"), // [RFC4055] + HashAlgorithm::Ed25519 => write!(f, "null"), // [RFC4055] + _ => write!(f, "unknown or unsupported hash algorithm"), + } + } +} + +impl HashAlgorithm { + pub(crate) fn insecure(&self) -> bool { + matches!( + *self, + HashAlgorithm::Md2 | HashAlgorithm::Md5 | HashAlgorithm::Sha1 + ) + } + + pub(crate) fn invalid(&self) -> bool { + matches!(*self, HashAlgorithm::Md2) + } +} + +// https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-16 +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum SignatureAlgorithm { + Rsa = 1, + Ecdsa = 3, + Ed25519 = 7, + Unsupported, +} + +impl From for SignatureAlgorithm { + fn from(val: u8) -> Self { + match val { + 1 => SignatureAlgorithm::Rsa, + 3 => SignatureAlgorithm::Ecdsa, + 7 => SignatureAlgorithm::Ed25519, + _ => SignatureAlgorithm::Unsupported, + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct SignatureHashAlgorithm { + pub hash: HashAlgorithm, + pub signature: SignatureAlgorithm, +} + +impl SignatureHashAlgorithm { + // is_compatible checks that given private key is compatible with the signature scheme. + pub(crate) fn is_compatible(&self, private_key: &CryptoPrivateKey) -> bool { + match &private_key.kind { + CryptoPrivateKeyKind::Ed25519(_) => self.signature == SignatureAlgorithm::Ed25519, + CryptoPrivateKeyKind::Ecdsa256(_) => self.signature == SignatureAlgorithm::Ecdsa, + CryptoPrivateKeyKind::Rsa256(_) => self.signature == SignatureAlgorithm::Rsa, + } + } +} + +pub(crate) fn default_signature_schemes() -> Vec { + vec![ + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha256, + signature: SignatureAlgorithm::Ecdsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha384, + signature: SignatureAlgorithm::Ecdsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha512, + signature: SignatureAlgorithm::Ecdsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha256, + signature: SignatureAlgorithm::Rsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha384, + signature: SignatureAlgorithm::Rsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha512, + signature: SignatureAlgorithm::Rsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Ed25519, + signature: SignatureAlgorithm::Ed25519, + }, + ] +} + +// select Signature Scheme returns most preferred and compatible scheme. +pub(crate) fn select_signature_scheme( + sigs: &[SignatureHashAlgorithm], + private_key: &CryptoPrivateKey, +) -> Result { + for ss in sigs { + if ss.is_compatible(private_key) { + return Ok(*ss); + } + } + + Err(Error::ErrNoAvailableSignatureSchemes) +} + +// SignatureScheme identifies a signature algorithm supported by TLS. See +// RFC 8446, Section 4.2.3. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum SignatureScheme { + // RSASSA-PKCS1-v1_5 algorithms. + Pkcs1WithSha256 = 0x0401, + Pkcs1WithSha384 = 0x0501, + Pkcs1WithSha512 = 0x0601, + + // RSASSA-PSS algorithms with public key OID rsaEncryption. + PssWithSha256 = 0x0804, + PssWithSha384 = 0x0805, + PssWithSha512 = 0x0806, + + // ECDSA algorithms. Only constrained to a specific curve in TLS 1.3. + EcdsaWithP256AndSha256 = 0x0403, + EcdsaWithP384AndSha384 = 0x0503, + EcdsaWithP521AndSha512 = 0x0603, + + // EdDSA algorithms. + Ed25519 = 0x0807, + + // Legacy signature and hash algorithms for TLS 1.2. + Pkcs1WithSha1 = 0x0201, + EcdsaWithSha1 = 0x0203, +} + +// parse_signature_schemes translates []tls.SignatureScheme to []signatureHashAlgorithm. +// It returns default signature scheme list if no SignatureScheme is passed. +pub(crate) fn parse_signature_schemes( + sigs: &[u16], + insecure_hashes: bool, +) -> Result> { + if sigs.is_empty() { + return Ok(default_signature_schemes()); + } + + let mut out = vec![]; + for ss in sigs { + let sig: SignatureAlgorithm = ((*ss & 0xFF) as u8).into(); + if sig == SignatureAlgorithm::Unsupported { + return Err(Error::ErrInvalidSignatureAlgorithm); + } + let h: HashAlgorithm = (((*ss >> 8) & 0xFF) as u8).into(); + if h == HashAlgorithm::Unsupported || h.invalid() { + return Err(Error::ErrInvalidHashAlgorithm); + } + if h.insecure() && !insecure_hashes { + continue; + } + out.push(SignatureHashAlgorithm { + hash: h, + signature: sig, + }) + } + + if out.is_empty() { + Err(Error::ErrNoAvailableSignatureSchemes) + } else { + Ok(out) + } +} diff --git a/vendor/dtls/src/signature_hash_algorithm/signature_hash_algorithm_test.rs b/vendor/dtls/src/signature_hash_algorithm/signature_hash_algorithm_test.rs new file mode 100644 index 00000000..775ef327 --- /dev/null +++ b/vendor/dtls/src/signature_hash_algorithm/signature_hash_algorithm_test.rs @@ -0,0 +1,141 @@ +use super::*; + +#[test] +fn test_parse_signature_schemes() -> Result<()> { + let tests = vec![ + ( + "Translate", + vec![ + SignatureScheme::EcdsaWithP256AndSha256 as u16, + SignatureScheme::EcdsaWithP384AndSha384 as u16, + SignatureScheme::EcdsaWithP521AndSha512 as u16, + SignatureScheme::Pkcs1WithSha256 as u16, + SignatureScheme::Pkcs1WithSha384 as u16, + SignatureScheme::Pkcs1WithSha512 as u16, + ], + vec![ + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha256, + signature: SignatureAlgorithm::Ecdsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha384, + signature: SignatureAlgorithm::Ecdsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha512, + signature: SignatureAlgorithm::Ecdsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha256, + signature: SignatureAlgorithm::Rsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha384, + signature: SignatureAlgorithm::Rsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha512, + signature: SignatureAlgorithm::Rsa, + }, + ], + false, + None, + ), + ( + "InvalidSignatureAlgorithm", + vec![ + SignatureScheme::EcdsaWithP256AndSha256 as u16, // Valid + 0x04FF, // Invalid: unknown signature with SHA-256 + ], + vec![], + false, + Some(Error::ErrInvalidSignatureAlgorithm), + ), + ( + "InvalidHashAlgorithm", + vec![ + SignatureScheme::EcdsaWithP256AndSha256 as u16, // Valid + 0x0003, // Invalid: ECDSA with MD2 + ], + vec![], + false, + Some(Error::ErrInvalidHashAlgorithm), + ), + ( + "InsecureHashAlgorithmDenied", + vec![ + SignatureScheme::EcdsaWithP256AndSha256 as u16, // Valid + SignatureScheme::EcdsaWithSha1 as u16, // Insecure + ], + vec![SignatureHashAlgorithm { + hash: HashAlgorithm::Sha256, + signature: SignatureAlgorithm::Ecdsa, + }], + false, + None, + ), + ( + "InsecureHashAlgorithmAllowed", + vec![ + SignatureScheme::EcdsaWithP256AndSha256 as u16, // Valid + SignatureScheme::EcdsaWithSha1 as u16, // Insecure + ], + vec![ + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha256, + signature: SignatureAlgorithm::Ecdsa, + }, + SignatureHashAlgorithm { + hash: HashAlgorithm::Sha1, + signature: SignatureAlgorithm::Ecdsa, + }, + ], + true, + None, + ), + ( + "OnlyInsecureHashAlgorithm", + vec![ + SignatureScheme::EcdsaWithSha1 as u16, // Insecure + ], + vec![], + false, + Some(Error::ErrNoAvailableSignatureSchemes), + ), + ( + "Translate", + vec![SignatureScheme::Ed25519 as u16], + vec![SignatureHashAlgorithm { + hash: HashAlgorithm::Ed25519, + signature: SignatureAlgorithm::Ed25519, + }], + false, + None, + ), + ]; + + for (name, inputs, expected, insecure_hashes, want_err) in tests { + let output = parse_signature_schemes(&inputs, insecure_hashes); + if let Some(err) = want_err { + if let Err(output_err) = output { + assert_eq!( + err.to_string(), + output_err.to_string(), + "Expected error: {err:?}, got: {output_err:?}" + ); + } else { + panic!("expect err, but got non-err for {name}"); + } + } else if let Ok(output_val) = output { + assert_eq!( + expected, output_val, + "Expected signatureHashAlgorithm:\n{expected:?}\ngot:\n{output_val:?}", + ); + } else { + panic!("expect non-err, but got err for {name}"); + } + } + + Ok(()) +} diff --git a/vendor/dtls/src/state.rs b/vendor/dtls/src/state.rs new file mode 100644 index 00000000..e911e7fa --- /dev/null +++ b/vendor/dtls/src/state.rs @@ -0,0 +1,336 @@ +use std::io::{BufWriter, Cursor}; +use std::marker::{Send, Sync}; +use std::sync::atomic::Ordering; +use std::sync::Arc; + +use async_trait::async_trait; +use portable_atomic::AtomicU16; +use rkyv::{Archive, Deserialize, Serialize}; +use tokio::sync::Mutex; +use util::{KeyingMaterialExporter, KeyingMaterialExporterError}; + +use super::cipher_suite::*; +use super::conn::*; +use super::curve::named_curve::*; +use super::extension::extension_use_srtp::SrtpProtectionProfile; +use super::handshake::handshake_random::*; +use super::prf::*; +use crate::error::*; + +// State holds the dtls connection state and implements both encoding.BinaryMarshaler and encoding.BinaryUnmarshaler +pub struct State { + pub(crate) local_epoch: Arc, + pub(crate) remote_epoch: Arc, + pub(crate) local_sequence_number: Arc>>, // uint48 + pub(crate) local_random: HandshakeRandom, + pub(crate) remote_random: HandshakeRandom, + pub(crate) master_secret: Vec, + pub(crate) cipher_suite: Arc>>>, // nil if a cipher_suite hasn't been chosen + + pub(crate) srtp_protection_profile: SrtpProtectionProfile, // Negotiated srtp_protection_profile + pub peer_certificates: Vec>, + pub identity_hint: Vec, + + pub(crate) is_client: bool, + + pub(crate) pre_master_secret: Vec, + pub(crate) extended_master_secret: bool, + + pub(crate) named_curve: NamedCurve, + pub(crate) local_keypair: Option, + pub(crate) cookie: Vec, + pub(crate) handshake_send_sequence: isize, + pub(crate) handshake_recv_sequence: isize, + pub(crate) server_name: String, + pub(crate) remote_requested_certificate: bool, // Did we get a CertificateRequest + pub(crate) local_certificates_verify: Vec, // cache CertificateVerify + pub(crate) local_verify_data: Vec, // cached VerifyData + pub(crate) local_key_signature: Vec, // cached keySignature + pub(crate) peer_certificates_verified: bool, + //pub(crate) replay_detector: Vec>, +} + +#[derive(Archive, Deserialize, Serialize, PartialEq, Debug)] +struct SerializedState { + local_epoch: u16, + remote_epoch: u16, + local_random: [u8; HANDSHAKE_RANDOM_LENGTH], + remote_random: [u8; HANDSHAKE_RANDOM_LENGTH], + cipher_suite_id: u16, + master_secret: Vec, + sequence_number: u64, + srtp_protection_profile: u16, + peer_certificates: Vec>, + identity_hint: Vec, + is_client: bool, +} + +#[derive(Archive, Deserialize, Serialize, Debug, Clone)] +pub struct KeyLogData { + pub local_random: Vec, + pub remote_random: Vec, + pub master_secret: Vec, +} + +impl Default for State { + fn default() -> Self { + State { + local_epoch: Arc::new(AtomicU16::new(0)), + remote_epoch: Arc::new(AtomicU16::new(0)), + local_sequence_number: Arc::new(Mutex::new(vec![])), + local_random: HandshakeRandom::default(), + remote_random: HandshakeRandom::default(), + master_secret: vec![], + cipher_suite: Arc::new(Mutex::new(None)), // nil if a cipher_suite hasn't been chosen + + srtp_protection_profile: SrtpProtectionProfile::Unsupported, // Negotiated srtp_protection_profile + peer_certificates: vec![], + identity_hint: vec![], + + is_client: false, + + pre_master_secret: vec![], + extended_master_secret: false, + + named_curve: NamedCurve::Unsupported, + local_keypair: None, + cookie: vec![], + handshake_send_sequence: 0, + handshake_recv_sequence: 0, + server_name: "".to_string(), + remote_requested_certificate: false, // Did we get a CertificateRequest + local_certificates_verify: vec![], // cache CertificateVerify + local_verify_data: vec![], // cached VerifyData + local_key_signature: vec![], // cached keySignature + peer_certificates_verified: false, + //replay_detector: vec![], + } + } +} + +impl State { + pub(crate) async fn clone(&self) -> Self { + let mut state = State::default(); + + if let Ok(serialized) = self.serialize().await { + let _ = state.deserialize(&serialized).await; + } + + state + } + + async fn serialize(&self) -> Result { + let mut local_rand = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(local_rand.as_mut()); + self.local_random.marshal(&mut writer)?; + } + let mut remote_rand = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(remote_rand.as_mut()); + self.remote_random.marshal(&mut writer)?; + } + + let mut local_random = [0u8; HANDSHAKE_RANDOM_LENGTH]; + let mut remote_random = [0u8; HANDSHAKE_RANDOM_LENGTH]; + + local_random.copy_from_slice(&local_rand); + remote_random.copy_from_slice(&remote_rand); + + let local_epoch = self.local_epoch.load(Ordering::SeqCst); + let remote_epoch = self.remote_epoch.load(Ordering::SeqCst); + let sequence_number = { + let lsn = self.local_sequence_number.lock().await; + lsn[local_epoch as usize] + }; + let cipher_suite_id = { + let cipher_suite = self.cipher_suite.lock().await; + match &*cipher_suite { + Some(cipher_suite) => cipher_suite.id() as u16, + None => return Err(Error::ErrCipherSuiteUnset), + } + }; + + Ok(SerializedState { + local_epoch, + remote_epoch, + local_random, + remote_random, + cipher_suite_id, + master_secret: self.master_secret.clone(), + sequence_number, + srtp_protection_profile: self.srtp_protection_profile as u16, + peer_certificates: self.peer_certificates.clone(), + identity_hint: self.identity_hint.clone(), + is_client: self.is_client, + }) + } + + async fn deserialize(&mut self, serialized: &SerializedState) -> Result<()> { + // Set epoch values + self.local_epoch + .store(serialized.local_epoch, Ordering::SeqCst); + self.remote_epoch + .store(serialized.remote_epoch, Ordering::SeqCst); + { + let mut lsn = self.local_sequence_number.lock().await; + while lsn.len() <= serialized.local_epoch as usize { + lsn.push(0); + } + lsn[serialized.local_epoch as usize] = serialized.sequence_number; + } + + // Set random values + let mut reader = Cursor::new(&serialized.local_random); + self.local_random = HandshakeRandom::unmarshal(&mut reader)?; + + let mut reader = Cursor::new(&serialized.remote_random); + self.remote_random = HandshakeRandom::unmarshal(&mut reader)?; + + self.is_client = serialized.is_client; + + // Set master secret + self.master_secret.clone_from(&serialized.master_secret); + + // Set cipher suite + self.cipher_suite = Arc::new(Mutex::new(Some(cipher_suite_for_id( + serialized.cipher_suite_id.into(), + )?))); + + self.srtp_protection_profile = serialized.srtp_protection_profile.into(); + + // Set remote certificate + self.peer_certificates + .clone_from(&serialized.peer_certificates); + self.identity_hint.clone_from(&serialized.identity_hint); + + Ok(()) + } + + pub async fn init_cipher_suite(&mut self) -> Result<()> { + let mut cipher_suite = self.cipher_suite.lock().await; + if let Some(cipher_suite) = &mut *cipher_suite { + if cipher_suite.is_initialized() { + return Ok(()); + } + + let mut local_random = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(local_random.as_mut()); + self.local_random.marshal(&mut writer)?; + } + let mut remote_random = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(remote_random.as_mut()); + self.remote_random.marshal(&mut writer)?; + } + + if self.is_client { + cipher_suite.init(&self.master_secret, &local_random, &remote_random, true) + } else { + cipher_suite.init(&self.master_secret, &remote_random, &local_random, false) + } + } else { + Err(Error::ErrCipherSuiteUnset) + } + } + + // marshal_binary is a binary.BinaryMarshaler.marshal_binary implementation + pub async fn marshal_binary(&self) -> Result> { + let serialized = self.serialize().await?; + + match rkyv::to_bytes::(&serialized).map(Vec::from) { + Ok(enc) => Ok(enc), + Err(err) => Err(Error::Other(err.to_string())), + } + } + + // unmarshal_binary is a binary.BinaryUnmarshaler.unmarshal_binary implementation + pub async fn unmarshal_binary(&mut self, data: &[u8]) -> Result<()> { + let serialized: SerializedState = + match rkyv::access::(data) + .and_then(rkyv::deserialize) + { + Ok(dec) => dec, + Err(err) => return Err(Error::Other(err.to_string())), + }; + self.deserialize(&serialized).await?; + self.init_cipher_suite().await?; + + Ok(()) + } + + /// key_log_data returns the key log data for the current state. + pub fn key_log_data(&self) -> Result { + let mut local_random = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(local_random.as_mut()); + self.local_random.marshal(&mut writer)?; + } + let mut remote_random = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(remote_random.as_mut()); + self.remote_random.marshal(&mut writer)?; + } + + Ok(KeyLogData { + local_random, + remote_random, + master_secret: self.master_secret.clone(), + }) + } +} + +#[async_trait] +impl KeyingMaterialExporter for State { + /// export_keying_material returns length bytes of exported key material in a new + /// slice as defined in RFC 5705. + /// This allows protocols to use DTLS for key establishment, but + /// then use some of the keying material for their own purposes + async fn export_keying_material( + &self, + label: &str, + context: &[u8], + length: usize, + ) -> std::result::Result, KeyingMaterialExporterError> { + use KeyingMaterialExporterError::*; + + if self.local_epoch.load(Ordering::SeqCst) == 0 { + return Err(HandshakeInProgress); + } else if !context.is_empty() { + return Err(ContextUnsupported); + } else if INVALID_KEYING_LABELS.contains(&label) { + return Err(ReservedExportKeyingMaterial); + } + + let mut local_random = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(local_random.as_mut()); + self.local_random.marshal(&mut writer)?; + } + let mut remote_random = vec![]; + { + let mut writer = BufWriter::<&mut Vec>::new(remote_random.as_mut()); + self.remote_random.marshal(&mut writer)?; + } + + let mut seed = label.as_bytes().to_vec(); + if self.is_client { + seed.extend_from_slice(&local_random); + seed.extend_from_slice(&remote_random); + } else { + seed.extend_from_slice(&remote_random); + seed.extend_from_slice(&local_random); + } + + let cipher_suite = self.cipher_suite.lock().await; + if let Some(cipher_suite) = &*cipher_suite { + match prf_p_hash(&self.master_secret, &seed, length, cipher_suite.hash_func()) { + Ok(v) => Ok(v), + Err(err) => Err(Hash(err.to_string())), + } + } else { + Err(CipherSuiteUnset) + } + } +}