From 7fbda616098e3ab489b61735fa27651b34cac67c Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Wed, 27 Dec 2017 16:04:19 -0700 Subject: [PATCH] Overhaul ALPN There was previously a lot of behind the scenes magic. We now bind much more directly to the relevant functions. Also remove APN support. That protocol is supersceded by ALPN - let's see if anyone actually needs to use it. --- openssl-sys/src/lib.rs | 1 + openssl/src/ssl/callbacks.rs | 104 ++++------------- openssl/src/ssl/mod.rs | 213 ++++++++++++++++------------------- openssl/src/ssl/test.rs | 170 ++++++++-------------------- 4 files changed, 171 insertions(+), 317 deletions(-) diff --git a/openssl-sys/src/lib.rs b/openssl-sys/src/lib.rs index eafda590..e855216a 100644 --- a/openssl-sys/src/lib.rs +++ b/openssl-sys/src/lib.rs @@ -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 #[cfg(not(ossl101))] pub fn SSL_CTX_set_alpn_select_cb( ssl: *mut SSL_CTX, diff --git a/openssl/src/ssl/callbacks.rs b/openssl/src/ssl/callbacks.rs index 26aa8b86..0d211691 100644 --- a/openssl/src/ssl/callbacks.rs +++ b/openssl/src/ssl/callbacks.rs @@ -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(preverify_ok: c_int, x509_ctx: *mut ffi::X509_STORE_CTX) -> c_int @@ -111,60 +111,34 @@ where } } -pub unsafe fn select_proto_using( - ssl: *mut ffi::SSL, - out: *mut *mut 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 = &*(protocols as *mut Vec); - // 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 - { - ffi::SSL_TLSEXT_ERR_NOACK - } else { - ffi::SSL_TLSEXT_ERR_OK - } -} - -/// 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( +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 { - unsafe { select_proto_using(ssl, out as *mut _, outlen, inbuf, inlen, *ALPN_PROTOS_IDX) } +) -> c_int +where + F: for<'a> Fn(&mut SslRef, &'a [u8]) -> Result<&'a [u8], AlpnError> + 'static + Sync + Send, +{ + unsafe { + let ssl_ctx = ffi::SSL_get_SSL_CTX(ssl); + let callback = ffi::SSL_CTX_get_ex_data(ssl_ctx, get_callback_idx::()); + 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, + } + } } pub unsafe extern "C" fn raw_tmp_dh( @@ -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 = &*(protocols as *mut Vec); - *out = protocols.as_ptr(); - *outlen = protocols.len() as c_uint; - } - } - - ffi::SSL_TLSEXT_ERR_OK -} diff --git a/openssl/src/ssl/mod.rs b/openssl/src/ssl/mod.rs index 5ef04fca..dbe53300 100644 --- a/openssl/src/ssl/mod.rs +++ b/openssl/src/ssl/mod.rs @@ -357,15 +357,6 @@ fn get_ssl_callback_idx() -> c_int { .or_insert_with(|| get_new_ssl_idx::()) } -lazy_static! { - static ref NPN_PROTOS_IDX: c_int = get_new_idx::>(); -} - -#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] -lazy_static! { - static ref ALPN_PROTOS_IDX: c_int = get_new_idx::>(); -} - unsafe extern "C" fn free_data_box( _parent: *mut c_void, ptr: *mut c_void, @@ -395,22 +386,6 @@ fn get_new_ssl_idx() -> 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 { - 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> = Box::new(ssl_encode_byte_strings(protocols)); - - 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( - self.as_ptr(), - raw_next_protos_advertise_cb, - ptr::null_mut(), - ); - Ok(()) - } - } - - /// 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 protocols to sent to the server for Application Layer Protocol Negotiation (ALPN). /// - /// Note that ordering of the protocols controls the priority with which they are chosen. + /// 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. - // FIXME overhaul + /// + /// [`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_protocols(&mut self, protocols: &[&[u8]]) -> Result<(), ErrorStack> { - let protocols: Box> = Box::new(ssl_encode_byte_strings(protocols)); + pub fn set_alpn_protos(&mut self, protocols: &[u8]) -> Result<(), ErrorStack> { unsafe { - // Set the context's internal protocol list for use if we are a server + 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 { - return Err(ErrorStack::get()); + if r == 0 { + Ok(()) + } else { + 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( + /// 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. + /// + /// 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. + /// + /// [`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_select_callback(&mut self, callback: F) + where + F: for<'a> Fn(&mut SslRef, &'a [u8]) -> Result<&'a [u8], AlpnError> + 'static + Sync + Send, + { + unsafe { + let callback = Box::new(callback); + ffi::SSL_CTX_set_ex_data( 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(()) + get_callback_idx::(), + Box::into_raw(callback) as *mut c_void, + ); + ffi::SSL_CTX_set_alpn_select_cb( + self.as_ptr(), + callbacks::raw_alpn_select::, + 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. diff --git a/openssl/src/ssl/test.rs b/openssl/src/ssl/test.rs index 3beaf846..f4e5b11b 100644 --- a/openssl/src/ssl/test.rs +++ b/openssl/src/ssl/test.rs @@ -479,7 +479,7 @@ fn test_connect_with_unilateral_alpn() { let (_s, stream) = Server::new(); let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_verify(SslVerifyMode::PEER); - ctx.set_alpn_protocols(&[b"http/1.1", b"spdy/3.1"]).unwrap(); + ctx.set_alpn_protos(b"\x08http/1.1\x08spdy/3.1").unwrap(); match ctx.set_ca_file(&Path::new("test/root-ca.pem")) { Ok(_) => {} Err(err) => panic!("Unexpected error {:?}", err), @@ -493,28 +493,6 @@ fn test_connect_with_unilateral_alpn() { assert!(stream.ssl().selected_alpn_protocol().is_none()); } -/// Tests that connecting with the client using NPN, but the server not does not -/// break the existing connection behavior. -#[test] -#[cfg(not(any(libressl261, libressl262, libressl26x)))] -fn test_connect_with_unilateral_npn() { - let (_s, stream) = Server::new(); - let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); - ctx.set_verify(SslVerifyMode::PEER); - ctx.set_npn_protocols(&[b"http/1.1", b"spdy/3.1"]).unwrap(); - match ctx.set_ca_file(&Path::new("test/root-ca.pem")) { - Ok(_) => {} - Err(err) => panic!("Unexpected error {:?}", err), - } - let stream = match Ssl::new(&ctx.build()).unwrap().connect(stream) { - Ok(stream) => stream, - Err(err) => panic!("Expected success, got {:?}", err), - }; - // Since the socket to which we connected is not configured to use NPN, - // there should be no selected protocol... - assert!(stream.ssl().selected_npn_protocol().is_none()); -} - /// Tests that when both the client as well as the server use ALPN and their /// lists of supported protocols have an overlap, the correct protocol is chosen. #[test] @@ -523,7 +501,7 @@ fn test_connect_with_alpn_successful_multiple_matching() { let (_s, stream) = Server::new_alpn(); let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_verify(SslVerifyMode::PEER); - ctx.set_alpn_protocols(&[b"spdy/3.1", b"http/1.1"]).unwrap(); + ctx.set_alpn_protos(b"\x08http/1.1\x08spdy/3.1").unwrap(); match ctx.set_ca_file(&Path::new("test/root-ca.pem")) { Ok(_) => {} Err(err) => panic!("Unexpected error {:?}", err), @@ -537,28 +515,6 @@ fn test_connect_with_alpn_successful_multiple_matching() { assert_eq!(b"http/1.1", stream.ssl().selected_alpn_protocol().unwrap()); } -/// Tests that when both the client as well as the server use NPN and their -/// lists of supported protocols have an overlap, the correct protocol is chosen. -#[test] -#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] -fn test_connect_with_npn_successful_multiple_matching() { - let (_s, stream) = Server::new_alpn(); - let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); - ctx.set_verify(SslVerifyMode::PEER); - ctx.set_npn_protocols(&[b"spdy/3.1", b"http/1.1"]).unwrap(); - match ctx.set_ca_file(&Path::new("test/root-ca.pem")) { - Ok(_) => {} - Err(err) => panic!("Unexpected error {:?}", err), - } - let stream = match Ssl::new(&ctx.build()).unwrap().connect(stream) { - Ok(stream) => stream, - Err(err) => panic!("Expected success, got {:?}", err), - }; - // The server prefers "http/1.1", so that is chosen, even though the client - // would prefer "spdy/3.1" - assert_eq!(b"http/1.1", stream.ssl().selected_npn_protocol().unwrap()); -} - /// Tests that when both the client as well as the server use ALPN and their /// lists of supported protocols have an overlap -- with only ONE protocol /// being valid for both. @@ -568,7 +524,7 @@ fn test_connect_with_alpn_successful_single_match() { let (_s, stream) = Server::new_alpn(); let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_verify(SslVerifyMode::PEER); - ctx.set_alpn_protocols(&[b"spdy/3.1"]).unwrap(); + ctx.set_alpn_protos(b"\x08spdy/3.1").unwrap(); match ctx.set_ca_file(&Path::new("test/root-ca.pem")) { Ok(_) => {} Err(err) => panic!("Unexpected error {:?}", err), @@ -582,72 +538,6 @@ fn test_connect_with_alpn_successful_single_match() { assert_eq!(b"spdy/3.1", stream.ssl().selected_alpn_protocol().unwrap()); } -/// Tests that when both the client as well as the server use NPN and their -/// lists of supported protocols have an overlap -- with only ONE protocol -/// being valid for both. -#[test] -#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] -fn test_connect_with_npn_successful_single_match() { - let (_s, stream) = Server::new_alpn(); - let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); - ctx.set_verify(SslVerifyMode::PEER); - ctx.set_npn_protocols(&[b"spdy/3.1"]).unwrap(); - match ctx.set_ca_file(&Path::new("test/root-ca.pem")) { - Ok(_) => {} - Err(err) => panic!("Unexpected error {:?}", err), - } - let stream = match Ssl::new(&ctx.build()).unwrap().connect(stream) { - Ok(stream) => stream, - Err(err) => panic!("Expected success, got {:?}", err), - }; - // The client now only supports one of the server's protocols, so that one - // is used. - assert_eq!(b"spdy/3.1", stream.ssl().selected_npn_protocol().unwrap()); -} - -/// Tests that when the `SslStream` is created as a server stream, the protocols -/// are correctly advertised to the client. -#[test] -#[cfg(not(any(libressl261, libressl262, libressl26x)))] -fn test_npn_server_advertise_multiple() { - let listener = TcpListener::bind("127.0.0.1:0").unwrap(); - let localhost = listener.local_addr().unwrap(); - // We create a different context instance for the server... - let listener_ctx = { - let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); - ctx.set_verify(SslVerifyMode::PEER); - ctx.set_npn_protocols(&[b"http/1.1", b"spdy/3.1"]).unwrap(); - assert!( - ctx.set_certificate_file(&Path::new("test/cert.pem"), X509Filetype::PEM) - .is_ok() - ); - ctx.set_private_key_file(&Path::new("test/key.pem"), X509Filetype::PEM) - .unwrap(); - ctx.build() - }; - // Have the listener wait on the connection in a different thread. - thread::spawn(move || { - let (stream, _) = listener.accept().unwrap(); - Ssl::new(&listener_ctx).unwrap().accept(stream).unwrap(); - }); - - let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); - ctx.set_verify(SslVerifyMode::PEER); - ctx.set_npn_protocols(&[b"spdy/3.1"]).unwrap(); - match ctx.set_ca_file(&Path::new("test/root-ca.pem")) { - Ok(_) => {} - Err(err) => panic!("Unexpected error {:?}", err), - } - // Now connect to the socket and make sure the protocol negotiation works... - let stream = TcpStream::connect(localhost).unwrap(); - let stream = match Ssl::new(&ctx.build()).unwrap().connect(stream) { - Ok(stream) => stream, - Err(err) => panic!("Expected success, got {:?}", err), - }; - // SPDY is selected since that's the only thing the client supports. - assert_eq!(b"spdy/3.1", stream.ssl().selected_npn_protocol().unwrap()); -} - /// Tests that when the `SslStream` is created as a server stream, the protocols /// are correctly advertised to the client. #[test] @@ -658,8 +548,9 @@ fn test_alpn_server_advertise_multiple() { // We create a different context instance for the server... let listener_ctx = { let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); - ctx.set_verify(SslVerifyMode::PEER); - ctx.set_alpn_protocols(&[b"http/1.1", b"spdy/3.1"]).unwrap(); + ctx.set_alpn_select_callback(|_, client| { + ssl::select_next_proto(b"\x08http/1.1\x08spdy/3.1", client).ok_or(ssl::AlpnError::NOACK) + }); assert!( ctx.set_certificate_file(&Path::new("test/cert.pem"), X509Filetype::PEM) .is_ok() @@ -676,7 +567,7 @@ fn test_alpn_server_advertise_multiple() { let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_verify(SslVerifyMode::PEER); - ctx.set_alpn_protocols(&[b"spdy/3.1"]).unwrap(); + ctx.set_alpn_protos(b"\x08spdy/3.1").unwrap(); match ctx.set_ca_file(&Path::new("test/root-ca.pem")) { Ok(_) => {} Err(err) => panic!("Unexpected error {:?}", err), @@ -691,18 +582,18 @@ fn test_alpn_server_advertise_multiple() { assert_eq!(b"spdy/3.1", stream.ssl().selected_alpn_protocol().unwrap()); } -/// Test that Servers supporting ALPN don't report a protocol when none of their protocols match -/// the client's reported protocol. #[test] -#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] -fn test_alpn_server_select_none() { +#[cfg(all(feature = "v110", ossl110))] +fn test_alpn_server_select_none_fatal() { let listener = TcpListener::bind("127.0.0.1:0").unwrap(); let localhost = listener.local_addr().unwrap(); // We create a different context instance for the server... let listener_ctx = { let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); - ctx.set_verify(SslVerifyMode::PEER); - ctx.set_alpn_protocols(&[b"http/1.1", b"spdy/3.1"]).unwrap(); + ctx.set_alpn_select_callback(|_, client| { + ssl::select_next_proto(b"\x08http/1.1\x08spdy/3.1", client) + .ok_or(ssl::AlpnError::ALERT_FATAL) + }); assert!( ctx.set_certificate_file(&Path::new("test/cert.pem"), X509Filetype::PEM) .is_ok() @@ -719,7 +610,40 @@ fn test_alpn_server_select_none() { let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_verify(SslVerifyMode::PEER); - ctx.set_alpn_protocols(&[b"http/2"]).unwrap(); + ctx.set_alpn_protos(b"\x06http/2").unwrap(); + ctx.set_ca_file(&Path::new("test/root-ca.pem")).unwrap(); + let stream = TcpStream::connect(localhost).unwrap(); + Ssl::new(&ctx.build()).unwrap().connect(stream).unwrap_err(); +} + +#[test] +#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] +fn test_alpn_server_select_none() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let localhost = listener.local_addr().unwrap(); + // We create a different context instance for the server... + let listener_ctx = { + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ctx.set_alpn_select_callback(|_, client| { + ssl::select_next_proto(b"\x08http/1.1\x08spdy/3.1", client).ok_or(ssl::AlpnError::NOACK) + }); + assert!( + ctx.set_certificate_file(&Path::new("test/cert.pem"), X509Filetype::PEM) + .is_ok() + ); + ctx.set_private_key_file(&Path::new("test/key.pem"), X509Filetype::PEM) + .unwrap(); + ctx.build() + }; + // Have the listener wait on the connection in a different thread. + thread::spawn(move || { + let (stream, _) = listener.accept().unwrap(); + Ssl::new(&listener_ctx).unwrap().accept(stream).unwrap(); + }); + + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ctx.set_verify(SslVerifyMode::PEER); + ctx.set_alpn_protos(b"\x06http/2").unwrap(); ctx.set_ca_file(&Path::new("test/root-ca.pem")).unwrap(); // Now connect to the socket and make sure the protocol negotiation works... let stream = TcpStream::connect(localhost).unwrap();