From 664600eadff8a0388bc9ab2544b382e56e4fae9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Sch=C3=B6lling?= Date: Tue, 10 Mar 2015 14:31:54 +0100 Subject: [PATCH] Add DTLSv1 and DTLSv1.2 support --- openssl-sys/Cargo.toml | 1 + openssl-sys/src/lib.rs | 4 + openssl/Cargo.toml | 1 + openssl/src/lib.rs | 2 + openssl/src/ssl/connected_socket.rs | 19 +-- openssl/src/ssl/mod.rs | 47 +++++- openssl/src/ssl/tests.rs | 215 +++++++++++++++++----------- 7 files changed, 195 insertions(+), 94 deletions(-) diff --git a/openssl-sys/Cargo.toml b/openssl-sys/Cargo.toml index 7b06dfed..334216e9 100644 --- a/openssl-sys/Cargo.toml +++ b/openssl-sys/Cargo.toml @@ -15,6 +15,7 @@ build = "build.rs" tlsv1_2 = [] tlsv1_1 = [] dtlsv1 = [] +dtlsv1_2 = [] sslv2 = [] aes_xts = [] npn = [] diff --git a/openssl-sys/src/lib.rs b/openssl-sys/src/lib.rs index c782f816..89b03e7d 100644 --- a/openssl-sys/src/lib.rs +++ b/openssl-sys/src/lib.rs @@ -134,6 +134,7 @@ pub const SSL_CTRL_CLEAR_OPTIONS: c_int = 77; pub const SSL_CTRL_SET_TLSEXT_HOSTNAME: c_int = 55; pub const SSL_CTRL_EXTRA_CHAIN_CERT: c_int = 14; +pub const SSL_CTRL_SET_READ_AHEAD: c_int = 41; pub const SSL_ERROR_NONE: c_int = 0; pub const SSL_ERROR_SSL: c_int = 1; pub const SSL_ERROR_SYSCALL: c_int = 5; @@ -484,6 +485,8 @@ extern "C" { pub fn TLSv1_2_method() -> *const SSL_METHOD; #[cfg(feature = "dtlsv1")] pub fn DTLSv1_method() -> *const SSL_METHOD; + #[cfg(feature = "dtlsv1_2")] + pub fn DTLSv1_2_method() -> *const SSL_METHOD; pub fn SSLv23_method() -> *const SSL_METHOD; pub fn SSL_new(ctx: *mut SSL_CTX) -> *mut SSL; @@ -507,6 +510,7 @@ extern "C" { pub fn SSL_CTX_new(method: *const SSL_METHOD) -> *mut SSL_CTX; pub fn SSL_CTX_free(ctx: *mut SSL_CTX); + pub fn SSL_CTX_ctrl(ctx: *mut SSL_CTX, cmd: c_int, mode: c_long, parg: *mut c_void) -> c_long; pub fn SSL_CTX_set_verify(ctx: *mut SSL_CTX, mode: c_int, verify_callback: Option c_int>); pub fn SSL_CTX_set_verify_depth(ctx: *mut SSL_CTX, depth: c_int); diff --git a/openssl/Cargo.toml b/openssl/Cargo.toml index ca3bd417..796beef2 100644 --- a/openssl/Cargo.toml +++ b/openssl/Cargo.toml @@ -13,6 +13,7 @@ keywords = ["crypto", "tls", "ssl", "dtls"] tlsv1_2 = ["openssl-sys/tlsv1_2"] tlsv1_1 = ["openssl-sys/tlsv1_1"] dtlsv1 = ["openssl-sys/dtlsv1"] +dtlsv1_2 = ["openssl-sys/dtlsv1_2"] sslv2 = ["openssl-sys/sslv2"] aes_xts = ["openssl-sys/aes_xts"] npn = ["openssl-sys/npn"] diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index 2ceafcd7..fae9aa12 100644 --- a/openssl/src/lib.rs +++ b/openssl/src/lib.rs @@ -18,3 +18,5 @@ pub mod bio; pub mod crypto; pub mod ssl; pub mod x509; +#[macro_use] +extern crate log; diff --git a/openssl/src/ssl/connected_socket.rs b/openssl/src/ssl/connected_socket.rs index 1ae5fc8d..55788465 100644 --- a/openssl/src/ssl/connected_socket.rs +++ b/openssl/src/ssl/connected_socket.rs @@ -81,7 +81,6 @@ impl IntoSockaddrIn for SocketAddr { if res == 1 { Ok(SockaddrIn::V4(addr)) } else { - warn!("inet_pton() failed for IPv4: {}", ip); Err(Error::new(ErrorKind::Other, "calling inet_pton() for ipv4", None)) } @@ -158,12 +157,10 @@ impl Read for ConnectedSocket { let flags = 0; let ptr = buf.as_mut_ptr() as *mut c_void; - debug!("recv'ing..."); let len = unsafe { recv(self.as_raw_fd(), ptr, buf.len() as u64, flags) }; - debug!("recv'ed len={:?}", len); match len { -1 => { match errno() { @@ -184,14 +181,12 @@ impl Write for ConnectedSocket { let flags = 0; let ptr = buf.as_ptr() as *const c_void; - debug!("sending {:?}", buf.len()); let res = unsafe { send(self.as_raw_fd(), ptr, buf.len() as u64, flags) }; if res == (buf.len() as i64) { Ok(res as usize) } else { - warn!("send() found {}, expected {}", res, buf.len()); Err(Error::new(ErrorKind::Other, "send() failed", Some(os::error_string(os::errno() as i32)))) } } @@ -223,8 +218,8 @@ impl SetTimeout for S { fn connect4_works() { let socket1 = UdpSocket::bind("127.0.0.1:34200").unwrap(); let socket2 = UdpSocket::bind("127.0.0.1:34201").unwrap(); - let conn1 = socket1.connect("127.0.0.1:34200").unwrap(); - let conn2 = socket2.connect("127.0.0.1:34201").unwrap(); + socket1.connect("127.0.0.1:34200").unwrap(); + socket2.connect("127.0.0.1:34201").unwrap(); } #[test] @@ -273,26 +268,26 @@ fn sendrecv_respects_packet_borders() { fn connect6_works() { let socket1 = UdpSocket::bind("::1:34200").unwrap(); let socket2 = UdpSocket::bind("::1:34201").unwrap(); - let conn1 = socket1.connect("::1:34200").unwrap(); - let conn2 = socket2.connect("::1:34201").unwrap(); + socket1.connect("::1:34200").unwrap(); + socket2.connect("::1:34201").unwrap(); } #[test] -#[should_fail] +#[should_panic] fn detect_invalid_ipv4() { let s = UdpSocket::bind("127.0.0.1:34300").unwrap(); s.connect("254.254.254.254:34200").unwrap(); } #[test] -#[should_fail] +#[should_panic] fn detect_invalid_ipv6() { let s = UdpSocket::bind("::1:34300").unwrap(); s.connect("1200::AB00:1234::2552:7777:1313:34300").unwrap(); } #[test] -#[should_fail] +#[should_panic] fn double_bind() { let socket1 = UdpSocket::bind("127.0.0.1:34301").unwrap(); let socket2 = UdpSocket::bind("127.0.0.1:34301").unwrap(); diff --git a/openssl/src/ssl/mod.rs b/openssl/src/ssl/mod.rs index 710a287d..9cf09bc8 100644 --- a/openssl/src/ssl/mod.rs +++ b/openssl/src/ssl/mod.rs @@ -101,6 +101,9 @@ pub enum SslMethod { #[cfg(feature = "dtlsv1")] /// Support DTLSv1 protocol, requires the `dtlsv1` feature. Dtlsv1, + #[cfg(feature = "dtlsv1_2")] + /// Support DTLSv1.2 protocol, requires the `dtlsv1_2` feature. + Dtlsv1_2, } impl SslMethod { @@ -116,9 +119,35 @@ impl SslMethod { #[cfg(feature = "tlsv1_2")] SslMethod::Tlsv1_2 => ffi::TLSv1_2_method(), #[cfg(feature = "dtlsv1")] - SslMethod::Dtlsv1 => ffi::TLSv1_method(), + SslMethod::Dtlsv1 => ffi::DTLSv1_method(), + #[cfg(feature = "dtlsv1_2")] + SslMethod::Dtlsv1_2 => ffi::DTLSv1_2_method(), } } + + #[cfg(feature = "dtlsv1")] + pub fn is_dtlsv1(&self) -> bool { + *self == SslMethod::Dtlsv1 + } + + #[cfg(feature = "dtlsv1_2")] + pub fn is_dtlsv1_2(&self) -> bool { + *self == SslMethod::Dtlsv1 + } + + pub fn is_dtls(&self) -> bool { + self.is_dtlsv1() || self.is_dtlsv1_2() + } + + #[cfg(not(feature = "dtlsv1"))] + pub fn is_dtlsv1(&self) -> bool { + false + } + + #[cfg(not(feature = "dtlsv1_2"))] + pub fn is_dtlsv1_2(&self) -> bool { + false + } } /// Determines the type of certificate verification used @@ -345,7 +374,13 @@ impl SslContext { return Err(SslError::get()); } - Ok(SslContext { ctx: ctx }) + let ctx = SslContext { ctx: ctx }; + + if method.is_dtls() { + ctx.set_read_ahead(); + } + + Ok(ctx) } /// Configures the certificate verification method for new connections. @@ -356,6 +391,7 @@ impl SslContext { mem::transmute(verify)); let f: extern fn(c_int, *mut ffi::X509_STORE_CTX) -> c_int = raw_verify; + ffi::SSL_CTX_set_verify(self.ctx, mode.bits as c_int, Some(f)); } } @@ -376,6 +412,7 @@ impl SslContext { mem::transmute(data)); let f: extern fn(c_int, *mut ffi::X509_STORE_CTX) -> c_int = raw_verify_with_data::; + ffi::SSL_CTX_set_verify(self.ctx, mode.bits as c_int, Some(f)); } } @@ -387,6 +424,12 @@ impl SslContext { } } + pub fn set_read_ahead(&self) { + unsafe { + ffi::SSL_CTX_ctrl(*self.ctx, ffi::SSL_CTRL_SET_READ_AHEAD, 1, ptr::null_mut()); + } + } + #[allow(non_snake_case)] /// Specifies the file that contains trusted CA certificates. pub fn set_CA_file(&mut self, file: &Path) -> Result<(),SslError> { diff --git a/openssl/src/ssl/tests.rs b/openssl/src/ssl/tests.rs index 1da42082..4d78e182 100644 --- a/openssl/src/ssl/tests.rs +++ b/openssl/src/ssl/tests.rs @@ -1,4 +1,5 @@ -use serialize::hex::FromHex; +#![allow(unused_imports)] + use std::net::TcpStream; use std::io; use std::io::prelude::*; @@ -13,6 +14,8 @@ use crypto::hash::Type::{SHA256}; use ssl; use ssl::SslMethod; use ssl::SslMethod::Sslv23; +#[cfg(feature="dtlsv1")] +use ssl::SslMethod::Dtlsv1; use ssl::{SslContext, SslStream, VerifyCallback}; use ssl::SSL_VERIFY_PEER; use x509::X509StoreContext; @@ -21,34 +24,89 @@ use x509::X509FileType; use x509::X509; use crypto::pkey::PKey; +#[cfg(feature="dtlsv1")] +use ssl::connected_socket::Connect; +#[cfg(feature="dtlsv1")] +use std::net::UdpSocket; + const PROTOCOL:SslMethod = Sslv23; +mod udp { + static mut udp_port:u16 = 15410; + + pub fn next_server<'a>() -> String { + unsafe { + udp_port += 1; + format!("127.0.0.1:{}", udp_port) + } + } +} + #[test] fn test_new_ctx() { SslContext::new(PROTOCOL).unwrap(); } -#[test] -fn test_new_sslstream() { - let stream = TcpStream::connect("127.0.0.1:15418").unwrap(); - SslStream::new(&SslContext::new(PROTOCOL).unwrap(), stream).unwrap(); -} +macro_rules! run_test( + ($module:ident, $blk:expr) => ( + #[cfg(test)] + mod $module { + use ssl::tests::udp; + use std::io; + use std::io::prelude::*; + use std::path::Path; + use std::net::UdpSocket; + use std::net::TcpStream; + use ssl::SslMethod::Sslv23; + #[cfg(feature="dtlsv1")] + use ssl; + use ssl::SslMethod::Dtlsv1; + use ssl::{SslContext, SslStream, VerifyCallback}; + use ssl::connected_socket::Connect; + use ssl::SslVerifyMode::SSL_VERIFY_PEER; + use crypto::hash::Type::SHA256; + use x509::X509StoreContext; + use serialize::hex::FromHex; + use std::time::duration::Duration; + + #[test] + fn sslv23() { + let stream = TcpStream::connect("127.0.0.1:15418").unwrap(); + $blk(Sslv23, stream); + } + + #[test] + #[cfg(feature="dtlsv1")] + fn dtlsv1() { + let sock = UdpSocket::bind("127.0.0.1:0").unwrap(); + let stream = sock.connect(udp::next_server().as_slice()).unwrap(); -#[test] -fn test_verify_untrusted() { - let stream = TcpStream::connect("127.0.0.1:15418").unwrap(); - let mut ctx = SslContext::new(PROTOCOL).unwrap(); + $blk(Dtlsv1, stream); + } + } + ); +); + +run_test!(new_ctx, |method, _| { + SslContext::new(method).unwrap(); +}); + +run_test!(new_sslstream, |method, stream| { + SslStream::new(&SslContext::new(method).unwrap(), stream).unwrap(); +}); + +run_test!(verify_untrusted, |method, stream| { + let mut ctx = SslContext::new(method).unwrap(); ctx.set_verify(SSL_VERIFY_PEER, None); + match SslStream::new(&ctx, stream) { Ok(_) => panic!("expected failure"), Err(err) => println!("error {:?}", err) } -} +}); -#[test] -fn test_verify_trusted() { - let stream = TcpStream::connect("127.0.0.1:15418").unwrap(); - let mut ctx = SslContext::new(PROTOCOL).unwrap(); +run_test!(verify_trusted, |method, stream| { + let mut ctx = SslContext::new(method).unwrap(); ctx.set_verify(SSL_VERIFY_PEER, None); match ctx.set_CA_file(&Path::new("test/cert.pem")) { @@ -59,42 +117,39 @@ fn test_verify_trusted() { Ok(_) => (), Err(err) => panic!("Expected success, got {:?}", err) } -} +}); -#[test] -fn test_verify_untrusted_callback_override_ok() { +run_test!(verify_untrusted_callback_override_ok, |method, stream| { fn callback(_preverify_ok: bool, _x509_ctx: &X509StoreContext) -> bool { true } - let stream = TcpStream::connect("127.0.0.1:15418").unwrap(); - let mut ctx = SslContext::new(PROTOCOL).unwrap(); + + let mut ctx = SslContext::new(method).unwrap(); ctx.set_verify(SSL_VERIFY_PEER, Some(callback as VerifyCallback)); match SslStream::new(&ctx, stream) { Ok(_) => (), Err(err) => panic!("Expected success, got {:?}", err) } -} +}); -#[test] -fn test_verify_untrusted_callback_override_bad() { +run_test!(verify_untrusted_callback_override_bad, |method, stream| { fn callback(_preverify_ok: bool, _x509_ctx: &X509StoreContext) -> bool { false } - let stream = TcpStream::connect("127.0.0.1:15418").unwrap(); - let mut ctx = SslContext::new(PROTOCOL).unwrap(); + + let mut ctx = SslContext::new(method).unwrap(); ctx.set_verify(SSL_VERIFY_PEER, Some(callback as VerifyCallback)); assert!(SslStream::new(&ctx, stream).is_err()); -} +}); -#[test] -fn test_verify_trusted_callback_override_ok() { +run_test!(verify_trusted_callback_override_ok, |method, stream| { fn callback(_preverify_ok: bool, _x509_ctx: &X509StoreContext) -> bool { true } - let stream = TcpStream::connect("127.0.0.1:15418").unwrap(); - let mut ctx = SslContext::new(PROTOCOL).unwrap(); + + let mut ctx = SslContext::new(method).unwrap(); ctx.set_verify(SSL_VERIFY_PEER, Some(callback as VerifyCallback)); match ctx.set_CA_file(&Path::new("test/cert.pem")) { @@ -105,15 +160,14 @@ fn test_verify_trusted_callback_override_ok() { Ok(_) => (), Err(err) => panic!("Expected success, got {:?}", err) } -} +}); -#[test] -fn test_verify_trusted_callback_override_bad() { +run_test!(verify_trusted_callback_override_bad, |method, stream| { fn callback(_preverify_ok: bool, _x509_ctx: &X509StoreContext) -> bool { false } - let stream = TcpStream::connect("127.0.0.1:15418").unwrap(); - let mut ctx = SslContext::new(PROTOCOL).unwrap(); + + let mut ctx = SslContext::new(method).unwrap(); ctx.set_verify(SSL_VERIFY_PEER, Some(callback as VerifyCallback)); match ctx.set_CA_file(&Path::new("test/cert.pem")) { @@ -121,29 +175,27 @@ fn test_verify_trusted_callback_override_bad() { Err(err) => panic!("Unexpected error {:?}", err) } assert!(SslStream::new(&ctx, stream).is_err()); -} +}); -#[test] -fn test_verify_callback_load_certs() { +run_test!(verify_callback_load_certs, |method, stream| { fn callback(_preverify_ok: bool, x509_ctx: &X509StoreContext) -> bool { assert!(x509_ctx.get_current_cert().is_some()); true } - let stream = TcpStream::connect("127.0.0.1:15418").unwrap(); - let mut ctx = SslContext::new(PROTOCOL).unwrap(); + + let mut ctx = SslContext::new(method).unwrap(); ctx.set_verify(SSL_VERIFY_PEER, Some(callback as VerifyCallback)); assert!(SslStream::new(&ctx, stream).is_ok()); -} +}); -#[test] -fn test_verify_trusted_get_error_ok() { +run_test!(verify_trusted_get_error_ok, |method, stream| { fn callback(_preverify_ok: bool, x509_ctx: &X509StoreContext) -> bool { assert!(x509_ctx.get_error().is_none()); true } - let stream = TcpStream::connect("127.0.0.1:15418").unwrap(); - let mut ctx = SslContext::new(PROTOCOL).unwrap(); + + let mut ctx = SslContext::new(method).unwrap(); ctx.set_verify(SSL_VERIFY_PEER, Some(callback as VerifyCallback)); match ctx.set_CA_file(&Path::new("test/cert.pem")) { @@ -151,23 +203,21 @@ fn test_verify_trusted_get_error_ok() { Err(err) => panic!("Unexpected error {:?}", err) } assert!(SslStream::new(&ctx, stream).is_ok()); -} +}); -#[test] -fn test_verify_trusted_get_error_err() { +run_test!(verify_trusted_get_error_err, |method, stream| { fn callback(_preverify_ok: bool, x509_ctx: &X509StoreContext) -> bool { assert!(x509_ctx.get_error().is_some()); false } - let stream = TcpStream::connect("127.0.0.1:15418").unwrap(); - let mut ctx = SslContext::new(PROTOCOL).unwrap(); + + let mut ctx = SslContext::new(method).unwrap(); ctx.set_verify(SSL_VERIFY_PEER, Some(callback as VerifyCallback)); assert!(SslStream::new(&ctx, stream).is_err()); -} +}); -#[test] -fn test_verify_callback_data() { +run_test!(verify_callback_data, |method, stream| { fn callback(_preverify_ok: bool, x509_ctx: &X509StoreContext, node_id: &Vec) -> bool { let cert = x509_ctx.get_current_cert(); match cert { @@ -178,8 +228,7 @@ fn test_verify_callback_data() { } } } - let stream = TcpStream::connect("127.0.0.1:15418").unwrap(); - let mut ctx = SslContext::new(PROTOCOL).unwrap(); + let mut ctx = SslContext::new(method).unwrap(); // Node id was generated as SHA256 hash of certificate "test/cert.pem" // in DER format. @@ -194,7 +243,7 @@ fn test_verify_callback_data() { Ok(_) => (), Err(err) => panic!("Expected success, got {:?}", err) } -} +}); #[test] fn test_set_certificate_and_private_key() { @@ -217,51 +266,46 @@ fn test_set_certificate_and_private_key() { assert!(ctx.check_private_key().is_ok()); } -#[test] -fn test_get_ctx_options() { - let mut ctx = SslContext::new(Sslv23).unwrap(); +run_test!(get_ctx_options, |method, _| { + let mut ctx = SslContext::new(method).unwrap(); ctx.get_options(); -} +}); -#[test] -fn test_set_ctx_options() { - let mut ctx = SslContext::new(Sslv23).unwrap(); +run_test!(set_ctx_options, |method, _| { + let mut ctx = SslContext::new(method).unwrap(); let opts = ctx.set_options(ssl::SSL_OP_NO_TICKET); assert!(opts.contains(ssl::SSL_OP_NO_TICKET)); assert!(!opts.contains(ssl::SSL_OP_CISCO_ANYCONNECT)); let more_opts = ctx.set_options(ssl::SSL_OP_CISCO_ANYCONNECT); assert!(more_opts.contains(ssl::SSL_OP_NO_TICKET)); assert!(more_opts.contains(ssl::SSL_OP_CISCO_ANYCONNECT)); -} +}); -#[test] -fn test_clear_ctx_options() { - let mut ctx = SslContext::new(Sslv23).unwrap(); +run_test!(clear_ctx_options, |method, _| { + let mut ctx = SslContext::new(method).unwrap(); ctx.set_options(ssl::SSL_OP_ALL); let opts = ctx.clear_options(ssl::SSL_OP_ALL); assert!(!opts.contains(ssl::SSL_OP_ALL)); -} +}); -#[test] -fn test_write() { - let stream = TcpStream::connect("127.0.0.1:15418").unwrap(); - let mut stream = SslStream::new(&SslContext::new(PROTOCOL).unwrap(), stream).unwrap(); - stream.write_all("hello".as_bytes()).unwrap(); - stream.flush().unwrap(); - stream.write_all(" there".as_bytes()).unwrap(); - stream.flush().unwrap(); -} +run_test!(write, |method, stream| { + let mut s = SslStream::new(&SslContext::new(method).unwrap(), stream).unwrap(); + s.write_all("hello".as_bytes()).unwrap(); + s.flush().unwrap(); + s.write_all(" there".as_bytes()).unwrap(); + s.flush().unwrap(); +}); #[test] fn test_read() { - let stream = TcpStream::connect("127.0.0.1:15418").unwrap(); - let mut stream = SslStream::new(&SslContext::new(PROTOCOL).unwrap(), stream).unwrap(); + let tcp = TcpStream::connect("127.0.0.1:15418").unwrap(); + let mut stream = SslStream::new(&SslContext::new(Sslv23).unwrap(), tcp).unwrap(); stream.write_all("GET /\r\n\r\n".as_bytes()).unwrap(); stream.flush().unwrap(); - println!("written"); io::copy(&mut stream, &mut io::sink()).ok().expect("read error"); } + /// Tests that connecting with the client using NPN, but the server not does not /// break the existing connection behavior. #[test] @@ -396,3 +440,14 @@ mod dtlsv1 { SslContext::new(PROTOCOL).unwrap(); } } + +#[test] +#[cfg(feature = "dtlsv1")] +fn test_read_dtlsv1() { + let sock = UdpSocket::bind("127.0.0.1:0").unwrap(); + let stream = sock.connect(udp::next_server().as_slice()).unwrap(); + + let mut stream = SslStream::new(&SslContext::new(Dtlsv1).unwrap(), stream).unwrap(); + let mut buf = [0u8;100]; + assert!(stream.read(&mut buf).is_ok()); +}