From b7ba5773396fb4ba7fb1ff5d770b5b2632adc471 Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Sun, 25 Feb 2018 23:04:54 -0800 Subject: [PATCH] Add min/max protocol version support --- openssl-sys/src/ossl110.rs | 64 ++++++++++++++++++++--- openssl-sys/src/ossl111.rs | 10 ++-- openssl/src/ssl/mod.rs | 104 +++++++++++++++++++++++++++++++++++-- openssl/src/ssl/test.rs | 33 ++++++++++++ 4 files changed, 196 insertions(+), 15 deletions(-) diff --git a/openssl-sys/src/ossl110.rs b/openssl-sys/src/ossl110.rs index 6eb18a2f..b02c296d 100644 --- a/openssl-sys/src/ossl110.rs +++ b/openssl-sys/src/ossl110.rs @@ -33,9 +33,10 @@ pub enum X509_ALGOR {} pub enum X509_VERIFY_PARAM {} pub enum X509_REQ {} -#[cfg(ossl111)] -pub type SSL_CTX_keylog_cb_func = - Option; +pub const SSL_CTRL_SET_MIN_PROTO_VERSION: c_int = 123; +pub const SSL_CTRL_SET_MAX_PROTO_VERSION: c_int = 124; +pub const SSL_CTRL_GET_MIN_PROTO_VERSION: c_int = 130; +pub const SSL_CTRL_GET_MAX_PROTO_VERSION: c_int = 131; pub const SSL_OP_MICROSOFT_SESS_ID_BUG: c_ulong = 0x00000000; pub const SSL_OP_NETSCAPE_CHALLENGE_BUG: c_ulong = 0x00000000; @@ -48,9 +49,6 @@ pub const SSL_OP_SINGLE_ECDH_USE: c_ulong = 0x00000000; pub const SSL_OP_SINGLE_DH_USE: c_ulong = 0x00000000; pub const SSL_OP_NO_SSLv2: c_ulong = 0x00000000; -#[cfg(ossl111)] -pub const TLS1_3_VERSION: c_int = 0x304; - pub const OPENSSL_VERSION: c_int = 0; pub const OPENSSL_CFLAGS: c_int = 1; pub const OPENSSL_BUILT_ON: c_int = 2; @@ -81,6 +79,58 @@ pub fn init() { }) } +pub unsafe fn SSL_CTX_set_min_proto_version(ctx: *mut ::SSL_CTX, version: c_int) -> c_int { + ::SSL_CTX_ctrl( + ctx, + SSL_CTRL_SET_MIN_PROTO_VERSION, + version as c_long, + ptr::null_mut(), + ) as c_int +} + +pub unsafe fn SSL_CTX_set_max_proto_version(ctx: *mut ::SSL_CTX, version: c_int) -> c_int { + ::SSL_CTX_ctrl( + ctx, + SSL_CTRL_SET_MAX_PROTO_VERSION, + version as c_long, + ptr::null_mut(), + ) as c_int +} + +pub unsafe fn SSL_CTX_get_min_proto_version(ctx: *mut ::SSL_CTX) -> c_int { + ::SSL_CTX_ctrl(ctx, SSL_CTRL_GET_MIN_PROTO_VERSION, 0, ptr::null_mut()) as c_int +} + +pub unsafe fn SSL_CTX_get_max_proto_version(ctx: *mut ::SSL_CTX) -> c_int { + ::SSL_CTX_ctrl(ctx, SSL_CTRL_GET_MAX_PROTO_VERSION, 0, ptr::null_mut()) as c_int +} + +pub unsafe fn SSL_set_min_proto_version(s: *mut ::SSL, version: c_int) -> c_int { + ::SSL_ctrl( + s, + SSL_CTRL_SET_MIN_PROTO_VERSION, + version as c_long, + ptr::null_mut(), + ) as c_int +} + +pub unsafe fn SSL_set_max_proto_version(s: *mut ::SSL, version: c_int) -> c_int { + ::SSL_ctrl( + s, + SSL_CTRL_SET_MAX_PROTO_VERSION, + version as c_long, + ptr::null_mut(), + ) as c_int +} + +pub unsafe fn SSL_get_min_proto_version(s: *mut ::SSL) -> c_int { + ::SSL_ctrl(s, SSL_CTRL_GET_MIN_PROTO_VERSION, 0, ptr::null_mut()) as c_int +} + +pub unsafe fn SSL_get_max_proto_version(s: *mut ::SSL) -> c_int { + ::SSL_ctrl(s, SSL_CTRL_GET_MAX_PROTO_VERSION, 0, ptr::null_mut()) as c_int +} + extern "C" { pub fn BIO_new(type_: *const BIO_METHOD) -> *mut BIO; pub fn BIO_s_file() -> *const BIO_METHOD; @@ -221,8 +271,6 @@ extern "C" { ); pub fn SSL_get_client_random(ssl: *const SSL, out: *mut c_uchar, len: size_t) -> size_t; pub fn SSL_get_server_random(ssl: *const SSL, out: *mut c_uchar, len: size_t) -> size_t; - #[cfg(ossl111)] - pub fn SSL_CTX_set_keylog_callback(ctx: *mut ::SSL_CTX, cb: SSL_CTX_keylog_cb_func); pub fn X509_getm_notAfter(x: *const ::X509) -> *mut ::ASN1_TIME; pub fn X509_getm_notBefore(x: *const ::X509) -> *mut ::ASN1_TIME; pub fn X509_get0_signature( diff --git a/openssl-sys/src/ossl111.rs b/openssl-sys/src/ossl111.rs index c22d506c..27bc4b54 100644 --- a/openssl-sys/src/ossl111.rs +++ b/openssl-sys/src/ossl111.rs @@ -1,11 +1,15 @@ -use libc::{c_int, c_ulong}; +use libc::{c_char, c_int, c_ulong}; -use ossl110::*; +pub type SSL_CTX_keylog_cb_func = + Option; pub const SSL_COOKIE_LENGTH: c_int = 255; pub const SSL_OP_ENABLE_MIDDLEBOX_COMPAT: c_ulong = 0x00100000; +pub const TLS1_3_VERSION: c_int = 0x304; + extern "C" { - pub fn SSL_stateless(s: *mut SSL) -> c_int; + pub fn SSL_CTX_set_keylog_callback(ctx: *mut ::SSL_CTX, cb: SSL_CTX_keylog_cb_func); + pub fn SSL_stateless(s: *mut ::SSL) -> c_int; } diff --git a/openssl/src/ssl/mod.rs b/openssl/src/ssl/mod.rs index 91b97818..1ae480c5 100644 --- a/openssl/src/ssl/mod.rs +++ b/openssl/src/ssl/mod.rs @@ -1007,6 +1007,11 @@ impl SslContextBuilder { /// /// This corresponds to [`SSL_CTX_set_options`]. /// + /// # Note + /// + /// This *enables* the specified options, but does not disable unspecified options. Use + /// `clear_options` for that. + /// /// [`SSL_CTX_set_options`]: https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_options.html pub fn set_options(&mut self, option: SslOptions) -> SslOptions { let bits = unsafe { compat::SSL_CTX_set_options(self.as_ptr(), option.bits()) }; @@ -1033,6 +1038,90 @@ impl SslContextBuilder { SslOptions { bits } } + /// Sets the minimum supported protocol version. + /// + /// A value of `None` will enable protocol versions down the the lowest version supported by + /// OpenSSL. + /// + /// This corresponds to [`SSL_CTX_set_min_proto_version`]. + /// + /// Requires OpenSSL 1.1.0 or 1.1.1 and the corresponding Cargo feature. + /// + /// [`SSL_CTX_set_min_proto_version`]: https://www.openssl.org/docs/man1.1.0/ssl/SSL_set_min_proto_version.html + #[cfg(any(all(feature = "v110", ossl110), all(feature = "v111", ossl111)))] + pub fn set_min_proto_version(&mut self, version: Option) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::SSL_CTX_set_min_proto_version( + self.as_ptr(), + version.map_or(0, |v| v.0), + )).map(|_| ()) + } + } + + /// Sets the maximum supported protocol version. + /// + /// A value of `None` will enable protocol versions down the the highest version supported by + /// OpenSSL. + /// + /// This corresponds to [`SSL_CTX_set_max_proto_version`]. + /// + /// Requires OpenSSL 1.1.0 or 1.1.1 and the corresponding Cargo feature. + /// + /// [`SSL_CTX_set_max_proto_version`]: https://www.openssl.org/docs/man1.1.0/ssl/SSL_set_min_proto_version.html + #[cfg(any(all(feature = "v110", ossl110), all(feature = "v111", ossl111)))] + pub fn set_max_proto_version(&mut self, version: Option) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::SSL_CTX_set_max_proto_version( + self.as_ptr(), + version.map_or(0, |v| v.0), + )).map(|_| ()) + } + } + + /// Gets the minimum supported protocol version. + /// + /// A value of `None` indicates that all versions down the the lowest version supported by + /// OpenSSL are enabled. + /// + /// This corresponds to [`SSL_CTX_get_min_proto_version`]. + /// + /// Requires OpenSSL 1.1.0 or 1.1.1 and the corresponding Cargo feature. + /// + /// [`SSL_CTX_get_min_proto_version`]: https://www.openssl.org/docs/man1.1.0/ssl/SSL_set_min_proto_version.html + #[cfg(any(all(feature = "v110", ossl110), all(feature = "v111", ossl111)))] + pub fn min_proto_version(&mut self) -> Option { + unsafe { + let r = ffi::SSL_CTX_get_min_proto_version(self.as_ptr()); + if r == 0 { + None + } else { + Some(SslVersion(r)) + } + } + } + + /// Gets the maximum supported protocol version. + /// + /// A value of `None` indicates that all versions down the the highest version supported by + /// OpenSSL are enabled. + /// + /// This corresponds to [`SSL_CTX_get_max_proto_version`]. + /// + /// Requires OpenSSL 1.1.0 or 1.1.1 and the corresponding Cargo feature. + /// + /// [`SSL_CTX_get_max_proto_version`]: https://www.openssl.org/docs/man1.1.0/ssl/SSL_set_min_proto_version.html + #[cfg(any(all(feature = "v110", ossl110), all(feature = "v111", ossl111)))] + pub fn max_proto_version(&mut self) -> Option { + unsafe { + let r = ffi::SSL_CTX_get_max_proto_version(self.as_ptr()); + if r == 0 { + None + } else { + Some(SslVersion(r)) + } + } + } + /// 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 @@ -1319,7 +1408,7 @@ impl SslContextBuilder { /// This corresponds to `SSL_CTX_set_cookie_generate_cb`. pub fn set_cookie_generate_cb(&mut self, callback: F) where - F: Fn(&mut SslRef, &mut [u8]) -> Result + 'static + Sync + Send + F: Fn(&mut SslRef, &mut [u8]) -> Result + 'static + Sync + Send, { unsafe { let callback = Box::new(callback); @@ -1343,7 +1432,7 @@ impl SslContextBuilder { /// This corresponds to `SSL_CTX_set_cookie_verify_cb`. pub fn set_cookie_verify_cb(&mut self, callback: F) where - F: Fn(&mut SslRef, &[u8]) -> bool + 'static + Sync + Send + F: Fn(&mut SslRef, &[u8]) -> bool + 'static + Sync + Send, { unsafe { let callback = Box::new(callback); @@ -2011,8 +2100,15 @@ impl SslRef { /// This corresponds to [`SSL_version`]. /// /// [`SSL_version`]: https://www.openssl.org/docs/manmaster/man3/SSL_version.html - pub fn version2(&self) -> SslVersion { - unsafe { SslVersion(ffi::SSL_version(self.as_ptr())) } + pub fn version2(&self) -> Option { + unsafe { + let r = ffi::SSL_version(self.as_ptr()); + if r == 0 { + None + } else { + Some(SslVersion(r)) + } + } } /// Returns a string describing the protocol version of the session. diff --git a/openssl/src/ssl/test.rs b/openssl/src/ssl/test.rs index b8e9b8c2..ae8c12de 100644 --- a/openssl/src/ssl/test.rs +++ b/openssl/src/ssl/test.rs @@ -20,6 +20,8 @@ use ocsp::{OcspResponse, OcspResponseStatus}; use ssl; use ssl::{Error, HandshakeError, ShutdownResult, Ssl, SslAcceptor, SslConnector, SslContext, SslFiletype, SslMethod, SslSessionCacheMode, SslStream, SslVerifyMode, StatusType}; +#[cfg(any(all(feature = "v110", ossl110), all(feature = "v111", ossl111)))] +use ssl::SslVersion; use x509::{X509, X509Name, X509StoreContext, X509VerifyResult}; #[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110), all(feature = "v111", ossl111)))] @@ -1320,6 +1322,37 @@ fn keying_export() { assert_eq!(buf, buf2); } +#[test] +#[cfg(any(all(feature = "v110", ossl110), all(feature = "v111", ossl111)))] +fn no_version_overlap() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let addr = listener.local_addr().unwrap(); + + let guard = thread::spawn(move || { + let stream = listener.accept().unwrap().0; + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ctx.set_certificate_file(&Path::new("test/cert.pem"), SslFiletype::PEM) + .unwrap(); + ctx.set_private_key_file(&Path::new("test/key.pem"), SslFiletype::PEM) + .unwrap(); + ctx.set_max_proto_version(Some(SslVersion::TLS1_1)).unwrap(); + assert_eq!(ctx.min_proto_version(), None); + assert_eq!(ctx.max_proto_version(), Some(SslVersion::TLS1_1)); + let ssl = Ssl::new(&ctx.build()).unwrap(); + ssl.accept(stream).unwrap_err(); + }); + + let stream = TcpStream::connect(addr).unwrap(); + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ctx.set_min_proto_version(Some(SslVersion::TLS1_2)).unwrap(); + assert_eq!(ctx.min_proto_version(), Some(SslVersion::TLS1_2)); + assert_eq!(ctx.max_proto_version(), None); + let ssl = Ssl::new(&ctx.build()).unwrap(); + ssl.connect(stream).unwrap_err(); + + guard.join().unwrap(); +} + fn _check_kinds() { fn is_send() {} fn is_sync() {}