security_framework/os/macos/
secure_transport.rsuse core_foundation::array::CFArray;
use core_foundation::base::TCFType;
use security_framework_sys::secure_transport::*;
use std::ptr;
use std::slice;
use crate::base::Result;
use crate::certificate::SecCertificate;
use crate::secure_transport::{MidHandshakeSslStream, SslContext};
use crate::{cvt, AsInner};
pub trait SslContextExt {
fn diffie_hellman_params(&self) -> Result<Option<&[u8]>>;
fn set_diffie_hellman_params(&mut self, dh_params: &[u8]) -> Result<()>;
fn certificate_authorities(&self) -> Result<Option<Vec<SecCertificate>>>;
fn set_certificate_authorities(&mut self, certs: &[SecCertificate]) -> Result<()>;
fn add_certificate_authorities(&mut self, certs: &[SecCertificate]) -> Result<()>;
fn allow_server_identity_change(&self) -> Result<bool>;
#[deprecated(note = "kSSLSessionOptionAllowServerIdentityChange is deprecated by Apple")]
fn set_allow_server_identity_change(&mut self, value: bool) -> Result<()>;
fn fallback(&self) -> Result<bool>;
#[deprecated(note = "kSSLSessionOptionFallback is deprecated by Apple")]
fn set_fallback(&mut self, value: bool) -> Result<()>;
fn break_on_client_hello(&self) -> Result<bool>;
#[deprecated(note = "kSSLSessionOptionBreakOnClientHello is deprecated by Apple")]
fn set_break_on_client_hello(&mut self, value: bool) -> Result<()>;
}
macro_rules! impl_options {
($($(#[$a:meta])* const $opt:ident: $get:ident & $set:ident,)*) => {
$(
#[allow(deprecated)]
$(#[$a])*
#[inline]
fn $set(&mut self, value: bool) -> Result<()> {
unsafe {
cvt(SSLSetSessionOption(self.as_inner(),
$opt,
value as ::core_foundation::base::Boolean))
}
}
#[allow(deprecated)]
$(#[$a])*
#[inline]
fn $get(&self) -> Result<bool> {
let mut value = 0;
unsafe { cvt(SSLGetSessionOption(self.as_inner(), $opt, &mut value))?; }
Ok(value != 0)
}
)*
}
}
impl SslContextExt for SslContext {
fn diffie_hellman_params(&self) -> Result<Option<&[u8]>> {
unsafe {
let mut ptr = ptr::null();
let mut len = 0;
cvt(SSLGetDiffieHellmanParams(
self.as_inner(),
&mut ptr,
&mut len,
))?;
if ptr.is_null() {
Ok(None)
} else {
Ok(Some(slice::from_raw_parts(ptr.cast::<u8>(), len)))
}
}
}
fn set_diffie_hellman_params(&mut self, dh_params: &[u8]) -> Result<()> {
unsafe {
cvt(SSLSetDiffieHellmanParams(
self.as_inner(),
dh_params.as_ptr().cast(),
dh_params.len(),
))
}
}
fn certificate_authorities(&self) -> Result<Option<Vec<SecCertificate>>> {
unsafe {
let mut raw_certs = ptr::null();
cvt(SSLCopyCertificateAuthorities(
self.as_inner(),
&mut raw_certs,
))?;
if raw_certs.is_null() {
Ok(None)
} else {
let certs = CFArray::<SecCertificate>::wrap_under_create_rule(raw_certs)
.iter()
.map(|c| c.clone())
.collect();
Ok(Some(certs))
}
}
}
fn set_certificate_authorities(&mut self, certs: &[SecCertificate]) -> Result<()> {
unsafe {
let certs = CFArray::from_CFTypes(certs);
cvt(SSLSetCertificateAuthorities(
self.as_inner(),
certs.as_CFTypeRef(),
1,
))
}
}
fn add_certificate_authorities(&mut self, certs: &[SecCertificate]) -> Result<()> {
unsafe {
let certs = CFArray::from_CFTypes(certs);
cvt(SSLSetCertificateAuthorities(
self.as_inner(),
certs.as_CFTypeRef(),
0,
))
}
}
impl_options! {
const kSSLSessionOptionAllowServerIdentityChange: allow_server_identity_change & set_allow_server_identity_change,
const kSSLSessionOptionFallback: fallback & set_fallback,
const kSSLSessionOptionBreakOnClientHello: break_on_client_hello & set_break_on_client_hello,
}
}
pub trait MidHandshakeSslStreamExt {
fn client_hello_received(&self) -> bool;
}
impl<S> MidHandshakeSslStreamExt for MidHandshakeSslStream<S> {
fn client_hello_received(&self) -> bool {
self.error().code() == errSSLClientHelloReceived
}
}
#[cfg(test)]
mod test {
use std::io::prelude::*;
use std::net::{TcpListener, TcpStream};
use std::thread;
use tempfile::tempdir;
use super::*;
use crate::cipher_suite::CipherSuite;
use crate::os::macos::test::identity;
use crate::secure_transport::*;
use crate::test::certificate;
#[test]
fn server_client() {
let listener = p!(TcpListener::bind("localhost:0"));
let port = p!(listener.local_addr()).port();
let handle = thread::spawn(move || {
let dir = p!(tempdir());
let mut ctx = p!(SslContext::new(
SslProtocolSide::SERVER,
SslConnectionType::STREAM
));
let identity = identity(dir.path());
p!(ctx.set_certificate(&identity, &[]));
let stream = p!(listener.accept()).0;
let mut stream = p!(ctx.handshake(stream));
let mut buf = [0; 12];
p!(stream.read(&mut buf));
assert_eq!(&buf[..], b"hello world!");
});
let mut ctx = p!(SslContext::new(
SslProtocolSide::CLIENT,
SslConnectionType::STREAM
));
p!(ctx.set_break_on_server_auth(true));
let stream = p!(TcpStream::connect(("localhost", port)));
let stream = match ctx.handshake(stream) {
Ok(_) => panic!("unexpected success"),
Err(HandshakeError::Interrupted(stream)) => stream,
Err(err) => panic!("unexpected error {err:?}"),
};
assert!(stream.server_auth_completed());
let mut peer_trust = p!(stream.context().peer_trust2()).unwrap();
p!(peer_trust.set_anchor_certificates(&[certificate()]));
p!(peer_trust.evaluate_with_error());
let mut stream = p!(stream.handshake());
p!(stream.write_all(b"hello world!"));
handle.join().unwrap();
}
#[test]
#[ignore]
fn server_client_builders() {
let listener = p!(TcpListener::bind("localhost:0"));
let port = p!(listener.local_addr()).port();
let handle = thread::spawn(move || {
let dir = p!(tempdir());
let identity = identity(dir.path());
let builder = ServerBuilder::new(&identity, &[]);
let stream = p!(listener.accept()).0;
let mut stream = p!(builder.handshake(stream));
let mut buf = [0; 12];
p!(stream.read(&mut buf));
assert_eq!(&buf[..], b"hello world!");
});
let stream = p!(TcpStream::connect(("localhost", port)));
let mut stream = p!(ClientBuilder::new()
.anchor_certificates(&[certificate()])
.handshake("foobar.com", stream));
p!(stream.write_all(b"hello world!"));
handle.join().unwrap();
}
#[test]
fn client_bad_cert() {
let _ = env_logger::try_init();
let listener = p!(TcpListener::bind("localhost:0"));
let port = p!(listener.local_addr()).port();
let handle = thread::spawn(move || {
let dir = p!(tempdir());
let mut ctx = p!(SslContext::new(
SslProtocolSide::SERVER,
SslConnectionType::STREAM
));
let identity = identity(dir.path());
p!(ctx.set_certificate(&identity, &[]));
let stream = p!(listener.accept()).0;
let _ = ctx.handshake(stream);
});
let stream = p!(TcpStream::connect(("localhost", port)));
assert!(ClientBuilder::new()
.handshake("foobar.com", stream)
.is_err());
handle.join().unwrap();
}
#[test]
#[ignore]
fn client() {
let listener = p!(TcpListener::bind("localhost:0"));
let port = p!(listener.local_addr()).port();
let handle = thread::spawn(move || {
let dir = p!(tempdir());
let mut ctx = p!(SslContext::new(
SslProtocolSide::SERVER,
SslConnectionType::STREAM
));
let identity = identity(dir.path());
p!(ctx.set_certificate(&identity, &[]));
let stream = p!(listener.accept()).0;
let mut stream = p!(ctx.handshake(stream));
let mut buf = [0; 12];
p!(stream.read(&mut buf));
assert_eq!(&buf[..], b"hello world!");
});
let stream = p!(TcpStream::connect(("localhost", port)));
let mut stream = p!(ClientBuilder::new()
.anchor_certificates(&[certificate()])
.handshake("foobar.com", stream));
p!(stream.write_all(b"hello world!"));
handle.join().unwrap();
}
#[test]
fn negotiated_cipher() {
let listener = p!(TcpListener::bind("localhost:0"));
let port = p!(listener.local_addr()).port();
let handle = thread::spawn(move || {
let dir = p!(tempdir());
let mut ctx = p!(SslContext::new(
SslProtocolSide::SERVER,
SslConnectionType::STREAM
));
let identity = identity(dir.path());
p!(ctx.set_certificate(&identity, &[]));
p!(ctx.set_enabled_ciphers(&[
CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
]));
let stream = p!(listener.accept()).0;
let mut stream = p!(ctx.handshake(stream));
assert_eq!(
CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
p!(stream.context().negotiated_cipher())
);
let mut buf = [0; 1];
p!(stream.read(&mut buf));
});
let mut ctx = p!(SslContext::new(
SslProtocolSide::CLIENT,
SslConnectionType::STREAM
));
p!(ctx.set_break_on_server_auth(true));
p!(ctx.set_enabled_ciphers(&[
CipherSuite::TLS_DHE_PSK_WITH_AES_128_CBC_SHA256,
CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
]));
let stream = p!(TcpStream::connect(("localhost", port)));
let stream = match ctx.handshake(stream) {
Ok(_) => panic!("unexpected success"),
Err(HandshakeError::Interrupted(stream)) => stream,
Err(err) => panic!("unexpected error {err:?}"),
};
let mut stream = p!(stream.handshake());
assert_eq!(
CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
p!(stream.context().negotiated_cipher())
);
p!(stream.write(&[0]));
handle.join().unwrap();
}
#[test]
fn dh_params() {
let params = include_bytes!("../../../test/dhparam.der");
let mut ctx = p!(SslContext::new(
SslProtocolSide::SERVER,
SslConnectionType::STREAM
));
assert!(p!(ctx.diffie_hellman_params()).is_none());
p!(ctx.set_diffie_hellman_params(params));
assert_eq!(p!(ctx.diffie_hellman_params()).unwrap(), ¶ms[..]);
}
#[test]
fn try_authenticate_no_cert() {
let listener = p!(TcpListener::bind("localhost:0"));
let port = p!(listener.local_addr()).port();
let handle = thread::spawn(move || {
let dir = p!(tempdir());
let mut ctx = p!(SslContext::new(
SslProtocolSide::SERVER,
SslConnectionType::STREAM
));
let identity = identity(dir.path());
p!(ctx.set_certificate(&identity, &[]));
p!(ctx.set_client_side_authenticate(SslAuthenticate::TRY));
let cert = certificate();
p!(ctx.add_certificate_authorities(&[cert]));
let stream = p!(listener.accept()).0;
let mut stream = p!(ctx.handshake(stream));
let mut buf = [0; 1];
p!(stream.read(&mut buf));
});
let mut ctx = p!(SslContext::new(
SslProtocolSide::CLIENT,
SslConnectionType::STREAM
));
p!(ctx.set_break_on_server_auth(true));
let stream = p!(TcpStream::connect(("localhost", port)));
let stream = match ctx.handshake(stream) {
Ok(_) => panic!("unexpected success"),
Err(HandshakeError::Interrupted(stream)) => stream,
Err(err) => panic!("unexpected error {err:?}"),
};
let mut stream = p!(stream.handshake());
p!(stream.write(&[0]));
handle.join().unwrap();
}
#[test]
fn always_authenticate_no_cert() {
let listener = p!(TcpListener::bind("localhost:0"));
let port = p!(listener.local_addr()).port();
let handle = thread::spawn(move || {
let dir = p!(tempdir());
let mut ctx = p!(SslContext::new(
SslProtocolSide::SERVER,
SslConnectionType::STREAM
));
let identity = identity(dir.path());
p!(ctx.set_certificate(&identity, &[]));
p!(ctx.set_client_side_authenticate(SslAuthenticate::ALWAYS));
let stream = p!(listener.accept()).0;
match ctx.handshake(stream) {
Ok(_) => panic!("unexpected success"),
Err(HandshakeError::Failure(_)) => {}
Err(err) => panic!("unexpected error {err:?}"),
}
});
let mut ctx = p!(SslContext::new(
SslProtocolSide::CLIENT,
SslConnectionType::STREAM
));
p!(ctx.set_break_on_server_auth(true));
let stream = p!(TcpStream::connect(("localhost", port)));
let stream = match ctx.handshake(stream) {
Ok(_) => panic!("unexpected success"),
Err(HandshakeError::Interrupted(stream)) => stream,
Err(err) => panic!("unexpected error {err:?}"),
};
match stream.handshake() {
Ok(_) => panic!("unexpected success"),
Err(HandshakeError::Failure(_)) => {}
Err(err) => panic!("unexpected error {err:?}"),
}
handle.join().unwrap();
}
#[test]
fn always_authenticate_with_cert() {
let listener = p!(TcpListener::bind("localhost:0"));
let port = p!(listener.local_addr()).port();
let handle = thread::spawn(move || {
let dir = p!(tempdir());
let mut ctx = p!(SslContext::new(
SslProtocolSide::SERVER,
SslConnectionType::STREAM
));
let identity = identity(dir.path());
p!(ctx.set_certificate(&identity, &[]));
p!(ctx.set_client_side_authenticate(SslAuthenticate::ALWAYS));
let stream = p!(listener.accept()).0;
match ctx.handshake(stream) {
Ok(_) => panic!("unexpected success"),
Err(HandshakeError::Failure(_)) => {}
Err(err) => panic!("unexpected error {err:?}"),
}
});
let mut ctx = p!(SslContext::new(
SslProtocolSide::CLIENT,
SslConnectionType::STREAM
));
p!(ctx.set_break_on_server_auth(true));
let dir = p!(tempdir());
let identity = identity(dir.path());
p!(ctx.set_certificate(&identity, &[]));
let stream = p!(TcpStream::connect(("localhost", port)));
let stream = match ctx.handshake(stream) {
Ok(_) => panic!("unexpected success"),
Err(HandshakeError::Interrupted(stream)) => stream,
Err(err) => panic!("unexpected error {err:?}"),
};
match stream.handshake() {
Ok(_) => panic!("unexpected success"),
Err(HandshakeError::Failure(_)) => {}
Err(err) => panic!("unexpected error {err:?}"),
}
handle.join().unwrap();
}
#[test]
fn certificate_authorities() {
let mut ctx = p!(SslContext::new(
SslProtocolSide::SERVER,
SslConnectionType::STREAM
));
assert!(p!(ctx.certificate_authorities()).is_none());
p!(ctx.set_certificate_authorities(&[certificate()]));
assert_eq!(p!(ctx.certificate_authorities()).unwrap().len(), 1);
}
#[test]
#[ignore]
fn close() {
let listener = p!(TcpListener::bind("localhost:0"));
let port = p!(listener.local_addr()).port();
let handle = thread::spawn(move || {
let dir = p!(tempdir());
let identity = identity(dir.path());
let builder = ServerBuilder::new(&identity, &[]);
let stream = p!(listener.accept()).0;
let mut stream = p!(builder.handshake(stream));
p!(stream.close());
});
let stream = p!(TcpStream::connect(("localhost", port)));
let mut stream = p!(ClientBuilder::new()
.anchor_certificates(&[certificate()])
.handshake("foobar.com", stream));
let mut buf = [0; 1];
assert_eq!(p!(stream.read(&mut buf)), 0);
p!(stream.close());
p!(handle.join());
}
#[test]
#[ignore]
fn short_read() {
let listener = p!(TcpListener::bind("localhost:0"));
let port = p!(listener.local_addr()).port();
let handle = thread::spawn(move || {
let dir = p!(tempdir());
let identity = identity(dir.path());
let builder = ServerBuilder::new(&identity, &[]);
let stream = p!(listener.accept()).0;
let mut stream = p!(builder.handshake(stream));
stream.write_all(b"hello").unwrap();
stream
});
let stream = p!(TcpStream::connect(("localhost", port)));
let mut stream = p!(ClientBuilder::new()
.anchor_certificates(&[certificate()])
.handshake("foobar.com", stream));
let mut b = [0; 1];
stream.read_exact(&mut b).unwrap();
assert_eq!(stream.context().buffered_read_size().unwrap(), 4);
let mut b = [0; 5];
let read = stream.read(&mut b).unwrap();
assert_eq!(read, 4);
p!(handle.join());
}
}