Implement client and server connectors
This commit is contained in:
parent
1a288da86c
commit
4c7a5a418e
|
|
@ -0,0 +1,329 @@
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use dh::Dh;
|
||||||
|
use error::ErrorStack;
|
||||||
|
use ssl::{self, SslMethod, SslContextBuilder, SslContext, Ssl, SSL_VERIFY_PEER, SslStream,
|
||||||
|
HandshakeError};
|
||||||
|
use pkey::PKey;
|
||||||
|
use x509::X509Ref;
|
||||||
|
|
||||||
|
// apps/dh2048.pem
|
||||||
|
const DHPARAM_PEM: &'static str = r#"
|
||||||
|
-----BEGIN DH PARAMETERS-----
|
||||||
|
MIIBCAKCAQEA///////////JD9qiIWjCNMTGYouA3BzRKQJOCIpnzHQCC76mOxOb
|
||||||
|
IlFKCHmONATd75UZs806QxswKwpt8l8UN0/hNW1tUcJF5IW1dmJefsb0TELppjft
|
||||||
|
awv/XLb0Brft7jhr+1qJn6WunyQRfEsf5kkoZlHs5Fs9wgB8uKFjvwWY2kg2HFXT
|
||||||
|
mmkWP6j9JM9fg2VdI9yjrZYcYvNWIIVSu57VKQdwlpZtZww1Tkq8mATxdGwIyhgh
|
||||||
|
fDKQXkYuNs474553LBgOhgObJ4Oi7Aeij7XFXfBvTFLJ3ivL9pVYFxg5lUl86pVq
|
||||||
|
5RXSJhiY+gUQFXKOWoqsqmj//////////wIBAg==
|
||||||
|
-----END DH PARAMETERS-----
|
||||||
|
|
||||||
|
These are the 2048-bit DH parameters from "More Modular Exponential
|
||||||
|
(MODP) Diffie-Hellman groups for Internet Key Exchange (IKE)":
|
||||||
|
https://tools.ietf.org/html/rfc3526
|
||||||
|
|
||||||
|
See https://tools.ietf.org/html/rfc2412 for how they were generated."#;
|
||||||
|
|
||||||
|
fn ctx(method: SslMethod) -> Result<SslContextBuilder, ErrorStack> {
|
||||||
|
let mut ctx = try!(SslContextBuilder::new(method));
|
||||||
|
|
||||||
|
// options to enable and cipher list lifted from libcurl
|
||||||
|
let mut opts = ssl::SSL_OP_ALL;
|
||||||
|
opts |= ssl::SSL_OP_NO_TICKET;
|
||||||
|
opts |= ssl::SSL_OP_NO_COMPRESSION;
|
||||||
|
opts &= !ssl::SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG;
|
||||||
|
opts &= !ssl::SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
|
||||||
|
opts |= ssl::SSL_OP_NO_SSLV2;
|
||||||
|
opts |= ssl::SSL_OP_NO_SSLV3;
|
||||||
|
ctx.set_options(opts);
|
||||||
|
|
||||||
|
Ok(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ClientConnectorBuilder(SslContextBuilder);
|
||||||
|
|
||||||
|
impl ClientConnectorBuilder {
|
||||||
|
pub fn tls() -> Result<ClientConnectorBuilder, ErrorStack> {
|
||||||
|
ClientConnectorBuilder::new(SslMethod::tls())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(method: SslMethod) -> Result<ClientConnectorBuilder, ErrorStack> {
|
||||||
|
let mut ctx = try!(ctx(method));
|
||||||
|
try!(ctx.set_default_verify_paths());
|
||||||
|
try!(ctx.set_cipher_list("ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH"));
|
||||||
|
|
||||||
|
Ok(ClientConnectorBuilder(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn context(&self) -> &SslContextBuilder {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn context_mut(&mut self) -> &mut SslContextBuilder {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> ClientConnector {
|
||||||
|
ClientConnector(self.0.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ClientConnector(SslContext);
|
||||||
|
|
||||||
|
impl ClientConnector {
|
||||||
|
pub fn connect<S>(&self, domain: &str, stream: S) -> Result<SslStream<S>, HandshakeError<S>>
|
||||||
|
where S: Read + Write
|
||||||
|
{
|
||||||
|
let mut ssl = try!(Ssl::new(&self.0));
|
||||||
|
try!(ssl.set_hostname(domain));
|
||||||
|
try!(setup_verify(&mut ssl, domain));
|
||||||
|
|
||||||
|
ssl.connect(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ServerConnectorBuilder(SslContextBuilder);
|
||||||
|
|
||||||
|
impl ServerConnectorBuilder {
|
||||||
|
pub fn tls<I, T>(private_key: &PKey,
|
||||||
|
certificate: &X509Ref,
|
||||||
|
chain: I)
|
||||||
|
-> Result<ServerConnectorBuilder, ErrorStack>
|
||||||
|
where I: IntoIterator<Item = T>,
|
||||||
|
T: AsRef<X509Ref>
|
||||||
|
{
|
||||||
|
ServerConnectorBuilder::new(SslMethod::tls(), private_key, certificate, chain)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new<I, T>(method: SslMethod,
|
||||||
|
private_key: &PKey,
|
||||||
|
certificate: &X509Ref,
|
||||||
|
chain: I)
|
||||||
|
-> Result<ServerConnectorBuilder, ErrorStack>
|
||||||
|
where I: IntoIterator<Item = T>,
|
||||||
|
T: AsRef<X509Ref>
|
||||||
|
{
|
||||||
|
let mut ctx = try!(ctx(method));
|
||||||
|
ctx.set_options(ssl::SSL_OP_SINGLE_DH_USE | ssl::SSL_OP_CIPHER_SERVER_PREFERENCE);
|
||||||
|
let dh = try!(Dh::from_pem(DHPARAM_PEM.as_bytes()));
|
||||||
|
try!(ctx.set_tmp_dh(&dh));
|
||||||
|
try!(ctx.set_cipher_list(
|
||||||
|
"ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:\
|
||||||
|
ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:\
|
||||||
|
ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:\
|
||||||
|
DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:\
|
||||||
|
ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:\
|
||||||
|
ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:\
|
||||||
|
ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:\
|
||||||
|
DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:\
|
||||||
|
EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:\
|
||||||
|
AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"));
|
||||||
|
try!(ctx.set_private_key(private_key));
|
||||||
|
try!(ctx.set_certificate(certificate));
|
||||||
|
try!(ctx.check_private_key());
|
||||||
|
for cert in chain {
|
||||||
|
try!(ctx.add_extra_chain_cert(cert.as_ref().to_owned()));
|
||||||
|
}
|
||||||
|
Ok(ServerConnectorBuilder(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn context(&self) -> &SslContextBuilder {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn context_mut(&mut self) -> &mut SslContextBuilder {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> ServerConnector {
|
||||||
|
ServerConnector(self.0.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ServerConnector(SslContext);
|
||||||
|
|
||||||
|
impl ServerConnector {
|
||||||
|
pub fn connect<S>(&self, stream: S) -> Result<SslStream<S>, HandshakeError<S>>
|
||||||
|
where S: Read + Write
|
||||||
|
{
|
||||||
|
let ssl = try!(Ssl::new(&self.0));
|
||||||
|
ssl.accept(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(ossl102, ossl110))]
|
||||||
|
fn setup_verify(ssl: &mut Ssl, domain: &str) -> Result<(), ErrorStack> {
|
||||||
|
ssl.set_verify(SSL_VERIFY_PEER);
|
||||||
|
let param = ssl._param_mut();
|
||||||
|
param.set_hostflags(::verify::X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
|
||||||
|
param.set_host(domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(ossl102, ossl110)))]
|
||||||
|
fn setup_verify(ssl: &mut Ssl, domain: &str) -> Result<(), ErrorStack> {
|
||||||
|
let domain = domain.to_owned();
|
||||||
|
ssl.set_verify_callback(SSL_VERIFY_PEER, move |p, x| verify::verify_callback(&domain, p, x));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(ossl102, ossl110)))]
|
||||||
|
mod verify {
|
||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
use nid;
|
||||||
|
use x509::{X509StoreContextRef, X509Ref, GeneralNames, X509NameRef};
|
||||||
|
|
||||||
|
pub fn verify_callback(domain: &str,
|
||||||
|
preverify_ok: bool,
|
||||||
|
x509_ctx: &X509StoreContextRef)
|
||||||
|
-> bool {
|
||||||
|
if !preverify_ok || x509_ctx.error_depth() != 0 {
|
||||||
|
return preverify_ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
match x509_ctx.current_cert() {
|
||||||
|
Some(x509) => verify_hostname(domain, &x509),
|
||||||
|
None => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_hostname(domain: &str, cert: &X509Ref) -> bool {
|
||||||
|
match cert.subject_alt_names() {
|
||||||
|
Some(names) => verify_subject_alt_names(domain, &names),
|
||||||
|
None => verify_subject_name(domain, &cert.subject_name()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_subject_alt_names(domain: &str, names: &GeneralNames) -> bool {
|
||||||
|
let ip = domain.parse();
|
||||||
|
|
||||||
|
for name in names {
|
||||||
|
match ip {
|
||||||
|
Ok(ip) => {
|
||||||
|
if let Some(actual) = name.ipaddress() {
|
||||||
|
if matches_ip(&ip, actual) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
if let Some(pattern) = name.dnsname() {
|
||||||
|
if matches_dns(pattern, domain, false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_subject_name(domain: &str, subject_name: &X509NameRef) -> bool {
|
||||||
|
if let Some(pattern) = subject_name.text_by_nid(nid::COMMONNAME) {
|
||||||
|
// Unlike with SANs, IP addresses in the subject name don't have a
|
||||||
|
// different encoding. We need to pass this down to matches_dns to
|
||||||
|
// disallow wildcard matches with bogus patterns like *.0.0.1
|
||||||
|
let is_ip = domain.parse::<IpAddr>().is_ok();
|
||||||
|
|
||||||
|
if matches_dns(&pattern, domain, is_ip) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matches_dns(mut pattern: &str, mut hostname: &str, is_ip: bool) -> bool {
|
||||||
|
// first strip trailing . off of pattern and hostname to normalize
|
||||||
|
if pattern.ends_with('.') {
|
||||||
|
pattern = &pattern[..pattern.len() - 1];
|
||||||
|
}
|
||||||
|
if hostname.ends_with('.') {
|
||||||
|
hostname = &hostname[..hostname.len() - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
matches_wildcard(pattern, hostname, is_ip).unwrap_or_else(|| pattern == hostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matches_wildcard(pattern: &str, hostname: &str, is_ip: bool) -> Option<bool> {
|
||||||
|
// IP addresses and internationalized domains can't involved in wildcards
|
||||||
|
if is_ip || pattern.starts_with("xn--") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let wildcard_location = match pattern.find('*') {
|
||||||
|
Some(l) => l,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut dot_idxs = pattern.match_indices('.').map(|(l, _)| l);
|
||||||
|
let wildcard_end = match dot_idxs.next() {
|
||||||
|
Some(l) => l,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Never match wildcards if the pattern has less than 2 '.'s (no *.com)
|
||||||
|
//
|
||||||
|
// This is a bit dubious, as it doesn't disallow other TLDs like *.co.uk.
|
||||||
|
// Chrome has a black- and white-list for this, but Firefox (via NSS) does
|
||||||
|
// the same thing we do here.
|
||||||
|
//
|
||||||
|
// The Public Suffix (https://www.publicsuffix.org/) list could
|
||||||
|
// potentically be used here, but it's both huge and updated frequently
|
||||||
|
// enough that management would be a PITA.
|
||||||
|
if dot_idxs.next().is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wildcards can only be in the first component
|
||||||
|
if wildcard_location > wildcard_end {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hostname_label_end = match hostname.find('.') {
|
||||||
|
Some(l) => l,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// check that the non-wildcard parts are identical
|
||||||
|
if pattern[wildcard_end..] != hostname[hostname_label_end..] {
|
||||||
|
return Some(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let wildcard_prefix = &pattern[..wildcard_location];
|
||||||
|
let wildcard_suffix = &pattern[wildcard_location + 1..wildcard_end];
|
||||||
|
|
||||||
|
let hostname_label = &hostname[..hostname_label_end];
|
||||||
|
|
||||||
|
// check the prefix of the first label
|
||||||
|
if !hostname_label.starts_with(wildcard_prefix) {
|
||||||
|
return Some(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// and the suffix
|
||||||
|
if !hostname_label[wildcard_prefix.len()..].ends_with(wildcard_suffix) {
|
||||||
|
return Some(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matches_ip(expected: &IpAddr, actual: &[u8]) -> bool {
|
||||||
|
match (expected, actual.len()) {
|
||||||
|
(&IpAddr::V4(ref addr), 4) => actual == addr.octets(),
|
||||||
|
(&IpAddr::V6(ref addr), 16) => {
|
||||||
|
let segments = [((actual[0] as u16) << 8) | actual[1] as u16,
|
||||||
|
((actual[2] as u16) << 8) | actual[3] as u16,
|
||||||
|
((actual[4] as u16) << 8) | actual[5] as u16,
|
||||||
|
((actual[6] as u16) << 8) | actual[7] as u16,
|
||||||
|
((actual[8] as u16) << 8) | actual[9] as u16,
|
||||||
|
((actual[10] as u16) << 8) | actual[11] as u16,
|
||||||
|
((actual[12] as u16) << 8) | actual[13] as u16,
|
||||||
|
((actual[14] as u16) << 8) | actual[15] as u16];
|
||||||
|
segments == addr.segments()
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,19 +22,22 @@ use ffi;
|
||||||
use {init, cvt, cvt_p};
|
use {init, cvt, cvt_p};
|
||||||
use dh::Dh;
|
use dh::Dh;
|
||||||
use x509::{X509StoreContextRef, X509FileType, X509, X509Ref, X509VerifyError};
|
use x509::{X509StoreContextRef, X509FileType, X509, X509Ref, X509VerifyError};
|
||||||
#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))]
|
#[cfg(any(ossl102, ossl110))]
|
||||||
use x509::verify::X509VerifyParamRef;
|
use verify::X509VerifyParamRef;
|
||||||
use pkey::PKey;
|
use pkey::PKey;
|
||||||
use error::ErrorStack;
|
use error::ErrorStack;
|
||||||
use opaque::Opaque;
|
use opaque::Opaque;
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
mod connector;
|
||||||
mod bio;
|
mod bio;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
use self::bio::BioMethod;
|
use self::bio::BioMethod;
|
||||||
|
|
||||||
|
pub use ssl::connector::{ClientConnectorBuilder, ClientConnector, ServerConnectorBuilder,
|
||||||
|
ServerConnector};
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use ssl::error::Error;
|
pub use ssl::error::Error;
|
||||||
|
|
||||||
|
|
@ -1030,6 +1033,11 @@ impl SslRef {
|
||||||
/// Requires the `v102` or `v110` features and OpenSSL 1.0.2 or 1.1.0.
|
/// Requires the `v102` or `v110` features and OpenSSL 1.0.2 or 1.1.0.
|
||||||
#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))]
|
#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))]
|
||||||
pub fn param_mut(&mut self) -> &mut X509VerifyParamRef {
|
pub fn param_mut(&mut self) -> &mut X509VerifyParamRef {
|
||||||
|
self._param_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(ossl102, ossl110))]
|
||||||
|
fn _param_mut(&mut self) -> &mut X509VerifyParamRef {
|
||||||
unsafe {
|
unsafe {
|
||||||
X509VerifyParamRef::from_ptr_mut(ffi::SSL_get0_param(self.as_ptr()))
|
X509VerifyParamRef::from_ptr_mut(ffi::SSL_get0_param(self.as_ptr()))
|
||||||
}
|
}
|
||||||
|
|
@ -1153,6 +1161,8 @@ impl Ssl {
|
||||||
/// An error or intermediate state after a TLS handshake attempt.
|
/// An error or intermediate state after a TLS handshake attempt.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum HandshakeError<S> {
|
pub enum HandshakeError<S> {
|
||||||
|
/// Setup failed.
|
||||||
|
SetupFailure(ErrorStack),
|
||||||
/// The handshake failed.
|
/// The handshake failed.
|
||||||
Failure(MidHandshakeSslStream<S>),
|
Failure(MidHandshakeSslStream<S>),
|
||||||
/// The handshake was interrupted midway through.
|
/// The handshake was interrupted midway through.
|
||||||
|
|
@ -1162,6 +1172,7 @@ pub enum HandshakeError<S> {
|
||||||
impl<S: Any + fmt::Debug> stderror::Error for HandshakeError<S> {
|
impl<S: Any + fmt::Debug> stderror::Error for HandshakeError<S> {
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
match *self {
|
match *self {
|
||||||
|
HandshakeError::SetupFailure(_) => "stream setup failed",
|
||||||
HandshakeError::Failure(_) => "the handshake failed",
|
HandshakeError::Failure(_) => "the handshake failed",
|
||||||
HandshakeError::Interrupted(_) => "the handshake was interrupted",
|
HandshakeError::Interrupted(_) => "the handshake was interrupted",
|
||||||
}
|
}
|
||||||
|
|
@ -1169,6 +1180,7 @@ impl<S: Any + fmt::Debug> stderror::Error for HandshakeError<S> {
|
||||||
|
|
||||||
fn cause(&self) -> Option<&stderror::Error> {
|
fn cause(&self) -> Option<&stderror::Error> {
|
||||||
match *self {
|
match *self {
|
||||||
|
HandshakeError::SetupFailure(ref e) => Some(e),
|
||||||
HandshakeError::Failure(ref s) | HandshakeError::Interrupted(ref s) => Some(s.error()),
|
HandshakeError::Failure(ref s) | HandshakeError::Interrupted(ref s) => Some(s.error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1178,6 +1190,7 @@ impl<S: Any + fmt::Debug> fmt::Display for HandshakeError<S> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
try!(f.write_str(stderror::Error::description(self)));
|
try!(f.write_str(stderror::Error::description(self)));
|
||||||
match *self {
|
match *self {
|
||||||
|
HandshakeError::SetupFailure(ref e) => try!(write!(f, ": {}", e)),
|
||||||
HandshakeError::Failure(ref s) | HandshakeError::Interrupted(ref s) => {
|
HandshakeError::Failure(ref s) | HandshakeError::Interrupted(ref s) => {
|
||||||
try!(write!(f, ": {}", s.error()));
|
try!(write!(f, ": {}", s.error()));
|
||||||
if let Some(err) = s.ssl().verify_result() {
|
if let Some(err) = s.ssl().verify_result() {
|
||||||
|
|
@ -1189,6 +1202,12 @@ impl<S: Any + fmt::Debug> fmt::Display for HandshakeError<S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<S> From<ErrorStack> for HandshakeError<S> {
|
||||||
|
fn from(e: ErrorStack) -> HandshakeError<S> {
|
||||||
|
HandshakeError::SetupFailure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An SSL stream midway through the handshake process.
|
/// An SSL stream midway through the handshake process.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MidHandshakeSslStream<S> {
|
pub struct MidHandshakeSslStream<S> {
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ use hash::MessageDigest;
|
||||||
use ssl;
|
use ssl;
|
||||||
use ssl::SSL_VERIFY_PEER;
|
use ssl::SSL_VERIFY_PEER;
|
||||||
use ssl::{SslMethod, HandshakeError};
|
use ssl::{SslMethod, HandshakeError};
|
||||||
use ssl::error::Error;
|
use ssl::{SslContext, SslStream, Ssl, ShutdownResult, ClientConnectorBuilder,
|
||||||
use ssl::{SslContext, SslStream, Ssl, ShutdownResult};
|
ServerConnectorBuilder, Error};
|
||||||
use x509::X509StoreContextRef;
|
use x509::X509StoreContextRef;
|
||||||
use x509::X509FileType;
|
use x509::X509FileType;
|
||||||
use x509::X509;
|
use x509::X509;
|
||||||
|
|
@ -31,6 +31,9 @@ use std::net::UdpSocket;
|
||||||
|
|
||||||
mod select;
|
mod select;
|
||||||
|
|
||||||
|
static CERT: &'static [u8] = include_bytes!("../../../test/cert.pem");
|
||||||
|
static KEY: &'static [u8] = include_bytes!("../../../test/key.pem");
|
||||||
|
|
||||||
fn next_addr() -> SocketAddr {
|
fn next_addr() -> SocketAddr {
|
||||||
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
|
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
|
||||||
static PORT: AtomicUsize = ATOMIC_USIZE_INIT;
|
static PORT: AtomicUsize = ATOMIC_USIZE_INIT;
|
||||||
|
|
@ -46,10 +49,6 @@ struct Server {
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
fn spawn(args: &[&str], input: Option<Box<FnMut(ChildStdin) + Send>>) -> (Server, SocketAddr) {
|
fn spawn(args: &[&str], input: Option<Box<FnMut(ChildStdin) + Send>>) -> (Server, SocketAddr) {
|
||||||
static CERT: &'static [u8] = include_bytes!("../../../test/cert.pem");
|
|
||||||
static KEY: &'static [u8] = include_bytes!("../../../test/key.pem");
|
|
||||||
|
|
||||||
|
|
||||||
let td = TempDir::new("openssl").unwrap();
|
let td = TempDir::new("openssl").unwrap();
|
||||||
let cert = td.path().join("cert.pem");
|
let cert = td.path().join("cert.pem");
|
||||||
let key = td.path().join("key.pem");
|
let key = td.path().join("key.pem");
|
||||||
|
|
@ -1047,7 +1046,7 @@ fn add_extra_chain_cert() {
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(windows, ignore)] // don't have a trusted CA list easily available :(
|
#[cfg_attr(windows, ignore)] // don't have a trusted CA list easily available :(
|
||||||
#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))]
|
#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))]
|
||||||
fn valid_hostname() {
|
fn verify_valid_hostname() {
|
||||||
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
|
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
|
||||||
ctx.set_default_verify_paths().unwrap();
|
ctx.set_default_verify_paths().unwrap();
|
||||||
ctx.set_verify(SSL_VERIFY_PEER);
|
ctx.set_verify(SSL_VERIFY_PEER);
|
||||||
|
|
@ -1071,7 +1070,7 @@ fn valid_hostname() {
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(windows, ignore)] // don't have a trusted CA list easily available :(
|
#[cfg_attr(windows, ignore)] // don't have a trusted CA list easily available :(
|
||||||
#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))]
|
#[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))]
|
||||||
fn invalid_hostname() {
|
fn verify_invalid_hostname() {
|
||||||
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
|
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
|
||||||
ctx.set_default_verify_paths().unwrap();
|
ctx.set_default_verify_paths().unwrap();
|
||||||
ctx.set_verify(SSL_VERIFY_PEER);
|
ctx.set_verify(SSL_VERIFY_PEER);
|
||||||
|
|
@ -1084,6 +1083,61 @@ fn invalid_hostname() {
|
||||||
assert!(ssl.connect(s).is_err());
|
assert!(ssl.connect(s).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg_attr(windows, ignore)] // don't have a trusted CA list easily available :(
|
||||||
|
fn connector_valid_hostname() {
|
||||||
|
let connector = ClientConnectorBuilder::tls().unwrap().build();
|
||||||
|
|
||||||
|
let s = TcpStream::connect("google.com:443").unwrap();
|
||||||
|
let mut socket = connector.connect("google.com", s).unwrap();
|
||||||
|
|
||||||
|
socket.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap();
|
||||||
|
let mut result = vec![];
|
||||||
|
socket.read_to_end(&mut result).unwrap();
|
||||||
|
|
||||||
|
println!("{}", String::from_utf8_lossy(&result));
|
||||||
|
assert!(result.starts_with(b"HTTP/1.0"));
|
||||||
|
assert!(result.ends_with(b"</HTML>\r\n") || result.ends_with(b"</html>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg_attr(windows, ignore)] // don't have a trusted CA list easily available :(
|
||||||
|
fn connector_invalid_hostname() {
|
||||||
|
let connector = ClientConnectorBuilder::tls().unwrap().build();
|
||||||
|
|
||||||
|
let s = TcpStream::connect("google.com:443").unwrap();
|
||||||
|
assert!(connector.connect("foobar.com", s).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn connector_client_server() {
|
||||||
|
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||||
|
let port = listener.local_addr().unwrap().port();
|
||||||
|
|
||||||
|
let t = thread::spawn(move || {
|
||||||
|
let key = PKey::private_key_from_pem(KEY).unwrap();
|
||||||
|
let cert = X509::from_pem(CERT).unwrap();
|
||||||
|
let connector = ServerConnectorBuilder::tls(&key, &cert, None::<X509>).unwrap().build();
|
||||||
|
let stream = listener.accept().unwrap().0;
|
||||||
|
let mut stream = connector.connect(stream).unwrap();
|
||||||
|
|
||||||
|
stream.write_all(b"hello").unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut connector = ClientConnectorBuilder::tls().unwrap();
|
||||||
|
connector.context_mut().set_CA_file("test/root-ca.pem").unwrap();
|
||||||
|
let connector = connector.build();
|
||||||
|
|
||||||
|
let stream = TcpStream::connect(("127.0.0.1", port)).unwrap();
|
||||||
|
let mut stream = connector.connect("foobar.com", stream).unwrap();
|
||||||
|
|
||||||
|
let mut buf = [0; 5];
|
||||||
|
stream.read_exact(&mut buf).unwrap();
|
||||||
|
assert_eq!(b"hello", &buf);
|
||||||
|
|
||||||
|
t.join().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn shutdown() {
|
fn shutdown() {
|
||||||
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use libc::{c_char, c_int, c_long, c_ulong, c_void};
|
use libc::{c_char, c_int, c_long, c_ulong, c_void};
|
||||||
|
use std::borrow::Borrow;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
@ -447,6 +448,17 @@ impl X509Ref {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToOwned for X509Ref {
|
||||||
|
type Owned = X509;
|
||||||
|
|
||||||
|
fn to_owned(&self) -> X509 {
|
||||||
|
unsafe {
|
||||||
|
compat::X509_up_ref(self.as_ptr());
|
||||||
|
X509::from_ptr(self.as_ptr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An owned public key certificate.
|
/// An owned public key certificate.
|
||||||
pub struct X509(*mut ffi::X509);
|
pub struct X509(*mut ffi::X509);
|
||||||
|
|
||||||
|
|
@ -491,10 +503,7 @@ impl Deref for X509 {
|
||||||
|
|
||||||
impl Clone for X509 {
|
impl Clone for X509 {
|
||||||
fn clone(&self) -> X509 {
|
fn clone(&self) -> X509 {
|
||||||
unsafe {
|
self.to_owned()
|
||||||
compat::X509_up_ref(self.as_ptr());
|
|
||||||
X509::from_ptr(self.as_ptr())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -504,6 +513,18 @@ impl Drop for X509 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRef<X509Ref> for X509 {
|
||||||
|
fn as_ref(&self) -> &X509Ref {
|
||||||
|
&*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Borrow<X509Ref> for X509 {
|
||||||
|
fn borrow(&self) -> &X509Ref {
|
||||||
|
&*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct X509NameRef(Opaque);
|
pub struct X509NameRef(Opaque);
|
||||||
|
|
||||||
impl X509NameRef {
|
impl X509NameRef {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue