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.
This commit is contained in:
Marko Lalic 2015-03-18 15:50:29 +01:00
parent 83c279013b
commit 5689ad9260
1 changed files with 34 additions and 1 deletions

View File

@ -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::ffi::{CStr, CString};
use std::fmt; use std::fmt;
use std::io; use std::io;
@ -6,6 +6,7 @@ use std::io::prelude::*;
use std::ffi::AsOsStr; use std::ffi::AsOsStr;
use std::mem; use std::mem;
use std::net; use std::net;
use std::slice;
use std::num::FromPrimitive; use std::num::FromPrimitive;
use std::num::Int; use std::num::Int;
use std::path::Path; use std::path::Path;
@ -220,6 +221,34 @@ extern fn raw_verify_with_data<T>(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<u8> = 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 /// The signature of functions that can be used to manually verify certificates
pub type VerifyCallback = fn(preverify_ok: bool, pub type VerifyCallback = fn(preverify_ok: bool,
x509_ctx: &X509StoreContext) -> bool; x509_ctx: &X509StoreContext) -> bool;
@ -393,6 +422,10 @@ impl SslContext {
// so that we can refer to it within the callback. // so that we can refer to it within the callback.
ffi::SSL_CTX_set_ex_data(*self.ctx, get_npn_protos_idx(), ffi::SSL_CTX_set_ex_data(*self.ctx, get_npn_protos_idx(),
mem::transmute(protocols)); 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());
} }
} }
} }