Unverified Commit e1d442e6 authored by Steven Fackler's avatar Steven Fackler Committed by GitHub
Browse files

Merge pull request #804 from sfackler/alpn-overhaul

Overhaul ALPN
parents dcfe1dfa 7fbda616
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -2495,6 +2495,7 @@ extern "C" {
    #[cfg(not(ossl101))]
    pub fn SSL_set_alpn_protos(s: *mut SSL, data: *const c_uchar, len: c_uint) -> c_int;

    // FIXME should take an Option<unsafe extern "C" fn>
    #[cfg(not(ossl101))]
    pub fn SSL_CTX_set_alpn_select_cb(
        ssl: *mut SSL_CTX,
+23 −81
Original line number Diff line number Diff line
@@ -10,9 +10,9 @@ use error::ErrorStack;
use dh::Dh;
#[cfg(any(all(feature = "v101", ossl101), all(feature = "v102", ossl102)))]
use ec::EcKey;
use ssl::{get_callback_idx, get_ssl_callback_idx, SniError, SslRef, NPN_PROTOS_IDX};
use ssl::{get_callback_idx, get_ssl_callback_idx, SniError, SslRef};
#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))]
use ssl::ALPN_PROTOS_IDX;
use ssl::AlpnError;
use x509::X509StoreContextRef;

pub extern "C" fn raw_verify<F>(preverify_ok: c_int, x509_ctx: *mut ffi::X509_STORE_CTX) -> c_int
@@ -111,60 +111,34 @@ where
    }
}

pub unsafe fn select_proto_using(
#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))]
pub extern "C" fn raw_alpn_select<F>(
    ssl: *mut ffi::SSL,
    out: *mut *mut c_uchar,
    out: *mut *const c_uchar,
    outlen: *mut c_uchar,
    inbuf: *const c_uchar,
    inlen: c_uint,
    ex_data: c_int,
) -> c_int {
    // First, get the list of protocols (that the client should support) saved in the context
    // extra data.
    let ssl_ctx = ffi::SSL_get_SSL_CTX(ssl);
    let protocols = ffi::SSL_CTX_get_ex_data(ssl_ctx, ex_data);
    let protocols: &Vec<u8> = &*(protocols as *mut Vec<u8>);
    // Prepare the client list parameters to be passed to the OpenSSL function...
    let client = protocols.as_ptr();
    let client_len = protocols.len() as c_uint;
    // Finally, let OpenSSL find a protocol to be used, by matching the given server and
    // client lists.
    if ffi::SSL_select_next_proto(out, outlen, inbuf, inlen, client, client_len)
        != ffi::OPENSSL_NPN_NEGOTIATED
    _arg: *mut c_void,
) -> c_int
where
    F: for<'a> Fn(&mut SslRef, &'a [u8]) -> Result<&'a [u8], AlpnError> + 'static + Sync + Send,
{
        ffi::SSL_TLSEXT_ERR_NOACK
    } else {
    unsafe {
        let ssl_ctx = ffi::SSL_get_SSL_CTX(ssl);
        let callback = ffi::SSL_CTX_get_ex_data(ssl_ctx, get_callback_idx::<F>());
        let callback: &F = &*(callback as *mut F);
        let ssl = SslRef::from_ptr_mut(ssl);
        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,
        }

/// The function is given as the callback to `SSL_CTX_set_next_proto_select_cb`.
///
/// It chooses the protocol that the client wishes to use, out of the given list of protocols
/// supported by the server. It achieves this by delegating to the `SSL_select_next_proto`
/// function. The list of protocols supported by the client is found in the extra data of the
/// OpenSSL context.
pub extern "C" fn raw_next_proto_select_cb(
    ssl: *mut ffi::SSL,
    out: *mut *mut c_uchar,
    outlen: *mut c_uchar,
    inbuf: *const c_uchar,
    inlen: c_uint,
    _arg: *mut c_void,
) -> c_int {
    unsafe { select_proto_using(ssl, out, outlen, inbuf, inlen, *NPN_PROTOS_IDX) }
    }

#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))]
pub extern "C" fn raw_alpn_select_cb(
    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 {
    unsafe { select_proto_using(ssl, out as *mut _, outlen, inbuf, inlen, *ALPN_PROTOS_IDX) }
}

pub unsafe extern "C" fn raw_tmp_dh<F>(
@@ -302,35 +276,3 @@ where
        }
    }
}

/// The function is given as the callback to `SSL_CTX_set_next_protos_advertised_cb`.
///
/// It causes the parameter `out` to point at a `*const c_uchar` instance that
/// represents the list of protocols that the server should advertise as those
/// that it supports.
/// The list of supported protocols is found in the extra data of the OpenSSL
/// context.
pub extern "C" fn raw_next_protos_advertise_cb(
    ssl: *mut ffi::SSL,
    out: *mut *const c_uchar,
    outlen: *mut c_uint,
    _arg: *mut c_void,
) -> c_int {
    unsafe {
        // First, get the list of (supported) protocols saved in the context extra data.
        let ssl_ctx = ffi::SSL_get_SSL_CTX(ssl);
        let protocols = ffi::SSL_CTX_get_ex_data(ssl_ctx, *NPN_PROTOS_IDX);
        if protocols.is_null() {
            *out = b"".as_ptr();
            *outlen = 0;
        } else {
            // If the pointer is valid, put the pointer to the actual byte array into the
            // output parameter `out`, as well as its length into `outlen`.
            let protocols: &Vec<u8> = &*(protocols as *mut Vec<u8>);
            *out = protocols.as_ptr();
            *outlen = protocols.len() as c_uint;
        }
    }

    ffi::SSL_TLSEXT_ERR_OK
}
+100 −113
Original line number Diff line number Diff line
@@ -357,15 +357,6 @@ fn get_ssl_callback_idx<T: 'static>() -> c_int {
        .or_insert_with(|| get_new_ssl_idx::<T>())
}

lazy_static! {
    static ref NPN_PROTOS_IDX: c_int = get_new_idx::<Vec<u8>>();
}

#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))]
lazy_static! {
    static ref ALPN_PROTOS_IDX: c_int = get_new_idx::<Vec<u8>>();
}

unsafe extern "C" fn free_data_box<T>(
    _parent: *mut c_void,
    ptr: *mut c_void,
@@ -395,22 +386,6 @@ fn get_new_ssl_idx<T>() -> c_int {
    }
}

/// Convert a set of byte slices into a series of byte strings encoded for SSL. Encoding is a byte
/// containing the length followed by the string.
fn ssl_encode_byte_strings(strings: &[&[u8]]) -> Vec<u8> {
    let mut enc = Vec::new();
    for string in strings {
        let len = string.len() as u8;
        if len as usize != string.len() {
            // If the item does not fit, discard it
            continue;
        }
        enc.push(len);
        enc.extend(string[..len as usize].to_vec());
    }
    enc
}

// FIXME look into this
/// An error returned from an SNI callback.
pub enum SniError {
@@ -419,6 +394,57 @@ pub enum SniError {
    NoAck,
}

/// An error returned from an ALPN selection callback.
///
/// Requires the `v102` or `v110` features and OpenSSL 1.0.2 or OpenSSL 1.1.0.
#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))]
pub struct AlpnError(c_int);

#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))]
impl AlpnError {
    /// Terminate the handshake with a fatal alert.
    ///
    /// Requires the `v110` feature and OpenSSL 1.1.0.
    #[cfg(all(feature = "v110", 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);
}

/// 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.
///
/// This corresponds to [`SSL_select_next_proto`].
///
/// [`SslContextBuilder::set_alpn_protos`]: struct.SslContextBuilder.html#method.set_alpn_protos
/// [`SSL_select_next_proto`]: https://www.openssl.org/docs/man1.1.0/ssl/SSL_CTX_set_alpn_protos.html
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(*mut ffi::SSL_CTX);

@@ -888,82 +914,68 @@ impl SslContextBuilder {
        SslOptions::from_bits(ret).unwrap()
    }

    /// Set the protocols to be used during Next Protocol Negotiation (the protocols
    /// supported by the application).
    // FIXME overhaul
    #[cfg(not(any(libressl261, libressl262, libressl26x)))]
    pub fn set_npn_protocols(&mut self, protocols: &[&[u8]]) -> Result<(), ErrorStack> {
        // Firstly, convert the list of protocols to a byte-array that can be passed to OpenSSL
        // APIs -- a list of length-prefixed strings.
        let protocols: Box<Vec<u8>> = Box::new(ssl_encode_byte_strings(protocols));

    /// 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.
    ///
    /// This corresponds to [`SSL_CTX_set_alpn_protos`].
    ///
    /// Requires the `v102` or `v110` features and OpenSSL 1.0.2 or OpenSSL 1.1.0.
    ///
    /// [`SSL_CTX_set_alpn_protos`]: https://www.openssl.org/docs/man1.1.0/ssl/SSL_CTX_set_alpn_protos.html
    #[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))]
    pub fn set_alpn_protos(&mut self, protocols: &[u8]) -> Result<(), ErrorStack> {
        unsafe {
            // Attach the protocol list to the OpenSSL context structure,
            // so that we can refer to it within the callback.
            cvt(ffi::SSL_CTX_set_ex_data(
                self.as_ptr(),
                *NPN_PROTOS_IDX,
                Box::into_raw(protocols) as *mut c_void,
            ))?;
            // Now register the callback that performs the default protocol
            // matching based on the client-supported list of protocols that
            // has been saved.
            ffi::SSL_CTX_set_next_proto_select_cb(
                self.as_ptr(),
                raw_next_proto_select_cb,
                ptr::null_mut(),
            );
            // Also register the callback to advertise these protocols, if a server socket is
            // created with the context.
            ffi::SSL_CTX_set_next_protos_advertised_cb(
            assert!(protocols.len() <= c_uint::max_value() as usize);
            let r = ffi::SSL_CTX_set_alpn_protos(
                self.as_ptr(),
                raw_next_protos_advertise_cb,
                ptr::null_mut(),
                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())
            }
        }
    }

    /// Set the protocols to be used during ALPN (application layer protocol negotiation).
    /// If this is a server, these are the protocols we report to the client.
    /// If this is a client, these are the protocols we try to match with those reported by the
    /// server.
    /// Sets the callback used by a server to select a protocol for Application Layer Protocol
    /// Negotiation (ALPN).
    ///
    /// Note that ordering of the protocols controls the priority with which they are chosen.
    /// 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.
    ///
    /// This corresponds to [`SSL_CTX_set_alpn_select_cb`].
    ///
    /// Requires the `v102` or `v110` features and OpenSSL 1.0.2 or OpenSSL 1.1.0.
    // FIXME overhaul
    ///
    /// [`SslContextBuilder::set_alpn_protos`]: struct.SslContextBuilder.html#method.set_alpn_protos
    /// [`select_next_proto`]: fn.select_next_proto.html
    /// [`SSL_CTX_set_alpn_select_cb`]: https://www.openssl.org/docs/man1.1.0/ssl/SSL_CTX_set_alpn_protos.html
    #[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))]
    pub fn set_alpn_protocols(&mut self, protocols: &[&[u8]]) -> Result<(), ErrorStack> {
        let protocols: Box<Vec<u8>> = Box::new(ssl_encode_byte_strings(protocols));
    pub fn set_alpn_select_callback<F>(&mut self, callback: F)
    where
        F: for<'a> Fn(&mut SslRef, &'a [u8]) -> Result<&'a [u8], AlpnError> + 'static + Sync + Send,
    {
        unsafe {
            // Set the context's internal protocol list for use if we are a server
            let r = ffi::SSL_CTX_set_alpn_protos(
            let callback = Box::new(callback);
            ffi::SSL_CTX_set_ex_data(
                self.as_ptr(),
                protocols.as_ptr(),
                protocols.len() as c_uint,
                get_callback_idx::<F>(),
                Box::into_raw(callback) as *mut c_void,
            );
            // fun fact, SSL_CTX_set_alpn_protos has a reversed return code D:
            if r != 0 {
                return Err(ErrorStack::get());
            }

            // Rather than use the argument to the callback to contain our data, store it in the
            // ssl ctx's ex_data so that we can configure a function to free it later. In the
            // future, it might make sense to pull this into our internal struct Ssl instead of
            // leaning on openssl and using function pointers.
            cvt(ffi::SSL_CTX_set_ex_data(
            ffi::SSL_CTX_set_alpn_select_cb(
                self.as_ptr(),
                *ALPN_PROTOS_IDX,
                Box::into_raw(protocols) as *mut c_void,
            ))?;

            // Now register the callback that performs the default protocol
            // matching based on the client-supported list of protocols that
            // has been saved.
            ffi::SSL_CTX_set_alpn_select_cb(self.as_ptr(), raw_alpn_select_cb, ptr::null_mut());

            Ok(())
                callbacks::raw_alpn_select::<F>,
                ptr::null_mut(),
            );
        }
    }

@@ -1719,32 +1731,7 @@ impl SslRef {
        str::from_utf8(version.to_bytes()).unwrap()
    }

    /// Returns the protocol selected by performing Next Protocol Negotiation, if any.
    ///
    /// The protocol's name is returned is an opaque sequence of bytes. It is up to the client
    /// to interpret it.
    ///
    /// This corresponds to [`SSL_get0_next_proto_negotiated`].
    ///
    /// [`SSL_get0_next_proto_negotiated`]: https://www.openssl.org/docs/manmaster/man3/SSL_get0_next_proto_negotiated.html
    #[cfg(not(any(libressl261, libressl262, libressl26x)))]
    pub fn selected_npn_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_next_proto_negotiated(self.as_ptr(), &mut data, &mut len);

            if data.is_null() {
                None
            } else {
                Some(slice::from_raw_parts(data, len as usize))
            }
        }
    }

    /// Returns the protocol selected by performing ALPN, if any.
    /// 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.
+23 −99

File changed.

Preview size limit exceeded, changes collapsed.