From 06fc4a53c266d3caa81fc5b47768fbbc53a48862 Mon Sep 17 00:00:00 2001 From: Kazuyoshi Kato Date: Wed, 25 Jul 2018 00:16:31 -0700 Subject: [PATCH] Support ALPN with weak linkage --- security-framework-sys/src/base.rs | 1 + security-framework/Cargo.toml | 4 +- security-framework/src/dlsym.rs | 50 ++++++++++++++ security-framework/src/lib.rs | 4 ++ security-framework/src/secure_transport.rs | 79 ++++++++++++++++++---- 5 files changed, 125 insertions(+), 13 deletions(-) create mode 100644 security-framework/src/dlsym.rs diff --git a/security-framework-sys/src/base.rs b/security-framework-sys/src/base.rs index 9b135771..ea5db304 100644 --- a/security-framework-sys/src/base.rs +++ b/security-framework-sys/src/base.rs @@ -24,6 +24,7 @@ pub enum OpaqueSecPolicyRef {} pub type SecPolicyRef = *mut OpaqueSecPolicyRef; pub const errSecSuccess: OSStatus = 0; +pub const errSecUnimplemented: OSStatus = -4; pub const errSecIO: OSStatus = -36; pub const errSecParam: OSStatus = -50; pub const errSecBadReq: OSStatus = -909; diff --git a/security-framework/Cargo.toml b/security-framework/Cargo.toml index 94741e84..074209a5 100644 --- a/security-framework/Cargo.toml +++ b/security-framework/Cargo.toml @@ -21,11 +21,13 @@ tempdir = "0.3" hex = "0.2" [features] +alpn = [] + OSX_10_9 = ["security-framework-sys/OSX_10_9"] OSX_10_10 = ["OSX_10_9", "security-framework-sys/OSX_10_10"] OSX_10_11 = ["OSX_10_10", "security-framework-sys/OSX_10_11"] OSX_10_12 = ["OSX_10_11", "security-framework-sys/OSX_10_12"] -OSX_10_13 = ["OSX_10_12", "security-framework-sys/OSX_10_13"] +OSX_10_13 = ["OSX_10_12", "security-framework-sys/OSX_10_13", "alpn"] nightly = [] diff --git a/security-framework/src/dlsym.rs b/security-framework/src/dlsym.rs new file mode 100644 index 00000000..489cf51c --- /dev/null +++ b/security-framework/src/dlsym.rs @@ -0,0 +1,50 @@ +// dlsym.rs is taken from mio +// https://github.com/carllerche/mio/blob/master/src/sys/unix/dlsym.rs + +use std::marker; +use std::mem; +use std::sync::atomic::{AtomicUsize, Ordering}; + +use libc; + +macro_rules! dlsym { + (fn $name:ident($($t:ty),*) -> $ret:ty) => ( + #[allow(bad_style)] + static $name: ::dlsym::DlSym $ret> = + ::dlsym::DlSym { + name: concat!(stringify!($name), "\0"), + addr: ::std::sync::atomic::ATOMIC_USIZE_INIT, + _marker: ::std::marker::PhantomData, + }; + ) +} + +pub struct DlSym { + pub name: &'static str, + pub addr: AtomicUsize, + pub _marker: marker::PhantomData, +} + +impl DlSym { + pub fn get(&self) -> Option<&F> { + assert_eq!(mem::size_of::(), mem::size_of::()); + unsafe { + if self.addr.load(Ordering::SeqCst) == 0 { + self.addr.store(fetch(self.name), Ordering::SeqCst); + } + if self.addr.load(Ordering::SeqCst) == 1 { + None + } else { + mem::transmute::<&AtomicUsize, Option<&F>>(&self.addr) + } + } + } +} + +unsafe fn fetch(name: &str) -> usize { + assert_eq!(name.as_bytes()[name.len() - 1], 0); + match libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr() as *const _) as usize { + 0 => 1, + n => n, + } +} diff --git a/security-framework/src/lib.rs b/security-framework/src/lib.rs index d8b8642f..3f087628 100644 --- a/security-framework/src/lib.rs +++ b/security-framework/src/lib.rs @@ -34,6 +34,10 @@ macro_rules! p { }; } +#[cfg(all(not(feature = "OSX_10_13"), feature = "alpn"))] +#[macro_use] +mod dlsym; + pub mod base; pub mod certificate; pub mod cipher_suite; diff --git a/security-framework/src/secure_transport.rs b/security-framework/src/secure_transport.rs index 2b9b8286..a1702b77 100644 --- a/security-framework/src/secure_transport.rs +++ b/security-framework/src/secure_transport.rs @@ -72,16 +72,19 @@ //! } //! //! ``` +#[allow(unused_imports)] +use core_foundation::array::{CFArray, CFArrayRef}; -use core_foundation::array::CFArray; use core_foundation::base::{Boolean, TCFType}; -#[cfg(feature = "OSX_10_13")] use core_foundation::string::CFString; use core_foundation_sys::base::{kCFAllocatorDefault, OSStatus}; use libc::{c_void, size_t}; + +#[allow(unused_imports)] use security_framework_sys::base::{ - errSecBadReq, errSecIO, errSecNotTrusted, errSecSuccess, errSecTrustSettingDeny, + errSecBadReq, errSecIO, errSecNotTrusted, errSecSuccess, errSecTrustSettingDeny, errSecUnimplemented }; + use security_framework_sys::secure_transport::*; use std::any::Any; use std::cmp; @@ -701,11 +704,25 @@ impl SslContext { } /// Returns the set of protocols selected via ALPN if it succeeded. - #[cfg(feature = "OSX_10_13")] + #[cfg(feature = "alpn")] pub fn alpn_protocols(&self) -> Result> { + let mut array = ptr::null(); unsafe { - let mut array = ptr::null(); - cvt(SSLCopyALPNProtocols(self.0, &mut array))?; + #[cfg(feature = "OSX_10_13")] + { + cvt(SSLCopyALPNProtocols(self.0, &mut array))?; + } + + #[cfg(not(feature = "OSX_10_13"))] + { + dlsym! { fn SSLCopyALPNProtocols(SSLContextRef, *mut CFArrayRef) -> OSStatus } + if let Some(f) = SSLCopyALPNProtocols.get() { + cvt(f(self.0, &mut array))?; + } else { + return Err(Error::from_code(errSecUnimplemented)) + } + } + if array.is_null() { return Ok(vec![]); } @@ -718,7 +735,7 @@ impl SslContext { /// Configures the set of protocols use for ALPN. /// /// This is only used for client-side connections. - #[cfg(feature = "OSX_10_13")] + #[cfg(feature = "alpn")] pub fn set_alpn_protocols(&mut self, protocols: &[&str]) -> Result<()> { // When CFMutableArray is added to core-foundation and IntoIterator trait // is implemented for CFMutableArray, the code below should directly collect @@ -730,7 +747,19 @@ impl SslContext { .collect::>(), ); - unsafe { cvt(SSLSetALPNProtocols(self.0, protocols.as_concrete_TypeRef())) } + #[cfg(feature = "OSX_10_13")] + { + unsafe { cvt(SSLSetALPNProtocols(self.0, protocols.as_concrete_TypeRef())) } + } + #[cfg(not(feature = "OSX_10_13"))] + { + dlsym! { fn SSLSetALPNProtocols(SSLContextRef, CFArrayRef) -> OSStatus } + if let Some(f) = SSLSetALPNProtocols.get() { + unsafe { cvt(f(self.0, protocols.as_concrete_TypeRef())) } + } else { + Err(Error::from_code(errSecUnimplemented)) + } + } } /// Sets whether a protocol is enabled or not. @@ -1117,7 +1146,6 @@ pub struct ClientBuilder { danger_accept_invalid_hostnames: bool, whitelisted_ciphers: Vec, blacklisted_ciphers: Vec, - #[cfg(feature = "OSX_10_13")] alpn: Option>, } @@ -1142,7 +1170,6 @@ impl ClientBuilder { danger_accept_invalid_hostnames: false, whitelisted_ciphers: Vec::new(), blacklisted_ciphers: Vec::new(), - #[cfg(feature = "OSX_10_13")] alpn: None, } } @@ -1227,7 +1254,7 @@ impl ClientBuilder { } /// Configures the set of protocols used for ALPN. - #[cfg(feature = "OSX_10_13")] + #[cfg(feature = "alpn")] pub fn alpn_protocols(&mut self, protocols: &[&str]) -> &mut Self { self.alpn = Some(protocols.iter().map(|s| s.to_string()).collect()); self @@ -1278,7 +1305,7 @@ impl ClientBuilder { if let Some(ref identity) = self.identity { ctx.set_certificate(identity, &self.chain)?; } - #[cfg(feature = "OSX_10_13")] + #[cfg(feature = "alpn")] { if let Some(ref alpn) = self.alpn { ctx.set_alpn_protocols(&alpn.iter().map(|s| &**s).collect::>())?; @@ -1398,6 +1425,34 @@ mod test { println!("{}", String::from_utf8_lossy(&buf)); } + #[test] + #[cfg(feature = "alpn")] + fn client_alpn_accept() { + let mut ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + p!(ctx.set_peer_domain_name("google.com")); + p!(ctx.set_alpn_protocols(&vec!["h2"])); + let stream = p!(TcpStream::connect("google.com:443")); + let stream = ctx.handshake(stream).unwrap(); + assert_eq!(vec!["h2"], stream.context().alpn_protocols().unwrap()); + } + + #[test] + #[cfg(feature = "alpn")] + fn client_alpn_reject() { + let mut ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + p!(ctx.set_peer_domain_name("google.com")); + p!(ctx.set_alpn_protocols(&vec!["h2c"])); + let stream = p!(TcpStream::connect("google.com:443")); + let stream = ctx.handshake(stream).unwrap(); + assert!(stream.context().alpn_protocols().is_err()); + } + #[test] fn client_no_anchor_certs() { let stream = p!(TcpStream::connect("google.com:443"));