From 59c578cf04f96e02871c509d9c64a3d26a6467a4 Mon Sep 17 00:00:00 2001 From: Aron Wieck Date: Thu, 9 Aug 2018 15:37:23 +0200 Subject: [PATCH] Add methods for DTLS/SRTP key handshake --- openssl-sys/src/lib.rs | 16 +++++ openssl-sys/src/libressl/mod.rs | 11 +++ openssl-sys/src/openssl/v10x.rs | 12 ++++ openssl-sys/src/openssl/v110.rs | 8 +++ openssl/src/lib.rs | 1 + openssl/src/srtp.rs | 54 +++++++++++++++ openssl/src/ssl/mod.rs | 95 ++++++++++++++++++++++++++ openssl/src/ssl/test.rs | 114 ++++++++++++++++++++++++++++++++ 8 files changed, 311 insertions(+) create mode 100644 openssl/src/srtp.rs diff --git a/openssl-sys/src/lib.rs b/openssl-sys/src/lib.rs index 8002ab21..eeb664d5 100644 --- a/openssl-sys/src/lib.rs +++ b/openssl-sys/src/lib.rs @@ -184,6 +184,22 @@ pub struct ERR_STRING_DATA { pub string: *const c_char, } +pub const SRTP_AES128_CM_SHA1_80: c_ulong = 0x0001; +pub const SRTP_AES128_CM_SHA1_32: c_ulong = 0x0002; +pub const SRTP_AES128_F8_SHA1_80: c_ulong = 0x0003; +pub const SRTP_AES128_F8_SHA1_32: c_ulong = 0x0004; +pub const SRTP_NULL_SHA1_80: c_ulong = 0x0005; +pub const SRTP_NULL_SHA1_32: c_ulong = 0x0006; + +#[repr(C)] +pub struct SRTP_PROTECTION_PROFILE { + pub name: *const c_char, + pub id: c_ulong, +} + +/// fake free method, since SRTP_PROTECTION_PROFILE is static +pub unsafe fn SRTP_PROTECTION_PROFILE_free(_profile: *mut SRTP_PROTECTION_PROFILE) {} + pub type SHA_LONG = c_uint; pub type SHA_LONG64 = u64; diff --git a/openssl-sys/src/libressl/mod.rs b/openssl-sys/src/libressl/mod.rs index b9732cdd..6de87eba 100644 --- a/openssl-sys/src/libressl/mod.rs +++ b/openssl-sys/src/libressl/mod.rs @@ -10,6 +10,7 @@ pub use libressl::v250::*; pub use libressl::v251::*; #[cfg(libressl273)] pub use libressl::v273::*; +use SRTP_PROTECTION_PROFILE; #[cfg(not(libressl251))] mod v250; @@ -62,6 +63,11 @@ pub struct stack_st_SSL_CIPHER { pub struct stack_st_OPENSSL_STRING { pub stack: _STACK, } +#[repr(C)] +pub struct stack_st_SRTP_PROTECTION_PROFILE { + pub stack: _STACK, +} + #[repr(C)] pub struct _STACK { @@ -633,4 +639,9 @@ extern "C" { pub fn SSLeay() -> c_ulong; pub fn SSLeay_version(key: c_int) -> *const c_char; + + pub fn SSL_set_tlsext_use_srtp(ssl: *mut ::SSL, profiles: *const c_char) -> c_int; + pub fn SSL_CTX_set_tlsext_use_srtp(ctx: *mut ::SSL_CTX, profiles: *const c_char) -> c_int; + pub fn SSL_get_srtp_profiles(ssl: *mut ::SSL) -> *mut stack_st_SRTP_PROTECTION_PROFILE; + pub fn SSL_get_selected_srtp_profile(ssl: *mut ::SSL) -> *mut SRTP_PROTECTION_PROFILE; } diff --git a/openssl-sys/src/openssl/v10x.rs b/openssl-sys/src/openssl/v10x.rs index 92ad295c..6816f748 100644 --- a/openssl-sys/src/openssl/v10x.rs +++ b/openssl-sys/src/openssl/v10x.rs @@ -4,6 +4,7 @@ use std::process; use std::ptr; use std::sync::{Mutex, MutexGuard}; use std::sync::{Once, ONCE_INIT}; +use SRTP_PROTECTION_PROFILE; #[cfg(ossl102)] use libc::time_t; @@ -54,6 +55,12 @@ pub struct stack_st_OPENSSL_STRING { pub stack: _STACK, } + +#[repr(C)] +pub struct stack_st_SRTP_PROTECTION_PROFILE { + pub stack: _STACK, +} + #[repr(C)] pub struct _STACK { pub num: c_int, @@ -1002,4 +1009,9 @@ extern "C" { #[cfg(ossl102)] pub fn SSL_extension_supported(ext_type: c_uint) -> c_int; + + pub fn SSL_set_tlsext_use_srtp(ssl: *mut ::SSL, profiles: *const c_char) -> c_int; + pub fn SSL_CTX_set_tlsext_use_srtp(ctx: *mut ::SSL_CTX, profiles: *const c_char) -> c_int; + pub fn SSL_get_srtp_profiles(ssl: *mut ::SSL) -> *mut stack_st_SRTP_PROTECTION_PROFILE; + pub fn SSL_get_selected_srtp_profile(ssl: *mut ::SSL) -> *mut SRTP_PROTECTION_PROFILE; } diff --git a/openssl-sys/src/openssl/v110.rs b/openssl-sys/src/openssl/v110.rs index 3c633491..b3bca4a2 100644 --- a/openssl-sys/src/openssl/v110.rs +++ b/openssl-sys/src/openssl/v110.rs @@ -1,6 +1,7 @@ use libc::{c_char, c_int, c_long, c_uchar, c_uint, c_ulong, c_void, size_t}; use std::ptr; use std::sync::{Once, ONCE_INIT}; +use SRTP_PROTECTION_PROFILE; pub enum BIGNUM {} pub enum BIO {} @@ -27,6 +28,7 @@ pub enum stack_st_X509 {} pub enum stack_st_X509_NAME {} pub enum stack_st_X509_ATTRIBUTE {} pub enum stack_st_X509_EXTENSION {} +pub enum stack_st_SRTP_PROTECTION_PROFILE {} pub enum stack_st_SSL_CIPHER {} pub enum OPENSSL_INIT_SETTINGS {} pub enum X509 {} @@ -140,6 +142,7 @@ pub unsafe fn SSL_get_max_proto_version(s: *mut ::SSL) -> c_int { ::SSL_ctrl(s, SSL_CTRL_GET_MAX_PROTO_VERSION, 0, ptr::null_mut()) as c_int } + extern "C" { pub fn BIO_new(type_: *const BIO_METHOD) -> *mut BIO; pub fn BIO_s_file() -> *const BIO_METHOD; @@ -392,4 +395,9 @@ extern "C" { pub fn SSL_CIPHER_get_cipher_nid(c: *const ::SSL_CIPHER) -> c_int; pub fn SSL_CIPHER_get_digest_nid(c: *const ::SSL_CIPHER) -> c_int; + + pub fn SSL_set_tlsext_use_srtp(ssl: *mut ::SSL, profiles: *const c_char) -> c_int; + pub fn SSL_CTX_set_tlsext_use_srtp(ctx: *mut ::SSL_CTX, profiles: *const c_char) -> c_int; + pub fn SSL_get_srtp_profiles(ssl: *mut ::SSL) -> *mut stack_st_SRTP_PROTECTION_PROFILE; + pub fn SSL_get_selected_srtp_profile(ssl: *mut ::SSL) -> *mut SRTP_PROTECTION_PROFILE; } diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index d74ce65f..e6455482 100644 --- a/openssl/src/lib.rs +++ b/openssl/src/lib.rs @@ -57,6 +57,7 @@ pub mod rand; pub mod rsa; pub mod sha; pub mod sign; +pub mod srtp; pub mod ssl; pub mod stack; pub mod string; diff --git a/openssl/src/srtp.rs b/openssl/src/srtp.rs new file mode 100644 index 00000000..136ddbd2 --- /dev/null +++ b/openssl/src/srtp.rs @@ -0,0 +1,54 @@ +use ffi; +use foreign_types::ForeignTypeRef; +use libc::c_ulong; +use stack::Stackable; +use std::ffi::CStr; +use std::str; + +#[allow(unused_unsafe)] +foreign_type_and_impl_send_sync! { + type CType = ffi::SRTP_PROTECTION_PROFILE; + fn drop = ffi::SRTP_PROTECTION_PROFILE_free; + + pub struct SrtpProtectionProfile; + /// Reference to `SrtpProtectionProfile`. + pub struct SrtpProtectionProfileRef; +} + +impl Stackable for SrtpProtectionProfile { + type StackType = ffi::stack_st_SRTP_PROTECTION_PROFILE; +} + + +impl SrtpProtectionProfileRef { + pub fn id(&self) -> SrtpProfileId { + SrtpProfileId::from_raw(unsafe { (*self.as_ptr()).id }) + } + pub fn name(&self) -> &'static str { + unsafe { CStr::from_ptr((*self.as_ptr()).name as *const _) }.to_str().expect("should be UTF-8") + } +} + + +/// type of SRTP profile to use. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct SrtpProfileId(c_ulong); + +impl SrtpProfileId { + /// Creates a `SrtpProfileId` from an integer representation. + pub fn from_raw(value: c_ulong) -> SrtpProfileId { + SrtpProfileId(value) + } + + /// Returns the integer representation of `SrtpProfileId`. + pub fn as_raw(&self) -> c_ulong { + self.0 + } + + pub const SRTP_AES128_CM_SHA1_80: SrtpProfileId = SrtpProfileId(ffi::SRTP_AES128_CM_SHA1_80); + pub const SRTP_AES128_CM_SHA1_32: SrtpProfileId = SrtpProfileId(ffi::SRTP_AES128_CM_SHA1_32); + pub const SRTP_AES128_F8_SHA1_80: SrtpProfileId = SrtpProfileId(ffi::SRTP_AES128_F8_SHA1_80); + pub const SRTP_AES128_F8_SHA1_32: SrtpProfileId = SrtpProfileId(ffi::SRTP_AES128_F8_SHA1_32); + pub const SRTP_NULL_SHA1_80: SrtpProfileId = SrtpProfileId(ffi::SRTP_NULL_SHA1_80); + pub const SRTP_NULL_SHA1_32: SrtpProfileId = SrtpProfileId(ffi::SRTP_NULL_SHA1_32); +} diff --git a/openssl/src/ssl/mod.rs b/openssl/src/ssl/mod.rs index 7732765a..c6305dcd 100644 --- a/openssl/src/ssl/mod.rs +++ b/openssl/src/ssl/mod.rs @@ -88,6 +88,7 @@ use hash::MessageDigest; #[cfg(ossl110)] use nid::Nid; use pkey::{HasPrivate, PKeyRef, Params, Private}; +use srtp::{SrtpProtectionProfile, SrtpProtectionProfileRef}; use ssl::bio::BioMethod; use ssl::callbacks::*; use ssl::error::InnerError; @@ -1156,6 +1157,28 @@ impl SslContextBuilder { } } + /// Enables the DTLS extension "use_srtp" as defined in RFC5764. + /// + /// This corresponds to [`SSL_CTX_set_tlsext_use_srtp`]. + /// + /// [`SSL_CTX_set_tlsext_use_srtp`]: https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_tlsext_use_srtp.html + pub fn set_tlsext_use_srtp(&mut self, protocols: &str) -> Result<(), ErrorStack> { + unsafe { + let cstr = CString::new(protocols).unwrap(); + + let r = ffi::SSL_CTX_set_tlsext_use_srtp( + self.as_ptr(), + cstr.as_ptr(), + ); + // fun fact, set_tlsext_use_srtp has a reversed return code D: + if r == 0 { + Ok(()) + } else { + Err(ErrorStack::get()) + } + } + } + /// Sets the callback used by a server to select a protocol for Application Layer Protocol /// Negotiation (ALPN). /// @@ -2455,6 +2478,78 @@ impl SslRef { } } + + /// Enables the DTLS extension "use_srtp" as defined in RFC5764. + /// + /// This corresponds to [`SSL_set_tlsext_use_srtp`]. + /// + /// [`SSL_set_tlsext_use_srtp`]: https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_tlsext_use_srtp.html + pub fn set_tlsext_use_srtp(&mut self, protocols: &str) -> Result<(), ErrorStack> { + unsafe { + let cstr = CString::new(protocols).unwrap(); + + let r = ffi::SSL_set_tlsext_use_srtp( + self.as_ptr(), + cstr.as_ptr(), + ); + // fun fact, set_tlsext_use_srtp has a reversed return code D: + if r == 0 { + Ok(()) + } else { + Err(ErrorStack::get()) + } + } + } + + /// Gets all SRTP profiles that are enabled for handshake via set_tlsext_use_srtp + /// + /// DTLS extension "use_srtp" as defined in RFC5764 has to be enabled. + /// + /// This corresponds to [`SSL_get_srtp_profiles`]. + /// + /// [`SSL_get_srtp_profiles`]: https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_tlsext_use_srtp.html + pub fn get_srtp_profiles(&self) -> Option<&StackRef> { + unsafe { + let chain = ffi::SSL_get_srtp_profiles(self.as_ptr()); + + if chain.is_null() { + None + } else { + Some(StackRef::from_ptr(chain)) + } + } + } + /// Gets the SRTP profile selected by handshake. + /// + /// DTLS extension "use_srtp" as defined in RFC5764 has to be enabled. + /// + /// This corresponds to [`SSL_get_selected_srtp_profile`]. + /// + /// [`SSL_get_selected_srtp_profile`]: https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_tlsext_use_srtp.html + pub fn selected_srtp_profile(&self) -> Option<&SrtpProtectionProfileRef> { + unsafe { + let profile = ffi::SSL_get_selected_srtp_profile(self.as_ptr()); + + if profile.is_null() { + None + } else { + Some(SrtpProtectionProfileRef::from_ptr(profile as *mut _)) + } + } + } + + /// Derives keying material for SRTP usage. + /// + /// DTLS extension "use_srtp" as defined in RFC5764 has to be enabled. + /// + /// This corresponds to [`SSL_export_keying_material`] with a label of "EXTRACTOR-dtls_srtp". + /// + /// [`SSL_export_keying_material`]: https://www.openssl.org/docs/manmaster/man3/SSL_export_keying_material.html + /// [`SSL_CTX_set_tlsext_use_srtp`]: https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_tlsext_use_srtp.html + pub fn export_srtp_keying_material(&self, out: &mut [u8]) -> Result<(), ErrorStack> { + self.export_keying_material(out, "EXTRACTOR-dtls_srtp", None) + } + /// Returns the number of bytes remaining in the currently processed TLS record. /// /// If this is greater than 0, the next call to `read` will not call down to the underlying diff --git a/openssl/src/ssl/test.rs b/openssl/src/ssl/test.rs index a8d69295..ad800e08 100644 --- a/openssl/src/ssl/test.rs +++ b/openssl/src/ssl/test.rs @@ -21,6 +21,7 @@ use pkey::PKey; use ssl; #[cfg(any(ossl110, ossl111, libressl261))] use ssl::SslVersion; +use srtp::SrtpProfileId; use ssl::{ Error, HandshakeError, MidHandshakeSslStream, ShutdownResult, ShutdownState, Ssl, SslAcceptor, SslConnector, SslContext, SslFiletype, SslMethod, SslSessionCacheMode, SslStream, @@ -546,6 +547,119 @@ fn test_connect_with_alpn_successful_single_match() { assert_eq!(b"spdy/3.1", stream.ssl().selected_alpn_protocol().unwrap()); } +/// Tests that when both the client as well as the server use SRTP and their +/// lists of supported protocols have an overlap -- with only ONE protocol +/// being valid for both. +#[test] +fn test_connect_with_srtp_ctx() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let addr = listener.local_addr().unwrap(); + + + let guard = thread::spawn(move || { + let stream = listener.accept().unwrap().0; + let mut ctx = SslContext::builder(SslMethod::dtls()).unwrap(); + ctx.set_tlsext_use_srtp("SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32").unwrap(); + ctx.set_certificate_file(&Path::new("test/cert.pem"), SslFiletype::PEM) + .unwrap(); + ctx.set_private_key_file(&Path::new("test/key.pem"), SslFiletype::PEM) + .unwrap(); + let ssl = Ssl::new(&ctx.build()).unwrap(); + let mut stream = ssl.accept(stream).unwrap(); + + let mut buf = [0; 60]; + stream + .ssl() + .export_srtp_keying_material(&mut buf) + .unwrap(); + + stream.write_all(&[0]).unwrap(); + + buf + }); + + let stream = TcpStream::connect(addr).unwrap(); + let mut ctx = SslContext::builder(SslMethod::dtls()).unwrap(); + ctx.set_tlsext_use_srtp("SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32").unwrap(); + let ssl = Ssl::new(&ctx.build()).unwrap(); + let mut stream = ssl.connect(stream).unwrap(); + + let mut buf = [1; 60]; + { + let srtp_profile = stream.ssl().selected_srtp_profile().unwrap(); + assert_eq!("SRTP_AES128_CM_SHA1_80", srtp_profile.name()); + assert_eq!(SrtpProfileId::SRTP_AES128_CM_SHA1_80, srtp_profile.id()); + } + stream.ssl().export_srtp_keying_material(&mut buf).expect("extract"); + + stream.read_exact(&mut [0]).unwrap(); + + let buf2 = guard.join().unwrap(); + + assert_eq!(buf[..], buf2[..]); +} + +/// Tests that when both the client as well as the server use SRTP and their +/// lists of supported protocols have an overlap -- with only ONE protocol +/// being valid for both. +#[test] +fn test_connect_with_srtp_ssl() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let addr = listener.local_addr().unwrap(); + + + let guard = thread::spawn(move || { + let stream = listener.accept().unwrap().0; + let mut ctx = SslContext::builder(SslMethod::dtls()).unwrap(); + ctx.set_certificate_file(&Path::new("test/cert.pem"), SslFiletype::PEM) + .unwrap(); + ctx.set_private_key_file(&Path::new("test/key.pem"), SslFiletype::PEM) + .unwrap(); + let mut ssl = Ssl::new(&ctx.build()).unwrap(); + ssl.set_tlsext_use_srtp("SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32").unwrap(); + let mut profilenames = String::new(); + for profile in ssl.get_srtp_profiles().unwrap() { + if profilenames.len()>0 { + profilenames.push(':'); + } + profilenames += profile.name(); + + } + assert_eq!("SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32", profilenames); + let mut stream = ssl.accept(stream).unwrap(); + + let mut buf = [0; 60]; + stream + .ssl() + .export_srtp_keying_material(&mut buf) + .unwrap(); + + stream.write_all(&[0]).unwrap(); + + buf + }); + + let stream = TcpStream::connect(addr).unwrap(); + let ctx = SslContext::builder(SslMethod::dtls()).unwrap(); + let mut ssl = Ssl::new(&ctx.build()).unwrap(); + ssl.set_tlsext_use_srtp("SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32").unwrap(); + let mut stream = ssl.connect(stream).unwrap(); + + let mut buf = [1; 60]; + { + let srtp_profile = stream.ssl().selected_srtp_profile().unwrap(); + assert_eq!("SRTP_AES128_CM_SHA1_80", srtp_profile.name()); + assert_eq!(SrtpProfileId::SRTP_AES128_CM_SHA1_80, srtp_profile.id()); + } + stream.ssl().export_srtp_keying_material(&mut buf).expect("extract"); + + stream.read_exact(&mut [0]).unwrap(); + + let buf2 = guard.join().unwrap(); + + assert_eq!(buf[..], buf2[..]); +} + /// Tests that when the `SslStream` is created as a server stream, the protocols /// are correctly advertised to the client. #[test]