Add methods for DTLS/SRTP key handshake

This commit is contained in:
Aron Wieck 2018-08-09 15:37:23 +02:00 committed by Aron Wieck
parent f42777b17c
commit 59c578cf04
8 changed files with 311 additions and 0 deletions

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

54
openssl/src/srtp.rs Normal file
View File

@ -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);
}

View File

@ -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<SrtpProtectionProfile>> {
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

View File

@ -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]