use std::collections::VecDeque;
use std::io::{self, Read, Write};
use std::time::{Duration, Instant};
use dimpl::SrtpProfile;
use openssl::dh::Dh;
use openssl::ssl::{Ssl, SslContext, SslContextBuilder, SslMethod, SslOptions, SslVerifyMode};
use super::cert::OsslDtlsCert;
use super::io_buf::IoBuffer;
use super::stream::TlsStream;
use super::{CryptoError, DATAGRAM_MTU, DATAGRAM_MTU_WARN, DtlsEvent};
const DTLS_CIPHERS: &str =
"ECDHE+CHACHA20:DHE+CHACHA20:ECDHE+AESGCM:DHE+AESGCM:ECDHE+AES256:DHE+AES256";
pub struct OsslDtlsImpl {
_cert: OsslDtlsCert,
_context: SslContext,
tls: TlsStream<IoBuffer>,
}
impl OsslDtlsImpl {
pub fn new(cert: OsslDtlsCert) -> Result<Self, CryptoError> {
Self::new_with_groups(cert, None)
}
pub fn new_with_groups(
cert: OsslDtlsCert,
groups_list: Option<&str>,
) -> Result<Self, CryptoError> {
let context = dtls_create_ctx_with_groups(&cert, groups_list)?;
let ssl = dtls_ssl_create(&context)?;
Ok(OsslDtlsImpl {
_cert: cert,
_context: context,
tls: TlsStream::new(ssl, IoBuffer::default()),
})
}
}
impl OsslDtlsImpl {
pub fn set_active(&mut self, active: bool) {
self.tls.set_active(active);
}
pub fn is_active(&self) -> Option<bool> {
self.tls.is_active()
}
pub fn handle_receive(
&mut self,
m: &[u8],
o: &mut VecDeque<DtlsEvent>,
) -> Result<(), CryptoError> {
self.tls.inner_mut().set_incoming(m);
if self.handle_handshake(o)? {
return Ok(());
}
let mut buf = vec![0; 2000];
let n = match self.tls.read(&mut buf) {
Ok(v) => v,
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
return Ok(());
}
Err(e) => return Err(e.into()),
};
buf.truncate(n);
o.push_back(DtlsEvent::Data(buf));
Ok(())
}
pub fn poll_datagram(&mut self) -> Option<super::DatagramSend> {
let x = self.tls.inner_mut().pop_outgoing();
if let Some(x) = &x {
if x.len() > DATAGRAM_MTU_WARN {
eprintln!("DTLS above MTU {}: {}", DATAGRAM_MTU_WARN, x.len());
}
}
x
}
pub fn poll_timeout(&mut self, now: Instant) -> Option<Instant> {
self.tls
.is_handshaking()
.then(|| now + Duration::from_millis(500))
}
pub fn handle_input(&mut self, data: &[u8]) -> Result<(), CryptoError> {
Ok(self.tls.write_all(data)?)
}
pub fn is_connected(&self) -> bool {
self.tls.is_connected()
}
pub fn handle_handshake(
&mut self,
output: &mut VecDeque<DtlsEvent>,
) -> Result<bool, CryptoError> {
if self.tls.is_connected() {
Ok(false)
} else if self.tls.complete_handshake_until_block()? {
output.push_back(DtlsEvent::Connected);
let (keying_material, srtp_profile, fingerprint) = self
.tls
.take_srtp_keying_material()
.expect("Exported keying material");
output.push_back(DtlsEvent::RemoteFingerprint(fingerprint));
output.push_back(DtlsEvent::SrtpKeyingMaterial(keying_material, srtp_profile));
Ok(false)
} else {
Ok(true)
}
}
}
pub fn dtls_create_ctx(cert: &OsslDtlsCert) -> Result<SslContext, CryptoError> {
dtls_create_ctx_with_groups(cert, None)
}
pub fn dtls_create_ctx_with_groups(
cert: &OsslDtlsCert,
groups_list: Option<&str>,
) -> Result<SslContext, CryptoError> {
let mut ctx = SslContextBuilder::new(SslMethod::dtls())?;
ctx.set_cipher_list(DTLS_CIPHERS)?;
if let Some(groups) = groups_list {
ctx.set_groups_list(groups)?;
}
let srtp_profiles = {
let all: Vec<_> = SrtpProfile::ALL.iter().map(openssl_name).collect();
all.join(":")
};
ctx.set_tlsext_use_srtp(&srtp_profiles)?;
let mut mode = SslVerifyMode::empty();
mode.insert(SslVerifyMode::PEER);
mode.insert(SslVerifyMode::FAIL_IF_NO_PEER_CERT);
ctx.set_verify_callback(mode, |_ok, _ctx| true);
ctx.set_private_key(&cert.pkey)?;
ctx.set_certificate(&cert.x509)?;
let mut options = SslOptions::empty();
options.insert(SslOptions::SINGLE_ECDH_USE);
options.insert(SslOptions::NO_DTLSV1);
ctx.set_options(options);
if let Ok(dh) = Dh::get_2048_256() {
ctx.set_tmp_dh(dh.as_ref())?;
}
let ctx = ctx.build();
Ok(ctx)
}
fn openssl_name(profile: &SrtpProfile) -> &'static str {
match profile {
SrtpProfile::AES128_CM_SHA1_80 => "SRTP_AES128_CM_SHA1_80",
SrtpProfile::AEAD_AES_128_GCM => "SRTP_AEAD_AES_128_GCM",
SrtpProfile::AEAD_AES_256_GCM => "SRTP_AEAD_AES_256_GCM",
_ => unreachable!(),
}
}
pub fn dtls_ssl_create(ctx: &SslContext) -> Result<Ssl, CryptoError> {
let mut ssl = Ssl::new(ctx)?;
ssl.set_mtu(DATAGRAM_MTU as u32)?;
Ok(ssl)
}