From b96bbf6961467fec0a4dd9dfc6321c194fb4e8b8 Mon Sep 17 00:00:00 2001 From: Marko Lalic Date: Wed, 18 Mar 2015 15:45:26 +0100 Subject: [PATCH 1/9] openssl-sys: Add NPN functions and constants --- openssl-sys/Cargo.toml | 1 + openssl-sys/src/lib.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/openssl-sys/Cargo.toml b/openssl-sys/Cargo.toml index cabde957..8dd3d1b2 100644 --- a/openssl-sys/Cargo.toml +++ b/openssl-sys/Cargo.toml @@ -16,6 +16,7 @@ tlsv1_2 = [] tlsv1_1 = [] sslv2 = [] aes_xts = [] +npn = [] [dependencies] libc = "0.1" diff --git a/openssl-sys/src/lib.rs b/openssl-sys/src/lib.rs index 1b404d14..f9ef4b4f 100644 --- a/openssl-sys/src/lib.rs +++ b/openssl-sys/src/lib.rs @@ -134,6 +134,13 @@ pub const SSL_VERIFY_PEER: c_int = 1; pub const TLSEXT_NAMETYPE_host_name: c_long = 0; +#[cfg(feature = "npn")] +pub const OPENSSL_NPN_UNSUPPORTED: c_int = 0; +#[cfg(feature = "npn")] +pub const OPENSSL_NPN_NEGOTIATED: c_int = 1; +#[cfg(feature = "npn")] +pub const OPENSSL_NPN_NO_OVERLAP: c_int = 2; + pub const V_ASN1_GENERALIZEDTIME: c_int = 24; pub const V_ASN1_UTCTIME: c_int = 23; @@ -490,6 +497,28 @@ extern "C" { pub fn SSL_CTX_set_cipher_list(ssl: *mut SSL_CTX, s: *const c_char) -> c_int; pub fn SSL_CTX_ctrl(ssl: *mut SSL_CTX, cmd: c_int, larg: c_long, parg: *mut c_void) -> c_long; + #[cfg(feature = "npn")] + pub fn SSL_CTX_set_next_protos_advertised_cb(ssl: *mut SSL_CTX, + cb: extern "C" fn(ssl: *mut SSL, + out: *mut *const c_uchar, + outlen: *mut c_uint, + arg: *mut c_void) -> c_int, + arg: *mut c_void); + #[cfg(feature = "npn")] + pub fn SSL_CTX_set_next_proto_select_cb(ssl: *mut SSL_CTX, + cb: extern "C" fn(ssl: *mut SSL, + out: *mut *mut c_uchar, + outlen: *mut c_uchar, + inbuf: *const c_uchar, + inlen: c_uint, + arg: *mut c_void) -> c_int, + arg: *mut c_void); + #[cfg(feature = "npn")] + pub fn SSL_select_next_proto(out: *mut *mut c_uchar, outlen: *mut c_uchar, + inbuf: *const c_uchar, inlen: c_uint, + client: *const c_uchar, client_len: c_uint) -> c_int; + #[cfg(feature = "npn")] + pub fn SSL_get0_next_proto_negotiated(s: *const SSL, data: *mut *const c_uchar, len: *mut c_uint); pub fn X509_add_ext(x: *mut X509, ext: *mut X509_EXTENSION, loc: c_int) -> c_int; pub fn X509_digest(x: *mut X509, digest: *const EVP_MD, buf: *mut c_char, len: *mut c_uint) -> c_int; From f09cfdfdd5217929b97d0fd7ac55632373b76801 Mon Sep 17 00:00:00 2001 From: Marko Lalic Date: Wed, 18 Mar 2015 15:08:45 +0100 Subject: [PATCH 2/9] openssl-sys: Add TLS extension constants --- openssl-sys/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openssl-sys/src/lib.rs b/openssl-sys/src/lib.rs index f9ef4b4f..59f11b65 100644 --- a/openssl-sys/src/lib.rs +++ b/openssl-sys/src/lib.rs @@ -134,6 +134,11 @@ pub const SSL_VERIFY_PEER: c_int = 1; pub const TLSEXT_NAMETYPE_host_name: c_long = 0; +pub const SSL_TLSEXT_ERR_OK: c_int = 0; +pub const SSL_TLSEXT_ERR_ALERT_WARNING: c_int = 1; +pub const SSL_TLSEXT_ERR_ALERT_FATAL: c_int = 2; +pub const SSL_TLSEXT_ERR_NOACK: c_int = 3; + #[cfg(feature = "npn")] pub const OPENSSL_NPN_UNSUPPORTED: c_int = 0; #[cfg(feature = "npn")] From 3388a12802765c34eb21e98e15f5e973ae7a50c1 Mon Sep 17 00:00:00 2001 From: Marko Lalic Date: Wed, 18 Mar 2015 15:45:47 +0100 Subject: [PATCH 3/9] openssl: Add NPN crate feature --- .travis.yml | 2 +- openssl/Cargo.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c383cbc1..06ac2c1c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ os: env: global: - secure: J4i75AV4KMrU/UQrLIzzIh35Xix40Ki0uWjm8j05oxlXVl5aPU2zB30AemDne2QXYzkN4kRG/iRnNORE/8D0lF7YipQNSNxgfiBVoOEfj/NSogvI2BftYX9vlLZJUvt+s/nbE3xa/Pyge1IPv7itDYGO7SMe8RTSqitgqyfE2Eg= - - FEATURES="tlsv1_1 tlsv1_2 aes_xts" + - FEATURES="tlsv1_1 tlsv1_2 aes_xts npn" before_script: - openssl s_server -accept 15418 -www -cert openssl/test/cert.pem -key openssl/test/key.pem >/dev/null 2>&1 & script: diff --git a/openssl/Cargo.toml b/openssl/Cargo.toml index 951fa958..8a1f5115 100644 --- a/openssl/Cargo.toml +++ b/openssl/Cargo.toml @@ -14,6 +14,7 @@ tlsv1_2 = ["openssl-sys/tlsv1_2"] tlsv1_1 = ["openssl-sys/tlsv1_1"] sslv2 = ["openssl-sys/sslv2"] aes_xts = ["openssl-sys/aes_xts"] +npn = ["openssl-sys/npn"] [dependencies.openssl-sys] path = "../openssl-sys" From 83c279013bda0495ffd02a45a15c0e529d2c9afd Mon Sep 17 00:00:00 2001 From: Marko Lalic Date: Wed, 18 Mar 2015 15:49:25 +0100 Subject: [PATCH 4/9] openssl: Add method for setting protocols to be used in NPN A new method `set_npn_protocols` is added to the `SslContext` struct, when the `npn` feature is enabled. The method takes a list of protocols that are supported by the peer. These protocols will be used during Next Protocol Negotiation. The method saves the given list within the extra data of the OpenSSL Context structure, so that the list can be referred to later on by the callbacks invoked during TLS connection establishment. --- openssl/src/ssl/mod.rs | 53 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/openssl/src/ssl/mod.rs b/openssl/src/ssl/mod.rs index d19f6678..058231ca 100644 --- a/openssl/src/ssl/mod.rs +++ b/openssl/src/ssl/mod.rs @@ -145,6 +145,34 @@ fn get_verify_data_idx() -> c_int { } } +/// Creates a static index for the list of NPN protocols. +/// Registers a destructor for the data which will be called +/// when the context is freed. +#[cfg(feature = "npn")] +fn get_npn_protos_idx() -> c_int { + static mut NPN_PROTOS_IDX: c_int = -1; + static mut INIT: Once = ONCE_INIT; + + extern 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> = unsafe { mem::transmute(ptr) }; + } + } + + unsafe { + INIT.call_once(|| { + let f: ffi::CRYPTO_EX_free = free_data_box; + let idx = ffi::SSL_CTX_get_ex_new_index(0, ptr::null(), None, + None, Some(f)); + assert!(idx >= 0); + NPN_PROTOS_IDX = idx; + }); + NPN_PROTOS_IDX + } +} + extern fn raw_verify(preverify_ok: c_int, x509_ctx: *mut ffi::X509_STORE_CTX) -> c_int { unsafe { @@ -342,6 +370,31 @@ impl SslContext { }; SslContextOptions::from_bits(ret).unwrap() } + + /// Set the protocols to be used during Next Protocol Negotiation (the protocols + /// supported by the application). + /// + /// This method needs the `npn` feature. + #[cfg(feature = "npn")] + pub fn set_npn_protocols(&mut self, protocols: &[&[u8]]) { + // Firstly, convert the list of protocols to a byte-array that can be passed to OpenSSL + // APIs -- a list of length-prefixed strings. + let mut npn_protocols = Vec::new(); + for protocol in protocols { + let len = protocol.len() as u8; + npn_protocols.push(len); + // If the length is greater than the max `u8`, this truncates the protocol name. + npn_protocols.extend(protocol[..len as usize].to_vec()); + } + let protocols: Box> = Box::new(npn_protocols); + + unsafe { + // Attach the protocol list to the OpenSSL context structure, + // so that we can refer to it within the callback. + ffi::SSL_CTX_set_ex_data(*self.ctx, get_npn_protos_idx(), + mem::transmute(protocols)); + } + } } #[allow(dead_code)] From 5689ad9260c8d90a738bf9d830186a1b31c05a7f Mon Sep 17 00:00:00 2001 From: Marko Lalic Date: Wed, 18 Mar 2015 15:50:29 +0100 Subject: [PATCH 5/9] openssl: Implement client-side NPN protocol selection After the `set_npn_protocols` method of the `SslContext` struct is called, any future TLS connections established with this context will perform NPN negotiation. The chosen protocol is the one with the highest priority in the server's protocol list that is also in the client's protocol list. (This is the default behavior provided by OpenSSL's `SSL_select_next_proto` function.) If there is no overlap between the two lists, no error is raised. --- openssl/src/ssl/mod.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/openssl/src/ssl/mod.rs b/openssl/src/ssl/mod.rs index 058231ca..e06d613b 100644 --- a/openssl/src/ssl/mod.rs +++ b/openssl/src/ssl/mod.rs @@ -1,4 +1,4 @@ -use libc::{c_int, c_void, c_long}; +use libc::{c_int, c_void, c_long, c_uint, c_uchar}; use std::ffi::{CStr, CString}; use std::fmt; use std::io; @@ -6,6 +6,7 @@ use std::io::prelude::*; use std::ffi::AsOsStr; use std::mem; use std::net; +use std::slice; use std::num::FromPrimitive; use std::num::Int; use std::path::Path; @@ -220,6 +221,34 @@ extern fn raw_verify_with_data(preverify_ok: c_int, } } +/// 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. +#[cfg(feature = "npn")] +extern 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 { + // 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, get_npn_protos_idx()); + let protocols: &Vec = mem::transmute(protocols); + // 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. + ffi::SSL_select_next_proto(out, outlen, inbuf, inlen, client, client_len); + } + + ffi::SSL_TLSEXT_ERR_OK +} + /// The signature of functions that can be used to manually verify certificates pub type VerifyCallback = fn(preverify_ok: bool, x509_ctx: &X509StoreContext) -> bool; @@ -393,6 +422,10 @@ impl SslContext { // so that we can refer to it within the callback. ffi::SSL_CTX_set_ex_data(*self.ctx, get_npn_protos_idx(), mem::transmute(protocols)); + // 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.ctx, raw_next_proto_select_cb, ptr::null_mut()); } } } From 8931299eab92e18f065d8e0ee3f3d8fcf9e905b0 Mon Sep 17 00:00:00 2001 From: Marko Lalic Date: Wed, 18 Mar 2015 15:41:03 +0100 Subject: [PATCH 6/9] openssl: Add methods to get the protocol selected by NPN The method is added to the `Ssl` struct, since this is how the native OpenSSL API works. It is also added to the `SslStream` convenience struct, since the `Ssl` instance that it wraps is not public and clients may want to check which protocol is in use on a particular SSL stream. --- openssl/src/ssl/mod.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/openssl/src/ssl/mod.rs b/openssl/src/ssl/mod.rs index e06d613b..fb5c2440 100644 --- a/openssl/src/ssl/mod.rs +++ b/openssl/src/ssl/mod.rs @@ -556,6 +556,28 @@ impl Ssl { } } + /// 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 method needs the `npn` feature. + #[cfg(feature = "npn")] + pub fn get_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.ssl, &mut data, &mut len); + + if data.is_null() { + None + } else { + Some(slice::from_raw_parts(data, len as usize)) + } + } + } } #[derive(FromPrimitive, Debug)] @@ -709,6 +731,17 @@ impl SslStream { Some(s) } + + /// 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 method needs the `npn` feature. + #[cfg(feature = "npn")] + pub fn get_selected_npn_protocol(&self) -> Option<&[u8]> { + self.ssl.get_selected_npn_protocol() + } } impl Read for SslStream { From be674a28e0791ee652e1397418290d19b263cf42 Mon Sep 17 00:00:00 2001 From: Marko Lalic Date: Wed, 18 Mar 2015 20:52:17 +0100 Subject: [PATCH 7/9] openssl: Advertise NPN protocols for server sockets If a server socket is created with a context on which the `set_npn_protocols` method has been called, during TLS connection establishment, the server will advertise the list of protocols given to the method, in case the client indicates that it supports the NPN TLS extension. --- openssl/src/ssl/mod.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/openssl/src/ssl/mod.rs b/openssl/src/ssl/mod.rs index fb5c2440..3aa01d41 100644 --- a/openssl/src/ssl/mod.rs +++ b/openssl/src/ssl/mod.rs @@ -249,6 +249,36 @@ extern fn raw_next_proto_select_cb(ssl: *mut ffi::SSL, ffi::SSL_TLSEXT_ERR_OK } +/// 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. +#[cfg(feature = "npn")] +extern 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, get_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 = mem::transmute(protocols); + *out = protocols.as_ptr(); + *outlen = protocols.len() as c_uint; + } + } + + ffi::SSL_TLSEXT_ERR_OK +} + /// The signature of functions that can be used to manually verify certificates pub type VerifyCallback = fn(preverify_ok: bool, x509_ctx: &X509StoreContext) -> bool; @@ -426,6 +456,9 @@ impl SslContext { // matching based on the client-supported list of protocols that // has been saved. ffi::SSL_CTX_set_next_proto_select_cb(*self.ctx, 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.ctx, raw_next_protos_advertise_cb, ptr::null_mut()); } } } From 8f05e0452a035200cdc10c03d8d4b1019c0c1907 Mon Sep 17 00:00:00 2001 From: Marko Lalic Date: Wed, 18 Mar 2015 20:53:59 +0100 Subject: [PATCH 8/9] openssl: Add tests for client-side NPN An additional `openssl` process is spun up before the tests are ran. This process has NPN enabled with some default protocols. --- .travis.yml | 1 + openssl/src/ssl/tests.rs | 76 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 06ac2c1c..3292dcdb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ env: - FEATURES="tlsv1_1 tlsv1_2 aes_xts npn" before_script: - openssl s_server -accept 15418 -www -cert openssl/test/cert.pem -key openssl/test/key.pem >/dev/null 2>&1 & +- openssl s_server -accept 15419 -www -cert openssl/test/cert.pem -key openssl/test/key.pem -nextprotoneg "http/1.1,spdy/3.1" >/dev/null 2>&1 & script: - (cd openssl && cargo test) - (test $TRAVIS_OS_NAME == "osx" || (cd openssl && cargo test --features "$FEATURES")) diff --git a/openssl/src/ssl/tests.rs b/openssl/src/ssl/tests.rs index 5196b870..33ae619f 100644 --- a/openssl/src/ssl/tests.rs +++ b/openssl/src/ssl/tests.rs @@ -1,15 +1,16 @@ use serialize::hex::FromHex; -use std::net::TcpStream; +use std::net::{TcpStream, TcpListener}; use std::io; use std::io::prelude::*; use std::path::Path; +use std::thread; use crypto::hash::Type::{SHA256}; use ssl; use ssl::SslMethod::Sslv23; use ssl::{SslContext, SslStream, VerifyCallback}; use ssl::SslVerifyMode::SslVerifyPeer; -use x509::{X509StoreContext}; +use x509::{X509StoreContext, X509FileType}; #[test] fn test_new_ctx() { @@ -220,3 +221,74 @@ fn test_read() { println!("written"); io::copy(&mut stream, &mut io::sink()).ok().expect("read error"); } + +/// Tests that connecting with the client using NPN, but the server not does not +/// break the existing connection behavior. +#[test] +#[cfg(feature = "npn")] +fn test_connect_with_unilateral_npn() { + let stream = TcpStream::connect("127.0.0.1:15418").unwrap(); + let mut ctx = SslContext::new(Sslv23).unwrap(); + ctx.set_verify(SslVerifyPeer, None); + ctx.set_npn_protocols(&[b"http/1.1", b"spdy/3.1"]); + match ctx.set_CA_file(&Path::new("test/cert.pem")) { + None => {} + Some(err) => panic!("Unexpected error {:?}", err) + } + let stream = match SslStream::new(&ctx, 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.get_selected_npn_protocol().is_none()); +} + +/// 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(feature = "npn")] +fn test_connect_with_npn_successful_multiple_matching() { + // A different port than the other tests: an `openssl` process that has + // NPN enabled. + let stream = TcpStream::connect("127.0.0.1:15419").unwrap(); + let mut ctx = SslContext::new(Sslv23).unwrap(); + ctx.set_verify(SslVerifyPeer, None); + ctx.set_npn_protocols(&[b"spdy/3.1", b"http/1.1"]); + match ctx.set_CA_file(&Path::new("test/cert.pem")) { + None => {} + Some(err) => panic!("Unexpected error {:?}", err) + } + let stream = match SslStream::new(&ctx, 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.get_selected_npn_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(feature = "npn")] +fn test_connect_with_npn_successful_single_match() { + // A different port than the other tests: an `openssl` process that has + // NPN enabled. + let stream = TcpStream::connect("127.0.0.1:15419").unwrap(); + let mut ctx = SslContext::new(Sslv23).unwrap(); + ctx.set_verify(SslVerifyPeer, None); + ctx.set_npn_protocols(&[b"spdy/3.1"]); + match ctx.set_CA_file(&Path::new("test/cert.pem")) { + None => {} + Some(err) => panic!("Unexpected error {:?}", err) + } + let stream = match SslStream::new(&ctx, 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.get_selected_npn_protocol().unwrap()); +} From f50577909ebea982313657594d6a58578a305e11 Mon Sep 17 00:00:00 2001 From: Marko Lalic Date: Wed, 18 Mar 2015 20:55:07 +0100 Subject: [PATCH 9/9] openssl: Add tests for server-side NPN --- openssl/src/ssl/tests.rs | 41 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/openssl/src/ssl/tests.rs b/openssl/src/ssl/tests.rs index 33ae619f..468b1053 100644 --- a/openssl/src/ssl/tests.rs +++ b/openssl/src/ssl/tests.rs @@ -292,3 +292,44 @@ fn test_connect_with_npn_successful_single_match() { // is used. assert_eq!(b"spdy/3.1", stream.get_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(feature = "npn")] +fn test_npn_server_advertise_multiple() { + let localhost = "127.0.0.1:15420"; + let listener = TcpListener::bind(localhost).unwrap(); + // We create a different context instance for the server... + let listener_ctx = { + let mut ctx = SslContext::new(Sslv23).unwrap(); + ctx.set_verify(SslVerifyPeer, None); + ctx.set_npn_protocols(&[b"http/1.1", b"spdy/3.1"]); + ctx.set_certificate_file( + &Path::new("test/cert.pem"), X509FileType::PEM); + ctx.set_private_key_file( + &Path::new("test/key.pem"), X509FileType::PEM); + ctx + }; + // Have the listener wait on the connection in a different thread. + thread::spawn(move || { + let (stream, _) = listener.accept().unwrap(); + let _ = SslStream::new_server(&listener_ctx, stream).unwrap(); + }); + + let mut ctx = SslContext::new(Sslv23).unwrap(); + ctx.set_verify(SslVerifyPeer, None); + ctx.set_npn_protocols(&[b"spdy/3.1"]); + match ctx.set_CA_file(&Path::new("test/cert.pem")) { + None => {} + Some(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 SslStream::new(&ctx, 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.get_selected_npn_protocol().unwrap()); +}