use crate::error::{br, br_zero_is_success, BoringResult}; use boring::error::ErrorStack; use boring::pkey::{HasPrivate, PKey}; use boring::ssl::{Ssl, SslContext, SslContextRef, SslSession}; use boring::x509::store::X509StoreBuilderRef; use boring::x509::X509; use boring_sys as bffi; use bytes::{Buf, BufMut}; use foreign_types_shared::{ForeignType, ForeignTypeRef}; use std::ffi::{c_char, c_int, c_uint, c_void, CStr}; use std::fmt::{Display, Formatter}; use std::result::Result as StdResult; use std::{ffi, fmt, mem, ptr, slice}; /// Provides additional methods to [SslContext] needed for QUIC. pub trait QuicSslContext { fn set_options(&mut self, options: u32) -> u32; fn verify_peer(&mut self, verify: bool); fn set_quic_method(&mut self, method: &bffi::SSL_QUIC_METHOD) -> BoringResult; fn set_session_cache_mode(&mut self, mode: c_int) -> c_int; fn set_new_session_callback( &mut self, cb: Option< unsafe extern "C" fn(ssl: *mut bffi::SSL, session: *mut bffi::SSL_SESSION) -> c_int, >, ); fn set_info_callback( &mut self, cb: Option, ); fn set_keylog_callback( &mut self, cb: Option, ); fn set_certificate(&mut self, cert: X509) -> BoringResult; fn load_certificate_from_pem_file(&mut self, path: &str) -> BoringResult; fn add_to_cert_chain(&mut self, cert: X509) -> BoringResult; fn load_cert_chain_from_pem_file(&mut self, path: &str) -> BoringResult; fn set_private_key(&mut self, key: PKey) -> BoringResult; fn load_private_key_from_pem_file(&mut self, path: &str) -> BoringResult; fn check_private_key(&self) -> BoringResult; fn cert_store_mut(&mut self) -> &mut X509StoreBuilderRef; fn enable_early_data(&mut self, enable: bool); fn set_alpn_protos(&mut self, protos: &[u8]) -> BoringResult; fn set_alpn_select_cb( &mut self, cb: Option< unsafe extern "C" fn( ssl: *mut bffi::SSL, out: *mut *const u8, out_len: *mut u8, in_: *const u8, in_len: c_uint, arg: *mut c_void, ) -> c_int, >, ); fn set_server_name_cb( &mut self, cb: Option< unsafe extern "C" fn( ssl: *mut bffi::SSL, out_alert: *mut c_int, arg: *mut c_void, ) -> c_int, >, ); fn set_select_certificate_cb( &mut self, cb: Option< unsafe extern "C" fn( arg1: *const bffi::SSL_CLIENT_HELLO, ) -> bffi::ssl_select_cert_result_t, >, ); } impl QuicSslContext for SslContext { fn set_options(&mut self, options: u32) -> u32 { unsafe { bffi::SSL_CTX_set_options(self.as_ptr(), options) } } fn verify_peer(&mut self, verify: bool) { let mode = if verify { bffi::SSL_VERIFY_PEER | bffi::SSL_VERIFY_FAIL_IF_NO_PEER_CERT } else { bffi::SSL_VERIFY_NONE }; unsafe { bffi::SSL_CTX_set_verify(self.as_ptr(), mode, None) } } fn set_quic_method(&mut self, method: &bffi::SSL_QUIC_METHOD) -> BoringResult { unsafe { br(bffi::SSL_CTX_set_quic_method(self.as_ptr(), method)) } } fn set_session_cache_mode(&mut self, mode: c_int) -> c_int { unsafe { bffi::SSL_CTX_set_session_cache_mode(self.as_ptr(), mode) } } fn set_new_session_callback( &mut self, cb: Option< unsafe extern "C" fn(ssl: *mut bffi::SSL, session: *mut bffi::SSL_SESSION) -> c_int, >, ) { unsafe { bffi::SSL_CTX_sess_set_new_cb(self.as_ptr(), cb); } } fn set_info_callback( &mut self, cb: Option, ) { unsafe { bffi::SSL_CTX_set_info_callback(self.as_ptr(), cb) } } fn set_keylog_callback( &mut self, cb: Option, ) { unsafe { bffi::SSL_CTX_set_keylog_callback(self.as_ptr(), cb) } } fn set_certificate(&mut self, cert: X509) -> BoringResult { unsafe { br(bffi::SSL_CTX_use_certificate(self.as_ptr(), cert.as_ptr()))?; mem::forget(cert); Ok(()) } } fn load_certificate_from_pem_file(&mut self, path: &str) -> BoringResult { let path = ffi::CString::new(path).unwrap(); unsafe { br(bffi::SSL_CTX_use_certificate_file( self.as_ptr(), path.as_ptr(), bffi::SSL_FILETYPE_PEM, )) } } fn add_to_cert_chain(&mut self, cert: X509) -> BoringResult { unsafe { br(bffi::SSL_CTX_add_extra_chain_cert(self.as_ptr(), cert.as_ptr()) as c_int)?; mem::forget(cert); Ok(()) } } fn load_cert_chain_from_pem_file(&mut self, path: &str) -> BoringResult { let path = ffi::CString::new(path).unwrap(); unsafe { br(bffi::SSL_CTX_use_certificate_chain_file( self.as_ptr(), path.as_ptr(), )) } } fn set_private_key(&mut self, key: PKey) -> BoringResult { unsafe { br(bffi::SSL_CTX_use_PrivateKey(self.as_ptr(), key.as_ptr()))?; mem::forget(key); Ok(()) } } fn load_private_key_from_pem_file(&mut self, path: &str) -> BoringResult { let path = ffi::CString::new(path).unwrap(); unsafe { br(bffi::SSL_CTX_use_PrivateKey_file( self.as_ptr(), path.as_ptr(), bffi::SSL_FILETYPE_PEM, )) } } fn check_private_key(&self) -> BoringResult { unsafe { br(bffi::SSL_CTX_check_private_key(self.as_ptr())) } } fn cert_store_mut(&mut self) -> &mut X509StoreBuilderRef { unsafe { X509StoreBuilderRef::from_ptr_mut(bffi::SSL_CTX_get_cert_store(self.as_ptr())) } } fn enable_early_data(&mut self, enable: bool) { unsafe { bffi::SSL_CTX_set_early_data_enabled(self.as_ptr(), enable.into()) } } fn set_alpn_protos(&mut self, protos: &[u8]) -> BoringResult { unsafe { br_zero_is_success(bffi::SSL_CTX_set_alpn_protos( self.as_ptr(), protos.as_ptr(), protos.len() as _, )) } } fn set_alpn_select_cb( &mut self, cb: Option< unsafe extern "C" fn( *mut bffi::SSL, *mut *const u8, *mut u8, *const u8, c_uint, *mut c_void, ) -> c_int, >, ) { unsafe { bffi::SSL_CTX_set_alpn_select_cb(self.as_ptr(), cb, ptr::null_mut()) } } fn set_server_name_cb( &mut self, cb: Option< unsafe extern "C" fn( ssl: *mut bffi::SSL, out_alert: *mut c_int, arg: *mut c_void, ) -> c_int, >, ) { // The function always returns 1. unsafe { let _ = bffi::SSL_CTX_set_tlsext_servername_callback(self.as_ptr(), cb); } } fn set_select_certificate_cb( &mut self, cb: Option< unsafe extern "C" fn( arg1: *const bffi::SSL_CLIENT_HELLO, ) -> bffi::ssl_select_cert_result_t, >, ) { unsafe { bffi::SSL_CTX_set_select_certificate_cb(self.as_ptr(), cb) } } } /// Provides additional methods to [Ssl] needed for QUIC. pub trait QuicSsl { fn set_connect_state(&mut self); fn set_accept_state(&mut self); fn state_string(&self) -> &'static str; fn set_quic_transport_params(&mut self, params: &[u8]) -> BoringResult; fn get_peer_quic_transport_params(&self) -> Option<&[u8]>; fn get_error(&self, raw: c_int) -> SslError; fn is_handshaking(&self) -> bool; fn do_handshake(&mut self) -> SslError; fn provide_quic_data(&mut self, level: Level, data: &[u8]) -> SslError; fn quic_max_handshake_flight_len(&self, level: Level) -> usize; fn quic_read_level(&self) -> Level; fn quic_write_level(&self) -> Level; fn process_post_handshake(&mut self) -> SslError; fn set_verify_hostname(&mut self, domain: &str) -> BoringResult; fn export_keyring_material( &self, output: &mut [u8], label: &[u8], context: &[u8], ) -> BoringResult; fn in_early_data(&self) -> bool; fn early_data_accepted(&self) -> bool; fn set_quic_method(&mut self, method: &bffi::SSL_QUIC_METHOD) -> BoringResult; fn set_quic_early_data_context(&mut self, value: &[u8]) -> BoringResult; fn get_early_data_reason(&self) -> bffi::ssl_early_data_reason_t; fn early_data_reason_string(reason: bffi::ssl_early_data_reason_t) -> &'static str; fn reset_early_rejected_data(&mut self); fn set_quic_use_legacy_codepoint(&mut self, use_legacy: bool); } impl QuicSsl for Ssl { fn set_connect_state(&mut self) { unsafe { bffi::SSL_set_connect_state(self.as_ptr()) } } fn set_accept_state(&mut self) { unsafe { bffi::SSL_set_accept_state(self.as_ptr()) } } fn state_string(&self) -> &'static str { unsafe { CStr::from_ptr(bffi::SSL_state_string_long(self.as_ptr())) .to_str() .unwrap() } } fn set_quic_transport_params(&mut self, params: &[u8]) -> BoringResult { unsafe { br(bffi::SSL_set_quic_transport_params( self.as_ptr(), params.as_ptr(), params.len(), )) } } fn get_peer_quic_transport_params(&self) -> Option<&[u8]> { let mut ptr: *const u8 = ptr::null(); let mut len: usize = 0; unsafe { bffi::SSL_get_peer_quic_transport_params(self.as_ptr(), &mut ptr, &mut len); if len == 0 { None } else { Some(slice::from_raw_parts(ptr, len)) } } } #[inline] fn get_error(&self, raw: c_int) -> SslError { unsafe { SslError(bffi::SSL_get_error(self.as_ptr(), raw)) } } #[inline] fn is_handshaking(&self) -> bool { unsafe { bffi::SSL_in_init(self.as_ptr()) == 1 } } #[inline] fn do_handshake(&mut self) -> SslError { self.get_error(unsafe { bffi::SSL_do_handshake(self.as_ptr()) }) } #[inline] fn provide_quic_data(&mut self, level: Level, plaintext: &[u8]) -> SslError { unsafe { self.get_error(bffi::SSL_provide_quic_data( self.as_ptr(), level.into(), plaintext.as_ptr(), plaintext.len(), )) } } #[inline] fn quic_max_handshake_flight_len(&self, level: Level) -> usize { unsafe { bffi::SSL_quic_max_handshake_flight_len(self.as_ptr(), level.into()) } } #[inline] fn quic_read_level(&self) -> Level { unsafe { bffi::SSL_quic_read_level(self.as_ptr()).into() } } #[inline] fn quic_write_level(&self) -> Level { unsafe { bffi::SSL_quic_write_level(self.as_ptr()).into() } } #[inline] fn process_post_handshake(&mut self) -> SslError { self.get_error(unsafe { bffi::SSL_process_quic_post_handshake(self.as_ptr()) }) } fn set_verify_hostname(&mut self, domain: &str) -> BoringResult { let param = self.param_mut(); param.set_hostflags(boring::x509::verify::X509CheckFlags::NO_PARTIAL_WILDCARDS); match domain.parse() { Ok(ip) => param.set_ip(ip)?, Err(_) => param.set_host(domain)?, } Ok(()) } #[inline] fn export_keyring_material( &self, output: &mut [u8], label: &[u8], context: &[u8], ) -> BoringResult { unsafe { br(bffi::SSL_export_keying_material( self.as_ptr(), output.as_mut_ptr(), output.len(), label.as_ptr() as *const c_char, label.len(), context.as_ptr(), context.len(), context.is_empty() as _, )) } } #[inline] fn in_early_data(&self) -> bool { unsafe { bffi::SSL_in_early_data(self.as_ptr()) == 1 } } #[inline] fn early_data_accepted(&self) -> bool { unsafe { bffi::SSL_early_data_accepted(self.as_ptr()) == 1 } } fn set_quic_method(&mut self, method: &bffi::SSL_QUIC_METHOD) -> BoringResult { unsafe { br(bffi::SSL_set_quic_method(self.as_ptr(), method)) } } fn set_quic_early_data_context(&mut self, value: &[u8]) -> BoringResult { unsafe { br(bffi::SSL_set_quic_early_data_context( self.as_ptr(), value.as_ptr(), value.len(), )) } } fn get_early_data_reason(&self) -> bffi::ssl_early_data_reason_t { unsafe { bffi::SSL_get_early_data_reason(self.as_ptr()) } } fn early_data_reason_string(reason: bffi::ssl_early_data_reason_t) -> &'static str { unsafe { bffi::SSL_early_data_reason_string(reason) .as_ref() .map_or("unknown", |reason| CStr::from_ptr(reason).to_str().unwrap()) } } #[inline] fn reset_early_rejected_data(&mut self) { unsafe { bffi::SSL_reset_early_data_reject(self.as_ptr()) } } fn set_quic_use_legacy_codepoint(&mut self, use_legacy: bool) { unsafe { bffi::SSL_set_quic_use_legacy_codepoint(self.as_ptr(), use_legacy as _) } } } pub trait QuicSslSession { fn early_data_capable(&self) -> bool; fn copy_without_early_data(&mut self) -> SslSession; fn encode(&self, out: &mut W) -> BoringResult; fn decode(ctx: &SslContextRef, r: &mut R) -> StdResult; } impl QuicSslSession for SslSession { fn early_data_capable(&self) -> bool { unsafe { bffi::SSL_SESSION_early_data_capable(self.as_ptr()) == 1 } } fn copy_without_early_data(&mut self) -> SslSession { unsafe { SslSession::from_ptr(bffi::SSL_SESSION_copy_without_early_data(self.as_ptr())) } } fn encode(&self, out: &mut W) -> BoringResult { unsafe { let mut buf: *mut u8 = ptr::null_mut(); let mut len = 0usize; br(bffi::SSL_SESSION_to_bytes( self.as_ptr(), &mut buf, &mut len, ))?; out.put_slice(slice::from_raw_parts(buf, len)); bffi::OPENSSL_free(buf as _); Ok(()) } } fn decode(ctx: &SslContextRef, r: &mut R) -> StdResult { unsafe { let in_len = r.remaining(); let in_ = r.chunk(); bffi::SSL_SESSION_from_bytes(in_.as_ptr(), in_len, ctx.as_ptr()) .as_mut() .map_or_else( || Err(ErrorStack::get()), |session| Ok(SslSession::from_ptr(session)), ) } } } #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum Level { Initial = 0, EarlyData = 1, Handshake = 2, Application = 3, } impl Level { pub const NUM_LEVELS: usize = 4; pub fn next(&self) -> Self { match self { Level::Initial => Level::Handshake, Level::EarlyData => Level::Handshake, _ => Level::Application, } } } impl From for Level { fn from(value: bffi::ssl_encryption_level_t) -> Self { match value { bffi::ssl_encryption_level_t::ssl_encryption_initial => Self::Initial, bffi::ssl_encryption_level_t::ssl_encryption_early_data => Self::EarlyData, bffi::ssl_encryption_level_t::ssl_encryption_handshake => Self::Handshake, bffi::ssl_encryption_level_t::ssl_encryption_application => Self::Application, _ => unreachable!(), } } } impl From for bffi::ssl_encryption_level_t { fn from(value: Level) -> Self { match value { Level::Initial => bffi::ssl_encryption_level_t::ssl_encryption_initial, Level::EarlyData => bffi::ssl_encryption_level_t::ssl_encryption_early_data, Level::Handshake => bffi::ssl_encryption_level_t::ssl_encryption_handshake, Level::Application => bffi::ssl_encryption_level_t::ssl_encryption_application, } } } #[derive(Copy, Clone)] pub struct SslError(c_int); impl SslError { #[inline] pub fn value(&self) -> c_int { self.0 } #[inline] pub fn is_none(&self) -> bool { self.0 == bffi::SSL_ERROR_NONE } #[inline] pub fn get_description(&self) -> &'static str { unsafe { CStr::from_ptr(bffi::SSL_error_description(self.0)) .to_str() .unwrap() } } } impl Display for SslError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "SSL_ERROR[{}]: {}", self.0, self.get_description()) } }