From 2aadc03ef15cb5ca5cc2af8a7c08e070742f0ac4 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 4 May 2024 14:47:55 +0200 Subject: Adding upstream version 0.70.1+ds1. Signed-off-by: Daniel Baumann --- vendor/openssl/src/ssl/bio.rs | 290 +++ vendor/openssl/src/ssl/callbacks.rs | 707 ++++++ vendor/openssl/src/ssl/connector.rs | 605 +++++ vendor/openssl/src/ssl/error.rs | 185 ++ vendor/openssl/src/ssl/mod.rs | 4211 +++++++++++++++++++++++++++++++++ vendor/openssl/src/ssl/test/mod.rs | 1570 ++++++++++++ vendor/openssl/src/ssl/test/server.rs | 167 ++ 7 files changed, 7735 insertions(+) create mode 100644 vendor/openssl/src/ssl/bio.rs create mode 100644 vendor/openssl/src/ssl/callbacks.rs create mode 100644 vendor/openssl/src/ssl/connector.rs create mode 100644 vendor/openssl/src/ssl/error.rs create mode 100644 vendor/openssl/src/ssl/mod.rs create mode 100644 vendor/openssl/src/ssl/test/mod.rs create mode 100644 vendor/openssl/src/ssl/test/server.rs (limited to 'vendor/openssl/src/ssl') diff --git a/vendor/openssl/src/ssl/bio.rs b/vendor/openssl/src/ssl/bio.rs new file mode 100644 index 0000000..36cea23 --- /dev/null +++ b/vendor/openssl/src/ssl/bio.rs @@ -0,0 +1,290 @@ +use cfg_if::cfg_if; +use ffi::{ + self, BIO_clear_retry_flags, BIO_new, BIO_set_retry_read, BIO_set_retry_write, BIO, + BIO_CTRL_DGRAM_QUERY_MTU, BIO_CTRL_FLUSH, +}; +use libc::{c_char, c_int, c_long, c_void, strlen}; +use std::any::Any; +use std::io; +use std::io::prelude::*; +use std::panic::{catch_unwind, AssertUnwindSafe}; +use std::ptr; +use std::slice; + +use crate::cvt_p; +use crate::error::ErrorStack; + +pub struct StreamState { + pub stream: S, + pub error: Option, + pub panic: Option>, + pub dtls_mtu_size: c_long, +} + +/// Safe wrapper for `BIO_METHOD` +pub struct BioMethod(BIO_METHOD); + +impl BioMethod { + fn new() -> Result { + BIO_METHOD::new::().map(BioMethod) + } +} + +unsafe impl Sync for BioMethod {} +unsafe impl Send for BioMethod {} + +pub fn new(stream: S) -> Result<(*mut BIO, BioMethod), ErrorStack> { + let method = BioMethod::new::()?; + + let state = Box::new(StreamState { + stream, + error: None, + panic: None, + dtls_mtu_size: 0, + }); + + unsafe { + let bio = cvt_p(BIO_new(method.0.get()))?; + BIO_set_data(bio, Box::into_raw(state) as *mut _); + BIO_set_init(bio, 1); + + Ok((bio, method)) + } +} + +pub unsafe fn take_error(bio: *mut BIO) -> Option { + let state = state::(bio); + state.error.take() +} + +pub unsafe fn take_panic(bio: *mut BIO) -> Option> { + let state = state::(bio); + state.panic.take() +} + +pub unsafe fn get_ref<'a, S: 'a>(bio: *mut BIO) -> &'a S { + let state = &*(BIO_get_data(bio) as *const StreamState); + &state.stream +} + +pub unsafe fn get_mut<'a, S: 'a>(bio: *mut BIO) -> &'a mut S { + &mut state(bio).stream +} + +pub unsafe fn set_dtls_mtu_size(bio: *mut BIO, mtu_size: usize) { + if mtu_size as u64 > c_long::max_value() as u64 { + panic!( + "Given MTU size {} can't be represented in a positive `c_long` range", + mtu_size + ) + } + state::(bio).dtls_mtu_size = mtu_size as c_long; +} + +unsafe fn state<'a, S: 'a>(bio: *mut BIO) -> &'a mut StreamState { + &mut *(BIO_get_data(bio) as *mut _) +} + +unsafe extern "C" fn bwrite(bio: *mut BIO, buf: *const c_char, len: c_int) -> c_int { + BIO_clear_retry_flags(bio); + + let state = state::(bio); + let buf = slice::from_raw_parts(buf as *const _, len as usize); + + match catch_unwind(AssertUnwindSafe(|| state.stream.write(buf))) { + Ok(Ok(len)) => len as c_int, + Ok(Err(err)) => { + if retriable_error(&err) { + BIO_set_retry_write(bio); + } + state.error = Some(err); + -1 + } + Err(err) => { + state.panic = Some(err); + -1 + } + } +} + +unsafe extern "C" fn bread(bio: *mut BIO, buf: *mut c_char, len: c_int) -> c_int { + BIO_clear_retry_flags(bio); + + let state = state::(bio); + let buf = slice::from_raw_parts_mut(buf as *mut _, len as usize); + + match catch_unwind(AssertUnwindSafe(|| state.stream.read(buf))) { + Ok(Ok(len)) => len as c_int, + Ok(Err(err)) => { + if retriable_error(&err) { + BIO_set_retry_read(bio); + } + state.error = Some(err); + -1 + } + Err(err) => { + state.panic = Some(err); + -1 + } + } +} + +#[allow(clippy::match_like_matches_macro)] // matches macro requires rust 1.42.0 +fn retriable_error(err: &io::Error) -> bool { + match err.kind() { + io::ErrorKind::WouldBlock | io::ErrorKind::NotConnected => true, + _ => false, + } +} + +unsafe extern "C" fn bputs(bio: *mut BIO, s: *const c_char) -> c_int { + bwrite::(bio, s, strlen(s) as c_int) +} + +unsafe extern "C" fn ctrl( + bio: *mut BIO, + cmd: c_int, + _num: c_long, + _ptr: *mut c_void, +) -> c_long { + let state = state::(bio); + + if cmd == BIO_CTRL_FLUSH { + match catch_unwind(AssertUnwindSafe(|| state.stream.flush())) { + Ok(Ok(())) => 1, + Ok(Err(err)) => { + state.error = Some(err); + 0 + } + Err(err) => { + state.panic = Some(err); + 0 + } + } + } else if cmd == BIO_CTRL_DGRAM_QUERY_MTU { + state.dtls_mtu_size + } else { + 0 + } +} + +unsafe extern "C" fn create(bio: *mut BIO) -> c_int { + BIO_set_init(bio, 0); + BIO_set_num(bio, 0); + BIO_set_data(bio, ptr::null_mut()); + BIO_set_flags(bio, 0); + 1 +} + +unsafe extern "C" fn destroy(bio: *mut BIO) -> c_int { + if bio.is_null() { + return 0; + } + + let data = BIO_get_data(bio); + assert!(!data.is_null()); + let _ = Box::>::from_raw(data as *mut _); + BIO_set_data(bio, ptr::null_mut()); + BIO_set_init(bio, 0); + 1 +} + +cfg_if! { + if #[cfg(any(ossl110, libressl273))] { + use ffi::{BIO_get_data, BIO_set_data, BIO_set_flags, BIO_set_init}; + use crate::cvt; + + #[allow(bad_style)] + unsafe fn BIO_set_num(_bio: *mut ffi::BIO, _num: c_int) {} + + #[allow(bad_style, clippy::upper_case_acronyms)] + struct BIO_METHOD(*mut ffi::BIO_METHOD); + + impl BIO_METHOD { + fn new() -> Result { + unsafe { + let ptr = cvt_p(ffi::BIO_meth_new(ffi::BIO_TYPE_NONE, b"rust\0".as_ptr() as *const _))?; + let method = BIO_METHOD(ptr); + cvt(ffi::BIO_meth_set_write__fixed_rust(method.0, Some(bwrite::)))?; + cvt(ffi::BIO_meth_set_read__fixed_rust(method.0, Some(bread::)))?; + cvt(ffi::BIO_meth_set_puts__fixed_rust(method.0, Some(bputs::)))?; + cvt(ffi::BIO_meth_set_ctrl__fixed_rust(method.0, Some(ctrl::)))?; + cvt(ffi::BIO_meth_set_create__fixed_rust(method.0, Some(create)))?; + cvt(ffi::BIO_meth_set_destroy__fixed_rust(method.0, Some(destroy::)))?; + Ok(method) + } + } + + fn get(&self) -> *mut ffi::BIO_METHOD { + self.0 + } + } + + impl Drop for BIO_METHOD { + fn drop(&mut self) { + unsafe { + ffi::BIO_meth_free(self.0); + } + } + } + } else { + #[allow(bad_style, clippy::upper_case_acronyms)] + struct BIO_METHOD(*mut ffi::BIO_METHOD); + + impl BIO_METHOD { + fn new() -> Result { + let ptr = Box::new(ffi::BIO_METHOD { + type_: ffi::BIO_TYPE_NONE, + name: b"rust\0".as_ptr() as *const _, + bwrite: Some(bwrite::), + bread: Some(bread::), + bputs: Some(bputs::), + bgets: None, + ctrl: Some(ctrl::), + create: Some(create), + destroy: Some(destroy::), + callback_ctrl: None, + }); + + Ok(BIO_METHOD(Box::into_raw(ptr))) + } + + fn get(&self) -> *mut ffi::BIO_METHOD { + self.0 + } + } + + impl Drop for BIO_METHOD { + fn drop(&mut self) { + unsafe { + let _ = Box::::from_raw(self.0); + } + } + } + + #[allow(bad_style)] + unsafe fn BIO_set_init(bio: *mut ffi::BIO, init: c_int) { + (*bio).init = init; + } + + #[allow(bad_style)] + unsafe fn BIO_set_flags(bio: *mut ffi::BIO, flags: c_int) { + (*bio).flags = flags; + } + + #[allow(bad_style)] + unsafe fn BIO_get_data(bio: *mut ffi::BIO) -> *mut c_void { + (*bio).ptr + } + + #[allow(bad_style)] + unsafe fn BIO_set_data(bio: *mut ffi::BIO, data: *mut c_void) { + (*bio).ptr = data; + } + + #[allow(bad_style)] + unsafe fn BIO_set_num(bio: *mut ffi::BIO, num: c_int) { + (*bio).num = num; + } + } +} diff --git a/vendor/openssl/src/ssl/callbacks.rs b/vendor/openssl/src/ssl/callbacks.rs new file mode 100644 index 0000000..c6414fb --- /dev/null +++ b/vendor/openssl/src/ssl/callbacks.rs @@ -0,0 +1,707 @@ +use cfg_if::cfg_if; +use foreign_types::ForeignType; +use foreign_types::ForeignTypeRef; +#[cfg(any(ossl111, not(osslconf = "OPENSSL_NO_PSK")))] +use libc::c_char; +#[cfg(ossl111)] +use libc::size_t; +use libc::{c_int, c_uchar, c_uint, c_void}; +#[cfg(any(ossl111, not(osslconf = "OPENSSL_NO_PSK")))] +use std::ffi::CStr; +use std::mem; +use std::ptr; +use std::slice; +#[cfg(ossl111)] +use std::str; +use std::sync::Arc; + +use crate::dh::Dh; +#[cfg(all(ossl101, not(ossl110)))] +use crate::ec::EcKey; +use crate::error::ErrorStack; +use crate::pkey::Params; +#[cfg(any(ossl102, libressl261))] +use crate::ssl::AlpnError; +use crate::ssl::{ + try_get_session_ctx_index, SniError, Ssl, SslAlert, SslContext, SslContextRef, SslRef, + SslSession, SslSessionRef, +}; +#[cfg(ossl111)] +use crate::ssl::{ClientHelloResponse, ExtensionContext}; +#[cfg(ossl111)] +use crate::util::ForeignTypeRefExt; +#[cfg(ossl111)] +use crate::x509::X509Ref; +use crate::x509::{X509StoreContext, X509StoreContextRef}; + +pub extern "C" fn raw_verify(preverify_ok: c_int, x509_ctx: *mut ffi::X509_STORE_CTX) -> c_int +where + F: Fn(bool, &mut X509StoreContextRef) -> bool + 'static + Sync + Send, +{ + unsafe { + let ctx = X509StoreContextRef::from_ptr_mut(x509_ctx); + let ssl_idx = X509StoreContext::ssl_idx().expect("BUG: store context ssl index missing"); + let verify_idx = SslContext::cached_ex_index::(); + + // raw pointer shenanigans to break the borrow of ctx + // the callback can't mess with its own ex_data slot so this is safe + let verify = ctx + .ex_data(ssl_idx) + .expect("BUG: store context missing ssl") + .ssl_context() + .ex_data(verify_idx) + .expect("BUG: verify callback missing") as *const F; + + (*verify)(preverify_ok != 0, ctx) as c_int + } +} + +#[cfg(not(osslconf = "OPENSSL_NO_PSK"))] +pub extern "C" fn raw_client_psk( + ssl: *mut ffi::SSL, + hint: *const c_char, + identity: *mut c_char, + max_identity_len: c_uint, + psk: *mut c_uchar, + max_psk_len: c_uint, +) -> c_uint +where + F: Fn(&mut SslRef, Option<&[u8]>, &mut [u8], &mut [u8]) -> Result + + 'static + + Sync + + Send, +{ + unsafe { + let ssl = SslRef::from_ptr_mut(ssl); + let callback_idx = SslContext::cached_ex_index::(); + + let callback = ssl + .ssl_context() + .ex_data(callback_idx) + .expect("BUG: psk callback missing") as *const F; + let hint = if !hint.is_null() { + Some(CStr::from_ptr(hint).to_bytes()) + } else { + None + }; + // Give the callback mutable slices into which it can write the identity and psk. + let identity_sl = slice::from_raw_parts_mut(identity as *mut u8, max_identity_len as usize); + #[allow(clippy::unnecessary_cast)] + let psk_sl = slice::from_raw_parts_mut(psk as *mut u8, max_psk_len as usize); + match (*callback)(ssl, hint, identity_sl, psk_sl) { + Ok(psk_len) => psk_len as u32, + Err(e) => { + e.put(); + 0 + } + } + } +} + +#[cfg(not(osslconf = "OPENSSL_NO_PSK"))] +pub extern "C" fn raw_server_psk( + ssl: *mut ffi::SSL, + identity: *const c_char, + psk: *mut c_uchar, + max_psk_len: c_uint, +) -> c_uint +where + F: Fn(&mut SslRef, Option<&[u8]>, &mut [u8]) -> Result + + 'static + + Sync + + Send, +{ + unsafe { + let ssl = SslRef::from_ptr_mut(ssl); + let callback_idx = SslContext::cached_ex_index::(); + + let callback = ssl + .ssl_context() + .ex_data(callback_idx) + .expect("BUG: psk callback missing") as *const F; + let identity = if identity.is_null() { + None + } else { + Some(CStr::from_ptr(identity).to_bytes()) + }; + // Give the callback mutable slices into which it can write the psk. + #[allow(clippy::unnecessary_cast)] + let psk_sl = slice::from_raw_parts_mut(psk as *mut u8, max_psk_len as usize); + match (*callback)(ssl, identity, psk_sl) { + Ok(psk_len) => psk_len as u32, + Err(e) => { + e.put(); + 0 + } + } + } +} + +pub extern "C" fn ssl_raw_verify( + preverify_ok: c_int, + x509_ctx: *mut ffi::X509_STORE_CTX, +) -> c_int +where + F: Fn(bool, &mut X509StoreContextRef) -> bool + 'static + Sync + Send, +{ + unsafe { + let ctx = X509StoreContextRef::from_ptr_mut(x509_ctx); + let ssl_idx = X509StoreContext::ssl_idx().expect("BUG: store context ssl index missing"); + let callback_idx = Ssl::cached_ex_index::>(); + + let callback = ctx + .ex_data(ssl_idx) + .expect("BUG: store context missing ssl") + .ex_data(callback_idx) + .expect("BUG: ssl verify callback missing") + .clone(); + + callback(preverify_ok != 0, ctx) as c_int + } +} + +pub extern "C" fn raw_sni(ssl: *mut ffi::SSL, al: *mut c_int, arg: *mut c_void) -> c_int +where + F: Fn(&mut SslRef, &mut SslAlert) -> Result<(), SniError> + 'static + Sync + Send, +{ + unsafe { + let ssl = SslRef::from_ptr_mut(ssl); + let callback = arg as *const F; + let mut alert = SslAlert(*al); + + let r = (*callback)(ssl, &mut alert); + *al = alert.0; + match r { + Ok(()) => ffi::SSL_TLSEXT_ERR_OK, + Err(e) => e.0, + } + } +} + +#[cfg(any(ossl102, libressl261))] +pub extern "C" fn raw_alpn_select( + ssl: *mut ffi::SSL, + out: *mut *const c_uchar, + outlen: *mut c_uchar, + inbuf: *const c_uchar, + inlen: c_uint, + _arg: *mut c_void, +) -> c_int +where + F: for<'a> Fn(&mut SslRef, &'a [u8]) -> Result<&'a [u8], AlpnError> + 'static + Sync + Send, +{ + unsafe { + let ssl = SslRef::from_ptr_mut(ssl); + let callback = ssl + .ssl_context() + .ex_data(SslContext::cached_ex_index::()) + .expect("BUG: alpn callback missing") as *const F; + #[allow(clippy::unnecessary_cast)] + let protos = slice::from_raw_parts(inbuf as *const u8, inlen as usize); + + match (*callback)(ssl, protos) { + Ok(proto) => { + *out = proto.as_ptr() as *const c_uchar; + *outlen = proto.len() as c_uchar; + ffi::SSL_TLSEXT_ERR_OK + } + Err(e) => e.0, + } + } +} + +pub unsafe extern "C" fn raw_tmp_dh( + ssl: *mut ffi::SSL, + is_export: c_int, + keylength: c_int, +) -> *mut ffi::DH +where + F: Fn(&mut SslRef, bool, u32) -> Result, ErrorStack> + 'static + Sync + Send, +{ + let ssl = SslRef::from_ptr_mut(ssl); + let callback = ssl + .ssl_context() + .ex_data(SslContext::cached_ex_index::()) + .expect("BUG: tmp dh callback missing") as *const F; + + match (*callback)(ssl, is_export != 0, keylength as u32) { + Ok(dh) => { + let ptr = dh.as_ptr(); + mem::forget(dh); + ptr + } + Err(e) => { + e.put(); + ptr::null_mut() + } + } +} + +#[cfg(all(ossl101, not(ossl110)))] +pub unsafe extern "C" fn raw_tmp_ecdh( + ssl: *mut ffi::SSL, + is_export: c_int, + keylength: c_int, +) -> *mut ffi::EC_KEY +where + F: Fn(&mut SslRef, bool, u32) -> Result, ErrorStack> + 'static + Sync + Send, +{ + let ssl = SslRef::from_ptr_mut(ssl); + let callback = ssl + .ssl_context() + .ex_data(SslContext::cached_ex_index::()) + .expect("BUG: tmp ecdh callback missing") as *const F; + + match (*callback)(ssl, is_export != 0, keylength as u32) { + Ok(ec_key) => { + let ptr = ec_key.as_ptr(); + mem::forget(ec_key); + ptr + } + Err(e) => { + e.put(); + ptr::null_mut() + } + } +} + +pub unsafe extern "C" fn raw_tmp_dh_ssl( + ssl: *mut ffi::SSL, + is_export: c_int, + keylength: c_int, +) -> *mut ffi::DH +where + F: Fn(&mut SslRef, bool, u32) -> Result, ErrorStack> + 'static + Sync + Send, +{ + let ssl = SslRef::from_ptr_mut(ssl); + let callback = ssl + .ex_data(Ssl::cached_ex_index::>()) + .expect("BUG: ssl tmp dh callback missing") + .clone(); + + match callback(ssl, is_export != 0, keylength as u32) { + Ok(dh) => { + let ptr = dh.as_ptr(); + mem::forget(dh); + ptr + } + Err(e) => { + e.put(); + ptr::null_mut() + } + } +} + +#[cfg(all(ossl101, not(ossl110)))] +pub unsafe extern "C" fn raw_tmp_ecdh_ssl( + ssl: *mut ffi::SSL, + is_export: c_int, + keylength: c_int, +) -> *mut ffi::EC_KEY +where + F: Fn(&mut SslRef, bool, u32) -> Result, ErrorStack> + 'static + Sync + Send, +{ + let ssl = SslRef::from_ptr_mut(ssl); + let callback = ssl + .ex_data(Ssl::cached_ex_index::>()) + .expect("BUG: ssl tmp ecdh callback missing") + .clone(); + + match callback(ssl, is_export != 0, keylength as u32) { + Ok(ec_key) => { + let ptr = ec_key.as_ptr(); + mem::forget(ec_key); + ptr + } + Err(e) => { + e.put(); + ptr::null_mut() + } + } +} + +pub unsafe extern "C" fn raw_tlsext_status(ssl: *mut ffi::SSL, _: *mut c_void) -> c_int +where + F: Fn(&mut SslRef) -> Result + 'static + Sync + Send, +{ + let ssl = SslRef::from_ptr_mut(ssl); + let callback = ssl + .ssl_context() + .ex_data(SslContext::cached_ex_index::()) + .expect("BUG: ocsp callback missing") as *const F; + let ret = (*callback)(ssl); + + if ssl.is_server() { + match ret { + Ok(true) => ffi::SSL_TLSEXT_ERR_OK, + Ok(false) => ffi::SSL_TLSEXT_ERR_NOACK, + Err(e) => { + e.put(); + ffi::SSL_TLSEXT_ERR_ALERT_FATAL + } + } + } else { + match ret { + Ok(true) => 1, + Ok(false) => 0, + Err(e) => { + e.put(); + -1 + } + } + } +} + +pub unsafe extern "C" fn raw_new_session( + ssl: *mut ffi::SSL, + session: *mut ffi::SSL_SESSION, +) -> c_int +where + F: Fn(&mut SslRef, SslSession) + 'static + Sync + Send, +{ + let session_ctx_index = + try_get_session_ctx_index().expect("BUG: session context index initialization failed"); + let ssl = SslRef::from_ptr_mut(ssl); + let callback = ssl + .ex_data(*session_ctx_index) + .expect("BUG: session context missing") + .ex_data(SslContext::cached_ex_index::()) + .expect("BUG: new session callback missing") as *const F; + let session = SslSession::from_ptr(session); + + (*callback)(ssl, session); + + // the return code doesn't indicate error vs success, but whether or not we consumed the session + 1 +} + +pub unsafe extern "C" fn raw_remove_session( + ctx: *mut ffi::SSL_CTX, + session: *mut ffi::SSL_SESSION, +) where + F: Fn(&SslContextRef, &SslSessionRef) + 'static + Sync + Send, +{ + let ctx = SslContextRef::from_ptr(ctx); + let callback = ctx + .ex_data(SslContext::cached_ex_index::()) + .expect("BUG: remove session callback missing"); + let session = SslSessionRef::from_ptr(session); + + callback(ctx, session) +} + +cfg_if! { + if #[cfg(any(ossl110, libressl280, boringssl))] { + type DataPtr = *const c_uchar; + } else { + type DataPtr = *mut c_uchar; + } +} + +pub unsafe extern "C" fn raw_get_session( + ssl: *mut ffi::SSL, + data: DataPtr, + len: c_int, + copy: *mut c_int, +) -> *mut ffi::SSL_SESSION +where + F: Fn(&mut SslRef, &[u8]) -> Option + 'static + Sync + Send, +{ + let session_ctx_index = + try_get_session_ctx_index().expect("BUG: session context index initialization failed"); + let ssl = SslRef::from_ptr_mut(ssl); + let callback = ssl + .ex_data(*session_ctx_index) + .expect("BUG: session context missing") + .ex_data(SslContext::cached_ex_index::()) + .expect("BUG: get session callback missing") as *const F; + #[allow(clippy::unnecessary_cast)] + let data = slice::from_raw_parts(data as *const u8, len as usize); + + match (*callback)(ssl, data) { + Some(session) => { + let p = session.as_ptr(); + mem::forget(session); + *copy = 0; + p + } + None => ptr::null_mut(), + } +} + +#[cfg(ossl111)] +pub unsafe extern "C" fn raw_keylog(ssl: *const ffi::SSL, line: *const c_char) +where + F: Fn(&SslRef, &str) + 'static + Sync + Send, +{ + let ssl = SslRef::from_const_ptr(ssl); + let callback = ssl + .ssl_context() + .ex_data(SslContext::cached_ex_index::()) + .expect("BUG: get session callback missing"); + let line = CStr::from_ptr(line).to_bytes(); + let line = str::from_utf8_unchecked(line); + + callback(ssl, line); +} + +#[cfg(ossl111)] +pub unsafe extern "C" fn raw_stateless_cookie_generate( + ssl: *mut ffi::SSL, + cookie: *mut c_uchar, + cookie_len: *mut size_t, +) -> c_int +where + F: Fn(&mut SslRef, &mut [u8]) -> Result + 'static + Sync + Send, +{ + let ssl = SslRef::from_ptr_mut(ssl); + let callback = ssl + .ssl_context() + .ex_data(SslContext::cached_ex_index::()) + .expect("BUG: stateless cookie generate callback missing") as *const F; + #[allow(clippy::unnecessary_cast)] + let slice = slice::from_raw_parts_mut(cookie as *mut u8, ffi::SSL_COOKIE_LENGTH as usize); + match (*callback)(ssl, slice) { + Ok(len) => { + *cookie_len = len as size_t; + 1 + } + Err(e) => { + e.put(); + 0 + } + } +} + +#[cfg(ossl111)] +pub unsafe extern "C" fn raw_stateless_cookie_verify( + ssl: *mut ffi::SSL, + cookie: *const c_uchar, + cookie_len: size_t, +) -> c_int +where + F: Fn(&mut SslRef, &[u8]) -> bool + 'static + Sync + Send, +{ + let ssl = SslRef::from_ptr_mut(ssl); + let callback = ssl + .ssl_context() + .ex_data(SslContext::cached_ex_index::()) + .expect("BUG: stateless cookie verify callback missing") as *const F; + #[allow(clippy::unnecessary_cast)] + let slice = slice::from_raw_parts(cookie as *const c_uchar as *const u8, cookie_len); + (*callback)(ssl, slice) as c_int +} + +#[cfg(not(boringssl))] +pub extern "C" fn raw_cookie_generate( + ssl: *mut ffi::SSL, + cookie: *mut c_uchar, + cookie_len: *mut c_uint, +) -> c_int +where + F: Fn(&mut SslRef, &mut [u8]) -> Result + 'static + Sync + Send, +{ + unsafe { + let ssl = SslRef::from_ptr_mut(ssl); + let callback = ssl + .ssl_context() + .ex_data(SslContext::cached_ex_index::()) + .expect("BUG: cookie generate callback missing") as *const F; + // We subtract 1 from DTLS1_COOKIE_LENGTH as the ostensible value, 256, is erroneous but retained for + // compatibility. See comments in dtls1.h. + #[allow(clippy::unnecessary_cast)] + let slice = + slice::from_raw_parts_mut(cookie as *mut u8, ffi::DTLS1_COOKIE_LENGTH as usize - 1); + match (*callback)(ssl, slice) { + Ok(len) => { + *cookie_len = len as c_uint; + 1 + } + Err(e) => { + e.put(); + 0 + } + } + } +} + +#[cfg(not(boringssl))] +cfg_if! { + if #[cfg(any(ossl110, libressl280))] { + type CookiePtr = *const c_uchar; + } else { + type CookiePtr = *mut c_uchar; + } +} + +#[cfg(not(boringssl))] +pub extern "C" fn raw_cookie_verify( + ssl: *mut ffi::SSL, + cookie: CookiePtr, + cookie_len: c_uint, +) -> c_int +where + F: Fn(&mut SslRef, &[u8]) -> bool + 'static + Sync + Send, +{ + unsafe { + let ssl = SslRef::from_ptr_mut(ssl); + let callback = ssl + .ssl_context() + .ex_data(SslContext::cached_ex_index::()) + .expect("BUG: cookie verify callback missing") as *const F; + #[allow(clippy::unnecessary_cast)] + let slice = + slice::from_raw_parts(cookie as *const c_uchar as *const u8, cookie_len as usize); + (*callback)(ssl, slice) as c_int + } +} + +#[cfg(ossl111)] +pub struct CustomExtAddState(Option); + +#[cfg(ossl111)] +pub extern "C" fn raw_custom_ext_add( + ssl: *mut ffi::SSL, + _: c_uint, + context: c_uint, + out: *mut *const c_uchar, + outlen: *mut size_t, + x: *mut ffi::X509, + chainidx: size_t, + al: *mut c_int, + _: *mut c_void, +) -> c_int +where + F: Fn(&mut SslRef, ExtensionContext, Option<(usize, &X509Ref)>) -> Result, SslAlert> + + 'static + + Sync + + Send, + T: AsRef<[u8]> + 'static + Sync + Send, +{ + unsafe { + let ssl = SslRef::from_ptr_mut(ssl); + let callback = ssl + .ssl_context() + .ex_data(SslContext::cached_ex_index::()) + .expect("BUG: custom ext add callback missing") as *const F; + let ectx = ExtensionContext::from_bits_truncate(context); + let cert = if ectx.contains(ExtensionContext::TLS1_3_CERTIFICATE) { + Some((chainidx, X509Ref::from_ptr(x))) + } else { + None + }; + match (*callback)(ssl, ectx, cert) { + Ok(None) => 0, + Ok(Some(buf)) => { + *outlen = buf.as_ref().len(); + *out = buf.as_ref().as_ptr(); + + let idx = Ssl::cached_ex_index::>(); + let mut buf = Some(buf); + let new = match ssl.ex_data_mut(idx) { + Some(state) => { + state.0 = buf.take(); + false + } + None => true, + }; + if new { + ssl.set_ex_data(idx, CustomExtAddState(buf)); + } + 1 + } + Err(alert) => { + *al = alert.0; + -1 + } + } + } +} + +#[cfg(ossl111)] +pub extern "C" fn raw_custom_ext_free( + ssl: *mut ffi::SSL, + _: c_uint, + _: c_uint, + _: *const c_uchar, + _: *mut c_void, +) where + T: 'static + Sync + Send, +{ + unsafe { + let ssl = SslRef::from_ptr_mut(ssl); + let idx = Ssl::cached_ex_index::>(); + if let Some(state) = ssl.ex_data_mut(idx) { + state.0 = None; + } + } +} + +#[cfg(ossl111)] +pub extern "C" fn raw_custom_ext_parse( + ssl: *mut ffi::SSL, + _: c_uint, + context: c_uint, + input: *const c_uchar, + inlen: size_t, + x: *mut ffi::X509, + chainidx: size_t, + al: *mut c_int, + _: *mut c_void, +) -> c_int +where + F: Fn(&mut SslRef, ExtensionContext, &[u8], Option<(usize, &X509Ref)>) -> Result<(), SslAlert> + + 'static + + Sync + + Send, +{ + unsafe { + let ssl = SslRef::from_ptr_mut(ssl); + let callback = ssl + .ssl_context() + .ex_data(SslContext::cached_ex_index::()) + .expect("BUG: custom ext parse callback missing") as *const F; + let ectx = ExtensionContext::from_bits_truncate(context); + #[allow(clippy::unnecessary_cast)] + let slice = slice::from_raw_parts(input as *const u8, inlen); + let cert = if ectx.contains(ExtensionContext::TLS1_3_CERTIFICATE) { + Some((chainidx, X509Ref::from_ptr(x))) + } else { + None + }; + match (*callback)(ssl, ectx, slice, cert) { + Ok(()) => 1, + Err(alert) => { + *al = alert.0; + 0 + } + } + } +} + +#[cfg(ossl111)] +pub unsafe extern "C" fn raw_client_hello( + ssl: *mut ffi::SSL, + al: *mut c_int, + arg: *mut c_void, +) -> c_int +where + F: Fn(&mut SslRef, &mut SslAlert) -> Result + + 'static + + Sync + + Send, +{ + let ssl = SslRef::from_ptr_mut(ssl); + let callback = arg as *const F; + let mut alert = SslAlert(*al); + + let r = (*callback)(ssl, &mut alert); + *al = alert.0; + match r { + Ok(c) => c.0, + Err(e) => { + e.put(); + ffi::SSL_CLIENT_HELLO_ERROR + } + } +} diff --git a/vendor/openssl/src/ssl/connector.rs b/vendor/openssl/src/ssl/connector.rs new file mode 100644 index 0000000..66d1bd8 --- /dev/null +++ b/vendor/openssl/src/ssl/connector.rs @@ -0,0 +1,605 @@ +use cfg_if::cfg_if; +use std::io::{Read, Write}; +use std::ops::{Deref, DerefMut}; + +use crate::dh::Dh; +use crate::error::ErrorStack; +#[cfg(any(ossl111, libressl340))] +use crate::ssl::SslVersion; +use crate::ssl::{ + HandshakeError, Ssl, SslContext, SslContextBuilder, SslContextRef, SslMethod, SslMode, + SslOptions, SslRef, SslStream, SslVerifyMode, +}; +use crate::version; +use std::net::IpAddr; + +const FFDHE_2048: &str = " +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz ++8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a +87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 +YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi +7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD +ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== +-----END DH PARAMETERS----- +"; + +#[allow(clippy::inconsistent_digit_grouping, clippy::unusual_byte_groupings)] +fn ctx(method: SslMethod) -> Result { + let mut ctx = SslContextBuilder::new(method)?; + + cfg_if! { + if #[cfg(not(boringssl))] { + let mut opts = SslOptions::ALL + | SslOptions::NO_COMPRESSION + | SslOptions::NO_SSLV2 + | SslOptions::NO_SSLV3 + | SslOptions::SINGLE_DH_USE + | SslOptions::SINGLE_ECDH_USE; + opts &= !SslOptions::DONT_INSERT_EMPTY_FRAGMENTS; + + ctx.set_options(opts); + } + } + + let mut mode = + SslMode::AUTO_RETRY | SslMode::ACCEPT_MOVING_WRITE_BUFFER | SslMode::ENABLE_PARTIAL_WRITE; + + // This is quite a useful optimization for saving memory, but historically + // caused CVEs in OpenSSL pre-1.0.1h, according to + // https://bugs.python.org/issue25672 + if version::number() >= 0x1_00_01_08_0 { + mode |= SslMode::RELEASE_BUFFERS; + } + + ctx.set_mode(mode); + + Ok(ctx) +} + +/// A type which wraps client-side streams in a TLS session. +/// +/// OpenSSL's default configuration is highly insecure. This connector manages the OpenSSL +/// structures, configuring cipher suites, session options, hostname verification, and more. +/// +/// OpenSSL's built-in hostname verification is used when linking against OpenSSL 1.0.2 or 1.1.0, +/// and a custom implementation is used when linking against OpenSSL 1.0.1. +#[derive(Clone, Debug)] +pub struct SslConnector(SslContext); + +impl SslConnector { + /// Creates a new builder for TLS connections. + /// + /// The default configuration is subject to change, and is currently derived from Python. + pub fn builder(method: SslMethod) -> Result { + let mut ctx = ctx(method)?; + ctx.set_default_verify_paths()?; + ctx.set_cipher_list( + "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK", + )?; + setup_verify(&mut ctx); + + Ok(SslConnectorBuilder(ctx)) + } + + /// Initiates a client-side TLS session on a stream. + /// + /// The domain is used for SNI and hostname verification. + pub fn connect(&self, domain: &str, stream: S) -> Result, HandshakeError> + where + S: Read + Write, + { + self.configure()?.connect(domain, stream) + } + + /// Returns a structure allowing for configuration of a single TLS session before connection. + pub fn configure(&self) -> Result { + Ssl::new(&self.0).map(|ssl| ConnectConfiguration { + ssl, + sni: true, + verify_hostname: true, + }) + } + + /// Consumes the `SslConnector`, returning the inner raw `SslContext`. + pub fn into_context(self) -> SslContext { + self.0 + } + + /// Returns a shared reference to the inner raw `SslContext`. + pub fn context(&self) -> &SslContextRef { + &self.0 + } +} + +/// A builder for `SslConnector`s. +pub struct SslConnectorBuilder(SslContextBuilder); + +impl SslConnectorBuilder { + /// Consumes the builder, returning an `SslConnector`. + pub fn build(self) -> SslConnector { + SslConnector(self.0.build()) + } +} + +impl Deref for SslConnectorBuilder { + type Target = SslContextBuilder; + + fn deref(&self) -> &SslContextBuilder { + &self.0 + } +} + +impl DerefMut for SslConnectorBuilder { + fn deref_mut(&mut self) -> &mut SslContextBuilder { + &mut self.0 + } +} + +/// A type which allows for configuration of a client-side TLS session before connection. +pub struct ConnectConfiguration { + ssl: Ssl, + sni: bool, + verify_hostname: bool, +} + +impl ConnectConfiguration { + /// A builder-style version of `set_use_server_name_indication`. + pub fn use_server_name_indication(mut self, use_sni: bool) -> ConnectConfiguration { + self.set_use_server_name_indication(use_sni); + self + } + + /// Configures the use of Server Name Indication (SNI) when connecting. + /// + /// Defaults to `true`. + pub fn set_use_server_name_indication(&mut self, use_sni: bool) { + self.sni = use_sni; + } + + /// A builder-style version of `set_verify_hostname`. + pub fn verify_hostname(mut self, verify_hostname: bool) -> ConnectConfiguration { + self.set_verify_hostname(verify_hostname); + self + } + + /// Configures the use of hostname verification when connecting. + /// + /// Defaults to `true`. + /// + /// # Warning + /// + /// You should think very carefully before you use this method. If hostname verification is not + /// used, *any* valid certificate for *any* site will be trusted for use from any other. This + /// introduces a significant vulnerability to man-in-the-middle attacks. + pub fn set_verify_hostname(&mut self, verify_hostname: bool) { + self.verify_hostname = verify_hostname; + } + + /// Returns an `Ssl` configured to connect to the provided domain. + /// + /// The domain is used for SNI (if it is not an IP address) and hostname verification if enabled. + pub fn into_ssl(mut self, domain: &str) -> Result { + if self.sni && domain.parse::().is_err() { + self.ssl.set_hostname(domain)?; + } + + if self.verify_hostname { + setup_verify_hostname(&mut self.ssl, domain)?; + } + + Ok(self.ssl) + } + + /// Initiates a client-side TLS session on a stream. + /// + /// The domain is used for SNI and hostname verification if enabled. + pub fn connect(self, domain: &str, stream: S) -> Result, HandshakeError> + where + S: Read + Write, + { + self.into_ssl(domain)?.connect(stream) + } +} + +impl Deref for ConnectConfiguration { + type Target = SslRef; + + fn deref(&self) -> &SslRef { + &self.ssl + } +} + +impl DerefMut for ConnectConfiguration { + fn deref_mut(&mut self) -> &mut SslRef { + &mut self.ssl + } +} + +/// A type which wraps server-side streams in a TLS session. +/// +/// OpenSSL's default configuration is highly insecure. This connector manages the OpenSSL +/// structures, configuring cipher suites, session options, and more. +#[derive(Clone)] +pub struct SslAcceptor(SslContext); + +impl SslAcceptor { + /// Creates a new builder configured to connect to non-legacy clients. This should generally be + /// considered a reasonable default choice. + /// + /// This corresponds to the intermediate configuration of version 5 of Mozilla's server side TLS + /// recommendations. See its [documentation][docs] for more details on specifics. + /// + /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS + pub fn mozilla_intermediate_v5(method: SslMethod) -> Result { + let mut ctx = ctx(method)?; + ctx.set_options(SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1); + let dh = Dh::params_from_pem(FFDHE_2048.as_bytes())?; + ctx.set_tmp_dh(&dh)?; + setup_curves(&mut ctx)?; + ctx.set_cipher_list( + "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:\ + ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:\ + DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384" + )?; + #[cfg(any(ossl111, libressl340))] + ctx.set_ciphersuites( + "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256", + )?; + Ok(SslAcceptorBuilder(ctx)) + } + + /// Creates a new builder configured to connect to modern clients. + /// + /// This corresponds to the modern configuration of version 5 of Mozilla's server side TLS recommendations. + /// See its [documentation][docs] for more details on specifics. + /// + /// Requires OpenSSL 1.1.1 or LibreSSL 3.4.0 or newer. + /// + /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS + #[cfg(any(ossl111, libressl340))] + pub fn mozilla_modern_v5(method: SslMethod) -> Result { + let mut ctx = ctx(method)?; + ctx.set_min_proto_version(Some(SslVersion::TLS1_3))?; + ctx.set_ciphersuites( + "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256", + )?; + Ok(SslAcceptorBuilder(ctx)) + } + + /// Creates a new builder configured to connect to non-legacy clients. This should generally be + /// considered a reasonable default choice. + /// + /// This corresponds to the intermediate configuration of version 4 of Mozilla's server side TLS + /// recommendations. See its [documentation][docs] for more details on specifics. + /// + /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS + // FIXME remove in next major version + pub fn mozilla_intermediate(method: SslMethod) -> Result { + let mut ctx = ctx(method)?; + ctx.set_options(SslOptions::CIPHER_SERVER_PREFERENCE); + #[cfg(any(ossl111, libressl340))] + ctx.set_options(SslOptions::NO_TLSV1_3); + let dh = Dh::params_from_pem(FFDHE_2048.as_bytes())?; + ctx.set_tmp_dh(&dh)?; + setup_curves(&mut ctx)?; + ctx.set_cipher_list( + "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:\ + ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:\ + DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:\ + ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:\ + ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:\ + DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:\ + EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:\ + AES256-SHA:DES-CBC3-SHA:!DSS", + )?; + Ok(SslAcceptorBuilder(ctx)) + } + + /// Creates a new builder configured to connect to modern clients. + /// + /// This corresponds to the modern configuration of version 4 of Mozilla's server side TLS recommendations. + /// See its [documentation][docs] for more details on specifics. + /// + /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS + // FIXME remove in next major version + pub fn mozilla_modern(method: SslMethod) -> Result { + let mut ctx = ctx(method)?; + ctx.set_options( + SslOptions::CIPHER_SERVER_PREFERENCE | SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1, + ); + #[cfg(any(ossl111, libressl340))] + ctx.set_options(SslOptions::NO_TLSV1_3); + setup_curves(&mut ctx)?; + ctx.set_cipher_list( + "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:\ + ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:\ + ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256", + )?; + Ok(SslAcceptorBuilder(ctx)) + } + + /// Initiates a server-side TLS session on a stream. + pub fn accept(&self, stream: S) -> Result, HandshakeError> + where + S: Read + Write, + { + let ssl = Ssl::new(&self.0)?; + ssl.accept(stream) + } + + /// Consumes the `SslAcceptor`, returning the inner raw `SslContext`. + pub fn into_context(self) -> SslContext { + self.0 + } + + /// Returns a shared reference to the inner raw `SslContext`. + pub fn context(&self) -> &SslContextRef { + &self.0 + } +} + +/// A builder for `SslAcceptor`s. +pub struct SslAcceptorBuilder(SslContextBuilder); + +impl SslAcceptorBuilder { + /// Consumes the builder, returning a `SslAcceptor`. + pub fn build(self) -> SslAcceptor { + SslAcceptor(self.0.build()) + } +} + +impl Deref for SslAcceptorBuilder { + type Target = SslContextBuilder; + + fn deref(&self) -> &SslContextBuilder { + &self.0 + } +} + +impl DerefMut for SslAcceptorBuilder { + fn deref_mut(&mut self) -> &mut SslContextBuilder { + &mut self.0 + } +} + +cfg_if! { + if #[cfg(ossl110)] { + #[allow(clippy::unnecessary_wraps)] + fn setup_curves(_: &mut SslContextBuilder) -> Result<(), ErrorStack> { + Ok(()) + } + } else if #[cfg(any(ossl102, libressl))] { + fn setup_curves(ctx: &mut SslContextBuilder) -> Result<(), ErrorStack> { + ctx.set_ecdh_auto(true) + } + } else { + fn setup_curves(ctx: &mut SslContextBuilder) -> Result<(), ErrorStack> { + use crate::ec::EcKey; + use crate::nid::Nid; + + let curve = EcKey::from_curve_name(Nid::X9_62_PRIME256V1)?; + ctx.set_tmp_ecdh(&curve) + } + } +} + +cfg_if! { + if #[cfg(any(ossl102, libressl261))] { + fn setup_verify(ctx: &mut SslContextBuilder) { + ctx.set_verify(SslVerifyMode::PEER); + } + + fn setup_verify_hostname(ssl: &mut SslRef, domain: &str) -> Result<(), ErrorStack> { + use crate::x509::verify::X509CheckFlags; + + let param = ssl.param_mut(); + param.set_hostflags(X509CheckFlags::NO_PARTIAL_WILDCARDS); + match domain.parse() { + Ok(ip) => param.set_ip(ip), + Err(_) => param.set_host(domain), + } + } + } else { + fn setup_verify(ctx: &mut SslContextBuilder) { + ctx.set_verify_callback(SslVerifyMode::PEER, verify::verify_callback); + } + + fn setup_verify_hostname(ssl: &mut Ssl, domain: &str) -> Result<(), ErrorStack> { + let domain = domain.to_string(); + let hostname_idx = verify::try_get_hostname_idx()?; + ssl.set_ex_data(*hostname_idx, domain); + Ok(()) + } + + mod verify { + use std::net::IpAddr; + use std::str; + use once_cell::sync::OnceCell; + + use crate::error::ErrorStack; + use crate::ex_data::Index; + use crate::nid::Nid; + use crate::ssl::Ssl; + use crate::stack::Stack; + use crate::x509::{ + GeneralName, X509NameRef, X509Ref, X509StoreContext, X509StoreContextRef, + X509VerifyResult, + }; + + static HOSTNAME_IDX: OnceCell> = OnceCell::new(); + + pub fn try_get_hostname_idx() -> Result<&'static Index, ErrorStack> { + HOSTNAME_IDX.get_or_try_init(Ssl::new_ex_index) + } + + pub fn verify_callback(preverify_ok: bool, x509_ctx: &mut X509StoreContextRef) -> bool { + if !preverify_ok || x509_ctx.error_depth() != 0 { + return preverify_ok; + } + + let hostname_idx = + try_get_hostname_idx().expect("failed to initialize hostname index"); + let ok = match ( + x509_ctx.current_cert(), + X509StoreContext::ssl_idx() + .ok() + .and_then(|idx| x509_ctx.ex_data(idx)) + .and_then(|ssl| ssl.ex_data(*hostname_idx)), + ) { + (Some(x509), Some(domain)) => verify_hostname(domain, &x509), + _ => true, + }; + + if !ok { + x509_ctx.set_error(X509VerifyResult::APPLICATION_VERIFICATION); + } + + ok + } + + fn verify_hostname(domain: &str, cert: &X509Ref) -> bool { + match cert.subject_alt_names() { + Some(names) => verify_subject_alt_names(domain, names), + None => verify_subject_name(domain, &cert.subject_name()), + } + } + + fn verify_subject_alt_names(domain: &str, names: Stack) -> bool { + let ip = domain.parse(); + + for name in &names { + match ip { + Ok(ip) => { + if let Some(actual) = name.ipaddress() { + if matches_ip(&ip, actual) { + return true; + } + } + } + Err(_) => { + if let Some(pattern) = name.dnsname() { + if matches_dns(pattern, domain) { + return true; + } + } + } + } + } + + false + } + + fn verify_subject_name(domain: &str, subject_name: &X509NameRef) -> bool { + match subject_name.entries_by_nid(Nid::COMMONNAME).next() { + Some(pattern) => { + let pattern = match str::from_utf8(pattern.data().as_slice()) { + Ok(pattern) => pattern, + Err(_) => return false, + }; + + // Unlike SANs, IP addresses in the subject name don't have a + // different encoding. + match domain.parse::() { + Ok(ip) => pattern + .parse::() + .ok() + .map_or(false, |pattern| pattern == ip), + Err(_) => matches_dns(pattern, domain), + } + } + None => false, + } + } + + fn matches_dns(mut pattern: &str, mut hostname: &str) -> bool { + // first strip trailing . off of pattern and hostname to normalize + if pattern.ends_with('.') { + pattern = &pattern[..pattern.len() - 1]; + } + if hostname.ends_with('.') { + hostname = &hostname[..hostname.len() - 1]; + } + + matches_wildcard(pattern, hostname).unwrap_or_else(|| pattern.eq_ignore_ascii_case(hostname)) + } + + fn matches_wildcard(pattern: &str, hostname: &str) -> Option { + let wildcard_location = match pattern.find('*') { + Some(l) => l, + None => return None, + }; + + let mut dot_idxs = pattern.match_indices('.').map(|(l, _)| l); + let wildcard_end = match dot_idxs.next() { + Some(l) => l, + None => return None, + }; + + // Never match wildcards if the pattern has less than 2 '.'s (no *.com) + // + // This is a bit dubious, as it doesn't disallow other TLDs like *.co.uk. + // Chrome has a black- and white-list for this, but Firefox (via NSS) does + // the same thing we do here. + // + // The Public Suffix (https://www.publicsuffix.org/) list could + // potentially be used here, but it's both huge and updated frequently + // enough that management would be a PITA. + if dot_idxs.next().is_none() { + return None; + } + + // Wildcards can only be in the first component, and must be the entire first label + if wildcard_location != 0 || wildcard_end != wildcard_location + 1 { + return None; + } + + let hostname_label_end = match hostname.find('.') { + Some(l) => l, + None => return None, + }; + + let pattern_after_wildcard = &pattern[wildcard_end..]; + let hostname_after_wildcard = &hostname[hostname_label_end..]; + + Some(pattern_after_wildcard.eq_ignore_ascii_case(hostname_after_wildcard)) + } + + fn matches_ip(expected: &IpAddr, actual: &[u8]) -> bool { + match *expected { + IpAddr::V4(ref addr) => actual == addr.octets(), + IpAddr::V6(ref addr) => actual == addr.octets(), + } + } + + #[test] + fn test_dns_match() { + use crate::ssl::connector::verify::matches_dns; + assert!(matches_dns("website.tld", "website.tld")); // A name should match itself. + assert!(matches_dns("website.tld", "wEbSiTe.tLd")); // DNS name matching ignores case of hostname. + assert!(matches_dns("wEbSiTe.TlD", "website.tld")); // DNS name matching ignores case of subject. + + assert!(matches_dns("xn--bcher-kva.tld", "xn--bcher-kva.tld")); // Likewise, nothing special to punycode names. + assert!(matches_dns("xn--bcher-kva.tld", "xn--BcHer-Kva.tLd")); // And punycode must be compared similarly case-insensitively. + + assert!(matches_dns("*.example.com", "subdomain.example.com")); // Wildcard matching works. + assert!(matches_dns("*.eXaMpLe.cOm", "subdomain.example.com")); // Wildcard matching ignores case of subject. + assert!(matches_dns("*.example.com", "sUbDoMaIn.eXaMpLe.cOm")); // Wildcard matching ignores case of hostname. + + assert!(!matches_dns("prefix*.example.com", "p.example.com")); // Prefix longer than the label works and does not match. + assert!(!matches_dns("*suffix.example.com", "s.example.com")); // Suffix longer than the label works and does not match. + + assert!(!matches_dns("prefix*.example.com", "prefix.example.com")); // Partial wildcards do not work. + assert!(!matches_dns("*suffix.example.com", "suffix.example.com")); // Partial wildcards do not work. + + assert!(!matches_dns("prefix*.example.com", "prefixdomain.example.com")); // Partial wildcards do not work. + assert!(!matches_dns("*suffix.example.com", "domainsuffix.example.com")); // Partial wildcards do not work. + + assert!(!matches_dns("xn--*.example.com", "subdomain.example.com")); // Punycode domains with wildcard parts do not match. + assert!(!matches_dns("xN--*.example.com", "subdomain.example.com")); // And we can't bypass a punycode test with weird casing. + assert!(!matches_dns("Xn--*.example.com", "subdomain.example.com")); // And we can't bypass a punycode test with weird casing. + assert!(!matches_dns("XN--*.example.com", "subdomain.example.com")); // And we can't bypass a punycode test with weird casing. + } + } + } +} diff --git a/vendor/openssl/src/ssl/error.rs b/vendor/openssl/src/ssl/error.rs new file mode 100644 index 0000000..5565833 --- /dev/null +++ b/vendor/openssl/src/ssl/error.rs @@ -0,0 +1,185 @@ +use libc::c_int; +use std::error; +use std::error::Error as StdError; +use std::fmt; +use std::io; + +use crate::error::ErrorStack; +use crate::ssl::MidHandshakeSslStream; +use crate::x509::X509VerifyResult; + +/// An error code returned from SSL functions. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct ErrorCode(c_int); + +impl ErrorCode { + /// The SSL session has been closed. + pub const ZERO_RETURN: ErrorCode = ErrorCode(ffi::SSL_ERROR_ZERO_RETURN); + + /// An attempt to read data from the underlying socket returned `WouldBlock`. + /// + /// Wait for read readiness and retry the operation. + pub const WANT_READ: ErrorCode = ErrorCode(ffi::SSL_ERROR_WANT_READ); + + /// An attempt to write data to the underlying socket returned `WouldBlock`. + /// + /// Wait for write readiness and retry the operation. + pub const WANT_WRITE: ErrorCode = ErrorCode(ffi::SSL_ERROR_WANT_WRITE); + + /// A non-recoverable IO error occurred. + pub const SYSCALL: ErrorCode = ErrorCode(ffi::SSL_ERROR_SYSCALL); + + /// An error occurred in the SSL library. + pub const SSL: ErrorCode = ErrorCode(ffi::SSL_ERROR_SSL); + + /// The client hello callback indicated that it needed to be retried. + /// + /// Requires OpenSSL 1.1.1 or newer. + #[cfg(ossl111)] + pub const WANT_CLIENT_HELLO_CB: ErrorCode = ErrorCode(ffi::SSL_ERROR_WANT_CLIENT_HELLO_CB); + + pub fn from_raw(raw: c_int) -> ErrorCode { + ErrorCode(raw) + } + + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn as_raw(&self) -> c_int { + self.0 + } +} + +#[derive(Debug)] +pub(crate) enum InnerError { + Io(io::Error), + Ssl(ErrorStack), +} + +/// An SSL error. +#[derive(Debug)] +pub struct Error { + pub(crate) code: ErrorCode, + pub(crate) cause: Option, +} + +impl Error { + pub fn code(&self) -> ErrorCode { + self.code + } + + pub fn io_error(&self) -> Option<&io::Error> { + match self.cause { + Some(InnerError::Io(ref e)) => Some(e), + _ => None, + } + } + + pub fn into_io_error(self) -> Result { + match self.cause { + Some(InnerError::Io(e)) => Ok(e), + _ => Err(self), + } + } + + pub fn ssl_error(&self) -> Option<&ErrorStack> { + match self.cause { + Some(InnerError::Ssl(ref e)) => Some(e), + _ => None, + } + } +} + +impl From for Error { + fn from(e: ErrorStack) -> Error { + Error { + code: ErrorCode::SSL, + cause: Some(InnerError::Ssl(e)), + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.code { + ErrorCode::ZERO_RETURN => fmt.write_str("the SSL session has been shut down"), + ErrorCode::WANT_READ => match self.io_error() { + Some(_) => fmt.write_str("a nonblocking read call would have blocked"), + None => fmt.write_str("the operation should be retried"), + }, + ErrorCode::WANT_WRITE => match self.io_error() { + Some(_) => fmt.write_str("a nonblocking write call would have blocked"), + None => fmt.write_str("the operation should be retried"), + }, + ErrorCode::SYSCALL => match self.io_error() { + Some(err) => write!(fmt, "{}", err), + None => fmt.write_str("unexpected EOF"), + }, + ErrorCode::SSL => match self.ssl_error() { + Some(e) => write!(fmt, "{}", e), + None => fmt.write_str("OpenSSL error"), + }, + ErrorCode(code) => write!(fmt, "unknown error code {}", code), + } + } +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self.cause { + Some(InnerError::Io(ref e)) => Some(e), + Some(InnerError::Ssl(ref e)) => Some(e), + None => None, + } + } +} + +/// An error or intermediate state after a TLS handshake attempt. +// FIXME overhaul +#[derive(Debug)] +pub enum HandshakeError { + /// Setup failed. + SetupFailure(ErrorStack), + /// The handshake failed. + Failure(MidHandshakeSslStream), + /// The handshake encountered a `WouldBlock` error midway through. + /// + /// This error will never be returned for blocking streams. + WouldBlock(MidHandshakeSslStream), +} + +impl StdError for HandshakeError { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + match *self { + HandshakeError::SetupFailure(ref e) => Some(e), + HandshakeError::Failure(ref s) | HandshakeError::WouldBlock(ref s) => Some(s.error()), + } + } +} + +impl fmt::Display for HandshakeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + HandshakeError::SetupFailure(ref e) => write!(f, "stream setup failed: {}", e)?, + HandshakeError::Failure(ref s) => { + write!(f, "the handshake failed: {}", s.error())?; + let verify = s.ssl().verify_result(); + if verify != X509VerifyResult::OK { + write!(f, ": {}", verify)?; + } + } + HandshakeError::WouldBlock(ref s) => { + write!(f, "the handshake was interrupted: {}", s.error())?; + let verify = s.ssl().verify_result(); + if verify != X509VerifyResult::OK { + write!(f, ": {}", verify)?; + } + } + } + Ok(()) + } +} + +impl From for HandshakeError { + fn from(e: ErrorStack) -> HandshakeError { + HandshakeError::SetupFailure(e) + } +} diff --git a/vendor/openssl/src/ssl/mod.rs b/vendor/openssl/src/ssl/mod.rs new file mode 100644 index 0000000..bdfbfc1 --- /dev/null +++ b/vendor/openssl/src/ssl/mod.rs @@ -0,0 +1,4211 @@ +//! SSL/TLS support. +//! +//! `SslConnector` and `SslAcceptor` should be used in most cases - they handle +//! configuration of the OpenSSL primitives for you. +//! +//! # Examples +//! +//! To connect as a client to a remote server: +//! +//! ```no_run +//! use openssl::ssl::{SslMethod, SslConnector}; +//! use std::io::{Read, Write}; +//! use std::net::TcpStream; +//! +//! let connector = SslConnector::builder(SslMethod::tls()).unwrap().build(); +//! +//! let stream = TcpStream::connect("google.com:443").unwrap(); +//! let mut stream = connector.connect("google.com", stream).unwrap(); +//! +//! stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); +//! let mut res = vec![]; +//! stream.read_to_end(&mut res).unwrap(); +//! println!("{}", String::from_utf8_lossy(&res)); +//! ``` +//! +//! To accept connections as a server from remote clients: +//! +//! ```no_run +//! use openssl::ssl::{SslMethod, SslAcceptor, SslStream, SslFiletype}; +//! use std::net::{TcpListener, TcpStream}; +//! use std::sync::Arc; +//! use std::thread; +//! +//! +//! let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); +//! acceptor.set_private_key_file("key.pem", SslFiletype::PEM).unwrap(); +//! acceptor.set_certificate_chain_file("certs.pem").unwrap(); +//! acceptor.check_private_key().unwrap(); +//! let acceptor = Arc::new(acceptor.build()); +//! +//! let listener = TcpListener::bind("0.0.0.0:8443").unwrap(); +//! +//! fn handle_client(stream: SslStream) { +//! // ... +//! } +//! +//! for stream in listener.incoming() { +//! match stream { +//! Ok(stream) => { +//! let acceptor = acceptor.clone(); +//! thread::spawn(move || { +//! let stream = acceptor.accept(stream).unwrap(); +//! handle_client(stream); +//! }); +//! } +//! Err(e) => { /* connection failed */ } +//! } +//! } +//! ``` +use crate::dh::{Dh, DhRef}; +#[cfg(all(ossl101, not(ossl110)))] +use crate::ec::EcKey; +use crate::ec::EcKeyRef; +use crate::error::ErrorStack; +use crate::ex_data::Index; +#[cfg(ossl111)] +use crate::hash::MessageDigest; +#[cfg(any(ossl110, libressl270))] +use crate::nid::Nid; +use crate::pkey::{HasPrivate, PKeyRef, Params, Private}; +use crate::srtp::{SrtpProtectionProfile, SrtpProtectionProfileRef}; +use crate::ssl::bio::BioMethod; +use crate::ssl::callbacks::*; +use crate::ssl::error::InnerError; +use crate::stack::{Stack, StackRef, Stackable}; +use crate::util::{ForeignTypeExt, ForeignTypeRefExt}; +use crate::x509::store::{X509Store, X509StoreBuilderRef, X509StoreRef}; +#[cfg(any(ossl102, libressl261))] +use crate::x509::verify::X509VerifyParamRef; +use crate::x509::{X509Name, X509Ref, X509StoreContextRef, X509VerifyResult, X509}; +use crate::{cvt, cvt_n, cvt_p, init}; +use bitflags::bitflags; +use cfg_if::cfg_if; +use foreign_types::{ForeignType, ForeignTypeRef, Opaque}; +use libc::{c_char, c_int, c_long, c_uchar, c_uint, c_void}; +use once_cell::sync::{Lazy, OnceCell}; +use openssl_macros::corresponds; +use std::any::TypeId; +use std::cmp; +use std::collections::HashMap; +use std::ffi::{CStr, CString}; +use std::fmt; +use std::io; +use std::io::prelude::*; +use std::marker::PhantomData; +use std::mem::{self, ManuallyDrop}; +use std::ops::{Deref, DerefMut}; +use std::panic::resume_unwind; +use std::path::Path; +use std::ptr; +use std::slice; +use std::str; +use std::sync::{Arc, Mutex}; + +pub use crate::ssl::connector::{ + ConnectConfiguration, SslAcceptor, SslAcceptorBuilder, SslConnector, SslConnectorBuilder, +}; +pub use crate::ssl::error::{Error, ErrorCode, HandshakeError}; + +mod bio; +mod callbacks; +mod connector; +mod error; +#[cfg(test)] +mod test; + +/// Returns the OpenSSL name of a cipher corresponding to an RFC-standard cipher name. +/// +/// If the cipher has no corresponding OpenSSL name, the string `(NONE)` is returned. +/// +/// Requires OpenSSL 1.1.1 or newer. +#[corresponds(OPENSSL_cipher_name)] +#[cfg(ossl111)] +pub fn cipher_name(std_name: &str) -> &'static str { + unsafe { + ffi::init(); + + let s = CString::new(std_name).unwrap(); + let ptr = ffi::OPENSSL_cipher_name(s.as_ptr()); + CStr::from_ptr(ptr).to_str().unwrap() + } +} + +cfg_if! { + if #[cfg(ossl300)] { + type SslOptionsRepr = u64; + } else if #[cfg(boringssl)] { + type SslOptionsRepr = u32; + } else { + type SslOptionsRepr = libc::c_ulong; + } +} + +bitflags! { + /// Options controlling the behavior of an `SslContext`. + #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + #[repr(transparent)] + pub struct SslOptions: SslOptionsRepr { + /// Disables a countermeasure against an SSLv3/TLSv1.0 vulnerability affecting CBC ciphers. + const DONT_INSERT_EMPTY_FRAGMENTS = ffi::SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS as SslOptionsRepr; + + /// A "reasonable default" set of options which enables compatibility flags. + #[cfg(not(boringssl))] + const ALL = ffi::SSL_OP_ALL as SslOptionsRepr; + + /// Do not query the MTU. + /// + /// Only affects DTLS connections. + const NO_QUERY_MTU = ffi::SSL_OP_NO_QUERY_MTU as SslOptionsRepr; + + /// Enables Cookie Exchange as described in [RFC 4347 Section 4.2.1]. + /// + /// Only affects DTLS connections. + /// + /// [RFC 4347 Section 4.2.1]: https://tools.ietf.org/html/rfc4347#section-4.2.1 + #[cfg(not(boringssl))] + const COOKIE_EXCHANGE = ffi::SSL_OP_COOKIE_EXCHANGE as SslOptionsRepr; + + /// Disables the use of session tickets for session resumption. + const NO_TICKET = ffi::SSL_OP_NO_TICKET as SslOptionsRepr; + + /// Always start a new session when performing a renegotiation on the server side. + #[cfg(not(boringssl))] + const NO_SESSION_RESUMPTION_ON_RENEGOTIATION = + ffi::SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION as SslOptionsRepr; + + /// Disables the use of TLS compression. + #[cfg(not(boringssl))] + const NO_COMPRESSION = ffi::SSL_OP_NO_COMPRESSION as SslOptionsRepr; + + /// Allow legacy insecure renegotiation with servers or clients that do not support secure + /// renegotiation. + const ALLOW_UNSAFE_LEGACY_RENEGOTIATION = + ffi::SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION as SslOptionsRepr; + + /// Creates a new key for each session when using ECDHE. + /// + /// This is always enabled in OpenSSL 1.1.0. + const SINGLE_ECDH_USE = ffi::SSL_OP_SINGLE_ECDH_USE as SslOptionsRepr; + + /// Creates a new key for each session when using DHE. + /// + /// This is always enabled in OpenSSL 1.1.0. + const SINGLE_DH_USE = ffi::SSL_OP_SINGLE_DH_USE as SslOptionsRepr; + + /// Use the server's preferences rather than the client's when selecting a cipher. + /// + /// This has no effect on the client side. + const CIPHER_SERVER_PREFERENCE = ffi::SSL_OP_CIPHER_SERVER_PREFERENCE as SslOptionsRepr; + + /// Disables version rollback attach detection. + const TLS_ROLLBACK_BUG = ffi::SSL_OP_TLS_ROLLBACK_BUG as SslOptionsRepr; + + /// Disables the use of SSLv2. + const NO_SSLV2 = ffi::SSL_OP_NO_SSLv2 as SslOptionsRepr; + + /// Disables the use of SSLv3. + const NO_SSLV3 = ffi::SSL_OP_NO_SSLv3 as SslOptionsRepr; + + /// Disables the use of TLSv1.0. + const NO_TLSV1 = ffi::SSL_OP_NO_TLSv1 as SslOptionsRepr; + + /// Disables the use of TLSv1.1. + const NO_TLSV1_1 = ffi::SSL_OP_NO_TLSv1_1 as SslOptionsRepr; + + /// Disables the use of TLSv1.2. + const NO_TLSV1_2 = ffi::SSL_OP_NO_TLSv1_2 as SslOptionsRepr; + + /// Disables the use of TLSv1.3. + /// + /// Requires OpenSSL 1.1.1 or LibreSSL 3.4.0 or newer. + #[cfg(any(boringssl, ossl111, libressl340))] + const NO_TLSV1_3 = ffi::SSL_OP_NO_TLSv1_3 as SslOptionsRepr; + + /// Disables the use of DTLSv1.0 + /// + /// Requires OpenSSL 1.0.2 or LibreSSL 3.3.2 or newer. + #[cfg(any(boringssl, ossl102, ossl110, libressl332))] + const NO_DTLSV1 = ffi::SSL_OP_NO_DTLSv1 as SslOptionsRepr; + + /// Disables the use of DTLSv1.2. + /// + /// Requires OpenSSL 1.0.2 or LibreSSL 3.3.2 or newer. + #[cfg(any(boringssl, ossl102, ossl110, libressl332))] + const NO_DTLSV1_2 = ffi::SSL_OP_NO_DTLSv1_2 as SslOptionsRepr; + + /// Disables the use of all (D)TLS protocol versions. + /// + /// This can be used as a mask when whitelisting protocol versions. + /// + /// Requires OpenSSL 1.0.2 or newer. + /// + /// # Examples + /// + /// Only support TLSv1.2: + /// + /// ```rust + /// use openssl::ssl::SslOptions; + /// + /// let options = SslOptions::NO_SSL_MASK & !SslOptions::NO_TLSV1_2; + /// ``` + #[cfg(any(ossl102, ossl110))] + const NO_SSL_MASK = ffi::SSL_OP_NO_SSL_MASK as SslOptionsRepr; + + /// Disallow all renegotiation in TLSv1.2 and earlier. + /// + /// Requires OpenSSL 1.1.0h or newer. + #[cfg(any(boringssl, ossl110h))] + const NO_RENEGOTIATION = ffi::SSL_OP_NO_RENEGOTIATION as SslOptionsRepr; + + /// Enable TLSv1.3 Compatibility mode. + /// + /// Requires OpenSSL 1.1.1 or newer. This is on by default in 1.1.1, but a future version + /// may have this disabled by default. + #[cfg(ossl111)] + const ENABLE_MIDDLEBOX_COMPAT = ffi::SSL_OP_ENABLE_MIDDLEBOX_COMPAT as SslOptionsRepr; + + /// Prioritize ChaCha ciphers when preferred by clients. + /// + /// Temporarily reprioritize ChaCha20-Poly1305 ciphers to the top of the server cipher list + /// if a ChaCha20-Poly1305 cipher is at the top of the client cipher list. This helps those + /// clients (e.g. mobile) use ChaCha20-Poly1305 if that cipher is anywhere in the server + /// cipher list; but still allows other clients to use AES and other ciphers. + /// + /// Requires enable [`SslOptions::CIPHER_SERVER_PREFERENCE`]. + /// Requires OpenSSL 1.1.1 or newer. + /// + /// [`SslOptions::CIPHER_SERVER_PREFERENCE`]: struct.SslOptions.html#associatedconstant.CIPHER_SERVER_PREFERENCE + #[cfg(ossl111)] + const PRIORITIZE_CHACHA = ffi::SSL_OP_PRIORITIZE_CHACHA as SslOptionsRepr; + } +} + +bitflags! { + /// Options controlling the behavior of an `SslContext`. + #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + #[repr(transparent)] + pub struct SslMode: SslBitType { + /// Enables "short writes". + /// + /// Normally, a write in OpenSSL will always write out all of the requested data, even if it + /// requires more than one TLS record or write to the underlying stream. This option will + /// cause a write to return after writing a single TLS record instead. + const ENABLE_PARTIAL_WRITE = ffi::SSL_MODE_ENABLE_PARTIAL_WRITE; + + /// Disables a check that the data buffer has not moved between calls when operating in a + /// non-blocking context. + const ACCEPT_MOVING_WRITE_BUFFER = ffi::SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER; + + /// Enables automatic retries after TLS session events such as renegotiations or heartbeats. + /// + /// By default, OpenSSL will return a `WantRead` error after a renegotiation or heartbeat. + /// This option will cause OpenSSL to automatically continue processing the requested + /// operation instead. + /// + /// Note that `SslStream::read` and `SslStream::write` will automatically retry regardless + /// of the state of this option. It only affects `SslStream::ssl_read` and + /// `SslStream::ssl_write`. + const AUTO_RETRY = ffi::SSL_MODE_AUTO_RETRY; + + /// Disables automatic chain building when verifying a peer's certificate. + /// + /// TLS peers are responsible for sending the entire certificate chain from the leaf to a + /// trusted root, but some will incorrectly not do so. OpenSSL will try to build the chain + /// out of certificates it knows of, and this option will disable that behavior. + const NO_AUTO_CHAIN = ffi::SSL_MODE_NO_AUTO_CHAIN; + + /// Release memory buffers when the session does not need them. + /// + /// This saves ~34 KiB of memory for idle streams. + const RELEASE_BUFFERS = ffi::SSL_MODE_RELEASE_BUFFERS; + + /// Sends the fake `TLS_FALLBACK_SCSV` cipher suite in the ClientHello message of a + /// handshake. + /// + /// This should only be enabled if a client has failed to connect to a server which + /// attempted to downgrade the protocol version of the session. + /// + /// Do not use this unless you know what you're doing! + #[cfg(not(libressl))] + const SEND_FALLBACK_SCSV = ffi::SSL_MODE_SEND_FALLBACK_SCSV; + } +} + +/// A type specifying the kind of protocol an `SslContext` will speak. +#[derive(Copy, Clone)] +pub struct SslMethod(*const ffi::SSL_METHOD); + +impl SslMethod { + /// Support all versions of the TLS protocol. + #[corresponds(TLS_method)] + pub fn tls() -> SslMethod { + unsafe { SslMethod(TLS_method()) } + } + + /// Support all versions of the DTLS protocol. + #[corresponds(DTLS_method)] + pub fn dtls() -> SslMethod { + unsafe { SslMethod(DTLS_method()) } + } + + /// Support all versions of the TLS protocol, explicitly as a client. + #[corresponds(TLS_client_method)] + pub fn tls_client() -> SslMethod { + unsafe { SslMethod(TLS_client_method()) } + } + + /// Support all versions of the TLS protocol, explicitly as a server. + #[corresponds(TLS_server_method)] + pub fn tls_server() -> SslMethod { + unsafe { SslMethod(TLS_server_method()) } + } + + /// Constructs an `SslMethod` from a pointer to the underlying OpenSSL value. + /// + /// # Safety + /// + /// The caller must ensure the pointer is valid. + pub unsafe fn from_ptr(ptr: *const ffi::SSL_METHOD) -> SslMethod { + SslMethod(ptr) + } + + /// Returns a pointer to the underlying OpenSSL value. + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn as_ptr(&self) -> *const ffi::SSL_METHOD { + self.0 + } +} + +unsafe impl Sync for SslMethod {} +unsafe impl Send for SslMethod {} + +bitflags! { + /// Options controlling the behavior of certificate verification. + #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + #[repr(transparent)] + pub struct SslVerifyMode: i32 { + /// Verifies that the peer's certificate is trusted. + /// + /// On the server side, this will cause OpenSSL to request a certificate from the client. + const PEER = ffi::SSL_VERIFY_PEER; + + /// Disables verification of the peer's certificate. + /// + /// On the server side, this will cause OpenSSL to not request a certificate from the + /// client. On the client side, the certificate will be checked for validity, but the + /// negotiation will continue regardless of the result of that check. + const NONE = ffi::SSL_VERIFY_NONE; + + /// On the server side, abort the handshake if the client did not send a certificate. + /// + /// This should be paired with `SSL_VERIFY_PEER`. It has no effect on the client side. + const FAIL_IF_NO_PEER_CERT = ffi::SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + } +} + +#[cfg(boringssl)] +type SslBitType = c_int; +#[cfg(not(boringssl))] +type SslBitType = c_long; + +#[cfg(boringssl)] +type SslTimeTy = u64; +#[cfg(not(boringssl))] +type SslTimeTy = c_long; + +bitflags! { + /// Options controlling the behavior of session caching. + #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + #[repr(transparent)] + pub struct SslSessionCacheMode: SslBitType { + /// No session caching for the client or server takes place. + const OFF = ffi::SSL_SESS_CACHE_OFF; + + /// Enable session caching on the client side. + /// + /// OpenSSL has no way of identifying the proper session to reuse automatically, so the + /// application is responsible for setting it explicitly via [`SslRef::set_session`]. + /// + /// [`SslRef::set_session`]: struct.SslRef.html#method.set_session + const CLIENT = ffi::SSL_SESS_CACHE_CLIENT; + + /// Enable session caching on the server side. + /// + /// This is the default mode. + const SERVER = ffi::SSL_SESS_CACHE_SERVER; + + /// Enable session caching on both the client and server side. + const BOTH = ffi::SSL_SESS_CACHE_BOTH; + + /// Disable automatic removal of expired sessions from the session cache. + const NO_AUTO_CLEAR = ffi::SSL_SESS_CACHE_NO_AUTO_CLEAR; + + /// Disable use of the internal session cache for session lookups. + const NO_INTERNAL_LOOKUP = ffi::SSL_SESS_CACHE_NO_INTERNAL_LOOKUP; + + /// Disable use of the internal session cache for session storage. + const NO_INTERNAL_STORE = ffi::SSL_SESS_CACHE_NO_INTERNAL_STORE; + + /// Disable use of the internal session cache for storage and lookup. + const NO_INTERNAL = ffi::SSL_SESS_CACHE_NO_INTERNAL; + } +} + +#[cfg(ossl111)] +bitflags! { + /// Which messages and under which conditions an extension should be added or expected. + #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + #[repr(transparent)] + pub struct ExtensionContext: c_uint { + /// This extension is only allowed in TLS + const TLS_ONLY = ffi::SSL_EXT_TLS_ONLY; + /// This extension is only allowed in DTLS + const DTLS_ONLY = ffi::SSL_EXT_DTLS_ONLY; + /// Some extensions may be allowed in DTLS but we don't implement them for it + const TLS_IMPLEMENTATION_ONLY = ffi::SSL_EXT_TLS_IMPLEMENTATION_ONLY; + /// Most extensions are not defined for SSLv3 but EXT_TYPE_renegotiate is + const SSL3_ALLOWED = ffi::SSL_EXT_SSL3_ALLOWED; + /// Extension is only defined for TLS1.2 and below + const TLS1_2_AND_BELOW_ONLY = ffi::SSL_EXT_TLS1_2_AND_BELOW_ONLY; + /// Extension is only defined for TLS1.3 and above + const TLS1_3_ONLY = ffi::SSL_EXT_TLS1_3_ONLY; + /// Ignore this extension during parsing if we are resuming + const IGNORE_ON_RESUMPTION = ffi::SSL_EXT_IGNORE_ON_RESUMPTION; + const CLIENT_HELLO = ffi::SSL_EXT_CLIENT_HELLO; + /// Really means TLS1.2 or below + const TLS1_2_SERVER_HELLO = ffi::SSL_EXT_TLS1_2_SERVER_HELLO; + const TLS1_3_SERVER_HELLO = ffi::SSL_EXT_TLS1_3_SERVER_HELLO; + const TLS1_3_ENCRYPTED_EXTENSIONS = ffi::SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS; + const TLS1_3_HELLO_RETRY_REQUEST = ffi::SSL_EXT_TLS1_3_HELLO_RETRY_REQUEST; + const TLS1_3_CERTIFICATE = ffi::SSL_EXT_TLS1_3_CERTIFICATE; + const TLS1_3_NEW_SESSION_TICKET = ffi::SSL_EXT_TLS1_3_NEW_SESSION_TICKET; + const TLS1_3_CERTIFICATE_REQUEST = ffi::SSL_EXT_TLS1_3_CERTIFICATE_REQUEST; + } +} + +/// An identifier of the format of a certificate or key file. +#[derive(Copy, Clone)] +pub struct SslFiletype(c_int); + +impl SslFiletype { + /// The PEM format. + /// + /// This corresponds to `SSL_FILETYPE_PEM`. + pub const PEM: SslFiletype = SslFiletype(ffi::SSL_FILETYPE_PEM); + + /// The ASN1 format. + /// + /// This corresponds to `SSL_FILETYPE_ASN1`. + pub const ASN1: SslFiletype = SslFiletype(ffi::SSL_FILETYPE_ASN1); + + /// Constructs an `SslFiletype` from a raw OpenSSL value. + pub fn from_raw(raw: c_int) -> SslFiletype { + SslFiletype(raw) + } + + /// Returns the raw OpenSSL value represented by this type. + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn as_raw(&self) -> c_int { + self.0 + } +} + +/// An identifier of a certificate status type. +#[derive(Copy, Clone)] +pub struct StatusType(c_int); + +impl StatusType { + /// An OSCP status. + pub const OCSP: StatusType = StatusType(ffi::TLSEXT_STATUSTYPE_ocsp); + + /// Constructs a `StatusType` from a raw OpenSSL value. + pub fn from_raw(raw: c_int) -> StatusType { + StatusType(raw) + } + + /// Returns the raw OpenSSL value represented by this type. + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn as_raw(&self) -> c_int { + self.0 + } +} + +/// An identifier of a session name type. +#[derive(Copy, Clone)] +pub struct NameType(c_int); + +impl NameType { + /// A host name. + pub const HOST_NAME: NameType = NameType(ffi::TLSEXT_NAMETYPE_host_name); + + /// Constructs a `StatusType` from a raw OpenSSL value. + pub fn from_raw(raw: c_int) -> StatusType { + StatusType(raw) + } + + /// Returns the raw OpenSSL value represented by this type. + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn as_raw(&self) -> c_int { + self.0 + } +} + +static INDEXES: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); +static SSL_INDEXES: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); +static SESSION_CTX_INDEX: OnceCell> = OnceCell::new(); + +fn try_get_session_ctx_index() -> Result<&'static Index, ErrorStack> { + SESSION_CTX_INDEX.get_or_try_init(Ssl::new_ex_index) +} + +unsafe extern "C" fn free_data_box( + _parent: *mut c_void, + ptr: *mut c_void, + _ad: *mut ffi::CRYPTO_EX_DATA, + _idx: c_int, + _argl: c_long, + _argp: *mut c_void, +) { + if !ptr.is_null() { + let _ = Box::::from_raw(ptr as *mut T); + } +} + +/// An error returned from the SNI callback. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct SniError(c_int); + +impl SniError { + /// Abort the handshake with a fatal alert. + pub const ALERT_FATAL: SniError = SniError(ffi::SSL_TLSEXT_ERR_ALERT_FATAL); + + /// Send a warning alert to the client and continue the handshake. + pub const ALERT_WARNING: SniError = SniError(ffi::SSL_TLSEXT_ERR_ALERT_WARNING); + + pub const NOACK: SniError = SniError(ffi::SSL_TLSEXT_ERR_NOACK); +} + +/// An SSL/TLS alert. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct SslAlert(c_int); + +impl SslAlert { + /// Alert 112 - `unrecognized_name`. + pub const UNRECOGNIZED_NAME: SslAlert = SslAlert(ffi::SSL_AD_UNRECOGNIZED_NAME); + pub const ILLEGAL_PARAMETER: SslAlert = SslAlert(ffi::SSL_AD_ILLEGAL_PARAMETER); + pub const DECODE_ERROR: SslAlert = SslAlert(ffi::SSL_AD_DECODE_ERROR); +} + +/// An error returned from an ALPN selection callback. +/// +/// Requires OpenSSL 1.0.2 or LibreSSL 2.6.1 or newer. +#[cfg(any(ossl102, libressl261))] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct AlpnError(c_int); + +#[cfg(any(ossl102, libressl261))] +impl AlpnError { + /// Terminate the handshake with a fatal alert. + /// + /// Requires OpenSSL 1.1.0 or newer. + #[cfg(ossl110)] + pub const ALERT_FATAL: AlpnError = AlpnError(ffi::SSL_TLSEXT_ERR_ALERT_FATAL); + + /// Do not select a protocol, but continue the handshake. + pub const NOACK: AlpnError = AlpnError(ffi::SSL_TLSEXT_ERR_NOACK); +} + +/// The result of a client hello callback. +/// +/// Requires OpenSSL 1.1.1 or newer. +#[cfg(ossl111)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct ClientHelloResponse(c_int); + +#[cfg(ossl111)] +impl ClientHelloResponse { + /// Continue the handshake. + pub const SUCCESS: ClientHelloResponse = ClientHelloResponse(ffi::SSL_CLIENT_HELLO_SUCCESS); + + /// Return from the handshake with an `ErrorCode::WANT_CLIENT_HELLO_CB` error. + pub const RETRY: ClientHelloResponse = ClientHelloResponse(ffi::SSL_CLIENT_HELLO_RETRY); +} + +/// An SSL/TLS protocol version. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct SslVersion(c_int); + +impl SslVersion { + /// SSLv3 + pub const SSL3: SslVersion = SslVersion(ffi::SSL3_VERSION); + + /// TLSv1.0 + pub const TLS1: SslVersion = SslVersion(ffi::TLS1_VERSION); + + /// TLSv1.1 + pub const TLS1_1: SslVersion = SslVersion(ffi::TLS1_1_VERSION); + + /// TLSv1.2 + pub const TLS1_2: SslVersion = SslVersion(ffi::TLS1_2_VERSION); + + /// TLSv1.3 + /// + /// Requires OpenSSL 1.1.1 or LibreSSL 3.4.0 or newer. + #[cfg(any(ossl111, libressl340))] + pub const TLS1_3: SslVersion = SslVersion(ffi::TLS1_3_VERSION); + + /// DTLSv1.0 + /// + /// DTLS 1.0 corresponds to TLS 1.1. + pub const DTLS1: SslVersion = SslVersion(ffi::DTLS1_VERSION); + + /// DTLSv1.2 + /// + /// DTLS 1.2 corresponds to TLS 1.2 to harmonize versions. There was never a DTLS 1.1. + #[cfg(any(ossl102, libressl332))] + pub const DTLS1_2: SslVersion = SslVersion(ffi::DTLS1_2_VERSION); +} + +cfg_if! { + if #[cfg(boringssl)] { + type SslCacheTy = i64; + type SslCacheSize = libc::c_ulong; + type MtuTy = u32; + type SizeTy = usize; + } else { + type SslCacheTy = i64; + type SslCacheSize = c_long; + type MtuTy = c_long; + type SizeTy = u32; + } +} + +/// A standard implementation of protocol selection for Application Layer Protocol Negotiation +/// (ALPN). +/// +/// `server` should contain the server's list of supported protocols and `client` the client's. They +/// must both be in the ALPN wire format. See the documentation for +/// [`SslContextBuilder::set_alpn_protos`] for details. +/// +/// It will select the first protocol supported by the server which is also supported by the client. +/// +/// [`SslContextBuilder::set_alpn_protos`]: struct.SslContextBuilder.html#method.set_alpn_protos +#[corresponds(SSL_select_next_proto)] +pub fn select_next_proto<'a>(server: &[u8], client: &'a [u8]) -> Option<&'a [u8]> { + unsafe { + let mut out = ptr::null_mut(); + let mut outlen = 0; + let r = ffi::SSL_select_next_proto( + &mut out, + &mut outlen, + server.as_ptr(), + server.len() as c_uint, + client.as_ptr(), + client.len() as c_uint, + ); + if r == ffi::OPENSSL_NPN_NEGOTIATED { + Some(slice::from_raw_parts(out as *const u8, outlen as usize)) + } else { + None + } + } +} + +/// A builder for `SslContext`s. +pub struct SslContextBuilder(SslContext); + +impl SslContextBuilder { + /// Creates a new `SslContextBuilder`. + #[corresponds(SSL_CTX_new)] + pub fn new(method: SslMethod) -> Result { + unsafe { + init(); + let ctx = cvt_p(ffi::SSL_CTX_new(method.as_ptr()))?; + + Ok(SslContextBuilder::from_ptr(ctx)) + } + } + + /// Creates an `SslContextBuilder` from a pointer to a raw OpenSSL value. + /// + /// # Safety + /// + /// The caller must ensure that the pointer is valid and uniquely owned by the builder. + pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX) -> SslContextBuilder { + SslContextBuilder(SslContext::from_ptr(ctx)) + } + + /// Returns a pointer to the raw OpenSSL value. + pub fn as_ptr(&self) -> *mut ffi::SSL_CTX { + self.0.as_ptr() + } + + /// Configures the certificate verification method for new connections. + #[corresponds(SSL_CTX_set_verify)] + pub fn set_verify(&mut self, mode: SslVerifyMode) { + unsafe { + ffi::SSL_CTX_set_verify(self.as_ptr(), mode.bits() as c_int, None); + } + } + + /// Configures the certificate verification method for new connections and + /// registers a verification callback. + /// + /// The callback is passed a boolean indicating if OpenSSL's internal verification succeeded as + /// well as a reference to the `X509StoreContext` which can be used to examine the certificate + /// chain. It should return a boolean indicating if verification succeeded. + #[corresponds(SSL_CTX_set_verify)] + pub fn set_verify_callback(&mut self, mode: SslVerifyMode, verify: F) + where + F: Fn(bool, &mut X509StoreContextRef) -> bool + 'static + Sync + Send, + { + unsafe { + self.set_ex_data(SslContext::cached_ex_index::(), verify); + ffi::SSL_CTX_set_verify(self.as_ptr(), mode.bits() as c_int, Some(raw_verify::)); + } + } + + /// Configures the server name indication (SNI) callback for new connections. + /// + /// SNI is used to allow a single server to handle requests for multiple domains, each of which + /// has its own certificate chain and configuration. + /// + /// Obtain the server name with the `servername` method and then set the corresponding context + /// with `set_ssl_context` + #[corresponds(SSL_CTX_set_tlsext_servername_callback)] + // FIXME tlsext prefix? + pub fn set_servername_callback(&mut self, callback: F) + where + F: Fn(&mut SslRef, &mut SslAlert) -> Result<(), SniError> + 'static + Sync + Send, + { + unsafe { + // The SNI callback is somewhat unique in that the callback associated with the original + // context associated with an SSL can be used even if the SSL's context has been swapped + // out. When that happens, we wouldn't be able to look up the callback's state in the + // context's ex data. Instead, pass the pointer directly as the servername arg. It's + // still stored in ex data to manage the lifetime. + let arg = self.set_ex_data_inner(SslContext::cached_ex_index::(), callback); + ffi::SSL_CTX_set_tlsext_servername_arg(self.as_ptr(), arg); + #[cfg(boringssl)] + ffi::SSL_CTX_set_tlsext_servername_callback(self.as_ptr(), Some(raw_sni::)); + #[cfg(not(boringssl))] + ffi::SSL_CTX_set_tlsext_servername_callback__fixed_rust( + self.as_ptr(), + Some(raw_sni::), + ); + } + } + + /// Sets the certificate verification depth. + /// + /// If the peer's certificate chain is longer than this value, verification will fail. + #[corresponds(SSL_CTX_set_verify_depth)] + pub fn set_verify_depth(&mut self, depth: u32) { + unsafe { + ffi::SSL_CTX_set_verify_depth(self.as_ptr(), depth as c_int); + } + } + + /// Sets a custom certificate store for verifying peer certificates. + /// + /// Requires OpenSSL 1.0.2 or newer. + #[corresponds(SSL_CTX_set0_verify_cert_store)] + #[cfg(ossl102)] + pub fn set_verify_cert_store(&mut self, cert_store: X509Store) -> Result<(), ErrorStack> { + unsafe { + let ptr = cert_store.as_ptr(); + cvt(ffi::SSL_CTX_set0_verify_cert_store(self.as_ptr(), ptr) as c_int)?; + mem::forget(cert_store); + + Ok(()) + } + } + + /// Replaces the context's certificate store. + #[corresponds(SSL_CTX_set_cert_store)] + pub fn set_cert_store(&mut self, cert_store: X509Store) { + unsafe { + ffi::SSL_CTX_set_cert_store(self.as_ptr(), cert_store.as_ptr()); + mem::forget(cert_store); + } + } + + /// Controls read ahead behavior. + /// + /// If enabled, OpenSSL will read as much data as is available from the underlying stream, + /// instead of a single record at a time. + /// + /// It has no effect when used with DTLS. + #[corresponds(SSL_CTX_set_read_ahead)] + pub fn set_read_ahead(&mut self, read_ahead: bool) { + unsafe { + ffi::SSL_CTX_set_read_ahead(self.as_ptr(), read_ahead as SslBitType); + } + } + + /// Sets the mode used by the context, returning the previous mode. + #[corresponds(SSL_CTX_set_mode)] + pub fn set_mode(&mut self, mode: SslMode) -> SslMode { + unsafe { + let bits = ffi::SSL_CTX_set_mode(self.as_ptr(), mode.bits() as MtuTy) as SslBitType; + SslMode::from_bits_retain(bits) + } + } + + /// Sets the parameters to be used during ephemeral Diffie-Hellman key exchange. + #[corresponds(SSL_CTX_set_tmp_dh)] + pub fn set_tmp_dh(&mut self, dh: &DhRef) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::SSL_CTX_set_tmp_dh(self.as_ptr(), dh.as_ptr()) as c_int).map(|_| ()) } + } + + /// Sets the callback which will generate parameters to be used during ephemeral Diffie-Hellman + /// key exchange. + /// + /// The callback is provided with a reference to the `Ssl` for the session, as well as a boolean + /// indicating if the selected cipher is export-grade, and the key length. The export and key + /// length options are archaic and should be ignored in almost all cases. + #[corresponds(SSL_CTX_set_tmp_dh_callback)] + pub fn set_tmp_dh_callback(&mut self, callback: F) + where + F: Fn(&mut SslRef, bool, u32) -> Result, ErrorStack> + 'static + Sync + Send, + { + unsafe { + self.set_ex_data(SslContext::cached_ex_index::(), callback); + + #[cfg(not(boringssl))] + ffi::SSL_CTX_set_tmp_dh_callback__fixed_rust(self.as_ptr(), Some(raw_tmp_dh::)); + #[cfg(boringssl)] + ffi::SSL_CTX_set_tmp_dh_callback(self.as_ptr(), Some(raw_tmp_dh::)); + } + } + + /// Sets the parameters to be used during ephemeral elliptic curve Diffie-Hellman key exchange. + #[corresponds(SSL_CTX_set_tmp_ecdh)] + pub fn set_tmp_ecdh(&mut self, key: &EcKeyRef) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::SSL_CTX_set_tmp_ecdh(self.as_ptr(), key.as_ptr()) as c_int).map(|_| ()) } + } + + /// Sets the callback which will generate parameters to be used during ephemeral elliptic curve + /// Diffie-Hellman key exchange. + /// + /// The callback is provided with a reference to the `Ssl` for the session, as well as a boolean + /// indicating if the selected cipher is export-grade, and the key length. The export and key + /// length options are archaic and should be ignored in almost all cases. + /// + /// Requires OpenSSL 1.0.1 or 1.0.2. + #[corresponds(SSL_CTX_set_tmp_ecdh_callback)] + #[cfg(all(ossl101, not(ossl110)))] + #[deprecated(note = "this function leaks memory and does not exist on newer OpenSSL versions")] + pub fn set_tmp_ecdh_callback(&mut self, callback: F) + where + F: Fn(&mut SslRef, bool, u32) -> Result, ErrorStack> + 'static + Sync + Send, + { + unsafe { + self.set_ex_data(SslContext::cached_ex_index::(), callback); + ffi::SSL_CTX_set_tmp_ecdh_callback__fixed_rust(self.as_ptr(), Some(raw_tmp_ecdh::)); + } + } + + /// Use the default locations of trusted certificates for verification. + /// + /// These locations are read from the `SSL_CERT_FILE` and `SSL_CERT_DIR` environment variables + /// if present, or defaults specified at OpenSSL build time otherwise. + #[corresponds(SSL_CTX_set_default_verify_paths)] + pub fn set_default_verify_paths(&mut self) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::SSL_CTX_set_default_verify_paths(self.as_ptr())).map(|_| ()) } + } + + /// Loads trusted root certificates from a file. + /// + /// The file should contain a sequence of PEM-formatted CA certificates. + #[corresponds(SSL_CTX_load_verify_locations)] + pub fn set_ca_file>(&mut self, file: P) -> Result<(), ErrorStack> { + let file = CString::new(file.as_ref().as_os_str().to_str().unwrap()).unwrap(); + unsafe { + cvt(ffi::SSL_CTX_load_verify_locations( + self.as_ptr(), + file.as_ptr() as *const _, + ptr::null(), + )) + .map(|_| ()) + } + } + + /// Sets the list of CA names sent to the client. + /// + /// The CA certificates must still be added to the trust root - they are not automatically set + /// as trusted by this method. + #[corresponds(SSL_CTX_set_client_CA_list)] + pub fn set_client_ca_list(&mut self, list: Stack) { + unsafe { + ffi::SSL_CTX_set_client_CA_list(self.as_ptr(), list.as_ptr()); + mem::forget(list); + } + } + + /// Add the provided CA certificate to the list sent by the server to the client when + /// requesting client-side TLS authentication. + #[corresponds(SSL_CTX_add_client_CA)] + pub fn add_client_ca(&mut self, cacert: &X509Ref) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::SSL_CTX_add_client_CA(self.as_ptr(), cacert.as_ptr())).map(|_| ()) } + } + + /// Set the context identifier for sessions. + /// + /// This value identifies the server's session cache to clients, telling them when they're + /// able to reuse sessions. It should be set to a unique value per server, unless multiple + /// servers share a session cache. + /// + /// This value should be set when using client certificates, or each request will fail its + /// handshake and need to be restarted. + #[corresponds(SSL_CTX_set_session_id_context)] + pub fn set_session_id_context(&mut self, sid_ctx: &[u8]) -> Result<(), ErrorStack> { + unsafe { + assert!(sid_ctx.len() <= c_uint::max_value() as usize); + cvt(ffi::SSL_CTX_set_session_id_context( + self.as_ptr(), + sid_ctx.as_ptr(), + sid_ctx.len() as SizeTy, + )) + .map(|_| ()) + } + } + + /// Loads a leaf certificate from a file. + /// + /// Only a single certificate will be loaded - use `add_extra_chain_cert` to add the remainder + /// of the certificate chain, or `set_certificate_chain_file` to load the entire chain from a + /// single file. + #[corresponds(SSL_CTX_use_certificate_file)] + pub fn set_certificate_file>( + &mut self, + file: P, + file_type: SslFiletype, + ) -> Result<(), ErrorStack> { + let file = CString::new(file.as_ref().as_os_str().to_str().unwrap()).unwrap(); + unsafe { + cvt(ffi::SSL_CTX_use_certificate_file( + self.as_ptr(), + file.as_ptr() as *const _, + file_type.as_raw(), + )) + .map(|_| ()) + } + } + + /// Loads a certificate chain from a file. + /// + /// The file should contain a sequence of PEM-formatted certificates, the first being the leaf + /// certificate, and the remainder forming the chain of certificates up to and including the + /// trusted root certificate. + #[corresponds(SSL_CTX_use_certificate_chain_file)] + pub fn set_certificate_chain_file>( + &mut self, + file: P, + ) -> Result<(), ErrorStack> { + let file = CString::new(file.as_ref().as_os_str().to_str().unwrap()).unwrap(); + unsafe { + cvt(ffi::SSL_CTX_use_certificate_chain_file( + self.as_ptr(), + file.as_ptr() as *const _, + )) + .map(|_| ()) + } + } + + /// Sets the leaf certificate. + /// + /// Use `add_extra_chain_cert` to add the remainder of the certificate chain. + #[corresponds(SSL_CTX_use_certificate)] + pub fn set_certificate(&mut self, cert: &X509Ref) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::SSL_CTX_use_certificate(self.as_ptr(), cert.as_ptr())).map(|_| ()) } + } + + /// Appends a certificate to the certificate chain. + /// + /// This chain should contain all certificates necessary to go from the certificate specified by + /// `set_certificate` to a trusted root. + #[corresponds(SSL_CTX_add_extra_chain_cert)] + pub fn add_extra_chain_cert(&mut self, cert: X509) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::SSL_CTX_add_extra_chain_cert(self.as_ptr(), cert.as_ptr()) as c_int)?; + mem::forget(cert); + Ok(()) + } + } + + /// Loads the private key from a file. + #[corresponds(SSL_CTX_use_PrivateKey_file)] + pub fn set_private_key_file>( + &mut self, + file: P, + file_type: SslFiletype, + ) -> Result<(), ErrorStack> { + let file = CString::new(file.as_ref().as_os_str().to_str().unwrap()).unwrap(); + unsafe { + cvt(ffi::SSL_CTX_use_PrivateKey_file( + self.as_ptr(), + file.as_ptr() as *const _, + file_type.as_raw(), + )) + .map(|_| ()) + } + } + + /// Sets the private key. + #[corresponds(SSL_CTX_use_PrivateKey)] + pub fn set_private_key(&mut self, key: &PKeyRef) -> Result<(), ErrorStack> + where + T: HasPrivate, + { + unsafe { cvt(ffi::SSL_CTX_use_PrivateKey(self.as_ptr(), key.as_ptr())).map(|_| ()) } + } + + /// Sets the list of supported ciphers for protocols before TLSv1.3. + /// + /// The `set_ciphersuites` method controls the cipher suites for TLSv1.3. + /// + /// See [`ciphers`] for details on the format. + /// + /// [`ciphers`]: https://www.openssl.org/docs/manmaster/apps/ciphers.html + #[corresponds(SSL_CTX_set_cipher_list)] + pub fn set_cipher_list(&mut self, cipher_list: &str) -> Result<(), ErrorStack> { + let cipher_list = CString::new(cipher_list).unwrap(); + unsafe { + cvt(ffi::SSL_CTX_set_cipher_list( + self.as_ptr(), + cipher_list.as_ptr() as *const _, + )) + .map(|_| ()) + } + } + + /// Sets the list of supported ciphers for the TLSv1.3 protocol. + /// + /// The `set_cipher_list` method controls the cipher suites for protocols before TLSv1.3. + /// + /// The format consists of TLSv1.3 cipher suite names separated by `:` characters in order of + /// preference. + /// + /// Requires OpenSSL 1.1.1 or LibreSSL 3.4.0 or newer. + #[corresponds(SSL_CTX_set_ciphersuites)] + #[cfg(any(ossl111, libressl340))] + pub fn set_ciphersuites(&mut self, cipher_list: &str) -> Result<(), ErrorStack> { + let cipher_list = CString::new(cipher_list).unwrap(); + unsafe { + cvt(ffi::SSL_CTX_set_ciphersuites( + self.as_ptr(), + cipher_list.as_ptr() as *const _, + )) + .map(|_| ()) + } + } + + /// Enables ECDHE key exchange with an automatically chosen curve list. + /// + /// Requires OpenSSL 1.0.2. + #[corresponds(SSL_CTX_set_ecdh_auto)] + #[cfg(any(libressl, all(ossl102, not(ossl110))))] + pub fn set_ecdh_auto(&mut self, onoff: bool) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::SSL_CTX_set_ecdh_auto(self.as_ptr(), onoff as c_int)).map(|_| ()) } + } + + /// Sets the options used by the context, returning the old set. + /// + /// # Note + /// + /// This *enables* the specified options, but does not disable unspecified options. Use + /// `clear_options` for that. + #[corresponds(SSL_CTX_set_options)] + pub fn set_options(&mut self, option: SslOptions) -> SslOptions { + let bits = + unsafe { ffi::SSL_CTX_set_options(self.as_ptr(), option.bits()) } as SslOptionsRepr; + SslOptions::from_bits_retain(bits) + } + + /// Returns the options used by the context. + #[corresponds(SSL_CTX_get_options)] + pub fn options(&self) -> SslOptions { + let bits = unsafe { ffi::SSL_CTX_get_options(self.as_ptr()) } as SslOptionsRepr; + SslOptions::from_bits_retain(bits) + } + + /// Clears the options used by the context, returning the old set. + #[corresponds(SSL_CTX_clear_options)] + pub fn clear_options(&mut self, option: SslOptions) -> SslOptions { + let bits = + unsafe { ffi::SSL_CTX_clear_options(self.as_ptr(), option.bits()) } as SslOptionsRepr; + SslOptions::from_bits_retain(bits) + } + + /// Sets the minimum supported protocol version. + /// + /// A value of `None` will enable protocol versions down to the lowest version supported by + /// OpenSSL. + /// + /// Requires OpenSSL 1.1.0 or LibreSSL 2.6.1 or newer. + #[corresponds(SSL_CTX_set_min_proto_version)] + #[cfg(any(ossl110, libressl261))] + pub fn set_min_proto_version(&mut self, version: Option) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::SSL_CTX_set_min_proto_version( + self.as_ptr(), + version.map_or(0, |v| v.0 as _), + )) + .map(|_| ()) + } + } + + /// Sets the maximum supported protocol version. + /// + /// A value of `None` will enable protocol versions up to the highest version supported by + /// OpenSSL. + /// + /// Requires OpenSSL 1.1.0 or or LibreSSL 2.6.1 or newer. + #[corresponds(SSL_CTX_set_max_proto_version)] + #[cfg(any(ossl110, libressl261))] + pub fn set_max_proto_version(&mut self, version: Option) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::SSL_CTX_set_max_proto_version( + self.as_ptr(), + version.map_or(0, |v| v.0 as _), + )) + .map(|_| ()) + } + } + + /// Gets the minimum supported protocol version. + /// + /// A value of `None` indicates that all versions down to the lowest version supported by + /// OpenSSL are enabled. + /// + /// Requires OpenSSL 1.1.0g or LibreSSL 2.7.0 or newer. + #[corresponds(SSL_CTX_get_min_proto_version)] + #[cfg(any(ossl110g, libressl270))] + pub fn min_proto_version(&mut self) -> Option { + unsafe { + let r = ffi::SSL_CTX_get_min_proto_version(self.as_ptr()); + if r == 0 { + None + } else { + Some(SslVersion(r)) + } + } + } + + /// Gets the maximum supported protocol version. + /// + /// A value of `None` indicates that all versions up to the highest version supported by + /// OpenSSL are enabled. + /// + /// Requires OpenSSL 1.1.0g or LibreSSL 2.7.0 or newer. + #[corresponds(SSL_CTX_get_max_proto_version)] + #[cfg(any(ossl110g, libressl270))] + pub fn max_proto_version(&mut self) -> Option { + unsafe { + let r = ffi::SSL_CTX_get_max_proto_version(self.as_ptr()); + if r == 0 { + None + } else { + Some(SslVersion(r)) + } + } + } + + /// Sets the protocols to sent to the server for Application Layer Protocol Negotiation (ALPN). + /// + /// The input must be in ALPN "wire format". It consists of a sequence of supported protocol + /// names prefixed by their byte length. For example, the protocol list consisting of `spdy/1` + /// and `http/1.1` is encoded as `b"\x06spdy/1\x08http/1.1"`. The protocols are ordered by + /// preference. + /// + /// Requires OpenSSL 1.0.2 or LibreSSL 2.6.1 or newer. + #[corresponds(SSL_CTX_set_alpn_protos)] + #[cfg(any(ossl102, libressl261))] + pub fn set_alpn_protos(&mut self, protocols: &[u8]) -> Result<(), ErrorStack> { + unsafe { + assert!(protocols.len() <= c_uint::max_value() as usize); + let r = ffi::SSL_CTX_set_alpn_protos( + self.as_ptr(), + protocols.as_ptr(), + protocols.len() as c_uint, + ); + // fun fact, SSL_CTX_set_alpn_protos has a reversed return code D: + if r == 0 { + Ok(()) + } else { + Err(ErrorStack::get()) + } + } + } + + /// Enables the DTLS extension "use_srtp" as defined in RFC5764. + #[corresponds(SSL_CTX_set_tlsext_use_srtp)] + 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). + /// + /// The callback is provided with the client's protocol list in ALPN wire format. See the + /// documentation for [`SslContextBuilder::set_alpn_protos`] for details. It should return one + /// of those protocols on success. The [`select_next_proto`] function implements the standard + /// protocol selection algorithm. + /// + /// Requires OpenSSL 1.0.2 or LibreSSL 2.6.1 or newer. + /// + /// [`SslContextBuilder::set_alpn_protos`]: struct.SslContextBuilder.html#method.set_alpn_protos + /// [`select_next_proto`]: fn.select_next_proto.html + #[corresponds(SSL_CTX_set_alpn_select_cb)] + #[cfg(any(ossl102, libressl261))] + pub fn set_alpn_select_callback(&mut self, callback: F) + where + F: for<'a> Fn(&mut SslRef, &'a [u8]) -> Result<&'a [u8], AlpnError> + 'static + Sync + Send, + { + unsafe { + self.set_ex_data(SslContext::cached_ex_index::(), callback); + ffi::SSL_CTX_set_alpn_select_cb__fixed_rust( + self.as_ptr(), + Some(callbacks::raw_alpn_select::), + ptr::null_mut(), + ); + } + } + + /// Checks for consistency between the private key and certificate. + #[corresponds(SSL_CTX_check_private_key)] + pub fn check_private_key(&self) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::SSL_CTX_check_private_key(self.as_ptr())).map(|_| ()) } + } + + /// Returns a shared reference to the context's certificate store. + #[corresponds(SSL_CTX_get_cert_store)] + pub fn cert_store(&self) -> &X509StoreBuilderRef { + unsafe { X509StoreBuilderRef::from_ptr(ffi::SSL_CTX_get_cert_store(self.as_ptr())) } + } + + /// Returns a mutable reference to the context's certificate store. + #[corresponds(SSL_CTX_get_cert_store)] + pub fn cert_store_mut(&mut self) -> &mut X509StoreBuilderRef { + unsafe { X509StoreBuilderRef::from_ptr_mut(ffi::SSL_CTX_get_cert_store(self.as_ptr())) } + } + + /// Returns a reference to the X509 verification configuration. + /// + /// Requires OpenSSL 1.0.2 or newer. + #[corresponds(SSL_CTX_get0_param)] + #[cfg(any(ossl102, libressl261))] + pub fn verify_param(&self) -> &X509VerifyParamRef { + unsafe { X509VerifyParamRef::from_ptr(ffi::SSL_CTX_get0_param(self.as_ptr())) } + } + + /// Returns a mutable reference to the X509 verification configuration. + /// + /// Requires OpenSSL 1.0.2 or newer. + #[corresponds(SSL_CTX_get0_param)] + #[cfg(any(ossl102, libressl261))] + pub fn verify_param_mut(&mut self) -> &mut X509VerifyParamRef { + unsafe { X509VerifyParamRef::from_ptr_mut(ffi::SSL_CTX_get0_param(self.as_ptr())) } + } + + /// Sets the callback dealing with OCSP stapling. + /// + /// On the client side, this callback is responsible for validating the OCSP status response + /// returned by the server. The status may be retrieved with the `SslRef::ocsp_status` method. + /// A response of `Ok(true)` indicates that the OCSP status is valid, and a response of + /// `Ok(false)` indicates that the OCSP status is invalid and the handshake should be + /// terminated. + /// + /// On the server side, this callback is responsible for setting the OCSP status response to be + /// returned to clients. The status may be set with the `SslRef::set_ocsp_status` method. A + /// response of `Ok(true)` indicates that the OCSP status should be returned to the client, and + /// `Ok(false)` indicates that the status should not be returned to the client. + #[corresponds(SSL_CTX_set_tlsext_status_cb)] + pub fn set_status_callback(&mut self, callback: F) -> Result<(), ErrorStack> + where + F: Fn(&mut SslRef) -> Result + 'static + Sync + Send, + { + unsafe { + self.set_ex_data(SslContext::cached_ex_index::(), callback); + cvt( + ffi::SSL_CTX_set_tlsext_status_cb(self.as_ptr(), Some(raw_tlsext_status::)) + as c_int, + ) + .map(|_| ()) + } + } + + /// Sets the callback for providing an identity and pre-shared key for a TLS-PSK client. + /// + /// The callback will be called with the SSL context, an identity hint if one was provided + /// by the server, a mutable slice for each of the identity and pre-shared key bytes. The + /// identity must be written as a null-terminated C string. + #[corresponds(SSL_CTX_set_psk_client_callback)] + #[cfg(not(osslconf = "OPENSSL_NO_PSK"))] + pub fn set_psk_client_callback(&mut self, callback: F) + where + F: Fn(&mut SslRef, Option<&[u8]>, &mut [u8], &mut [u8]) -> Result + + 'static + + Sync + + Send, + { + unsafe { + self.set_ex_data(SslContext::cached_ex_index::(), callback); + ffi::SSL_CTX_set_psk_client_callback(self.as_ptr(), Some(raw_client_psk::)); + } + } + + #[deprecated(since = "0.10.10", note = "renamed to `set_psk_client_callback`")] + #[cfg(not(osslconf = "OPENSSL_NO_PSK"))] + pub fn set_psk_callback(&mut self, callback: F) + where + F: Fn(&mut SslRef, Option<&[u8]>, &mut [u8], &mut [u8]) -> Result + + 'static + + Sync + + Send, + { + self.set_psk_client_callback(callback) + } + + /// Sets the callback for providing an identity and pre-shared key for a TLS-PSK server. + /// + /// The callback will be called with the SSL context, an identity provided by the client, + /// and, a mutable slice for the pre-shared key bytes. The callback returns the number of + /// bytes in the pre-shared key. + #[corresponds(SSL_CTX_set_psk_server_callback)] + #[cfg(not(osslconf = "OPENSSL_NO_PSK"))] + pub fn set_psk_server_callback(&mut self, callback: F) + where + F: Fn(&mut SslRef, Option<&[u8]>, &mut [u8]) -> Result + + 'static + + Sync + + Send, + { + unsafe { + self.set_ex_data(SslContext::cached_ex_index::(), callback); + ffi::SSL_CTX_set_psk_server_callback(self.as_ptr(), Some(raw_server_psk::)); + } + } + + /// Sets the callback which is called when new sessions are negotiated. + /// + /// This can be used by clients to implement session caching. While in TLSv1.2 the session is + /// available to access via [`SslRef::session`] immediately after the handshake completes, this + /// is not the case for TLSv1.3. There, a session is not generally available immediately, and + /// the server may provide multiple session tokens to the client over a single session. The new + /// session callback is a portable way to deal with both cases. + /// + /// Note that session caching must be enabled for the callback to be invoked, and it defaults + /// off for clients. [`set_session_cache_mode`] controls that behavior. + /// + /// [`SslRef::session`]: struct.SslRef.html#method.session + /// [`set_session_cache_mode`]: #method.set_session_cache_mode + #[corresponds(SSL_CTX_sess_set_new_cb)] + pub fn set_new_session_callback(&mut self, callback: F) + where + F: Fn(&mut SslRef, SslSession) + 'static + Sync + Send, + { + unsafe { + self.set_ex_data(SslContext::cached_ex_index::(), callback); + ffi::SSL_CTX_sess_set_new_cb(self.as_ptr(), Some(callbacks::raw_new_session::)); + } + } + + /// Sets the callback which is called when sessions are removed from the context. + /// + /// Sessions can be removed because they have timed out or because they are considered faulty. + #[corresponds(SSL_CTX_sess_set_remove_cb)] + pub fn set_remove_session_callback(&mut self, callback: F) + where + F: Fn(&SslContextRef, &SslSessionRef) + 'static + Sync + Send, + { + unsafe { + self.set_ex_data(SslContext::cached_ex_index::(), callback); + ffi::SSL_CTX_sess_set_remove_cb( + self.as_ptr(), + Some(callbacks::raw_remove_session::), + ); + } + } + + /// Sets the callback which is called when a client proposed to resume a session but it was not + /// found in the internal cache. + /// + /// The callback is passed a reference to the session ID provided by the client. It should + /// return the session corresponding to that ID if available. This is only used for servers, not + /// clients. + /// + /// # Safety + /// + /// The returned `SslSession` must not be associated with a different `SslContext`. + #[corresponds(SSL_CTX_sess_set_get_cb)] + pub unsafe fn set_get_session_callback(&mut self, callback: F) + where + F: Fn(&mut SslRef, &[u8]) -> Option + 'static + Sync + Send, + { + self.set_ex_data(SslContext::cached_ex_index::(), callback); + ffi::SSL_CTX_sess_set_get_cb(self.as_ptr(), Some(callbacks::raw_get_session::)); + } + + /// Sets the TLS key logging callback. + /// + /// The callback is invoked whenever TLS key material is generated, and is passed a line of NSS + /// SSLKEYLOGFILE-formatted text. This can be used by tools like Wireshark to decrypt message + /// traffic. The line does not contain a trailing newline. + /// + /// Requires OpenSSL 1.1.1 or newer. + #[corresponds(SSL_CTX_set_keylog_callback)] + #[cfg(ossl111)] + pub fn set_keylog_callback(&mut self, callback: F) + where + F: Fn(&SslRef, &str) + 'static + Sync + Send, + { + unsafe { + self.set_ex_data(SslContext::cached_ex_index::(), callback); + ffi::SSL_CTX_set_keylog_callback(self.as_ptr(), Some(callbacks::raw_keylog::)); + } + } + + /// Sets the session caching mode use for connections made with the context. + /// + /// Returns the previous session caching mode. + #[corresponds(SSL_CTX_set_session_cache_mode)] + pub fn set_session_cache_mode(&mut self, mode: SslSessionCacheMode) -> SslSessionCacheMode { + unsafe { + let bits = ffi::SSL_CTX_set_session_cache_mode(self.as_ptr(), mode.bits()); + SslSessionCacheMode::from_bits_retain(bits) + } + } + + /// Sets the callback for generating an application cookie for TLS1.3 + /// stateless handshakes. + /// + /// The callback will be called with the SSL context and a slice into which the cookie + /// should be written. The callback should return the number of bytes written. + #[corresponds(SSL_CTX_set_stateless_cookie_generate_cb)] + #[cfg(ossl111)] + pub fn set_stateless_cookie_generate_cb(&mut self, callback: F) + where + F: Fn(&mut SslRef, &mut [u8]) -> Result + 'static + Sync + Send, + { + unsafe { + self.set_ex_data(SslContext::cached_ex_index::(), callback); + ffi::SSL_CTX_set_stateless_cookie_generate_cb( + self.as_ptr(), + Some(raw_stateless_cookie_generate::), + ); + } + } + + /// Sets the callback for verifying an application cookie for TLS1.3 + /// stateless handshakes. + /// + /// The callback will be called with the SSL context and the cookie supplied by the + /// client. It should return true if and only if the cookie is valid. + /// + /// Note that the OpenSSL implementation independently verifies the integrity of + /// application cookies using an HMAC before invoking the supplied callback. + #[corresponds(SSL_CTX_set_stateless_cookie_verify_cb)] + #[cfg(ossl111)] + pub fn set_stateless_cookie_verify_cb(&mut self, callback: F) + where + F: Fn(&mut SslRef, &[u8]) -> bool + 'static + Sync + Send, + { + unsafe { + self.set_ex_data(SslContext::cached_ex_index::(), callback); + ffi::SSL_CTX_set_stateless_cookie_verify_cb( + self.as_ptr(), + Some(raw_stateless_cookie_verify::), + ) + } + } + + /// Sets the callback for generating a DTLSv1 cookie + /// + /// The callback will be called with the SSL context and a slice into which the cookie + /// should be written. The callback should return the number of bytes written. + #[corresponds(SSL_CTX_set_cookie_generate_cb)] + #[cfg(not(boringssl))] + pub fn set_cookie_generate_cb(&mut self, callback: F) + where + F: Fn(&mut SslRef, &mut [u8]) -> Result + 'static + Sync + Send, + { + unsafe { + self.set_ex_data(SslContext::cached_ex_index::(), callback); + ffi::SSL_CTX_set_cookie_generate_cb(self.as_ptr(), Some(raw_cookie_generate::)); + } + } + + /// Sets the callback for verifying a DTLSv1 cookie + /// + /// The callback will be called with the SSL context and the cookie supplied by the + /// client. It should return true if and only if the cookie is valid. + #[corresponds(SSL_CTX_set_cookie_verify_cb)] + #[cfg(not(boringssl))] + pub fn set_cookie_verify_cb(&mut self, callback: F) + where + F: Fn(&mut SslRef, &[u8]) -> bool + 'static + Sync + Send, + { + unsafe { + self.set_ex_data(SslContext::cached_ex_index::(), callback); + ffi::SSL_CTX_set_cookie_verify_cb(self.as_ptr(), Some(raw_cookie_verify::)); + } + } + + /// Sets the extra data at the specified index. + /// + /// This can be used to provide data to callbacks registered with the context. Use the + /// `SslContext::new_ex_index` method to create an `Index`. + #[corresponds(SSL_CTX_set_ex_data)] + pub fn set_ex_data(&mut self, index: Index, data: T) { + self.set_ex_data_inner(index, data); + } + + fn set_ex_data_inner(&mut self, index: Index, data: T) -> *mut c_void { + unsafe { + let data = Box::into_raw(Box::new(data)) as *mut c_void; + ffi::SSL_CTX_set_ex_data(self.as_ptr(), index.as_raw(), data); + data + } + } + + /// Adds a custom extension for a TLS/DTLS client or server for all supported protocol versions. + /// + /// Requires OpenSSL 1.1.1 or newer. + #[corresponds(SSL_CTX_add_custom_ext)] + #[cfg(ossl111)] + pub fn add_custom_ext( + &mut self, + ext_type: u16, + context: ExtensionContext, + add_cb: AddFn, + parse_cb: ParseFn, + ) -> Result<(), ErrorStack> + where + AddFn: Fn( + &mut SslRef, + ExtensionContext, + Option<(usize, &X509Ref)>, + ) -> Result, SslAlert> + + 'static + + Sync + + Send, + T: AsRef<[u8]> + 'static + Sync + Send, + ParseFn: Fn( + &mut SslRef, + ExtensionContext, + &[u8], + Option<(usize, &X509Ref)>, + ) -> Result<(), SslAlert> + + 'static + + Sync + + Send, + { + let ret = unsafe { + self.set_ex_data(SslContext::cached_ex_index::(), add_cb); + self.set_ex_data(SslContext::cached_ex_index::(), parse_cb); + + ffi::SSL_CTX_add_custom_ext( + self.as_ptr(), + ext_type as c_uint, + context.bits(), + Some(raw_custom_ext_add::), + Some(raw_custom_ext_free::), + ptr::null_mut(), + Some(raw_custom_ext_parse::), + ptr::null_mut(), + ) + }; + if ret == 1 { + Ok(()) + } else { + Err(ErrorStack::get()) + } + } + + /// Sets the maximum amount of early data that will be accepted on incoming connections. + /// + /// Defaults to 0. + /// + /// Requires OpenSSL 1.1.1 or LibreSSL 3.4.0 or newer. + #[corresponds(SSL_CTX_set_max_early_data)] + #[cfg(any(ossl111, libressl340))] + pub fn set_max_early_data(&mut self, bytes: u32) -> Result<(), ErrorStack> { + if unsafe { ffi::SSL_CTX_set_max_early_data(self.as_ptr(), bytes) } == 1 { + Ok(()) + } else { + Err(ErrorStack::get()) + } + } + + /// Sets a callback which will be invoked just after the client's hello message is received. + /// + /// Requires OpenSSL 1.1.1 or newer. + #[corresponds(SSL_CTX_set_client_hello_cb)] + #[cfg(ossl111)] + pub fn set_client_hello_callback(&mut self, callback: F) + where + F: Fn(&mut SslRef, &mut SslAlert) -> Result + + 'static + + Sync + + Send, + { + unsafe { + let ptr = self.set_ex_data_inner(SslContext::cached_ex_index::(), callback); + ffi::SSL_CTX_set_client_hello_cb( + self.as_ptr(), + Some(callbacks::raw_client_hello::), + ptr, + ); + } + } + + /// Sets the context's session cache size limit, returning the previous limit. + /// + /// A value of 0 means that the cache size is unbounded. + #[corresponds(SSL_CTX_sess_set_cache_size)] + #[allow(clippy::useless_conversion)] + pub fn set_session_cache_size(&mut self, size: i32) -> i64 { + unsafe { + ffi::SSL_CTX_sess_set_cache_size(self.as_ptr(), size as SslCacheSize) as SslCacheTy + } + } + + /// Sets the context's supported signature algorithms. + /// + /// Requires OpenSSL 1.0.2 or newer. + #[corresponds(SSL_CTX_set1_sigalgs_list)] + #[cfg(ossl102)] + pub fn set_sigalgs_list(&mut self, sigalgs: &str) -> Result<(), ErrorStack> { + let sigalgs = CString::new(sigalgs).unwrap(); + unsafe { + cvt(ffi::SSL_CTX_set1_sigalgs_list(self.as_ptr(), sigalgs.as_ptr()) as c_int) + .map(|_| ()) + } + } + + /// Sets the context's supported elliptic curve groups. + /// + /// Requires OpenSSL 1.1.1 or LibreSSL 2.5.1 or newer. + #[corresponds(SSL_CTX_set1_groups_list)] + #[cfg(any(ossl111, libressl251))] + pub fn set_groups_list(&mut self, groups: &str) -> Result<(), ErrorStack> { + let groups = CString::new(groups).unwrap(); + unsafe { + cvt(ffi::SSL_CTX_set1_groups_list(self.as_ptr(), groups.as_ptr()) as c_int).map(|_| ()) + } + } + + /// Sets the number of TLS 1.3 session tickets that will be sent to a client after a full + /// handshake. + /// + /// Requires OpenSSL 1.1.1 or newer. + #[corresponds(SSL_CTX_set_num_tickets)] + #[cfg(ossl111)] + pub fn set_num_tickets(&mut self, num_tickets: usize) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::SSL_CTX_set_num_tickets(self.as_ptr(), num_tickets)).map(|_| ()) } + } + + /// Consumes the builder, returning a new `SslContext`. + pub fn build(self) -> SslContext { + self.0 + } +} + +foreign_type_and_impl_send_sync! { + type CType = ffi::SSL_CTX; + fn drop = ffi::SSL_CTX_free; + + /// A context object for TLS streams. + /// + /// Applications commonly configure a single `SslContext` that is shared by all of its + /// `SslStreams`. + pub struct SslContext; + + /// Reference to [`SslContext`] + /// + /// [`SslContext`]: struct.SslContext.html + pub struct SslContextRef; +} + +impl Clone for SslContext { + fn clone(&self) -> Self { + (**self).to_owned() + } +} + +impl ToOwned for SslContextRef { + type Owned = SslContext; + + fn to_owned(&self) -> Self::Owned { + unsafe { + SSL_CTX_up_ref(self.as_ptr()); + SslContext::from_ptr(self.as_ptr()) + } + } +} + +// TODO: add useful info here +impl fmt::Debug for SslContext { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(fmt, "SslContext") + } +} + +impl SslContext { + /// Creates a new builder object for an `SslContext`. + pub fn builder(method: SslMethod) -> Result { + SslContextBuilder::new(method) + } + + /// Returns a new extra data index. + /// + /// Each invocation of this function is guaranteed to return a distinct index. These can be used + /// to store data in the context that can be retrieved later by callbacks, for example. + #[corresponds(SSL_CTX_get_ex_new_index)] + pub fn new_ex_index() -> Result, ErrorStack> + where + T: 'static + Sync + Send, + { + unsafe { + ffi::init(); + #[cfg(boringssl)] + let idx = cvt_n(get_new_idx(Some(free_data_box::)))?; + #[cfg(not(boringssl))] + let idx = cvt_n(get_new_idx(free_data_box::))?; + Ok(Index::from_raw(idx)) + } + } + + // FIXME should return a result? + fn cached_ex_index() -> Index + where + T: 'static + Sync + Send, + { + unsafe { + let idx = *INDEXES + .lock() + .unwrap_or_else(|e| e.into_inner()) + .entry(TypeId::of::()) + .or_insert_with(|| SslContext::new_ex_index::().unwrap().as_raw()); + Index::from_raw(idx) + } + } +} + +impl SslContextRef { + /// Returns the certificate associated with this `SslContext`, if present. + /// + /// Requires OpenSSL 1.0.2 or LibreSSL 2.7.0 or newer. + #[corresponds(SSL_CTX_get0_certificate)] + #[cfg(any(ossl102, libressl270))] + pub fn certificate(&self) -> Option<&X509Ref> { + unsafe { + let ptr = ffi::SSL_CTX_get0_certificate(self.as_ptr()); + X509Ref::from_const_ptr_opt(ptr) + } + } + + /// Returns the private key associated with this `SslContext`, if present. + /// + /// Requires OpenSSL 1.0.2 or LibreSSL 3.4.0 or newer. + #[corresponds(SSL_CTX_get0_privatekey)] + #[cfg(any(ossl102, libressl340))] + pub fn private_key(&self) -> Option<&PKeyRef> { + unsafe { + let ptr = ffi::SSL_CTX_get0_privatekey(self.as_ptr()); + PKeyRef::from_const_ptr_opt(ptr) + } + } + + /// Returns a shared reference to the certificate store used for verification. + #[corresponds(SSL_CTX_get_cert_store)] + pub fn cert_store(&self) -> &X509StoreRef { + unsafe { X509StoreRef::from_ptr(ffi::SSL_CTX_get_cert_store(self.as_ptr())) } + } + + /// Returns a shared reference to the stack of certificates making up the chain from the leaf. + #[corresponds(SSL_CTX_get_extra_chain_certs)] + pub fn extra_chain_certs(&self) -> &StackRef { + unsafe { + let mut chain = ptr::null_mut(); + ffi::SSL_CTX_get_extra_chain_certs(self.as_ptr(), &mut chain); + StackRef::from_const_ptr_opt(chain).expect("extra chain certs must not be null") + } + } + + /// Returns a reference to the extra data at the specified index. + #[corresponds(SSL_CTX_get_ex_data)] + pub fn ex_data(&self, index: Index) -> Option<&T> { + unsafe { + let data = ffi::SSL_CTX_get_ex_data(self.as_ptr(), index.as_raw()); + if data.is_null() { + None + } else { + Some(&*(data as *const T)) + } + } + } + + /// Gets the maximum amount of early data that will be accepted on incoming connections. + /// + /// Requires OpenSSL 1.1.1 or LibreSSL 3.4.0 or newer. + #[corresponds(SSL_CTX_get_max_early_data)] + #[cfg(any(ossl111, libressl340))] + pub fn max_early_data(&self) -> u32 { + unsafe { ffi::SSL_CTX_get_max_early_data(self.as_ptr()) } + } + + /// Adds a session to the context's cache. + /// + /// Returns `true` if the session was successfully added to the cache, and `false` if it was already present. + /// + /// # Safety + /// + /// The caller of this method is responsible for ensuring that the session has never been used with another + /// `SslContext` than this one. + #[corresponds(SSL_CTX_add_session)] + pub unsafe fn add_session(&self, session: &SslSessionRef) -> bool { + ffi::SSL_CTX_add_session(self.as_ptr(), session.as_ptr()) != 0 + } + + /// Removes a session from the context's cache and marks it as non-resumable. + /// + /// Returns `true` if the session was successfully found and removed, and `false` otherwise. + /// + /// # Safety + /// + /// The caller of this method is responsible for ensuring that the session has never been used with another + /// `SslContext` than this one. + #[corresponds(SSL_CTX_remove_session)] + pub unsafe fn remove_session(&self, session: &SslSessionRef) -> bool { + ffi::SSL_CTX_remove_session(self.as_ptr(), session.as_ptr()) != 0 + } + + /// Returns the context's session cache size limit. + /// + /// A value of 0 means that the cache size is unbounded. + #[corresponds(SSL_CTX_sess_get_cache_size)] + #[allow(clippy::unnecessary_cast)] + pub fn session_cache_size(&self) -> i64 { + unsafe { ffi::SSL_CTX_sess_get_cache_size(self.as_ptr()) as i64 } + } + + /// Returns the verify mode that was set on this context from [`SslContextBuilder::set_verify`]. + /// + /// [`SslContextBuilder::set_verify`]: struct.SslContextBuilder.html#method.set_verify + #[corresponds(SSL_CTX_get_verify_mode)] + pub fn verify_mode(&self) -> SslVerifyMode { + let mode = unsafe { ffi::SSL_CTX_get_verify_mode(self.as_ptr()) }; + SslVerifyMode::from_bits(mode).expect("SSL_CTX_get_verify_mode returned invalid mode") + } + + /// Gets the number of TLS 1.3 session tickets that will be sent to a client after a full + /// handshake. + /// + /// Requires OpenSSL 1.1.1 or newer. + #[corresponds(SSL_CTX_get_num_tickets)] + #[cfg(ossl111)] + pub fn num_tickets(&self) -> usize { + unsafe { ffi::SSL_CTX_get_num_tickets(self.as_ptr()) } + } +} + +/// Information about the state of a cipher. +pub struct CipherBits { + /// The number of secret bits used for the cipher. + pub secret: i32, + + /// The number of bits processed by the chosen algorithm. + pub algorithm: i32, +} + +/// Information about a cipher. +pub struct SslCipher(*mut ffi::SSL_CIPHER); + +impl ForeignType for SslCipher { + type CType = ffi::SSL_CIPHER; + type Ref = SslCipherRef; + + #[inline] + unsafe fn from_ptr(ptr: *mut ffi::SSL_CIPHER) -> SslCipher { + SslCipher(ptr) + } + + #[inline] + fn as_ptr(&self) -> *mut ffi::SSL_CIPHER { + self.0 + } +} + +impl Stackable for SslCipher { + type StackType = ffi::stack_st_SSL_CIPHER; +} + +impl Deref for SslCipher { + type Target = SslCipherRef; + + fn deref(&self) -> &SslCipherRef { + unsafe { SslCipherRef::from_ptr(self.0) } + } +} + +impl DerefMut for SslCipher { + fn deref_mut(&mut self) -> &mut SslCipherRef { + unsafe { SslCipherRef::from_ptr_mut(self.0) } + } +} + +/// Reference to an [`SslCipher`]. +/// +/// [`SslCipher`]: struct.SslCipher.html +pub struct SslCipherRef(Opaque); + +impl ForeignTypeRef for SslCipherRef { + type CType = ffi::SSL_CIPHER; +} + +impl SslCipherRef { + /// Returns the name of the cipher. + #[corresponds(SSL_CIPHER_get_name)] + pub fn name(&self) -> &'static str { + unsafe { + let ptr = ffi::SSL_CIPHER_get_name(self.as_ptr()); + CStr::from_ptr(ptr).to_str().unwrap() + } + } + + /// Returns the RFC-standard name of the cipher, if one exists. + /// + /// Requires OpenSSL 1.1.1 or newer. + #[corresponds(SSL_CIPHER_standard_name)] + #[cfg(ossl111)] + pub fn standard_name(&self) -> Option<&'static str> { + unsafe { + let ptr = ffi::SSL_CIPHER_standard_name(self.as_ptr()); + if ptr.is_null() { + None + } else { + Some(CStr::from_ptr(ptr).to_str().unwrap()) + } + } + } + + /// Returns the SSL/TLS protocol version that first defined the cipher. + #[corresponds(SSL_CIPHER_get_version)] + pub fn version(&self) -> &'static str { + let version = unsafe { + let ptr = ffi::SSL_CIPHER_get_version(self.as_ptr()); + CStr::from_ptr(ptr as *const _) + }; + + str::from_utf8(version.to_bytes()).unwrap() + } + + /// Returns the number of bits used for the cipher. + #[corresponds(SSL_CIPHER_get_bits)] + #[allow(clippy::useless_conversion)] + pub fn bits(&self) -> CipherBits { + unsafe { + let mut algo_bits = 0; + let secret_bits = ffi::SSL_CIPHER_get_bits(self.as_ptr(), &mut algo_bits); + CipherBits { + secret: secret_bits.into(), + algorithm: algo_bits.into(), + } + } + } + + /// Returns a textual description of the cipher. + #[corresponds(SSL_CIPHER_description)] + pub fn description(&self) -> String { + unsafe { + // SSL_CIPHER_description requires a buffer of at least 128 bytes. + let mut buf = [0; 128]; + let ptr = ffi::SSL_CIPHER_description(self.as_ptr(), buf.as_mut_ptr(), 128); + String::from_utf8(CStr::from_ptr(ptr as *const _).to_bytes().to_vec()).unwrap() + } + } + + /// Returns the handshake digest of the cipher. + /// + /// Requires OpenSSL 1.1.1 or newer. + #[corresponds(SSL_CIPHER_get_handshake_digest)] + #[cfg(ossl111)] + pub fn handshake_digest(&self) -> Option { + unsafe { + let ptr = ffi::SSL_CIPHER_get_handshake_digest(self.as_ptr()); + if ptr.is_null() { + None + } else { + Some(MessageDigest::from_ptr(ptr)) + } + } + } + + /// Returns the NID corresponding to the cipher. + /// + /// Requires OpenSSL 1.1.0 or LibreSSL 2.7.0 or newer. + #[corresponds(SSL_CIPHER_get_cipher_nid)] + #[cfg(any(ossl110, libressl270))] + pub fn cipher_nid(&self) -> Option { + let n = unsafe { ffi::SSL_CIPHER_get_cipher_nid(self.as_ptr()) }; + if n == 0 { + None + } else { + Some(Nid::from_raw(n)) + } + } +} + +impl fmt::Debug for SslCipherRef { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(fmt, "{}", self.name()) + } +} + +/// A stack of selected ciphers, and a stack of selected signalling cipher suites +#[derive(Debug)] +pub struct CipherLists { + pub suites: Stack, + pub signalling_suites: Stack, +} + +foreign_type_and_impl_send_sync! { + type CType = ffi::SSL_SESSION; + fn drop = ffi::SSL_SESSION_free; + + /// An encoded SSL session. + /// + /// These can be cached to share sessions across connections. + pub struct SslSession; + + /// Reference to [`SslSession`]. + /// + /// [`SslSession`]: struct.SslSession.html + pub struct SslSessionRef; +} + +impl Clone for SslSession { + fn clone(&self) -> SslSession { + SslSessionRef::to_owned(self) + } +} + +impl SslSession { + from_der! { + /// Deserializes a DER-encoded session structure. + #[corresponds(d2i_SSL_SESSION)] + from_der, + SslSession, + ffi::d2i_SSL_SESSION + } +} + +impl ToOwned for SslSessionRef { + type Owned = SslSession; + + fn to_owned(&self) -> SslSession { + unsafe { + SSL_SESSION_up_ref(self.as_ptr()); + SslSession(self.as_ptr()) + } + } +} + +impl SslSessionRef { + /// Returns the SSL session ID. + #[corresponds(SSL_SESSION_get_id)] + pub fn id(&self) -> &[u8] { + unsafe { + let mut len = 0; + let p = ffi::SSL_SESSION_get_id(self.as_ptr(), &mut len); + #[allow(clippy::unnecessary_cast)] + slice::from_raw_parts(p as *const u8, len as usize) + } + } + + /// Returns the length of the master key. + #[corresponds(SSL_SESSION_get_master_key)] + pub fn master_key_len(&self) -> usize { + unsafe { SSL_SESSION_get_master_key(self.as_ptr(), ptr::null_mut(), 0) } + } + + /// Copies the master key into the provided buffer. + /// + /// Returns the number of bytes written, or the size of the master key if the buffer is empty. + #[corresponds(SSL_SESSION_get_master_key)] + pub fn master_key(&self, buf: &mut [u8]) -> usize { + unsafe { SSL_SESSION_get_master_key(self.as_ptr(), buf.as_mut_ptr(), buf.len()) } + } + + /// Gets the maximum amount of early data that can be sent on this session. + /// + /// Requires OpenSSL 1.1.1 or LibreSSL 3.4.0 or newer. + #[corresponds(SSL_SESSION_get_max_early_data)] + #[cfg(any(ossl111, libressl340))] + pub fn max_early_data(&self) -> u32 { + unsafe { ffi::SSL_SESSION_get_max_early_data(self.as_ptr()) } + } + + /// Returns the time at which the session was established, in seconds since the Unix epoch. + #[corresponds(SSL_SESSION_get_time)] + #[allow(clippy::useless_conversion)] + pub fn time(&self) -> SslTimeTy { + unsafe { ffi::SSL_SESSION_get_time(self.as_ptr()) } + } + + /// Returns the sessions timeout, in seconds. + /// + /// A session older than this time should not be used for session resumption. + #[corresponds(SSL_SESSION_get_timeout)] + #[allow(clippy::useless_conversion)] + pub fn timeout(&self) -> i64 { + unsafe { ffi::SSL_SESSION_get_timeout(self.as_ptr()).into() } + } + + /// Returns the session's TLS protocol version. + /// + /// Requires OpenSSL 1.1.0 or LibreSSL 2.7.0 or newer. + #[corresponds(SSL_SESSION_get_protocol_version)] + #[cfg(any(ossl110, libressl270))] + pub fn protocol_version(&self) -> SslVersion { + unsafe { + let version = ffi::SSL_SESSION_get_protocol_version(self.as_ptr()); + SslVersion(version) + } + } + + to_der! { + /// Serializes the session into a DER-encoded structure. + #[corresponds(i2d_SSL_SESSION)] + to_der, + ffi::i2d_SSL_SESSION + } +} + +foreign_type_and_impl_send_sync! { + type CType = ffi::SSL; + fn drop = ffi::SSL_free; + + /// The state of an SSL/TLS session. + /// + /// `Ssl` objects are created from an [`SslContext`], which provides configuration defaults. + /// These defaults can be overridden on a per-`Ssl` basis, however. + /// + /// [`SslContext`]: struct.SslContext.html + pub struct Ssl; + + /// Reference to an [`Ssl`]. + /// + /// [`Ssl`]: struct.Ssl.html + pub struct SslRef; +} + +impl fmt::Debug for Ssl { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, fmt) + } +} + +impl Ssl { + /// Returns a new extra data index. + /// + /// Each invocation of this function is guaranteed to return a distinct index. These can be used + /// to store data in the context that can be retrieved later by callbacks, for example. + #[corresponds(SSL_get_ex_new_index)] + pub fn new_ex_index() -> Result, ErrorStack> + where + T: 'static + Sync + Send, + { + unsafe { + ffi::init(); + #[cfg(boringssl)] + let idx = cvt_n(get_new_ssl_idx(Some(free_data_box::)))?; + #[cfg(not(boringssl))] + let idx = cvt_n(get_new_ssl_idx(free_data_box::))?; + Ok(Index::from_raw(idx)) + } + } + + // FIXME should return a result? + fn cached_ex_index() -> Index + where + T: 'static + Sync + Send, + { + unsafe { + let idx = *SSL_INDEXES + .lock() + .unwrap_or_else(|e| e.into_inner()) + .entry(TypeId::of::()) + .or_insert_with(|| Ssl::new_ex_index::().unwrap().as_raw()); + Index::from_raw(idx) + } + } + + /// Creates a new `Ssl`. + /// + /// This corresponds to [`SSL_new`]. + /// + /// [`SSL_new`]: https://www.openssl.org/docs/manmaster/ssl/SSL_new.html + #[corresponds(SSL_new)] + pub fn new(ctx: &SslContextRef) -> Result { + let session_ctx_index = try_get_session_ctx_index()?; + unsafe { + let ptr = cvt_p(ffi::SSL_new(ctx.as_ptr()))?; + let mut ssl = Ssl::from_ptr(ptr); + ssl.set_ex_data(*session_ctx_index, ctx.to_owned()); + + Ok(ssl) + } + } + + /// Initiates a client-side TLS handshake. + /// + /// This corresponds to [`SSL_connect`]. + /// + /// # Warning + /// + /// OpenSSL's default configuration is insecure. It is highly recommended to use + /// `SslConnector` rather than `Ssl` directly, as it manages that configuration. + /// + /// [`SSL_connect`]: https://www.openssl.org/docs/manmaster/man3/SSL_connect.html + #[corresponds(SSL_connect)] + #[allow(deprecated)] + pub fn connect(self, stream: S) -> Result, HandshakeError> + where + S: Read + Write, + { + SslStreamBuilder::new(self, stream).connect() + } + + /// Initiates a server-side TLS handshake. + /// + /// This corresponds to [`SSL_accept`]. + /// + /// # Warning + /// + /// OpenSSL's default configuration is insecure. It is highly recommended to use + /// `SslAcceptor` rather than `Ssl` directly, as it manages that configuration. + /// + /// [`SSL_accept`]: https://www.openssl.org/docs/manmaster/man3/SSL_accept.html + #[corresponds(SSL_accept)] + #[allow(deprecated)] + pub fn accept(self, stream: S) -> Result, HandshakeError> + where + S: Read + Write, + { + SslStreamBuilder::new(self, stream).accept() + } +} + +impl fmt::Debug for SslRef { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("Ssl") + .field("state", &self.state_string_long()) + .field("verify_result", &self.verify_result()) + .finish() + } +} + +impl SslRef { + fn get_raw_rbio(&self) -> *mut ffi::BIO { + unsafe { ffi::SSL_get_rbio(self.as_ptr()) } + } + + fn read(&mut self, buf: &mut [u8]) -> c_int { + let len = cmp::min(c_int::max_value() as usize, buf.len()) as c_int; + unsafe { ffi::SSL_read(self.as_ptr(), buf.as_ptr() as *mut c_void, len) } + } + + fn peek(&mut self, buf: &mut [u8]) -> c_int { + let len = cmp::min(c_int::max_value() as usize, buf.len()) as c_int; + unsafe { ffi::SSL_peek(self.as_ptr(), buf.as_ptr() as *mut c_void, len) } + } + + fn write(&mut self, buf: &[u8]) -> c_int { + let len = cmp::min(c_int::max_value() as usize, buf.len()) as c_int; + unsafe { ffi::SSL_write(self.as_ptr(), buf.as_ptr() as *const c_void, len) } + } + + fn get_error(&self, ret: c_int) -> ErrorCode { + unsafe { ErrorCode::from_raw(ffi::SSL_get_error(self.as_ptr(), ret)) } + } + + /// Configure as an outgoing stream from a client. + #[corresponds(SSL_set_connect_state)] + pub fn set_connect_state(&mut self) { + unsafe { ffi::SSL_set_connect_state(self.as_ptr()) } + } + + /// Configure as an incoming stream to a server. + #[corresponds(SSL_set_accept_state)] + pub fn set_accept_state(&mut self) { + unsafe { ffi::SSL_set_accept_state(self.as_ptr()) } + } + + /// Like [`SslContextBuilder::set_verify`]. + /// + /// [`SslContextBuilder::set_verify`]: struct.SslContextBuilder.html#method.set_verify + #[corresponds(SSL_set_verify)] + pub fn set_verify(&mut self, mode: SslVerifyMode) { + unsafe { ffi::SSL_set_verify(self.as_ptr(), mode.bits() as c_int, None) } + } + + /// Returns the verify mode that was set using `set_verify`. + #[corresponds(SSL_set_verify_mode)] + pub fn verify_mode(&self) -> SslVerifyMode { + let mode = unsafe { ffi::SSL_get_verify_mode(self.as_ptr()) }; + SslVerifyMode::from_bits(mode).expect("SSL_get_verify_mode returned invalid mode") + } + + /// Like [`SslContextBuilder::set_verify_callback`]. + /// + /// [`SslContextBuilder::set_verify_callback`]: struct.SslContextBuilder.html#method.set_verify_callback + #[corresponds(SSL_set_verify)] + pub fn set_verify_callback(&mut self, mode: SslVerifyMode, verify: F) + where + F: Fn(bool, &mut X509StoreContextRef) -> bool + 'static + Sync + Send, + { + unsafe { + // this needs to be in an Arc since the callback can register a new callback! + self.set_ex_data(Ssl::cached_ex_index(), Arc::new(verify)); + ffi::SSL_set_verify( + self.as_ptr(), + mode.bits() as c_int, + Some(ssl_raw_verify::), + ); + } + } + + /// Like [`SslContextBuilder::set_tmp_dh`]. + /// + /// [`SslContextBuilder::set_tmp_dh`]: struct.SslContextBuilder.html#method.set_tmp_dh + #[corresponds(SSL_set_tmp_dh)] + pub fn set_tmp_dh(&mut self, dh: &DhRef) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::SSL_set_tmp_dh(self.as_ptr(), dh.as_ptr()) as c_int).map(|_| ()) } + } + + /// Like [`SslContextBuilder::set_tmp_dh_callback`]. + /// + /// [`SslContextBuilder::set_tmp_dh_callback`]: struct.SslContextBuilder.html#method.set_tmp_dh_callback + #[corresponds(SSL_set_tmp_dh_callback)] + pub fn set_tmp_dh_callback(&mut self, callback: F) + where + F: Fn(&mut SslRef, bool, u32) -> Result, ErrorStack> + 'static + Sync + Send, + { + unsafe { + // this needs to be in an Arc since the callback can register a new callback! + self.set_ex_data(Ssl::cached_ex_index(), Arc::new(callback)); + #[cfg(boringssl)] + ffi::SSL_set_tmp_dh_callback(self.as_ptr(), Some(raw_tmp_dh_ssl::)); + #[cfg(not(boringssl))] + ffi::SSL_set_tmp_dh_callback__fixed_rust(self.as_ptr(), Some(raw_tmp_dh_ssl::)); + } + } + + /// Like [`SslContextBuilder::set_tmp_ecdh`]. + /// + /// [`SslContextBuilder::set_tmp_ecdh`]: struct.SslContextBuilder.html#method.set_tmp_ecdh + #[corresponds(SSL_set_tmp_ecdh)] + pub fn set_tmp_ecdh(&mut self, key: &EcKeyRef) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::SSL_set_tmp_ecdh(self.as_ptr(), key.as_ptr()) as c_int).map(|_| ()) } + } + + /// Like [`SslContextBuilder::set_tmp_ecdh_callback`]. + /// + /// Requires OpenSSL 1.0.1 or 1.0.2. + #[corresponds(SSL_set_tmp_ecdh_callback)] + #[cfg(all(ossl101, not(ossl110)))] + #[deprecated(note = "this function leaks memory and does not exist on newer OpenSSL versions")] + pub fn set_tmp_ecdh_callback(&mut self, callback: F) + where + F: Fn(&mut SslRef, bool, u32) -> Result, ErrorStack> + 'static + Sync + Send, + { + unsafe { + // this needs to be in an Arc since the callback can register a new callback! + self.set_ex_data(Ssl::cached_ex_index(), Arc::new(callback)); + ffi::SSL_set_tmp_ecdh_callback__fixed_rust(self.as_ptr(), Some(raw_tmp_ecdh_ssl::)); + } + } + + /// Like [`SslContextBuilder::set_ecdh_auto`]. + /// + /// Requires OpenSSL 1.0.2 or LibreSSL. + /// + /// [`SslContextBuilder::set_tmp_ecdh`]: struct.SslContextBuilder.html#method.set_tmp_ecdh + #[corresponds(SSL_set_ecdh_auto)] + #[cfg(any(all(ossl102, not(ossl110)), libressl))] + pub fn set_ecdh_auto(&mut self, onoff: bool) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::SSL_set_ecdh_auto(self.as_ptr(), onoff as c_int)).map(|_| ()) } + } + + /// Like [`SslContextBuilder::set_alpn_protos`]. + /// + /// Requires OpenSSL 1.0.2 or LibreSSL 2.6.1 or newer. + /// + /// [`SslContextBuilder::set_alpn_protos`]: struct.SslContextBuilder.html#method.set_alpn_protos + #[corresponds(SSL_set_alpn_protos)] + #[cfg(any(ossl102, libressl261))] + pub fn set_alpn_protos(&mut self, protocols: &[u8]) -> Result<(), ErrorStack> { + unsafe { + assert!(protocols.len() <= c_uint::max_value() as usize); + let r = ffi::SSL_set_alpn_protos( + self.as_ptr(), + protocols.as_ptr(), + protocols.len() as c_uint, + ); + // fun fact, SSL_set_alpn_protos has a reversed return code D: + if r == 0 { + Ok(()) + } else { + Err(ErrorStack::get()) + } + } + } + + /// Returns the current cipher if the session is active. + #[corresponds(SSL_get_current_cipher)] + pub fn current_cipher(&self) -> Option<&SslCipherRef> { + unsafe { + let ptr = ffi::SSL_get_current_cipher(self.as_ptr()); + + SslCipherRef::from_const_ptr_opt(ptr) + } + } + + /// Returns a short string describing the state of the session. + #[corresponds(SSL_state_string)] + pub fn state_string(&self) -> &'static str { + let state = unsafe { + let ptr = ffi::SSL_state_string(self.as_ptr()); + CStr::from_ptr(ptr as *const _) + }; + + str::from_utf8(state.to_bytes()).unwrap() + } + + /// Returns a longer string describing the state of the session. + #[corresponds(SSL_state_string_long)] + pub fn state_string_long(&self) -> &'static str { + let state = unsafe { + let ptr = ffi::SSL_state_string_long(self.as_ptr()); + CStr::from_ptr(ptr as *const _) + }; + + str::from_utf8(state.to_bytes()).unwrap() + } + + /// Sets the host name to be sent to the server for Server Name Indication (SNI). + /// + /// It has no effect for a server-side connection. + #[corresponds(SSL_set_tlsext_host_name)] + pub fn set_hostname(&mut self, hostname: &str) -> Result<(), ErrorStack> { + let cstr = CString::new(hostname).unwrap(); + unsafe { + cvt(ffi::SSL_set_tlsext_host_name(self.as_ptr(), cstr.as_ptr() as *mut _) as c_int) + .map(|_| ()) + } + } + + /// Returns the peer's certificate, if present. + #[corresponds(SSL_get_peer_certificate)] + pub fn peer_certificate(&self) -> Option { + unsafe { + let ptr = SSL_get1_peer_certificate(self.as_ptr()); + X509::from_ptr_opt(ptr) + } + } + + /// Returns the certificate chain of the peer, if present. + /// + /// On the client side, the chain includes the leaf certificate, but on the server side it does + /// not. Fun! + #[corresponds(SSL_get_peer_cert_chain)] + pub fn peer_cert_chain(&self) -> Option<&StackRef> { + unsafe { + let ptr = ffi::SSL_get_peer_cert_chain(self.as_ptr()); + StackRef::from_const_ptr_opt(ptr) + } + } + + /// Returns the verified certificate chain of the peer, including the leaf certificate. + /// + /// If verification was not successful (i.e. [`verify_result`] does not return + /// [`X509VerifyResult::OK`]), this chain may be incomplete or invalid. + /// + /// Requires OpenSSL 1.1.0 or newer. + /// + /// [`verify_result`]: #method.verify_result + /// [`X509VerifyResult::OK`]: ../x509/struct.X509VerifyResult.html#associatedconstant.OK + #[corresponds(SSL_get0_verified_chain)] + #[cfg(ossl110)] + pub fn verified_chain(&self) -> Option<&StackRef> { + unsafe { + let ptr = ffi::SSL_get0_verified_chain(self.as_ptr()); + StackRef::from_const_ptr_opt(ptr) + } + } + + /// Like [`SslContext::certificate`]. + #[corresponds(SSL_get_certificate)] + pub fn certificate(&self) -> Option<&X509Ref> { + unsafe { + let ptr = ffi::SSL_get_certificate(self.as_ptr()); + X509Ref::from_const_ptr_opt(ptr) + } + } + + /// Like [`SslContext::private_key`]. + /// + /// [`SslContext::private_key`]: struct.SslContext.html#method.private_key + #[corresponds(SSL_get_privatekey)] + pub fn private_key(&self) -> Option<&PKeyRef> { + unsafe { + let ptr = ffi::SSL_get_privatekey(self.as_ptr()); + PKeyRef::from_const_ptr_opt(ptr) + } + } + + #[deprecated(since = "0.10.5", note = "renamed to `version_str`")] + pub fn version(&self) -> &str { + self.version_str() + } + + /// Returns the protocol version of the session. + #[corresponds(SSL_version)] + pub fn version2(&self) -> Option { + unsafe { + let r = ffi::SSL_version(self.as_ptr()); + if r == 0 { + None + } else { + Some(SslVersion(r)) + } + } + } + + /// Returns a string describing the protocol version of the session. + #[corresponds(SSL_get_version)] + pub fn version_str(&self) -> &'static str { + let version = unsafe { + let ptr = ffi::SSL_get_version(self.as_ptr()); + CStr::from_ptr(ptr as *const _) + }; + + str::from_utf8(version.to_bytes()).unwrap() + } + + /// Returns the protocol selected via Application Layer Protocol Negotiation (ALPN). + /// + /// The protocol's name is returned is an opaque sequence of bytes. It is up to the client + /// to interpret it. + /// + /// Requires OpenSSL 1.0.2 or LibreSSL 2.6.1 or newer. + #[corresponds(SSL_get0_alpn_selected)] + #[cfg(any(ossl102, libressl261))] + pub fn selected_alpn_protocol(&self) -> Option<&[u8]> { + unsafe { + let mut data: *const c_uchar = ptr::null(); + let mut len: c_uint = 0; + // Get the negotiated protocol from the SSL instance. + // `data` will point at a `c_uchar` array; `len` will contain the length of this array. + ffi::SSL_get0_alpn_selected(self.as_ptr(), &mut data, &mut len); + + if data.is_null() { + None + } else { + Some(slice::from_raw_parts(data, len as usize)) + } + } + } + + /// 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 + #[corresponds(SSL_set_tlsext_use_srtp)] + 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 + #[corresponds(SSL_get_srtp_profiles)] + pub fn srtp_profiles(&self) -> Option<&StackRef> { + unsafe { + let chain = ffi::SSL_get_srtp_profiles(self.as_ptr()); + + StackRef::from_const_ptr_opt(chain) + } + } + + /// Gets the SRTP profile selected by handshake. + /// + /// DTLS extension "use_srtp" as defined in RFC5764 has to be enabled. + #[corresponds(SSL_get_selected_srtp_profile)] + pub fn selected_srtp_profile(&self) -> Option<&SrtpProtectionProfileRef> { + unsafe { + let profile = ffi::SSL_get_selected_srtp_profile(self.as_ptr()); + + SrtpProtectionProfileRef::from_const_ptr_opt(profile) + } + } + + /// 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 + /// stream. + #[corresponds(SSL_pending)] + pub fn pending(&self) -> usize { + unsafe { ffi::SSL_pending(self.as_ptr()) as usize } + } + + /// Returns the servername sent by the client via Server Name Indication (SNI). + /// + /// It is only useful on the server side. + /// + /// # Note + /// + /// While the SNI specification requires that servernames be valid domain names (and therefore + /// ASCII), OpenSSL does not enforce this restriction. If the servername provided by the client + /// is not valid UTF-8, this function will return `None`. The `servername_raw` method returns + /// the raw bytes and does not have this restriction. + /// + /// [`SSL_get_servername`]: https://www.openssl.org/docs/manmaster/man3/SSL_get_servername.html + #[corresponds(SSL_get_servername)] + // FIXME maybe rethink in 0.11? + pub fn servername(&self, type_: NameType) -> Option<&str> { + self.servername_raw(type_) + .and_then(|b| str::from_utf8(b).ok()) + } + + /// Returns the servername sent by the client via Server Name Indication (SNI). + /// + /// It is only useful on the server side. + /// + /// # Note + /// + /// Unlike `servername`, this method does not require the name be valid UTF-8. + #[corresponds(SSL_get_servername)] + pub fn servername_raw(&self, type_: NameType) -> Option<&[u8]> { + unsafe { + let name = ffi::SSL_get_servername(self.as_ptr(), type_.0); + if name.is_null() { + None + } else { + Some(CStr::from_ptr(name as *const _).to_bytes()) + } + } + } + + /// Changes the context corresponding to the current connection. + /// + /// It is most commonly used in the Server Name Indication (SNI) callback. + #[corresponds(SSL_set_SSL_CTX)] + pub fn set_ssl_context(&mut self, ctx: &SslContextRef) -> Result<(), ErrorStack> { + unsafe { cvt_p(ffi::SSL_set_SSL_CTX(self.as_ptr(), ctx.as_ptr())).map(|_| ()) } + } + + /// Returns the context corresponding to the current connection. + #[corresponds(SSL_get_SSL_CTX)] + pub fn ssl_context(&self) -> &SslContextRef { + unsafe { + let ssl_ctx = ffi::SSL_get_SSL_CTX(self.as_ptr()); + SslContextRef::from_ptr(ssl_ctx) + } + } + + /// Returns a mutable reference to the X509 verification configuration. + /// + /// Requires OpenSSL 1.0.2 or newer. + #[corresponds(SSL_get0_param)] + #[cfg(any(ossl102, libressl261))] + pub fn param_mut(&mut self) -> &mut X509VerifyParamRef { + unsafe { X509VerifyParamRef::from_ptr_mut(ffi::SSL_get0_param(self.as_ptr())) } + } + + /// Returns the certificate verification result. + #[corresponds(SSL_get_verify_result)] + pub fn verify_result(&self) -> X509VerifyResult { + unsafe { X509VerifyResult::from_raw(ffi::SSL_get_verify_result(self.as_ptr()) as c_int) } + } + + /// Returns a shared reference to the SSL session. + #[corresponds(SSL_get_session)] + pub fn session(&self) -> Option<&SslSessionRef> { + unsafe { + let p = ffi::SSL_get_session(self.as_ptr()); + SslSessionRef::from_const_ptr_opt(p) + } + } + + /// Copies the `client_random` value sent by the client in the TLS handshake into a buffer. + /// + /// Returns the number of bytes copied, or if the buffer is empty, the size of the `client_random` + /// value. + /// + /// Requires OpenSSL 1.1.0 or LibreSSL 2.7.0 or newer. + #[corresponds(SSL_get_client_random)] + #[cfg(any(ossl110, libressl270))] + pub fn client_random(&self, buf: &mut [u8]) -> usize { + unsafe { + ffi::SSL_get_client_random(self.as_ptr(), buf.as_mut_ptr() as *mut c_uchar, buf.len()) + } + } + + /// Copies the `server_random` value sent by the server in the TLS handshake into a buffer. + /// + /// Returns the number of bytes copied, or if the buffer is empty, the size of the `server_random` + /// value. + /// + /// Requires OpenSSL 1.1.0 or LibreSSL 2.7.0 or newer. + #[corresponds(SSL_get_server_random)] + #[cfg(any(ossl110, libressl270))] + pub fn server_random(&self, buf: &mut [u8]) -> usize { + unsafe { + ffi::SSL_get_server_random(self.as_ptr(), buf.as_mut_ptr() as *mut c_uchar, buf.len()) + } + } + + /// Derives keying material for application use in accordance to RFC 5705. + #[corresponds(SSL_export_keying_material)] + pub fn export_keying_material( + &self, + out: &mut [u8], + label: &str, + context: Option<&[u8]>, + ) -> Result<(), ErrorStack> { + unsafe { + let (context, contextlen, use_context) = match context { + Some(context) => (context.as_ptr() as *const c_uchar, context.len(), 1), + None => (ptr::null(), 0, 0), + }; + cvt(ffi::SSL_export_keying_material( + self.as_ptr(), + out.as_mut_ptr() as *mut c_uchar, + out.len(), + label.as_ptr() as *const c_char, + label.len(), + context, + contextlen, + use_context, + )) + .map(|_| ()) + } + } + + /// Derives keying material for application use in accordance to RFC 5705. + /// + /// This function is only usable with TLSv1.3, wherein there is no distinction between an empty context and no + /// context. Therefore, unlike `export_keying_material`, `context` must always be supplied. + /// + /// Requires OpenSSL 1.1.1 or newer. + #[corresponds(SSL_export_keying_material_early)] + #[cfg(ossl111)] + pub fn export_keying_material_early( + &self, + out: &mut [u8], + label: &str, + context: &[u8], + ) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::SSL_export_keying_material_early( + self.as_ptr(), + out.as_mut_ptr() as *mut c_uchar, + out.len(), + label.as_ptr() as *const c_char, + label.len(), + context.as_ptr() as *const c_uchar, + context.len(), + )) + .map(|_| ()) + } + } + + /// Sets the session to be used. + /// + /// This should be called before the handshake to attempt to reuse a previously established + /// session. If the server is not willing to reuse the session, a new one will be transparently + /// negotiated. + /// + /// # Safety + /// + /// The caller of this method is responsible for ensuring that the session is associated + /// with the same `SslContext` as this `Ssl`. + #[corresponds(SSL_set_session)] + pub unsafe fn set_session(&mut self, session: &SslSessionRef) -> Result<(), ErrorStack> { + cvt(ffi::SSL_set_session(self.as_ptr(), session.as_ptr())).map(|_| ()) + } + + /// Determines if the session provided to `set_session` was successfully reused. + #[corresponds(SSL_session_reused)] + pub fn session_reused(&self) -> bool { + unsafe { ffi::SSL_session_reused(self.as_ptr()) != 0 } + } + + /// Sets the status response a client wishes the server to reply with. + #[corresponds(SSL_set_tlsext_status_type)] + pub fn set_status_type(&mut self, type_: StatusType) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::SSL_set_tlsext_status_type(self.as_ptr(), type_.as_raw()) as c_int).map(|_| ()) + } + } + + /// Determines if current session used Extended Master Secret + /// + /// Returns `None` if the handshake is still in-progress. + #[corresponds(SSL_get_extms_support)] + #[cfg(ossl110)] + pub fn extms_support(&self) -> Option { + unsafe { + match ffi::SSL_get_extms_support(self.as_ptr()) { + -1 => None, + ret => Some(ret != 0), + } + } + } + + /// Returns the server's OCSP response, if present. + #[corresponds(SSL_get_tlsext_status_ocsp_resp)] + #[cfg(not(boringssl))] + pub fn ocsp_status(&self) -> Option<&[u8]> { + unsafe { + let mut p = ptr::null_mut(); + let len = ffi::SSL_get_tlsext_status_ocsp_resp(self.as_ptr(), &mut p); + + if len < 0 { + None + } else { + Some(slice::from_raw_parts(p as *const u8, len as usize)) + } + } + } + + /// Sets the OCSP response to be returned to the client. + #[corresponds(SSL_set_tlsext_status_oscp_resp)] + #[cfg(not(boringssl))] + pub fn set_ocsp_status(&mut self, response: &[u8]) -> Result<(), ErrorStack> { + unsafe { + assert!(response.len() <= c_int::max_value() as usize); + let p = cvt_p(ffi::OPENSSL_malloc(response.len() as _))?; + ptr::copy_nonoverlapping(response.as_ptr(), p as *mut u8, response.len()); + cvt(ffi::SSL_set_tlsext_status_ocsp_resp( + self.as_ptr(), + p as *mut c_uchar, + response.len() as c_long, + ) as c_int) + .map(|_| ()) + .map_err(|e| { + ffi::OPENSSL_free(p); + e + }) + } + } + + /// Determines if this `Ssl` is configured for server-side or client-side use. + #[corresponds(SSL_is_server)] + pub fn is_server(&self) -> bool { + unsafe { SSL_is_server(self.as_ptr()) != 0 } + } + + /// Sets the extra data at the specified index. + /// + /// This can be used to provide data to callbacks registered with the context. Use the + /// `Ssl::new_ex_index` method to create an `Index`. + #[corresponds(SSL_set_ex_data)] + pub fn set_ex_data(&mut self, index: Index, data: T) { + unsafe { + let data = Box::new(data); + ffi::SSL_set_ex_data( + self.as_ptr(), + index.as_raw(), + Box::into_raw(data) as *mut c_void, + ); + } + } + + /// Returns a reference to the extra data at the specified index. + #[corresponds(SSL_get_ex_data)] + pub fn ex_data(&self, index: Index) -> Option<&T> { + unsafe { + let data = ffi::SSL_get_ex_data(self.as_ptr(), index.as_raw()); + if data.is_null() { + None + } else { + Some(&*(data as *const T)) + } + } + } + + /// Returns a mutable reference to the extra data at the specified index. + #[corresponds(SSL_get_ex_data)] + pub fn ex_data_mut(&mut self, index: Index) -> Option<&mut T> { + unsafe { + let data = ffi::SSL_get_ex_data(self.as_ptr(), index.as_raw()); + if data.is_null() { + None + } else { + Some(&mut *(data as *mut T)) + } + } + } + + /// Sets the maximum amount of early data that will be accepted on this connection. + /// + /// Requires OpenSSL 1.1.1 or LibreSSL 3.4.0 or newer. + #[corresponds(SSL_set_max_early_data)] + #[cfg(any(ossl111, libressl340))] + pub fn set_max_early_data(&mut self, bytes: u32) -> Result<(), ErrorStack> { + if unsafe { ffi::SSL_set_max_early_data(self.as_ptr(), bytes) } == 1 { + Ok(()) + } else { + Err(ErrorStack::get()) + } + } + + /// Gets the maximum amount of early data that can be sent on this connection. + /// + /// Requires OpenSSL 1.1.1 or LibreSSL 3.4.0 or newer. + #[corresponds(SSL_get_max_early_data)] + #[cfg(any(ossl111, libressl340))] + pub fn max_early_data(&self) -> u32 { + unsafe { ffi::SSL_get_max_early_data(self.as_ptr()) } + } + + /// Copies the contents of the last Finished message sent to the peer into the provided buffer. + /// + /// The total size of the message is returned, so this can be used to determine the size of the + /// buffer required. + #[corresponds(SSL_get_finished)] + pub fn finished(&self, buf: &mut [u8]) -> usize { + unsafe { ffi::SSL_get_finished(self.as_ptr(), buf.as_mut_ptr() as *mut c_void, buf.len()) } + } + + /// Copies the contents of the last Finished message received from the peer into the provided + /// buffer. + /// + /// The total size of the message is returned, so this can be used to determine the size of the + /// buffer required. + #[corresponds(SSL_get_peer_finished)] + pub fn peer_finished(&self, buf: &mut [u8]) -> usize { + unsafe { + ffi::SSL_get_peer_finished(self.as_ptr(), buf.as_mut_ptr() as *mut c_void, buf.len()) + } + } + + /// Determines if the initial handshake has been completed. + #[corresponds(SSL_is_init_finished)] + #[cfg(ossl110)] + pub fn is_init_finished(&self) -> bool { + unsafe { ffi::SSL_is_init_finished(self.as_ptr()) != 0 } + } + + /// Determines if the client's hello message is in the SSLv2 format. + /// + /// This can only be used inside of the client hello callback. Otherwise, `false` is returned. + /// + /// Requires OpenSSL 1.1.1 or newer. + #[corresponds(SSL_client_hello_isv2)] + #[cfg(ossl111)] + pub fn client_hello_isv2(&self) -> bool { + unsafe { ffi::SSL_client_hello_isv2(self.as_ptr()) != 0 } + } + + /// Returns the legacy version field of the client's hello message. + /// + /// This can only be used inside of the client hello callback. Otherwise, `None` is returned. + /// + /// Requires OpenSSL 1.1.1 or newer. + #[corresponds(SSL_client_hello_get0_legacy_version)] + #[cfg(ossl111)] + pub fn client_hello_legacy_version(&self) -> Option { + unsafe { + let version = ffi::SSL_client_hello_get0_legacy_version(self.as_ptr()); + if version == 0 { + None + } else { + Some(SslVersion(version as c_int)) + } + } + } + + /// Returns the random field of the client's hello message. + /// + /// This can only be used inside of the client hello callback. Otherwise, `None` is returned. + /// + /// Requires OpenSSL 1.1.1 or newer. + #[corresponds(SSL_client_hello_get0_random)] + #[cfg(ossl111)] + pub fn client_hello_random(&self) -> Option<&[u8]> { + unsafe { + let mut ptr = ptr::null(); + let len = ffi::SSL_client_hello_get0_random(self.as_ptr(), &mut ptr); + if len == 0 { + None + } else { + Some(slice::from_raw_parts(ptr, len)) + } + } + } + + /// Returns the session ID field of the client's hello message. + /// + /// This can only be used inside of the client hello callback. Otherwise, `None` is returned. + /// + /// Requires OpenSSL 1.1.1 or newer. + #[corresponds(SSL_client_hello_get0_session_id)] + #[cfg(ossl111)] + pub fn client_hello_session_id(&self) -> Option<&[u8]> { + unsafe { + let mut ptr = ptr::null(); + let len = ffi::SSL_client_hello_get0_session_id(self.as_ptr(), &mut ptr); + if len == 0 { + None + } else { + Some(slice::from_raw_parts(ptr, len)) + } + } + } + + /// Returns the ciphers field of the client's hello message. + /// + /// This can only be used inside of the client hello callback. Otherwise, `None` is returned. + /// + /// Requires OpenSSL 1.1.1 or newer. + #[corresponds(SSL_client_hello_get0_ciphers)] + #[cfg(ossl111)] + pub fn client_hello_ciphers(&self) -> Option<&[u8]> { + unsafe { + let mut ptr = ptr::null(); + let len = ffi::SSL_client_hello_get0_ciphers(self.as_ptr(), &mut ptr); + if len == 0 { + None + } else { + Some(slice::from_raw_parts(ptr, len)) + } + } + } + + /// Decodes a slice of wire-format cipher suite specification bytes. Unsupported cipher suites + /// are ignored. + /// + /// Requires OpenSSL 1.1.1 or newer. + #[corresponds(SSL_bytes_to_cipher_list)] + #[cfg(ossl111)] + pub fn bytes_to_cipher_list( + &self, + bytes: &[u8], + isv2format: bool, + ) -> Result { + unsafe { + let ptr = bytes.as_ptr(); + let len = bytes.len(); + let mut sk = ptr::null_mut(); + let mut scsvs = ptr::null_mut(); + let res = ffi::SSL_bytes_to_cipher_list( + self.as_ptr(), + ptr, + len, + isv2format as c_int, + &mut sk, + &mut scsvs, + ); + if res == 1 { + Ok(CipherLists { + suites: Stack::from_ptr(sk), + signalling_suites: Stack::from_ptr(scsvs), + }) + } else { + Err(ErrorStack::get()) + } + } + } + + /// Returns the compression methods field of the client's hello message. + /// + /// This can only be used inside of the client hello callback. Otherwise, `None` is returned. + /// + /// Requires OpenSSL 1.1.1 or newer. + #[corresponds(SSL_client_hello_get0_compression_methods)] + #[cfg(ossl111)] + pub fn client_hello_compression_methods(&self) -> Option<&[u8]> { + unsafe { + let mut ptr = ptr::null(); + let len = ffi::SSL_client_hello_get0_compression_methods(self.as_ptr(), &mut ptr); + if len == 0 { + None + } else { + Some(slice::from_raw_parts(ptr, len)) + } + } + } + + /// Sets the MTU used for DTLS connections. + #[corresponds(SSL_set_mtu)] + pub fn set_mtu(&mut self, mtu: u32) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::SSL_set_mtu(self.as_ptr(), mtu as MtuTy) as c_int).map(|_| ()) } + } + + /// Returns the PSK identity hint used during connection setup. + /// + /// May return `None` if no PSK identity hint was used during the connection setup. + #[corresponds(SSL_get_psk_identity_hint)] + #[cfg(not(osslconf = "OPENSSL_NO_PSK"))] + pub fn psk_identity_hint(&self) -> Option<&[u8]> { + unsafe { + let ptr = ffi::SSL_get_psk_identity_hint(self.as_ptr()); + if ptr.is_null() { + None + } else { + Some(CStr::from_ptr(ptr).to_bytes()) + } + } + } + + /// Returns the PSK identity used during connection setup. + #[corresponds(SSL_get_psk_identity)] + #[cfg(not(osslconf = "OPENSSL_NO_PSK"))] + pub fn psk_identity(&self) -> Option<&[u8]> { + unsafe { + let ptr = ffi::SSL_get_psk_identity(self.as_ptr()); + if ptr.is_null() { + None + } else { + Some(CStr::from_ptr(ptr).to_bytes()) + } + } + } + + #[corresponds(SSL_add0_chain_cert)] + #[cfg(ossl102)] + pub fn add_chain_cert(&mut self, chain: X509) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::SSL_add0_chain_cert(self.as_ptr(), chain.as_ptr()) as c_int).map(|_| ())?; + mem::forget(chain); + } + Ok(()) + } + + /// Sets a new default TLS/SSL method for SSL objects + #[cfg(not(boringssl))] + pub fn set_method(&mut self, method: SslMethod) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::SSL_set_ssl_method(self.as_ptr(), method.as_ptr()))?; + }; + Ok(()) + } + + /// Loads the private key from a file. + #[corresponds(SSL_use_Private_Key_file)] + pub fn set_private_key_file>( + &mut self, + path: P, + ssl_file_type: SslFiletype, + ) -> Result<(), ErrorStack> { + let p = path.as_ref().as_os_str().to_str().unwrap(); + let key_file = CString::new(p).unwrap(); + unsafe { + cvt(ffi::SSL_use_PrivateKey_file( + self.as_ptr(), + key_file.as_ptr(), + ssl_file_type.as_raw(), + ))?; + }; + Ok(()) + } + + /// Sets the private key. + #[corresponds(SSL_use_PrivateKey)] + pub fn set_private_key(&mut self, pkey: &PKeyRef) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::SSL_use_PrivateKey(self.as_ptr(), pkey.as_ptr()))?; + }; + Ok(()) + } + + /// Sets the certificate + #[corresponds(SSL_use_certificate)] + pub fn set_certificate(&mut self, cert: &X509Ref) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::SSL_use_certificate(self.as_ptr(), cert.as_ptr()))?; + }; + Ok(()) + } + + /// Loads a certificate chain from a file. + /// + /// The file should contain a sequence of PEM-formatted certificates, the first being the leaf + /// certificate, and the remainder forming the chain of certificates up to and including the + /// trusted root certificate. + #[corresponds(SSL_use_certificate_chain_file)] + #[cfg(any(ossl110, libressl332))] + pub fn set_certificate_chain_file>( + &mut self, + path: P, + ) -> Result<(), ErrorStack> { + let p = path.as_ref().as_os_str().to_str().unwrap(); + let cert_file = CString::new(p).unwrap(); + unsafe { + cvt(ffi::SSL_use_certificate_chain_file( + self.as_ptr(), + cert_file.as_ptr(), + ))?; + }; + Ok(()) + } + + /// Sets ca certificate that client trusted + #[corresponds(SSL_add_client_CA)] + pub fn add_client_ca(&mut self, cacert: &X509Ref) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::SSL_add_client_CA(self.as_ptr(), cacert.as_ptr()))?; + }; + Ok(()) + } + + // Sets the list of CAs sent to the client when requesting a client certificate for the chosen ssl + #[corresponds(SSL_set_client_CA_list)] + pub fn set_client_ca_list(&mut self, list: Stack) { + unsafe { ffi::SSL_set_client_CA_list(self.as_ptr(), list.as_ptr()) } + mem::forget(list); + } + + /// Sets the minimum supported protocol version. + /// + /// A value of `None` will enable protocol versions down to the lowest version supported by + /// OpenSSL. + /// + /// Requires OpenSSL 1.1.0 or LibreSSL 2.6.1 or newer. + #[corresponds(SSL_set_min_proto_version)] + #[cfg(any(ossl110, libressl261))] + pub fn set_min_proto_version(&mut self, version: Option) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::SSL_set_min_proto_version( + self.as_ptr(), + version.map_or(0, |v| v.0 as _), + )) + .map(|_| ()) + } + } + + /// Sets the maximum supported protocol version. + /// + /// A value of `None` will enable protocol versions up to the highest version supported by + /// OpenSSL. + /// + /// Requires OpenSSL 1.1.0 or or LibreSSL 2.6.1 or newer. + #[corresponds(SSL_set_max_proto_version)] + #[cfg(any(ossl110, libressl261))] + pub fn set_max_proto_version(&mut self, version: Option) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::SSL_set_max_proto_version( + self.as_ptr(), + version.map_or(0, |v| v.0 as _), + )) + .map(|_| ()) + } + } + + /// Sets the list of supported ciphers for the TLSv1.3 protocol. + /// + /// The `set_cipher_list` method controls the cipher suites for protocols before TLSv1.3. + /// + /// The format consists of TLSv1.3 cipher suite names separated by `:` characters in order of + /// preference. + /// + /// Requires OpenSSL 1.1.1 or LibreSSL 3.4.0 or newer. + #[corresponds(SSL_set_ciphersuites)] + #[cfg(any(ossl111, libressl340))] + pub fn set_ciphersuites(&mut self, cipher_list: &str) -> Result<(), ErrorStack> { + let cipher_list = CString::new(cipher_list).unwrap(); + unsafe { + cvt(ffi::SSL_set_ciphersuites( + self.as_ptr(), + cipher_list.as_ptr() as *const _, + )) + .map(|_| ()) + } + } + + /// Sets the list of supported ciphers for protocols before TLSv1.3. + /// + /// The `set_ciphersuites` method controls the cipher suites for TLSv1.3. + /// + /// See [`ciphers`] for details on the format. + /// + /// [`ciphers`]: https://www.openssl.org/docs/manmaster/apps/ciphers.html + #[corresponds(SSL_set_cipher_list)] + pub fn set_cipher_list(&mut self, cipher_list: &str) -> Result<(), ErrorStack> { + let cipher_list = CString::new(cipher_list).unwrap(); + unsafe { + cvt(ffi::SSL_set_cipher_list( + self.as_ptr(), + cipher_list.as_ptr() as *const _, + )) + .map(|_| ()) + } + } + + /// Set the certificate store used for certificate verification + #[corresponds(SSL_set_cert_store)] + #[cfg(ossl102)] + pub fn set_verify_cert_store(&mut self, cert_store: X509Store) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::SSL_set0_verify_cert_store(self.as_ptr(), cert_store.as_ptr()) as c_int)?; + mem::forget(cert_store); + Ok(()) + } + } + + /// Sets the number of TLS 1.3 session tickets that will be sent to a client after a full + /// handshake. + /// + /// Requires OpenSSL 1.1.1 or newer. + #[corresponds(SSL_set_num_tickets)] + #[cfg(ossl111)] + pub fn set_num_tickets(&mut self, num_tickets: usize) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::SSL_set_num_tickets(self.as_ptr(), num_tickets)).map(|_| ()) } + } + + /// Gets the number of TLS 1.3 session tickets that will be sent to a client after a full + /// handshake. + /// + /// Requires OpenSSL 1.1.1 or newer. + #[corresponds(SSL_get_num_tickets)] + #[cfg(ossl111)] + pub fn num_tickets(&self) -> usize { + unsafe { ffi::SSL_get_num_tickets(self.as_ptr()) } + } +} + +/// An SSL stream midway through the handshake process. +#[derive(Debug)] +pub struct MidHandshakeSslStream { + stream: SslStream, + error: Error, +} + +impl MidHandshakeSslStream { + /// Returns a shared reference to the inner stream. + pub fn get_ref(&self) -> &S { + self.stream.get_ref() + } + + /// Returns a mutable reference to the inner stream. + pub fn get_mut(&mut self) -> &mut S { + self.stream.get_mut() + } + + /// Returns a shared reference to the `Ssl` of the stream. + pub fn ssl(&self) -> &SslRef { + self.stream.ssl() + } + + /// Returns the underlying error which interrupted this handshake. + pub fn error(&self) -> &Error { + &self.error + } + + /// Consumes `self`, returning its error. + pub fn into_error(self) -> Error { + self.error + } +} + +impl MidHandshakeSslStream +where + S: Read + Write, +{ + /// Restarts the handshake process. + /// + /// This corresponds to [`SSL_do_handshake`]. + /// + /// [`SSL_do_handshake`]: https://www.openssl.org/docs/manmaster/man3/SSL_do_handshake.html + pub fn handshake(mut self) -> Result, HandshakeError> { + match self.stream.do_handshake() { + Ok(()) => Ok(self.stream), + Err(error) => { + self.error = error; + match self.error.code() { + ErrorCode::WANT_READ | ErrorCode::WANT_WRITE => { + Err(HandshakeError::WouldBlock(self)) + } + _ => Err(HandshakeError::Failure(self)), + } + } + } + } +} + +/// A TLS session over a stream. +pub struct SslStream { + ssl: ManuallyDrop, + method: ManuallyDrop, + _p: PhantomData, +} + +impl Drop for SslStream { + fn drop(&mut self) { + // ssl holds a reference to method internally so it has to drop first + unsafe { + ManuallyDrop::drop(&mut self.ssl); + ManuallyDrop::drop(&mut self.method); + } + } +} + +impl fmt::Debug for SslStream +where + S: fmt::Debug, +{ + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("SslStream") + .field("stream", &self.get_ref()) + .field("ssl", &self.ssl()) + .finish() + } +} + +impl SslStream { + /// Creates a new `SslStream`. + /// + /// This function performs no IO; the stream will not have performed any part of the handshake + /// with the peer. If the `Ssl` was configured with [`SslRef::set_connect_state`] or + /// [`SslRef::set_accept_state`], the handshake can be performed automatically during the first + /// call to read or write. Otherwise the `connect` and `accept` methods can be used to + /// explicitly perform the handshake. + #[corresponds(SSL_set_bio)] + pub fn new(ssl: Ssl, stream: S) -> Result { + let (bio, method) = bio::new(stream)?; + unsafe { + ffi::SSL_set_bio(ssl.as_ptr(), bio, bio); + } + + Ok(SslStream { + ssl: ManuallyDrop::new(ssl), + method: ManuallyDrop::new(method), + _p: PhantomData, + }) + } + + /// Constructs an `SslStream` from a pointer to the underlying OpenSSL `SSL` struct. + /// + /// This is useful if the handshake has already been completed elsewhere. + /// + /// # Safety + /// + /// The caller must ensure the pointer is valid. + #[deprecated( + since = "0.10.32", + note = "use Ssl::from_ptr and SslStream::new instead" + )] + pub unsafe fn from_raw_parts(ssl: *mut ffi::SSL, stream: S) -> Self { + let ssl = Ssl::from_ptr(ssl); + Self::new(ssl, stream).unwrap() + } + + /// Read application data transmitted by a client before handshake completion. + /// + /// Useful for reducing latency, but vulnerable to replay attacks. Call + /// [`SslRef::set_accept_state`] first. + /// + /// Returns `Ok(0)` if all early data has been read. + /// + /// Requires OpenSSL 1.1.1 or LibreSSL 3.4.0 or newer. + #[corresponds(SSL_read_early_data)] + #[cfg(any(ossl111, libressl340))] + pub fn read_early_data(&mut self, buf: &mut [u8]) -> Result { + let mut read = 0; + let ret = unsafe { + ffi::SSL_read_early_data( + self.ssl.as_ptr(), + buf.as_ptr() as *mut c_void, + buf.len(), + &mut read, + ) + }; + match ret { + ffi::SSL_READ_EARLY_DATA_ERROR => Err(self.make_error(ret)), + ffi::SSL_READ_EARLY_DATA_SUCCESS => Ok(read), + ffi::SSL_READ_EARLY_DATA_FINISH => Ok(0), + _ => unreachable!(), + } + } + + /// Send data to the server without blocking on handshake completion. + /// + /// Useful for reducing latency, but vulnerable to replay attacks. Call + /// [`SslRef::set_connect_state`] first. + /// + /// Requires OpenSSL 1.1.1 or LibreSSL 3.4.0 or newer. + #[corresponds(SSL_write_early_data)] + #[cfg(any(ossl111, libressl340))] + pub fn write_early_data(&mut self, buf: &[u8]) -> Result { + let mut written = 0; + let ret = unsafe { + ffi::SSL_write_early_data( + self.ssl.as_ptr(), + buf.as_ptr() as *const c_void, + buf.len(), + &mut written, + ) + }; + if ret > 0 { + Ok(written) + } else { + Err(self.make_error(ret)) + } + } + + /// Initiates a client-side TLS handshake. + /// + /// # Warning + /// + /// OpenSSL's default configuration is insecure. It is highly recommended to use + /// `SslConnector` rather than `Ssl` directly, as it manages that configuration. + #[corresponds(SSL_connect)] + pub fn connect(&mut self) -> Result<(), Error> { + let ret = unsafe { ffi::SSL_connect(self.ssl.as_ptr()) }; + if ret > 0 { + Ok(()) + } else { + Err(self.make_error(ret)) + } + } + + /// Initiates a server-side TLS handshake. + /// + /// # Warning + /// + /// OpenSSL's default configuration is insecure. It is highly recommended to use + /// `SslAcceptor` rather than `Ssl` directly, as it manages that configuration. + #[corresponds(SSL_accept)] + pub fn accept(&mut self) -> Result<(), Error> { + let ret = unsafe { ffi::SSL_accept(self.ssl.as_ptr()) }; + if ret > 0 { + Ok(()) + } else { + Err(self.make_error(ret)) + } + } + + /// Initiates the handshake. + /// + /// This will fail if `set_accept_state` or `set_connect_state` was not called first. + #[corresponds(SSL_do_handshake)] + pub fn do_handshake(&mut self) -> Result<(), Error> { + let ret = unsafe { ffi::SSL_do_handshake(self.ssl.as_ptr()) }; + if ret > 0 { + Ok(()) + } else { + Err(self.make_error(ret)) + } + } + + /// Perform a stateless server-side handshake. + /// + /// Requires that cookie generation and verification callbacks were + /// set on the SSL context. + /// + /// Returns `Ok(true)` if a complete ClientHello containing a valid cookie + /// was read, in which case the handshake should be continued via + /// `accept`. If a HelloRetryRequest containing a fresh cookie was + /// transmitted, `Ok(false)` is returned instead. If the handshake cannot + /// proceed at all, `Err` is returned. + #[corresponds(SSL_stateless)] + #[cfg(ossl111)] + pub fn stateless(&mut self) -> Result { + match unsafe { ffi::SSL_stateless(self.ssl.as_ptr()) } { + 1 => Ok(true), + 0 => Ok(false), + -1 => Err(ErrorStack::get()), + _ => unreachable!(), + } + } + + /// Like `read`, but returns an `ssl::Error` rather than an `io::Error`. + /// + /// It is particularly useful with a non-blocking socket, where the error value will identify if + /// OpenSSL is waiting on read or write readiness. + #[corresponds(SSL_read)] + pub fn ssl_read(&mut self, buf: &mut [u8]) -> Result { + // The interpretation of the return code here is a little odd with a + // zero-length write. OpenSSL will likely correctly report back to us + // that it read zero bytes, but zero is also the sentinel for "error". + // To avoid that confusion short-circuit that logic and return quickly + // if `buf` has a length of zero. + if buf.is_empty() { + return Ok(0); + } + + let ret = self.ssl.read(buf); + if ret > 0 { + Ok(ret as usize) + } else { + Err(self.make_error(ret)) + } + } + + /// Like `write`, but returns an `ssl::Error` rather than an `io::Error`. + /// + /// It is particularly useful with a non-blocking socket, where the error value will identify if + /// OpenSSL is waiting on read or write readiness. + #[corresponds(SSL_write)] + pub fn ssl_write(&mut self, buf: &[u8]) -> Result { + // See above for why we short-circuit on zero-length buffers + if buf.is_empty() { + return Ok(0); + } + + let ret = self.ssl.write(buf); + if ret > 0 { + Ok(ret as usize) + } else { + Err(self.make_error(ret)) + } + } + + /// Reads data from the stream, without removing it from the queue. + #[corresponds(SSL_peek)] + pub fn ssl_peek(&mut self, buf: &mut [u8]) -> Result { + // See above for why we short-circuit on zero-length buffers + if buf.is_empty() { + return Ok(0); + } + + let ret = self.ssl.peek(buf); + if ret > 0 { + Ok(ret as usize) + } else { + Err(self.make_error(ret)) + } + } + + /// Shuts down the session. + /// + /// The shutdown process consists of two steps. The first step sends a close notify message to + /// the peer, after which `ShutdownResult::Sent` is returned. The second step awaits the receipt + /// of a close notify message from the peer, after which `ShutdownResult::Received` is returned. + /// + /// While the connection may be closed after the first step, it is recommended to fully shut the + /// session down. In particular, it must be fully shut down if the connection is to be used for + /// further communication in the future. + #[corresponds(SSL_shutdown)] + pub fn shutdown(&mut self) -> Result { + match unsafe { ffi::SSL_shutdown(self.ssl.as_ptr()) } { + 0 => Ok(ShutdownResult::Sent), + 1 => Ok(ShutdownResult::Received), + n => Err(self.make_error(n)), + } + } + + /// Returns the session's shutdown state. + #[corresponds(SSL_get_shutdown)] + pub fn get_shutdown(&mut self) -> ShutdownState { + unsafe { + let bits = ffi::SSL_get_shutdown(self.ssl.as_ptr()); + ShutdownState::from_bits_retain(bits) + } + } + + /// Sets the session's shutdown state. + /// + /// This can be used to tell OpenSSL that the session should be cached even if a full two-way + /// shutdown was not completed. + #[corresponds(SSL_set_shutdown)] + pub fn set_shutdown(&mut self, state: ShutdownState) { + unsafe { ffi::SSL_set_shutdown(self.ssl.as_ptr(), state.bits()) } + } +} + +impl SslStream { + fn make_error(&mut self, ret: c_int) -> Error { + self.check_panic(); + + let code = self.ssl.get_error(ret); + + let cause = match code { + ErrorCode::SSL => Some(InnerError::Ssl(ErrorStack::get())), + ErrorCode::SYSCALL => { + let errs = ErrorStack::get(); + if errs.errors().is_empty() { + self.get_bio_error().map(InnerError::Io) + } else { + Some(InnerError::Ssl(errs)) + } + } + ErrorCode::ZERO_RETURN => None, + ErrorCode::WANT_READ | ErrorCode::WANT_WRITE => { + self.get_bio_error().map(InnerError::Io) + } + _ => None, + }; + + Error { code, cause } + } + + fn check_panic(&mut self) { + if let Some(err) = unsafe { bio::take_panic::(self.ssl.get_raw_rbio()) } { + resume_unwind(err) + } + } + + fn get_bio_error(&mut self) -> Option { + unsafe { bio::take_error::(self.ssl.get_raw_rbio()) } + } + + /// Returns a shared reference to the underlying stream. + pub fn get_ref(&self) -> &S { + unsafe { + let bio = self.ssl.get_raw_rbio(); + bio::get_ref(bio) + } + } + + /// Returns a mutable reference to the underlying stream. + /// + /// # Warning + /// + /// It is inadvisable to read from or write to the underlying stream as it + /// will most likely corrupt the SSL session. + pub fn get_mut(&mut self) -> &mut S { + unsafe { + let bio = self.ssl.get_raw_rbio(); + bio::get_mut(bio) + } + } + + /// Returns a shared reference to the `Ssl` object associated with this stream. + pub fn ssl(&self) -> &SslRef { + &self.ssl + } +} + +impl Read for SslStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + loop { + match self.ssl_read(buf) { + Ok(n) => return Ok(n), + Err(ref e) if e.code() == ErrorCode::ZERO_RETURN => return Ok(0), + Err(ref e) if e.code() == ErrorCode::SYSCALL && e.io_error().is_none() => { + return Ok(0); + } + Err(ref e) if e.code() == ErrorCode::WANT_READ && e.io_error().is_none() => {} + Err(e) => { + return Err(e + .into_io_error() + .unwrap_or_else(|e| io::Error::new(io::ErrorKind::Other, e))); + } + } + } + } +} + +impl Write for SslStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + loop { + match self.ssl_write(buf) { + Ok(n) => return Ok(n), + Err(ref e) if e.code() == ErrorCode::WANT_READ && e.io_error().is_none() => {} + Err(e) => { + return Err(e + .into_io_error() + .unwrap_or_else(|e| io::Error::new(io::ErrorKind::Other, e))); + } + } + } + } + + fn flush(&mut self) -> io::Result<()> { + self.get_mut().flush() + } +} + +/// A partially constructed `SslStream`, useful for unusual handshakes. +#[deprecated( + since = "0.10.32", + note = "use the methods directly on Ssl/SslStream instead" +)] +pub struct SslStreamBuilder { + inner: SslStream, +} + +#[allow(deprecated)] +impl SslStreamBuilder +where + S: Read + Write, +{ + /// Begin creating an `SslStream` atop `stream` + pub fn new(ssl: Ssl, stream: S) -> Self { + Self { + inner: SslStream::new(ssl, stream).unwrap(), + } + } + + /// Perform a stateless server-side handshake + /// + /// Requires that cookie generation and verification callbacks were + /// set on the SSL context. + /// + /// Returns `Ok(true)` if a complete ClientHello containing a valid cookie + /// was read, in which case the handshake should be continued via + /// `accept`. If a HelloRetryRequest containing a fresh cookie was + /// transmitted, `Ok(false)` is returned instead. If the handshake cannot + /// proceed at all, `Err` is returned. + /// + /// This corresponds to [`SSL_stateless`] + /// + /// [`SSL_stateless`]: https://www.openssl.org/docs/manmaster/man3/SSL_stateless.html + #[cfg(ossl111)] + pub fn stateless(&mut self) -> Result { + match unsafe { ffi::SSL_stateless(self.inner.ssl.as_ptr()) } { + 1 => Ok(true), + 0 => Ok(false), + -1 => Err(ErrorStack::get()), + _ => unreachable!(), + } + } + + /// Configure as an outgoing stream from a client. + /// + /// This corresponds to [`SSL_set_connect_state`]. + /// + /// [`SSL_set_connect_state`]: https://www.openssl.org/docs/manmaster/man3/SSL_set_connect_state.html + pub fn set_connect_state(&mut self) { + unsafe { ffi::SSL_set_connect_state(self.inner.ssl.as_ptr()) } + } + + /// Configure as an incoming stream to a server. + /// + /// This corresponds to [`SSL_set_accept_state`]. + /// + /// [`SSL_set_accept_state`]: https://www.openssl.org/docs/manmaster/man3/SSL_set_accept_state.html + pub fn set_accept_state(&mut self) { + unsafe { ffi::SSL_set_accept_state(self.inner.ssl.as_ptr()) } + } + + /// See `Ssl::connect` + pub fn connect(mut self) -> Result, HandshakeError> { + match self.inner.connect() { + Ok(()) => Ok(self.inner), + Err(error) => match error.code() { + ErrorCode::WANT_READ | ErrorCode::WANT_WRITE => { + Err(HandshakeError::WouldBlock(MidHandshakeSslStream { + stream: self.inner, + error, + })) + } + _ => Err(HandshakeError::Failure(MidHandshakeSslStream { + stream: self.inner, + error, + })), + }, + } + } + + /// See `Ssl::accept` + pub fn accept(mut self) -> Result, HandshakeError> { + match self.inner.accept() { + Ok(()) => Ok(self.inner), + Err(error) => match error.code() { + ErrorCode::WANT_READ | ErrorCode::WANT_WRITE => { + Err(HandshakeError::WouldBlock(MidHandshakeSslStream { + stream: self.inner, + error, + })) + } + _ => Err(HandshakeError::Failure(MidHandshakeSslStream { + stream: self.inner, + error, + })), + }, + } + } + + /// Initiates the handshake. + /// + /// This will fail if `set_accept_state` or `set_connect_state` was not called first. + /// + /// This corresponds to [`SSL_do_handshake`]. + /// + /// [`SSL_do_handshake`]: https://www.openssl.org/docs/manmaster/man3/SSL_do_handshake.html + pub fn handshake(mut self) -> Result, HandshakeError> { + match self.inner.do_handshake() { + Ok(()) => Ok(self.inner), + Err(error) => match error.code() { + ErrorCode::WANT_READ | ErrorCode::WANT_WRITE => { + Err(HandshakeError::WouldBlock(MidHandshakeSslStream { + stream: self.inner, + error, + })) + } + _ => Err(HandshakeError::Failure(MidHandshakeSslStream { + stream: self.inner, + error, + })), + }, + } + } + + /// Read application data transmitted by a client before handshake + /// completion. + /// + /// Useful for reducing latency, but vulnerable to replay attacks. Call + /// `set_accept_state` first. + /// + /// Returns `Ok(0)` if all early data has been read. + /// + /// Requires OpenSSL 1.1.1 or LibreSSL 3.4.0 or newer. + /// + /// This corresponds to [`SSL_read_early_data`]. + /// + /// [`SSL_read_early_data`]: https://www.openssl.org/docs/manmaster/man3/SSL_read_early_data.html + #[cfg(any(ossl111, libressl340))] + pub fn read_early_data(&mut self, buf: &mut [u8]) -> Result { + self.inner.read_early_data(buf) + } + + /// Send data to the server without blocking on handshake completion. + /// + /// Useful for reducing latency, but vulnerable to replay attacks. Call + /// `set_connect_state` first. + /// + /// Requires OpenSSL 1.1.1 or LibreSSL 3.4.0 or newer. + /// + /// This corresponds to [`SSL_write_early_data`]. + /// + /// [`SSL_write_early_data`]: https://www.openssl.org/docs/manmaster/man3/SSL_write_early_data.html + #[cfg(any(ossl111, libressl340))] + pub fn write_early_data(&mut self, buf: &[u8]) -> Result { + self.inner.write_early_data(buf) + } +} + +#[allow(deprecated)] +impl SslStreamBuilder { + /// Returns a shared reference to the underlying stream. + pub fn get_ref(&self) -> &S { + unsafe { + let bio = self.inner.ssl.get_raw_rbio(); + bio::get_ref(bio) + } + } + + /// Returns a mutable reference to the underlying stream. + /// + /// # Warning + /// + /// It is inadvisable to read from or write to the underlying stream as it + /// will most likely corrupt the SSL session. + pub fn get_mut(&mut self) -> &mut S { + unsafe { + let bio = self.inner.ssl.get_raw_rbio(); + bio::get_mut(bio) + } + } + + /// Returns a shared reference to the `Ssl` object associated with this builder. + pub fn ssl(&self) -> &SslRef { + &self.inner.ssl + } + + /// Set the DTLS MTU size. + /// + /// It will be ignored if the value is smaller than the minimum packet size + /// the DTLS protocol requires. + /// + /// # Panics + /// This function panics if the given mtu size can't be represented in a positive `c_long` range + #[deprecated(note = "Use SslRef::set_mtu instead", since = "0.10.30")] + pub fn set_dtls_mtu_size(&mut self, mtu_size: usize) { + unsafe { + let bio = self.inner.ssl.get_raw_rbio(); + bio::set_dtls_mtu_size::(bio, mtu_size); + } + } +} + +/// The result of a shutdown request. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ShutdownResult { + /// A close notify message has been sent to the peer. + Sent, + + /// A close notify response message has been received from the peer. + Received, +} + +bitflags! { + /// The shutdown state of a session. + #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + #[repr(transparent)] + pub struct ShutdownState: c_int { + /// A close notify message has been sent to the peer. + const SENT = ffi::SSL_SENT_SHUTDOWN; + /// A close notify message has been received from the peer. + const RECEIVED = ffi::SSL_RECEIVED_SHUTDOWN; + } +} + +cfg_if! { + if #[cfg(any(boringssl, ossl110, libressl273))] { + use ffi::{SSL_CTX_up_ref, SSL_SESSION_get_master_key, SSL_SESSION_up_ref, SSL_is_server}; + } else { + #[allow(bad_style)] + pub unsafe fn SSL_CTX_up_ref(ssl: *mut ffi::SSL_CTX) -> c_int { + ffi::CRYPTO_add_lock( + &mut (*ssl).references, + 1, + ffi::CRYPTO_LOCK_SSL_CTX, + "mod.rs\0".as_ptr() as *const _, + line!() as c_int, + ); + 0 + } + + #[allow(bad_style)] + pub unsafe fn SSL_SESSION_get_master_key( + session: *const ffi::SSL_SESSION, + out: *mut c_uchar, + mut outlen: usize, + ) -> usize { + if outlen == 0 { + return (*session).master_key_length as usize; + } + if outlen > (*session).master_key_length as usize { + outlen = (*session).master_key_length as usize; + } + ptr::copy_nonoverlapping((*session).master_key.as_ptr(), out, outlen); + outlen + } + + #[allow(bad_style)] + pub unsafe fn SSL_is_server(s: *mut ffi::SSL) -> c_int { + (*s).server + } + + #[allow(bad_style)] + pub unsafe fn SSL_SESSION_up_ref(ses: *mut ffi::SSL_SESSION) -> c_int { + ffi::CRYPTO_add_lock( + &mut (*ses).references, + 1, + ffi::CRYPTO_LOCK_SSL_CTX, + "mod.rs\0".as_ptr() as *const _, + line!() as c_int, + ); + 0 + } + } +} + +cfg_if! { + if #[cfg(ossl300)] { + use ffi::SSL_get1_peer_certificate; + } else { + use ffi::SSL_get_peer_certificate as SSL_get1_peer_certificate; + } +} +cfg_if! { + if #[cfg(any(boringssl, ossl110, libressl291))] { + use ffi::{TLS_method, DTLS_method, TLS_client_method, TLS_server_method}; + } else { + use ffi::{ + SSLv23_method as TLS_method, DTLSv1_method as DTLS_method, SSLv23_client_method as TLS_client_method, + SSLv23_server_method as TLS_server_method, + }; + } +} +cfg_if! { + if #[cfg(ossl110)] { + unsafe fn get_new_idx(f: ffi::CRYPTO_EX_free) -> c_int { + ffi::CRYPTO_get_ex_new_index( + ffi::CRYPTO_EX_INDEX_SSL_CTX, + 0, + ptr::null_mut(), + None, + None, + Some(f), + ) + } + + unsafe fn get_new_ssl_idx(f: ffi::CRYPTO_EX_free) -> c_int { + ffi::CRYPTO_get_ex_new_index( + ffi::CRYPTO_EX_INDEX_SSL, + 0, + ptr::null_mut(), + None, + None, + Some(f), + ) + } + } else { + use std::sync::Once; + + unsafe fn get_new_idx(f: ffi::CRYPTO_EX_free) -> c_int { + // hack around https://rt.openssl.org/Ticket/Display.html?id=3710&user=guest&pass=guest + static ONCE: Once = Once::new(); + ONCE.call_once(|| { + cfg_if! { + if #[cfg(not(boringssl))] { + ffi::SSL_CTX_get_ex_new_index(0, ptr::null_mut(), None, None, None); + } else { + ffi::SSL_CTX_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, None); + } + } + }); + + cfg_if! { + if #[cfg(not(boringssl))] { + ffi::SSL_CTX_get_ex_new_index(0, ptr::null_mut(), None, None, Some(f)) + } else { + ffi::SSL_CTX_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f) + } + } + } + + unsafe fn get_new_ssl_idx(f: ffi::CRYPTO_EX_free) -> c_int { + // hack around https://rt.openssl.org/Ticket/Display.html?id=3710&user=guest&pass=guest + static ONCE: Once = Once::new(); + ONCE.call_once(|| { + #[cfg(not(boringssl))] + ffi::SSL_get_ex_new_index(0, ptr::null_mut(), None, None, None); + #[cfg(boringssl)] + ffi::SSL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, None); + }); + + #[cfg(not(boringssl))] + return ffi::SSL_get_ex_new_index(0, ptr::null_mut(), None, None, Some(f)); + #[cfg(boringssl)] + return ffi::SSL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f); + } + } +} diff --git a/vendor/openssl/src/ssl/test/mod.rs b/vendor/openssl/src/ssl/test/mod.rs new file mode 100644 index 0000000..7707af2 --- /dev/null +++ b/vendor/openssl/src/ssl/test/mod.rs @@ -0,0 +1,1570 @@ +#![allow(unused_imports)] + +use std::env; +use std::fs::File; +use std::io::prelude::*; +use std::io::{self, BufReader}; +use std::iter; +use std::mem; +use std::net::UdpSocket; +use std::net::{SocketAddr, TcpListener, TcpStream}; +use std::path::Path; +use std::process::{Child, ChildStdin, Command, Stdio}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::thread; +use std::time::Duration; + +use crate::dh::Dh; +use crate::error::ErrorStack; +use crate::hash::MessageDigest; +#[cfg(not(boringssl))] +use crate::ocsp::{OcspResponse, OcspResponseStatus}; +use crate::pkey::PKey; +use crate::srtp::SrtpProfileId; +use crate::ssl::test::server::Server; +#[cfg(any(ossl110, ossl111, libressl261))] +use crate::ssl::SslVersion; +use crate::ssl::{self, NameType, SslConnectorBuilder}; +#[cfg(ossl111)] +use crate::ssl::{ClientHelloResponse, ExtensionContext}; +use crate::ssl::{ + Error, HandshakeError, MidHandshakeSslStream, ShutdownResult, ShutdownState, Ssl, SslAcceptor, + SslAcceptorBuilder, SslConnector, SslContext, SslContextBuilder, SslFiletype, SslMethod, + SslOptions, SslSessionCacheMode, SslStream, SslVerifyMode, StatusType, +}; +#[cfg(ossl102)] +use crate::x509::store::X509StoreBuilder; +#[cfg(ossl102)] +use crate::x509::verify::X509CheckFlags; +use crate::x509::{X509Name, X509StoreContext, X509VerifyResult, X509}; + +mod server; + +static ROOT_CERT: &[u8] = include_bytes!("../../../test/root-ca.pem"); +static CERT: &[u8] = include_bytes!("../../../test/cert.pem"); +static KEY: &[u8] = include_bytes!("../../../test/key.pem"); + +#[test] +fn verify_untrusted() { + let mut server = Server::builder(); + server.should_error(); + let server = server.build(); + + let mut client = server.client(); + client.ctx().set_verify(SslVerifyMode::PEER); + + client.connect_err(); +} + +#[test] +fn verify_trusted() { + let server = Server::builder().build(); + + let mut client = server.client(); + client.ctx().set_ca_file("test/root-ca.pem").unwrap(); + + client.connect(); +} + +#[test] +#[cfg(ossl102)] +fn verify_trusted_with_set_cert() { + let server = Server::builder().build(); + + let mut store = X509StoreBuilder::new().unwrap(); + let x509 = X509::from_pem(ROOT_CERT).unwrap(); + store.add_cert(x509).unwrap(); + + let mut client = server.client(); + client.ctx().set_verify(SslVerifyMode::PEER); + client.ctx().set_verify_cert_store(store.build()).unwrap(); + + client.connect(); +} + +#[test] +fn verify_untrusted_callback_override_ok() { + static CALLED_BACK: AtomicBool = AtomicBool::new(false); + + let server = Server::builder().build(); + + let mut client = server.client(); + client + .ctx() + .set_verify_callback(SslVerifyMode::PEER, |_, x509| { + CALLED_BACK.store(true, Ordering::SeqCst); + assert!(x509.current_cert().is_some()); + true + }); + + client.connect(); + assert!(CALLED_BACK.load(Ordering::SeqCst)); +} + +#[test] +fn verify_untrusted_callback_override_bad() { + let mut server = Server::builder(); + server.should_error(); + let server = server.build(); + + let mut client = server.client(); + client + .ctx() + .set_verify_callback(SslVerifyMode::PEER, |_, _| false); + + client.connect_err(); +} + +#[test] +fn verify_trusted_callback_override_ok() { + static CALLED_BACK: AtomicBool = AtomicBool::new(false); + + let server = Server::builder().build(); + + let mut client = server.client(); + client.ctx().set_ca_file("test/root-ca.pem").unwrap(); + client + .ctx() + .set_verify_callback(SslVerifyMode::PEER, |_, x509| { + CALLED_BACK.store(true, Ordering::SeqCst); + assert!(x509.current_cert().is_some()); + true + }); + + client.connect(); + assert!(CALLED_BACK.load(Ordering::SeqCst)); +} + +#[test] +fn verify_trusted_callback_override_bad() { + let mut server = Server::builder(); + server.should_error(); + let server = server.build(); + + let mut client = server.client(); + client.ctx().set_ca_file("test/root-ca.pem").unwrap(); + client + .ctx() + .set_verify_callback(SslVerifyMode::PEER, |_, _| false); + + client.connect_err(); +} + +#[test] +fn verify_callback_load_certs() { + static CALLED_BACK: AtomicBool = AtomicBool::new(false); + + let server = Server::builder().build(); + + let mut client = server.client(); + client + .ctx() + .set_verify_callback(SslVerifyMode::PEER, |_, x509| { + CALLED_BACK.store(true, Ordering::SeqCst); + assert!(x509.current_cert().is_some()); + true + }); + + client.connect(); + assert!(CALLED_BACK.load(Ordering::SeqCst)); +} + +#[test] +fn verify_trusted_get_error_ok() { + static CALLED_BACK: AtomicBool = AtomicBool::new(false); + + let server = Server::builder().build(); + + let mut client = server.client(); + client.ctx().set_ca_file("test/root-ca.pem").unwrap(); + client + .ctx() + .set_verify_callback(SslVerifyMode::PEER, |_, x509| { + CALLED_BACK.store(true, Ordering::SeqCst); + assert_eq!(x509.error(), X509VerifyResult::OK); + true + }); + + client.connect(); + assert!(CALLED_BACK.load(Ordering::SeqCst)); +} + +#[test] +fn verify_trusted_get_error_err() { + let mut server = Server::builder(); + server.should_error(); + let server = server.build(); + + let mut client = server.client(); + client + .ctx() + .set_verify_callback(SslVerifyMode::PEER, |_, x509| { + assert_ne!(x509.error(), X509VerifyResult::OK); + false + }); + + client.connect_err(); +} + +#[test] +fn verify_callback() { + static CALLED_BACK: AtomicBool = AtomicBool::new(false); + + let server = Server::builder().build(); + + let mut client = server.client(); + let expected = "59172d9313e84459bcff27f967e79e6e9217e584"; + client + .ctx() + .set_verify_callback(SslVerifyMode::PEER, move |_, x509| { + CALLED_BACK.store(true, Ordering::SeqCst); + let cert = x509.current_cert().unwrap(); + let digest = cert.digest(MessageDigest::sha1()).unwrap(); + assert_eq!(hex::encode(digest), expected); + true + }); + + client.connect(); + assert!(CALLED_BACK.load(Ordering::SeqCst)); +} + +#[test] +fn ssl_verify_callback() { + static CALLED_BACK: AtomicBool = AtomicBool::new(false); + + let server = Server::builder().build(); + + let mut client = server.client().build().builder(); + let expected = "59172d9313e84459bcff27f967e79e6e9217e584"; + client + .ssl() + .set_verify_callback(SslVerifyMode::PEER, move |_, x509| { + CALLED_BACK.store(true, Ordering::SeqCst); + let cert = x509.current_cert().unwrap(); + let digest = cert.digest(MessageDigest::sha1()).unwrap(); + assert_eq!(hex::encode(digest), expected); + true + }); + + client.connect(); + assert!(CALLED_BACK.load(Ordering::SeqCst)); +} + +#[test] +fn get_ctx_options() { + let ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ctx.options(); +} + +#[test] +fn set_ctx_options() { + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + let opts = ctx.set_options(SslOptions::NO_TICKET); + assert!(opts.contains(SslOptions::NO_TICKET)); +} + +#[test] +#[cfg(not(boringssl))] +fn clear_ctx_options() { + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ctx.set_options(SslOptions::ALL); + let opts = ctx.clear_options(SslOptions::ALL); + assert!(!opts.contains(SslOptions::ALL)); +} + +#[test] +fn zero_length_buffers() { + let server = Server::builder().build(); + + let mut s = server.client().connect(); + assert_eq!(s.write(&[]).unwrap(), 0); + assert_eq!(s.read(&mut []).unwrap(), 0); +} + +#[test] +fn peer_certificate() { + let server = Server::builder().build(); + + let s = server.client().connect(); + let cert = s.ssl().peer_certificate().unwrap(); + let fingerprint = cert.digest(MessageDigest::sha1()).unwrap(); + assert_eq!( + hex::encode(fingerprint), + "59172d9313e84459bcff27f967e79e6e9217e584" + ); +} + +#[test] +fn pending() { + let mut server = Server::builder(); + server.io_cb(|mut s| s.write_all(&[0; 10]).unwrap()); + let server = server.build(); + + let mut s = server.client().connect(); + s.read_exact(&mut [0]).unwrap(); + + assert_eq!(s.ssl().pending(), 9); + assert_eq!(s.read(&mut [0; 10]).unwrap(), 9); +} + +#[test] +fn state() { + let server = Server::builder().build(); + + let s = server.client().connect(); + #[cfg(not(boringssl))] + assert_eq!(s.ssl().state_string().trim(), "SSLOK"); + #[cfg(boringssl)] + assert_eq!(s.ssl().state_string(), "!!!!!!"); + assert_eq!( + s.ssl().state_string_long(), + "SSL negotiation finished successfully" + ); +} + +/// 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 mut ssl = Ssl::new(&ctx.build()).unwrap(); + ssl.set_mtu(1500).unwrap(); + let mut stream = ssl.accept(stream).unwrap(); + + let mut buf = [0; 60]; + stream + .ssl() + .export_keying_material(&mut buf, "EXTRACTOR-dtls_srtp", None) + .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 mut ssl = Ssl::new(&ctx.build()).unwrap(); + ssl.set_mtu(1500).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_keying_material(&mut buf, "EXTRACTOR-dtls_srtp", None) + .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.srtp_profiles().unwrap() { + if !profilenames.is_empty() { + profilenames.push(':'); + } + profilenames += profile.name(); + } + assert_eq!( + "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32", + profilenames + ); + ssl.set_mtu(1500).unwrap(); + let mut stream = ssl.accept(stream).unwrap(); + + let mut buf = [0; 60]; + stream + .ssl() + .export_keying_material(&mut buf, "EXTRACTOR-dtls_srtp", None) + .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(); + ssl.set_mtu(1500).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_keying_material(&mut buf, "EXTRACTOR-dtls_srtp", None) + .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] +#[cfg(any(ossl102, libressl261))] +fn test_alpn_server_advertise_multiple() { + let mut server = Server::builder(); + server.ctx().set_alpn_select_callback(|_, client| { + ssl::select_next_proto(b"\x08http/1.1\x08spdy/3.1", client).ok_or(ssl::AlpnError::NOACK) + }); + let server = server.build(); + + let mut client = server.client(); + client.ctx().set_alpn_protos(b"\x08spdy/3.1").unwrap(); + let s = client.connect(); + assert_eq!(s.ssl().selected_alpn_protocol(), Some(&b"spdy/3.1"[..])); +} + +#[test] +#[cfg(ossl110)] +fn test_alpn_server_select_none_fatal() { + let mut server = Server::builder(); + server.ctx().set_alpn_select_callback(|_, client| { + ssl::select_next_proto(b"\x08http/1.1\x08spdy/3.1", client) + .ok_or(ssl::AlpnError::ALERT_FATAL) + }); + server.should_error(); + let server = server.build(); + + let mut client = server.client(); + client.ctx().set_alpn_protos(b"\x06http/2").unwrap(); + client.connect_err(); +} + +#[test] +#[cfg(any(ossl102, libressl261))] +fn test_alpn_server_select_none() { + static CALLED_BACK: AtomicBool = AtomicBool::new(false); + + let mut server = Server::builder(); + server.ctx().set_alpn_select_callback(|_, client| { + CALLED_BACK.store(true, Ordering::SeqCst); + ssl::select_next_proto(b"\x08http/1.1\x08spdy/3.1", client).ok_or(ssl::AlpnError::NOACK) + }); + let server = server.build(); + + let mut client = server.client(); + client.ctx().set_alpn_protos(b"\x06http/2").unwrap(); + let s = client.connect(); + assert_eq!(None, s.ssl().selected_alpn_protocol()); + assert!(CALLED_BACK.load(Ordering::SeqCst)); +} + +#[test] +#[cfg(any(ossl102, libressl261))] +fn test_alpn_server_unilateral() { + let server = Server::builder().build(); + + let mut client = server.client(); + client.ctx().set_alpn_protos(b"\x06http/2").unwrap(); + let s = client.connect(); + assert_eq!(None, s.ssl().selected_alpn_protocol()); +} + +#[test] +#[should_panic(expected = "blammo")] +fn write_panic() { + struct ExplodingStream(TcpStream); + + impl Read for ExplodingStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.0.read(buf) + } + } + + impl Write for ExplodingStream { + fn write(&mut self, _: &[u8]) -> io::Result { + panic!("blammo"); + } + + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } + } + + let mut server = Server::builder(); + server.should_error(); + let server = server.build(); + + let stream = ExplodingStream(server.connect_tcp()); + + let ctx = SslContext::builder(SslMethod::tls()).unwrap(); + let _ = Ssl::new(&ctx.build()).unwrap().connect(stream); +} + +#[test] +#[should_panic(expected = "blammo")] +fn read_panic() { + struct ExplodingStream(TcpStream); + + impl Read for ExplodingStream { + fn read(&mut self, _: &mut [u8]) -> io::Result { + panic!("blammo"); + } + } + + impl Write for ExplodingStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } + } + + let mut server = Server::builder(); + server.should_error(); + let server = server.build(); + + let stream = ExplodingStream(server.connect_tcp()); + + let ctx = SslContext::builder(SslMethod::tls()).unwrap(); + let _ = Ssl::new(&ctx.build()).unwrap().connect(stream); +} + +#[test] +#[cfg_attr(all(libressl321, not(libressl340)), ignore)] +#[should_panic(expected = "blammo")] +fn flush_panic() { + struct ExplodingStream(TcpStream); + + impl Read for ExplodingStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.0.read(buf) + } + } + + impl Write for ExplodingStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + panic!("blammo"); + } + } + + let mut server = Server::builder(); + server.should_error(); + let server = server.build(); + + let stream = ExplodingStream(server.connect_tcp()); + + let ctx = SslContext::builder(SslMethod::tls()).unwrap(); + let _ = Ssl::new(&ctx.build()).unwrap().connect(stream); +} + +#[test] +fn refcount_ssl_context() { + let mut ssl = { + let ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ssl::Ssl::new(&ctx.build()).unwrap() + }; + + { + let new_ctx_a = SslContext::builder(SslMethod::tls()).unwrap().build(); + ssl.set_ssl_context(&new_ctx_a).unwrap(); + } +} + +#[test] +#[cfg_attr(libressl250, ignore)] +#[cfg_attr(target_os = "windows", ignore)] +#[cfg_attr(all(target_os = "macos", feature = "vendored"), ignore)] +fn default_verify_paths() { + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ctx.set_default_verify_paths().unwrap(); + ctx.set_verify(SslVerifyMode::PEER); + let ctx = ctx.build(); + let s = match TcpStream::connect("google.com:443") { + Ok(s) => s, + Err(_) => return, + }; + let mut ssl = Ssl::new(&ctx).unwrap(); + ssl.set_hostname("google.com").unwrap(); + let mut socket = ssl.connect(s).unwrap(); + + socket.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); + let mut result = vec![]; + socket.read_to_end(&mut result).unwrap(); + + println!("{}", String::from_utf8_lossy(&result)); + assert!(result.starts_with(b"HTTP/1.0")); + assert!(result.ends_with(b"\r\n") || result.ends_with(b"")); +} + +#[test] +fn add_extra_chain_cert() { + let cert = X509::from_pem(CERT).unwrap(); + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ctx.add_extra_chain_cert(cert).unwrap(); +} + +#[test] +#[cfg(ossl102)] +fn verify_valid_hostname() { + let server = Server::builder().build(); + + let mut client = server.client(); + client.ctx().set_ca_file("test/root-ca.pem").unwrap(); + client.ctx().set_verify(SslVerifyMode::PEER); + + let mut client = client.build().builder(); + client + .ssl() + .param_mut() + .set_hostflags(X509CheckFlags::NO_PARTIAL_WILDCARDS); + client.ssl().param_mut().set_host("foobar.com").unwrap(); + client.connect(); +} + +#[test] +#[cfg(ossl102)] +fn verify_invalid_hostname() { + let mut server = Server::builder(); + server.should_error(); + let server = server.build(); + + let mut client = server.client(); + client.ctx().set_ca_file("test/root-ca.pem").unwrap(); + client.ctx().set_verify(SslVerifyMode::PEER); + + let mut client = client.build().builder(); + client + .ssl() + .param_mut() + .set_hostflags(X509CheckFlags::NO_PARTIAL_WILDCARDS); + client.ssl().param_mut().set_host("bogus.com").unwrap(); + client.connect_err(); +} + +#[test] +fn connector_valid_hostname() { + let server = Server::builder().build(); + + let mut connector = SslConnector::builder(SslMethod::tls()).unwrap(); + connector.set_ca_file("test/root-ca.pem").unwrap(); + + let s = server.connect_tcp(); + let mut s = connector.build().connect("foobar.com", s).unwrap(); + s.read_exact(&mut [0]).unwrap(); +} + +#[test] +fn connector_invalid_hostname() { + let mut server = Server::builder(); + server.should_error(); + let server = server.build(); + + let mut connector = SslConnector::builder(SslMethod::tls()).unwrap(); + connector.set_ca_file("test/root-ca.pem").unwrap(); + + let s = server.connect_tcp(); + connector.build().connect("bogus.com", s).unwrap_err(); +} + +#[test] +fn connector_invalid_no_hostname_verification() { + let server = Server::builder().build(); + + let mut connector = SslConnector::builder(SslMethod::tls()).unwrap(); + connector.set_ca_file("test/root-ca.pem").unwrap(); + + let s = server.connect_tcp(); + let mut s = connector + .build() + .configure() + .unwrap() + .verify_hostname(false) + .connect("bogus.com", s) + .unwrap(); + s.read_exact(&mut [0]).unwrap(); +} + +#[test] +fn connector_no_hostname_still_verifies() { + let mut server = Server::builder(); + server.should_error(); + let server = server.build(); + + let connector = SslConnector::builder(SslMethod::tls()).unwrap().build(); + + let s = server.connect_tcp(); + assert!(connector + .configure() + .unwrap() + .verify_hostname(false) + .connect("fizzbuzz.com", s) + .is_err()); +} + +#[test] +fn connector_can_disable_verify() { + let server = Server::builder().build(); + + let mut connector = SslConnector::builder(SslMethod::tls()).unwrap(); + connector.set_verify(SslVerifyMode::NONE); + let connector = connector.build(); + + let s = server.connect_tcp(); + let mut s = connector + .configure() + .unwrap() + .connect("fizzbuzz.com", s) + .unwrap(); + s.read_exact(&mut [0]).unwrap(); +} + +#[test] +fn connector_does_use_sni_with_dnsnames() { + static CALLED_BACK: AtomicBool = AtomicBool::new(false); + + let mut builder = Server::builder(); + builder.ctx().set_servername_callback(|ssl, _| { + assert_eq!(ssl.servername(NameType::HOST_NAME), Some("foobar.com")); + CALLED_BACK.store(true, Ordering::SeqCst); + Ok(()) + }); + let server = builder.build(); + + let mut connector = SslConnector::builder(SslMethod::tls()).unwrap(); + connector.set_ca_file("test/root-ca.pem").unwrap(); + + let s = server.connect_tcp(); + let mut s = connector + .build() + .configure() + .unwrap() + .connect("foobar.com", s) + .unwrap(); + s.read_exact(&mut [0]).unwrap(); + + assert!(CALLED_BACK.load(Ordering::SeqCst)); +} + +#[test] +fn connector_doesnt_use_sni_with_ips() { + static CALLED_BACK: AtomicBool = AtomicBool::new(false); + + let mut builder = Server::builder(); + builder.ctx().set_servername_callback(|ssl, _| { + assert_eq!(ssl.servername(NameType::HOST_NAME), None); + CALLED_BACK.store(true, Ordering::SeqCst); + Ok(()) + }); + let server = builder.build(); + + let mut connector = SslConnector::builder(SslMethod::tls()).unwrap(); + // The server's cert isn't issued for 127.0.0.1 but we don't care for this test. + connector.set_verify(SslVerifyMode::NONE); + + let s = server.connect_tcp(); + let mut s = connector + .build() + .configure() + .unwrap() + .connect("127.0.0.1", s) + .unwrap(); + s.read_exact(&mut [0]).unwrap(); + + assert!(CALLED_BACK.load(Ordering::SeqCst)); +} + +fn test_mozilla_server(new: fn(SslMethod) -> Result) { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let port = listener.local_addr().unwrap().port(); + + let t = thread::spawn(move || { + let key = PKey::private_key_from_pem(KEY).unwrap(); + let cert = X509::from_pem(CERT).unwrap(); + let mut acceptor = new(SslMethod::tls()).unwrap(); + acceptor.set_private_key(&key).unwrap(); + acceptor.set_certificate(&cert).unwrap(); + let acceptor = acceptor.build(); + let stream = listener.accept().unwrap().0; + let mut stream = acceptor.accept(stream).unwrap(); + + stream.write_all(b"hello").unwrap(); + }); + + let mut connector = SslConnector::builder(SslMethod::tls()).unwrap(); + connector.set_ca_file("test/root-ca.pem").unwrap(); + let connector = connector.build(); + + let stream = TcpStream::connect(("127.0.0.1", port)).unwrap(); + let mut stream = connector.connect("foobar.com", stream).unwrap(); + + let mut buf = [0; 5]; + stream.read_exact(&mut buf).unwrap(); + assert_eq!(b"hello", &buf); + + t.join().unwrap(); +} + +#[test] +fn connector_client_server_mozilla_intermediate() { + test_mozilla_server(SslAcceptor::mozilla_intermediate); +} + +#[test] +fn connector_client_server_mozilla_modern() { + test_mozilla_server(SslAcceptor::mozilla_modern); +} + +#[test] +fn connector_client_server_mozilla_intermediate_v5() { + test_mozilla_server(SslAcceptor::mozilla_intermediate_v5); +} + +#[test] +#[cfg(any(ossl111, libressl340))] +fn connector_client_server_mozilla_modern_v5() { + test_mozilla_server(SslAcceptor::mozilla_modern_v5); +} + +#[test] +fn shutdown() { + let mut server = Server::builder(); + server.io_cb(|mut s| { + assert_eq!(s.read(&mut [0]).unwrap(), 0); + assert_eq!(s.shutdown().unwrap(), ShutdownResult::Received); + }); + let server = server.build(); + + let mut s = server.client().connect(); + + assert_eq!(s.get_shutdown(), ShutdownState::empty()); + assert_eq!(s.shutdown().unwrap(), ShutdownResult::Sent); + assert_eq!(s.get_shutdown(), ShutdownState::SENT); + assert_eq!(s.shutdown().unwrap(), ShutdownResult::Received); + assert_eq!( + s.get_shutdown(), + ShutdownState::SENT | ShutdownState::RECEIVED + ); +} + +#[test] +fn client_ca_list() { + let names = X509Name::load_client_ca_file("test/root-ca.pem").unwrap(); + assert_eq!(names.len(), 1); + + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ctx.set_client_ca_list(names); +} + +#[test] +fn cert_store() { + let server = Server::builder().build(); + + let mut client = server.client(); + let cert = X509::from_pem(ROOT_CERT).unwrap(); + client.ctx().cert_store_mut().add_cert(cert).unwrap(); + client.ctx().set_verify(SslVerifyMode::PEER); + + client.connect(); +} + +#[test] +#[cfg_attr(any(all(libressl321, not(libressl340)), boringssl), ignore)] +fn tmp_dh_callback() { + static CALLED_BACK: AtomicBool = AtomicBool::new(false); + + let mut server = Server::builder(); + server.ctx().set_tmp_dh_callback(|_, _, _| { + CALLED_BACK.store(true, Ordering::SeqCst); + let dh = include_bytes!("../../../test/dhparams.pem"); + Dh::params_from_pem(dh) + }); + + let server = server.build(); + + let mut client = server.client(); + // TLS 1.3 has no DH suites, so make sure we don't pick that version + #[cfg(any(ossl111, libressl340))] + client.ctx().set_options(super::SslOptions::NO_TLSV1_3); + client.ctx().set_cipher_list("EDH").unwrap(); + client.connect(); + + assert!(CALLED_BACK.load(Ordering::SeqCst)); +} + +#[test] +#[cfg(all(ossl101, not(ossl110)))] +#[allow(deprecated)] +fn tmp_ecdh_callback() { + use crate::ec::EcKey; + use crate::nid::Nid; + + static CALLED_BACK: AtomicBool = AtomicBool::new(false); + + let mut server = Server::builder(); + server.ctx().set_tmp_ecdh_callback(|_, _, _| { + CALLED_BACK.store(true, Ordering::SeqCst); + EcKey::from_curve_name(Nid::X9_62_PRIME256V1) + }); + + let server = server.build(); + + let mut client = server.client(); + client.ctx().set_cipher_list("ECDH").unwrap(); + client.connect(); + + assert!(CALLED_BACK.load(Ordering::SeqCst)); +} + +#[test] +#[cfg_attr(any(all(libressl321, not(libressl340)), boringssl), ignore)] +fn tmp_dh_callback_ssl() { + static CALLED_BACK: AtomicBool = AtomicBool::new(false); + + let mut server = Server::builder(); + server.ssl_cb(|ssl| { + ssl.set_tmp_dh_callback(|_, _, _| { + CALLED_BACK.store(true, Ordering::SeqCst); + let dh = include_bytes!("../../../test/dhparams.pem"); + Dh::params_from_pem(dh) + }); + }); + + let server = server.build(); + + let mut client = server.client(); + // TLS 1.3 has no DH suites, so make sure we don't pick that version + #[cfg(any(ossl111, libressl340))] + client.ctx().set_options(super::SslOptions::NO_TLSV1_3); + client.ctx().set_cipher_list("EDH").unwrap(); + client.connect(); + + assert!(CALLED_BACK.load(Ordering::SeqCst)); +} + +#[test] +#[cfg(all(ossl101, not(ossl110)))] +#[allow(deprecated)] +fn tmp_ecdh_callback_ssl() { + use crate::ec::EcKey; + use crate::nid::Nid; + + static CALLED_BACK: AtomicBool = AtomicBool::new(false); + + let mut server = Server::builder(); + server.ssl_cb(|ssl| { + ssl.set_tmp_ecdh_callback(|_, _, _| { + CALLED_BACK.store(true, Ordering::SeqCst); + EcKey::from_curve_name(Nid::X9_62_PRIME256V1) + }); + }); + + let server = server.build(); + + let mut client = server.client(); + client.ctx().set_cipher_list("ECDH").unwrap(); + client.connect(); + + assert!(CALLED_BACK.load(Ordering::SeqCst)); +} + +#[test] +fn idle_session() { + let ctx = SslContext::builder(SslMethod::tls()).unwrap().build(); + let ssl = Ssl::new(&ctx).unwrap(); + assert!(ssl.session().is_none()); +} + +/// possible LibreSSL bug since 3.2.1 +#[test] +#[cfg_attr(libressl321, ignore)] +fn active_session() { + let server = Server::builder().build(); + + let s = server.client().connect(); + + let session = s.ssl().session().unwrap(); + let len = session.master_key_len(); + let mut buf = vec![0; len - 1]; + let copied = session.master_key(&mut buf); + assert_eq!(copied, buf.len()); + let mut buf = vec![0; len + 1]; + let copied = session.master_key(&mut buf); + assert_eq!(copied, len); +} + +#[test] +#[cfg(not(boringssl))] +fn status_callbacks() { + static CALLED_BACK_SERVER: AtomicBool = AtomicBool::new(false); + static CALLED_BACK_CLIENT: AtomicBool = AtomicBool::new(false); + + let mut server = Server::builder(); + server + .ctx() + .set_status_callback(|ssl| { + CALLED_BACK_SERVER.store(true, Ordering::SeqCst); + let response = OcspResponse::create(OcspResponseStatus::UNAUTHORIZED, None).unwrap(); + let response = response.to_der().unwrap(); + ssl.set_ocsp_status(&response).unwrap(); + Ok(true) + }) + .unwrap(); + + let server = server.build(); + + let mut client = server.client(); + client + .ctx() + .set_status_callback(|ssl| { + CALLED_BACK_CLIENT.store(true, Ordering::SeqCst); + let response = OcspResponse::from_der(ssl.ocsp_status().unwrap()).unwrap(); + assert_eq!(response.status(), OcspResponseStatus::UNAUTHORIZED); + Ok(true) + }) + .unwrap(); + + let mut client = client.build().builder(); + client.ssl().set_status_type(StatusType::OCSP).unwrap(); + + client.connect(); + + assert!(CALLED_BACK_SERVER.load(Ordering::SeqCst)); + assert!(CALLED_BACK_CLIENT.load(Ordering::SeqCst)); +} + +/// possible LibreSSL bug since 3.2.1 +#[test] +#[cfg_attr(libressl321, ignore)] +fn new_session_callback() { + static CALLED_BACK: AtomicBool = AtomicBool::new(false); + + let mut server = Server::builder(); + server.ctx().set_session_id_context(b"foo").unwrap(); + + let server = server.build(); + + let mut client = server.client(); + + client + .ctx() + .set_session_cache_mode(SslSessionCacheMode::CLIENT | SslSessionCacheMode::NO_INTERNAL); + client + .ctx() + .set_new_session_callback(|_, _| CALLED_BACK.store(true, Ordering::SeqCst)); + + client.connect(); + + assert!(CALLED_BACK.load(Ordering::SeqCst)); +} + +/// possible LibreSSL bug since 3.2.1 +#[test] +#[cfg_attr(libressl321, ignore)] +fn new_session_callback_swapped_ctx() { + static CALLED_BACK: AtomicBool = AtomicBool::new(false); + + let mut server = Server::builder(); + server.ctx().set_session_id_context(b"foo").unwrap(); + + let server = server.build(); + + let mut client = server.client(); + + client + .ctx() + .set_session_cache_mode(SslSessionCacheMode::CLIENT | SslSessionCacheMode::NO_INTERNAL); + client + .ctx() + .set_new_session_callback(|_, _| CALLED_BACK.store(true, Ordering::SeqCst)); + + let mut client = client.build().builder(); + + let ctx = SslContextBuilder::new(SslMethod::tls()).unwrap().build(); + client.ssl().set_ssl_context(&ctx).unwrap(); + + client.connect(); + + assert!(CALLED_BACK.load(Ordering::SeqCst)); +} + +#[test] +fn keying_export() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let addr = listener.local_addr().unwrap(); + + let label = "EXPERIMENTAL test"; + let context = b"my context"; + + let guard = thread::spawn(move || { + let stream = listener.accept().unwrap().0; + let mut ctx = SslContext::builder(SslMethod::tls()).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; 32]; + stream + .ssl() + .export_keying_material(&mut buf, label, Some(context)) + .unwrap(); + + stream.write_all(&[0]).unwrap(); + + buf + }); + + let stream = TcpStream::connect(addr).unwrap(); + let ctx = SslContext::builder(SslMethod::tls()).unwrap(); + let ssl = Ssl::new(&ctx.build()).unwrap(); + let mut stream = ssl.connect(stream).unwrap(); + + let mut buf = [1; 32]; + stream + .ssl() + .export_keying_material(&mut buf, label, Some(context)) + .unwrap(); + + stream.read_exact(&mut [0]).unwrap(); + + let buf2 = guard.join().unwrap(); + + assert_eq!(buf, buf2); +} + +#[test] +#[cfg(any(ossl110, libressl261))] +fn no_version_overlap() { + let mut server = Server::builder(); + server.ctx().set_min_proto_version(None).unwrap(); + server + .ctx() + .set_max_proto_version(Some(SslVersion::TLS1_1)) + .unwrap(); + #[cfg(any(ossl110g, libressl270))] + assert_eq!(server.ctx().max_proto_version(), Some(SslVersion::TLS1_1)); + server.should_error(); + let server = server.build(); + + let mut client = server.client(); + client + .ctx() + .set_min_proto_version(Some(SslVersion::TLS1_2)) + .unwrap(); + #[cfg(ossl110g)] + assert_eq!(client.ctx().min_proto_version(), Some(SslVersion::TLS1_2)); + client.ctx().set_max_proto_version(None).unwrap(); + + client.connect_err(); +} + +#[test] +#[cfg(ossl111)] +fn custom_extensions() { + static FOUND_EXTENSION: AtomicBool = AtomicBool::new(false); + + let mut server = Server::builder(); + server + .ctx() + .add_custom_ext( + 12345, + ExtensionContext::CLIENT_HELLO, + |_, _, _| -> Result, _> { unreachable!() }, + |_, _, data, _| { + FOUND_EXTENSION.store(data == b"hello", Ordering::SeqCst); + Ok(()) + }, + ) + .unwrap(); + + let server = server.build(); + + let mut client = server.client(); + client + .ctx() + .add_custom_ext( + 12345, + ssl::ExtensionContext::CLIENT_HELLO, + |_, _, _| Ok(Some(b"hello")), + |_, _, _, _| unreachable!(), + ) + .unwrap(); + + client.connect(); + + assert!(FOUND_EXTENSION.load(Ordering::SeqCst)); +} + +fn _check_kinds() { + fn is_send() {} + fn is_sync() {} + + is_send::>(); + is_sync::>(); +} + +#[test] +#[cfg(ossl111)] +fn stateless() { + use super::SslOptions; + + #[derive(Debug)] + struct MemoryStream { + incoming: io::Cursor>, + outgoing: Vec, + } + + impl MemoryStream { + pub fn new() -> Self { + Self { + incoming: io::Cursor::new(Vec::new()), + outgoing: Vec::new(), + } + } + + pub fn extend_incoming(&mut self, data: &[u8]) { + self.incoming.get_mut().extend_from_slice(data); + } + + pub fn take_outgoing(&mut self) -> Outgoing<'_> { + Outgoing(&mut self.outgoing) + } + } + + impl Read for MemoryStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let n = self.incoming.read(buf)?; + if self.incoming.position() == self.incoming.get_ref().len() as u64 { + self.incoming.set_position(0); + self.incoming.get_mut().clear(); + } + if n == 0 { + return Err(io::Error::new( + io::ErrorKind::WouldBlock, + "no data available", + )); + } + Ok(n) + } + } + + impl Write for MemoryStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.outgoing.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + } + + pub struct Outgoing<'a>(&'a mut Vec); + + impl<'a> Drop for Outgoing<'a> { + fn drop(&mut self) { + self.0.clear(); + } + } + + impl<'a> ::std::ops::Deref for Outgoing<'a> { + type Target = [u8]; + fn deref(&self) -> &[u8] { + self.0 + } + } + + impl<'a> AsRef<[u8]> for Outgoing<'a> { + fn as_ref(&self) -> &[u8] { + self.0 + } + } + + fn send(from: &mut MemoryStream, to: &mut MemoryStream) { + to.extend_incoming(&from.take_outgoing()); + } + + // + // Setup + // + + let mut client_ctx = SslContext::builder(SslMethod::tls()).unwrap(); + client_ctx.clear_options(SslOptions::ENABLE_MIDDLEBOX_COMPAT); + let mut client_stream = + SslStream::new(Ssl::new(&client_ctx.build()).unwrap(), MemoryStream::new()).unwrap(); + + let mut server_ctx = SslContext::builder(SslMethod::tls()).unwrap(); + server_ctx + .set_certificate_file(Path::new("test/cert.pem"), SslFiletype::PEM) + .unwrap(); + server_ctx + .set_private_key_file(Path::new("test/key.pem"), SslFiletype::PEM) + .unwrap(); + const COOKIE: &[u8] = b"chocolate chip"; + server_ctx.set_stateless_cookie_generate_cb(|_tls, buf| { + buf[0..COOKIE.len()].copy_from_slice(COOKIE); + Ok(COOKIE.len()) + }); + server_ctx.set_stateless_cookie_verify_cb(|_tls, buf| buf == COOKIE); + let mut server_stream = + SslStream::new(Ssl::new(&server_ctx.build()).unwrap(), MemoryStream::new()).unwrap(); + + // + // Handshake + // + + // Initial ClientHello + client_stream.connect().unwrap_err(); + send(client_stream.get_mut(), server_stream.get_mut()); + // HelloRetryRequest + assert!(!server_stream.stateless().unwrap()); + send(server_stream.get_mut(), client_stream.get_mut()); + // Second ClientHello + client_stream.do_handshake().unwrap_err(); + send(client_stream.get_mut(), server_stream.get_mut()); + // OldServerHello + assert!(server_stream.stateless().unwrap()); + server_stream.accept().unwrap_err(); + send(server_stream.get_mut(), client_stream.get_mut()); + // Finished + client_stream.do_handshake().unwrap(); + send(client_stream.get_mut(), server_stream.get_mut()); + server_stream.do_handshake().unwrap(); +} + +#[cfg(not(osslconf = "OPENSSL_NO_PSK"))] +#[test] +fn psk_ciphers() { + const CIPHER: &str = "PSK-AES256-CBC-SHA"; + const PSK: &[u8] = b"thisisaverysecurekey"; + const CLIENT_IDENT: &[u8] = b"thisisaclient"; + static CLIENT_CALLED: AtomicBool = AtomicBool::new(false); + static SERVER_CALLED: AtomicBool = AtomicBool::new(false); + + let mut server = Server::builder(); + server.ctx().set_cipher_list(CIPHER).unwrap(); + server.ctx().set_psk_server_callback(|_, identity, psk| { + assert!(identity.unwrap_or(&[]) == CLIENT_IDENT); + psk[..PSK.len()].copy_from_slice(PSK); + SERVER_CALLED.store(true, Ordering::SeqCst); + Ok(PSK.len()) + }); + + let server = server.build(); + + let mut client = server.client(); + // This test relies on TLS 1.2 suites + #[cfg(any(boringssl, ossl111))] + client.ctx().set_options(super::SslOptions::NO_TLSV1_3); + client.ctx().set_cipher_list(CIPHER).unwrap(); + client + .ctx() + .set_psk_client_callback(move |_, _, identity, psk| { + identity[..CLIENT_IDENT.len()].copy_from_slice(CLIENT_IDENT); + identity[CLIENT_IDENT.len()] = 0; + psk[..PSK.len()].copy_from_slice(PSK); + CLIENT_CALLED.store(true, Ordering::SeqCst); + Ok(PSK.len()) + }); + + client.connect(); + + assert!(SERVER_CALLED.load(Ordering::SeqCst)); + assert!(CLIENT_CALLED.load(Ordering::SeqCst)); +} + +#[test] +fn sni_callback_swapped_ctx() { + static CALLED_BACK: AtomicBool = AtomicBool::new(false); + + let mut server = Server::builder(); + + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ctx.set_servername_callback(|_, _| { + CALLED_BACK.store(true, Ordering::SeqCst); + Ok(()) + }); + + let keyed_ctx = mem::replace(server.ctx(), ctx).build(); + server.ssl_cb(move |ssl| ssl.set_ssl_context(&keyed_ctx).unwrap()); + + let server = server.build(); + + server.client().connect(); + + assert!(CALLED_BACK.load(Ordering::SeqCst)); +} + +#[test] +#[cfg(ossl111)] +fn client_hello() { + static CALLED_BACK: AtomicBool = AtomicBool::new(false); + + let mut server = Server::builder(); + server.ctx().set_client_hello_callback(|ssl, _| { + assert!(!ssl.client_hello_isv2()); + assert_eq!(ssl.client_hello_legacy_version(), Some(SslVersion::TLS1_2)); + assert!(ssl.client_hello_random().is_some()); + assert!(ssl.client_hello_session_id().is_some()); + assert!(ssl.client_hello_ciphers().is_some()); + assert!(ssl.client_hello_compression_methods().is_some()); + assert!(ssl + .bytes_to_cipher_list(ssl.client_hello_ciphers().unwrap(), ssl.client_hello_isv2()) + .is_ok()); + + CALLED_BACK.store(true, Ordering::SeqCst); + Ok(ClientHelloResponse::SUCCESS) + }); + + let server = server.build(); + server.client().connect(); + + assert!(CALLED_BACK.load(Ordering::SeqCst)); +} + +#[test] +#[cfg(ossl111)] +fn openssl_cipher_name() { + assert_eq!( + super::cipher_name("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384"), + "ECDHE-RSA-AES256-SHA384", + ); + + assert_eq!(super::cipher_name("asdf"), "(NONE)"); +} + +#[test] +fn session_cache_size() { + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ctx.set_session_cache_size(1234); + let ctx = ctx.build(); + assert_eq!(ctx.session_cache_size(), 1234); +} + +#[test] +#[cfg(ossl102)] +fn add_chain_cert() { + let ctx = SslContext::builder(SslMethod::tls()).unwrap().build(); + let cert = X509::from_pem(CERT).unwrap(); + let mut ssl = Ssl::new(&ctx).unwrap(); + assert!(ssl.add_chain_cert(cert).is_ok()); +} +#[test] +#[cfg(ossl111)] +fn set_ssl_certificate_key_related_api() { + let cert_str: &str = include_str!("../../../test/cert.pem"); + let key_str: &str = include_str!("../../../test/key.pem"); + let ctx = SslContext::builder(SslMethod::tls()).unwrap().build(); + let cert_x509 = X509::from_pem(CERT).unwrap(); + let mut ssl = Ssl::new(&ctx).unwrap(); + assert!(ssl.set_method(SslMethod::tls()).is_ok()); + ssl.set_private_key_file("test/key.pem", SslFiletype::PEM) + .unwrap(); + { + let pkey = String::from_utf8( + ssl.private_key() + .unwrap() + .private_key_to_pem_pkcs8() + .unwrap(), + ) + .unwrap(); + assert!(pkey.lines().eq(key_str.lines())); + } + let pkey = PKey::private_key_from_pem(KEY).unwrap(); + ssl.set_private_key(pkey.as_ref()).unwrap(); + { + let pkey = String::from_utf8( + ssl.private_key() + .unwrap() + .private_key_to_pem_pkcs8() + .unwrap(), + ) + .unwrap(); + assert!(pkey.lines().eq(key_str.lines())); + } + ssl.set_certificate(cert_x509.as_ref()).unwrap(); + let cert = String::from_utf8(ssl.certificate().unwrap().to_pem().unwrap()).unwrap(); + assert!(cert.lines().eq(cert_str.lines())); + ssl.add_client_ca(cert_x509.as_ref()).unwrap(); + ssl.set_min_proto_version(Some(SslVersion::TLS1_2)).unwrap(); + ssl.set_max_proto_version(Some(SslVersion::TLS1_3)).unwrap(); + ssl.set_cipher_list("HIGH:!aNULL:!MD5").unwrap(); + ssl.set_ciphersuites("TLS_AES_128_GCM_SHA256").unwrap(); + let x509 = X509::from_pem(ROOT_CERT).unwrap(); + let mut builder = X509StoreBuilder::new().unwrap(); + builder.add_cert(x509).unwrap(); + let store = builder.build(); + ssl.set_verify_cert_store(store).unwrap(); +} + +#[test] +#[cfg(ossl110)] +fn test_ssl_set_cert_chain_file() { + let ctx = SslContext::builder(SslMethod::tls()).unwrap().build(); + let mut ssl = Ssl::new(&ctx).unwrap(); + ssl.set_certificate_chain_file("test/cert.pem").unwrap(); +} + +#[test] +#[cfg(ossl111)] +fn set_num_tickets() { + let mut ctx = SslContext::builder(SslMethod::tls_server()).unwrap(); + ctx.set_num_tickets(3).unwrap(); + let ctx = ctx.build(); + assert_eq!(3, ctx.num_tickets()); + + let mut ssl = Ssl::new(&ctx).unwrap(); + ssl.set_num_tickets(5).unwrap(); + let ssl = ssl; + assert_eq!(5, ssl.num_tickets()); +} diff --git a/vendor/openssl/src/ssl/test/server.rs b/vendor/openssl/src/ssl/test/server.rs new file mode 100644 index 0000000..41677e5 --- /dev/null +++ b/vendor/openssl/src/ssl/test/server.rs @@ -0,0 +1,167 @@ +use std::io::{Read, Write}; +use std::net::{SocketAddr, TcpListener, TcpStream}; +use std::thread::{self, JoinHandle}; + +use crate::ssl::{Ssl, SslContext, SslContextBuilder, SslFiletype, SslMethod, SslRef, SslStream}; + +pub struct Server { + handle: Option>, + addr: SocketAddr, +} + +impl Drop for Server { + fn drop(&mut self) { + if !thread::panicking() { + self.handle.take().unwrap().join().unwrap(); + } + } +} + +impl Server { + pub fn builder() -> Builder { + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ctx.set_certificate_chain_file("test/cert.pem").unwrap(); + ctx.set_private_key_file("test/key.pem", SslFiletype::PEM) + .unwrap(); + + Builder { + ctx, + ssl_cb: Box::new(|_| {}), + io_cb: Box::new(|_| {}), + should_error: false, + } + } + + pub fn client(&self) -> ClientBuilder { + ClientBuilder { + ctx: SslContext::builder(SslMethod::tls()).unwrap(), + addr: self.addr, + } + } + + pub fn connect_tcp(&self) -> TcpStream { + TcpStream::connect(self.addr).unwrap() + } +} + +pub struct Builder { + ctx: SslContextBuilder, + ssl_cb: Box, + io_cb: Box) + Send>, + should_error: bool, +} + +impl Builder { + pub fn ctx(&mut self) -> &mut SslContextBuilder { + &mut self.ctx + } + + pub fn ssl_cb(&mut self, cb: F) + where + F: 'static + FnMut(&mut SslRef) + Send, + { + self.ssl_cb = Box::new(cb); + } + + pub fn io_cb(&mut self, cb: F) + where + F: 'static + FnMut(SslStream) + Send, + { + self.io_cb = Box::new(cb); + } + + pub fn should_error(&mut self) { + self.should_error = true; + } + + pub fn build(self) -> Server { + let ctx = self.ctx.build(); + let socket = TcpListener::bind("127.0.0.1:0").unwrap(); + let addr = socket.local_addr().unwrap(); + let mut ssl_cb = self.ssl_cb; + let mut io_cb = self.io_cb; + let should_error = self.should_error; + + let handle = thread::spawn(move || { + let socket = socket.accept().unwrap().0; + let mut ssl = Ssl::new(&ctx).unwrap(); + ssl_cb(&mut ssl); + let r = ssl.accept(socket); + if should_error { + r.unwrap_err(); + } else { + let mut socket = r.unwrap(); + socket.write_all(&[0]).unwrap(); + io_cb(socket); + } + }); + + Server { + handle: Some(handle), + addr, + } + } +} + +pub struct ClientBuilder { + ctx: SslContextBuilder, + addr: SocketAddr, +} + +impl ClientBuilder { + pub fn ctx(&mut self) -> &mut SslContextBuilder { + &mut self.ctx + } + + pub fn build(self) -> Client { + Client { + ctx: self.ctx.build(), + addr: self.addr, + } + } + + pub fn connect(self) -> SslStream { + self.build().builder().connect() + } + + pub fn connect_err(self) { + self.build().builder().connect_err(); + } +} + +pub struct Client { + ctx: SslContext, + addr: SocketAddr, +} + +impl Client { + pub fn builder(&self) -> ClientSslBuilder { + ClientSslBuilder { + ssl: Ssl::new(&self.ctx).unwrap(), + addr: self.addr, + } + } +} + +pub struct ClientSslBuilder { + ssl: Ssl, + addr: SocketAddr, +} + +impl ClientSslBuilder { + pub fn ssl(&mut self) -> &mut SslRef { + &mut self.ssl + } + + pub fn connect(self) -> SslStream { + let socket = TcpStream::connect(self.addr).unwrap(); + let mut s = self.ssl.connect(socket).unwrap(); + s.read_exact(&mut [0]).unwrap(); + s + } + + pub fn connect_err(self) { + let socket = TcpStream::connect(self.addr).unwrap(); + self.ssl.connect(socket).unwrap_err(); + } +} -- cgit v1.2.3