Support the client hello callback
This commit is contained in:
parent
a548913e44
commit
22231d7547
|
|
@ -300,7 +300,6 @@ cfg_if! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(ossl101)] {
|
if #[cfg(ossl101)] {
|
||||||
pub const SSL_OP_NO_SSLv3: c_ulong = 0x02000000;
|
pub const SSL_OP_NO_SSLv3: c_ulong = 0x02000000;
|
||||||
|
|
@ -672,6 +671,8 @@ pub const SSL_ERROR_WANT_READ: c_int = 2;
|
||||||
pub const SSL_ERROR_WANT_WRITE: c_int = 3;
|
pub const SSL_ERROR_WANT_WRITE: c_int = 3;
|
||||||
pub const SSL_ERROR_WANT_X509_LOOKUP: c_int = 4;
|
pub const SSL_ERROR_WANT_X509_LOOKUP: c_int = 4;
|
||||||
pub const SSL_ERROR_ZERO_RETURN: c_int = 6;
|
pub const SSL_ERROR_ZERO_RETURN: c_int = 6;
|
||||||
|
#[cfg(ossl111)]
|
||||||
|
pub const SSL_ERROR_WANT_CLIENT_HELLO_CB: c_int = 11;
|
||||||
pub const SSL_VERIFY_NONE: c_int = 0;
|
pub const SSL_VERIFY_NONE: c_int = 0;
|
||||||
pub const SSL_VERIFY_PEER: c_int = 1;
|
pub const SSL_VERIFY_PEER: c_int = 1;
|
||||||
pub const SSL_VERIFY_FAIL_IF_NO_PEER_CERT: c_int = 2;
|
pub const SSL_VERIFY_FAIL_IF_NO_PEER_CERT: c_int = 2;
|
||||||
|
|
@ -942,6 +943,53 @@ extern "C" {
|
||||||
|
|
||||||
#[cfg(any(ossl102, libressl261))]
|
#[cfg(any(ossl102, libressl261))]
|
||||||
pub fn SSL_get0_param(ssl: *mut SSL) -> *mut X509_VERIFY_PARAM;
|
pub fn SSL_get0_param(ssl: *mut SSL) -> *mut X509_VERIFY_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(ossl111)]
|
||||||
|
pub const SSL_CLIENT_HELLO_SUCCESS: c_int = 1;
|
||||||
|
#[cfg(ossl111)]
|
||||||
|
pub const SSL_CLIENT_HELLO_ERROR: c_int = 0;
|
||||||
|
#[cfg(ossl111)]
|
||||||
|
pub const SSL_CLIENT_HELLO_RETRY: c_int = -1;
|
||||||
|
|
||||||
|
#[cfg(ossl111)]
|
||||||
|
pub type SSL_client_hello_cb_fn =
|
||||||
|
Option<unsafe extern "C" fn(s: *mut SSL, al: *mut c_int, arg: *mut c_void) -> c_int>;
|
||||||
|
extern "C" {
|
||||||
|
#[cfg(ossl111)]
|
||||||
|
pub fn SSL_CTX_set_client_hello_cb(
|
||||||
|
c: *mut SSL_CTX,
|
||||||
|
cb: SSL_client_hello_cb_fn,
|
||||||
|
arg: *mut c_void,
|
||||||
|
);
|
||||||
|
#[cfg(ossl111)]
|
||||||
|
pub fn SSL_client_hello_isv2(s: *mut SSL) -> c_int;
|
||||||
|
#[cfg(ossl111)]
|
||||||
|
pub fn SSL_client_hello_get0_legacy_version(s: *mut SSL) -> c_uint;
|
||||||
|
#[cfg(ossl111)]
|
||||||
|
pub fn SSL_client_hello_get0_random(s: *mut SSL, out: *mut *const c_uchar) -> size_t;
|
||||||
|
#[cfg(ossl111)]
|
||||||
|
pub fn SSL_client_hello_get0_session_id(s: *mut SSL, out: *mut *const c_uchar) -> size_t;
|
||||||
|
#[cfg(ossl111)]
|
||||||
|
pub fn SSL_client_hello_get0_ciphers(s: *mut SSL, out: *mut *const c_uchar) -> size_t;
|
||||||
|
#[cfg(ossl111)]
|
||||||
|
pub fn SSL_client_hello_get0_compression_methods(
|
||||||
|
s: *mut SSL,
|
||||||
|
out: *mut *const c_uchar,
|
||||||
|
) -> size_t;
|
||||||
|
#[cfg(ossl111)]
|
||||||
|
pub fn SSL_client_hello_get1_extensions_present(
|
||||||
|
s: *mut SSL,
|
||||||
|
out: *mut *mut c_int,
|
||||||
|
outlen: *mut size_t,
|
||||||
|
) -> c_int;
|
||||||
|
#[cfg(ossl111)]
|
||||||
|
pub fn SSL_client_hello_get0_ext(
|
||||||
|
s: *mut SSL,
|
||||||
|
type_: c_uint,
|
||||||
|
out: *mut *const c_uchar,
|
||||||
|
outlen: *mut size_t,
|
||||||
|
) -> c_int;
|
||||||
|
|
||||||
pub fn SSL_free(ssl: *mut SSL);
|
pub fn SSL_free(ssl: *mut SSL);
|
||||||
pub fn SSL_accept(ssl: *mut SSL) -> c_int;
|
pub fn SSL_accept(ssl: *mut SSL) -> c_int;
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ use pkey::Params;
|
||||||
#[cfg(any(ossl102, libressl261))]
|
#[cfg(any(ossl102, libressl261))]
|
||||||
use ssl::AlpnError;
|
use ssl::AlpnError;
|
||||||
#[cfg(ossl111)]
|
#[cfg(ossl111)]
|
||||||
use ssl::ExtensionContext;
|
use ssl::{ExtensionContext, ClientHelloResponse};
|
||||||
use ssl::{SniError, Ssl, SslAlert, SslContext, SslContextRef, SslRef, SslSession, SslSessionRef};
|
use ssl::{SniError, Ssl, SslAlert, SslContext, SslContextRef, SslRef, SslSession, SslSessionRef};
|
||||||
#[cfg(ossl111)]
|
#[cfg(ossl111)]
|
||||||
use x509::X509Ref;
|
use x509::X509Ref;
|
||||||
|
|
@ -655,3 +655,30 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(ossl111)]
|
||||||
|
pub unsafe extern "C" fn raw_client_hello<F>(
|
||||||
|
ssl: *mut ffi::SSL,
|
||||||
|
al: *mut c_int,
|
||||||
|
arg: *mut c_void,
|
||||||
|
) -> c_int
|
||||||
|
where
|
||||||
|
F: Fn(&mut SslRef, &mut SslAlert) -> Result<ClientHelloResponse, ErrorStack>
|
||||||
|
+ '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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,12 @@ impl ErrorCode {
|
||||||
|
|
||||||
/// An error occurred in the SSL library.
|
/// An error occurred in the SSL library.
|
||||||
pub const SSL: ErrorCode = ErrorCode(ffi::SSL_ERROR_SSL);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -131,6 +137,7 @@ impl error::Error for Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error or intermediate state after a TLS handshake attempt.
|
/// An error or intermediate state after a TLS handshake attempt.
|
||||||
|
// FIXME overhaul
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum HandshakeError<S> {
|
pub enum HandshakeError<S> {
|
||||||
/// Setup failed.
|
/// Setup failed.
|
||||||
|
|
|
||||||
|
|
@ -525,6 +525,22 @@ impl AlpnError {
|
||||||
pub const NOACK: AlpnError = AlpnError(ffi::SSL_TLSEXT_ERR_NOACK);
|
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.
|
/// An SSL/TLS protocol version.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct SslVersion(c_int);
|
pub struct SslVersion(c_int);
|
||||||
|
|
@ -1600,6 +1616,31 @@ impl SslContextBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets a callback which will be invoked just after the client's hello message is received.
|
||||||
|
///
|
||||||
|
/// Requires OpenSSL 1.1.1 or newer.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`SSL_CTX_set_client_hello_cb`].
|
||||||
|
///
|
||||||
|
/// [`SSL_CTX_set_client_hello_cb`]: https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_client_hello_cb.html
|
||||||
|
#[cfg(ossl111)]
|
||||||
|
pub fn set_client_hello_callback<F>(&mut self, callback: F)
|
||||||
|
where
|
||||||
|
F: Fn(&mut SslRef, &mut SslAlert) -> Result<ClientHelloResponse, ErrorStack>
|
||||||
|
+ 'static
|
||||||
|
+ Sync
|
||||||
|
+ Send,
|
||||||
|
{
|
||||||
|
unsafe {
|
||||||
|
let ptr = self.set_ex_data_inner(SslContext::cached_ex_index::<F>(), callback);
|
||||||
|
ffi::SSL_CTX_set_client_hello_cb(
|
||||||
|
self.as_ptr(),
|
||||||
|
Some(callbacks::raw_client_hello::<F>),
|
||||||
|
ptr,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Consumes the builder, returning a new `SslContext`.
|
/// Consumes the builder, returning a new `SslContext`.
|
||||||
pub fn build(self) -> SslContext {
|
pub fn build(self) -> SslContext {
|
||||||
self.0
|
self.0
|
||||||
|
|
@ -2934,6 +2975,131 @@ impl SslRef {
|
||||||
ffi::SSL_get_peer_finished(self.as_ptr(), buf.as_mut_ptr() as *mut c_void, buf.len())
|
ffi::SSL_get_peer_finished(self.as_ptr(), buf.as_mut_ptr() as *mut c_void, buf.len())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`SSL_client_hello_isv2`].
|
||||||
|
///
|
||||||
|
/// [`SSL_client_hello_isv2`]: https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_client_hello_cb.html
|
||||||
|
#[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.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`SSL_client_hello_get0_legacy_version`].
|
||||||
|
///
|
||||||
|
/// [`SSL_client_hello_get0_legacy_version`]: https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_client_hello_cb.html
|
||||||
|
#[cfg(ossl111)]
|
||||||
|
pub fn client_hello_legacy_version(&self) -> Option<SslVersion> {
|
||||||
|
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 returend.
|
||||||
|
///
|
||||||
|
/// Requires OpenSSL 1.1.1 or newer.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`SSL_client_hello_get0_random`].
|
||||||
|
///
|
||||||
|
/// [`SSL_client_hello_get0_random`]: https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_client_hello_cb.html
|
||||||
|
#[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 returend.
|
||||||
|
///
|
||||||
|
/// Requires OpenSSL 1.1.1 or newer.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`SSL_client_hello_get0_session_id`].
|
||||||
|
///
|
||||||
|
/// [`SSL_client_hello_get0_session_id`]: https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_client_hello_cb.html
|
||||||
|
#[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 returend.
|
||||||
|
///
|
||||||
|
/// Requires OpenSSL 1.1.1 or newer.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`SSL_client_hello_get0_ciphers`].
|
||||||
|
///
|
||||||
|
/// [`SSL_client_hello_get0_ciphers`]: https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_client_hello_cb.html
|
||||||
|
#[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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 returend.
|
||||||
|
///
|
||||||
|
/// Requires OpenSSL 1.1.1 or newer.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`SSL_client_hello_get0_compression_methods`].
|
||||||
|
///
|
||||||
|
/// [`SSL_client_hello_get0_compression_methods`]: https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_client_hello_cb.html
|
||||||
|
#[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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An SSL stream midway through the handshake process.
|
/// An SSL stream midway through the handshake process.
|
||||||
|
|
|
||||||
|
|
@ -1793,3 +1793,49 @@ fn sni_callback_swapped_ctx() {
|
||||||
|
|
||||||
guard.join().unwrap();
|
guard.join().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(ossl111)]
|
||||||
|
fn client_hello() {
|
||||||
|
use ssl::ClientHelloResponse;
|
||||||
|
|
||||||
|
static CALLED_BACK: AtomicBool = ATOMIC_BOOL_INIT;
|
||||||
|
|
||||||
|
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::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();
|
||||||
|
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());
|
||||||
|
|
||||||
|
CALLED_BACK.store(true, Ordering::SeqCst);
|
||||||
|
Ok(ClientHelloResponse::SUCCESS)
|
||||||
|
});
|
||||||
|
|
||||||
|
let ssl = Ssl::new(&ctx.build()).unwrap();
|
||||||
|
let mut stream = ssl.accept(stream).unwrap();
|
||||||
|
stream.write_all(&mut [0]).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
stream.read_exact(&mut [0]).unwrap();
|
||||||
|
|
||||||
|
assert!(CALLED_BACK.load(Ordering::SeqCst));
|
||||||
|
|
||||||
|
guard.join().unwrap();
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue