Skip to main content

security_framework/os/macos/
secure_transport.rs

1//! OSX specific extensions to Secure Transport functionality.
2
3use core_foundation::array::CFArray;
4use core_foundation::base::TCFType;
5use security_framework_sys::secure_transport::*;
6use std::ptr;
7use std::slice;
8
9use crate::base::Result;
10use crate::certificate::SecCertificate;
11use crate::cvt;
12use crate::secure_transport::{MidHandshakeSslStream, SslContext};
13
14/// An extension trait adding OSX specific functionality to the `SslContext`
15/// type.
16pub trait SslContextExt {
17    /// Returns the DER encoded data specifying the parameters used for
18    /// Diffie-Hellman key exchange.
19    fn diffie_hellman_params(&self) -> Result<Option<&[u8]>>;
20
21    /// Sets the parameters used for Diffie-Hellman key exchange, in the
22    /// DER format used by OpenSSL.
23    ///
24    /// If a cipher suite which uses Diffie-Hellman key exchange is selected,
25    /// parameters will automatically be generated if none are provided with
26    /// this method, but this process can take up to 30 seconds.
27    ///
28    /// This can only be called on server-side sessions.
29    fn set_diffie_hellman_params(&mut self, dh_params: &[u8]) -> Result<()>;
30
31    /// Returns the certificate authorities used to validate client
32    /// certificates.
33    fn certificate_authorities(&self) -> Result<Option<Vec<SecCertificate>>>;
34
35    /// Sets the certificate authorities used to validate client certificates,
36    /// replacing any that are already present.
37    fn set_certificate_authorities(&mut self, certs: &[SecCertificate]) -> Result<()>;
38
39    /// Adds certificate authorities used to validate client certificates.
40    fn add_certificate_authorities(&mut self, certs: &[SecCertificate]) -> Result<()>;
41
42    /// If enabled, server identity changes are allowed during renegotiation.
43    ///
44    /// It is disabled by default to protect against triple handshake attacks.
45    fn allow_server_identity_change(&self) -> Result<bool>;
46
47    /// If enabled, server identity changes are allowed during renegotiation.
48    ///
49    /// It is disabled by default to protect against triple handshake attacks.
50    #[deprecated(note = "kSSLSessionOptionAllowServerIdentityChange is deprecated by Apple")]
51    fn set_allow_server_identity_change(&mut self, value: bool) -> Result<()>;
52
53    /// If enabled, fallback countermeasures will be used during negotiation.
54    ///
55    /// It should be enabled when renegotiating with a peer with a lower
56    /// maximum protocol version due to an earlier failure to connect.
57    fn fallback(&self) -> Result<bool>;
58
59    /// If enabled, fallback countermeasures will be used during negotiation.
60    ///
61    /// It should be enabled when renegotiating with a peer with a lower
62    /// maximum protocol version due to an earlier failure to connect.
63    #[deprecated(note = "kSSLSessionOptionFallback is deprecated by Apple")]
64    fn set_fallback(&mut self, value: bool) -> Result<()>;
65
66    /// If enabled, the handshake process will pause and return when the client
67    /// hello is recieved to support server name identification.
68    fn break_on_client_hello(&self) -> Result<bool>;
69
70    /// If enabled, the handshake process will pause and return when the client
71    /// hello is recieved to support server name identification.
72    #[deprecated(note = "kSSLSessionOptionBreakOnClientHello is deprecated by Apple")]
73    fn set_break_on_client_hello(&mut self, value: bool) -> Result<()>;
74}
75
76macro_rules! impl_options {
77    ($($(#[$a:meta])* const $opt:ident: $get:ident & $set:ident,)*) => {
78        $(
79            #[allow(deprecated)]
80            $(#[$a])*
81            #[inline]
82            fn $set(&mut self, value: bool) -> Result<()> {
83                unsafe {
84                    cvt(SSLSetSessionOption(self.as_inner(),
85                                            $opt,
86                                            ::core_foundation::base::Boolean::from(value)))
87                }
88            }
89
90            #[allow(deprecated)]
91            $(#[$a])*
92            #[inline]
93            fn $get(&self) -> Result<bool> {
94                let mut value = 0;
95                unsafe { cvt(SSLGetSessionOption(self.as_inner(), $opt, &mut value))?; }
96                Ok(value != 0)
97            }
98        )*
99    }
100}
101
102impl SslContextExt for SslContext {
103    fn diffie_hellman_params(&self) -> Result<Option<&[u8]>> {
104        unsafe {
105            let mut ptr = ptr::null();
106            let mut len = 0;
107            cvt(SSLGetDiffieHellmanParams(
108                self.as_inner(),
109                &mut ptr,
110                &mut len,
111            ))?;
112            if ptr.is_null() {
113                Ok(None)
114            } else {
115                Ok(Some(slice::from_raw_parts(ptr.cast::<u8>(), len)))
116            }
117        }
118    }
119
120    fn set_diffie_hellman_params(&mut self, dh_params: &[u8]) -> Result<()> {
121        unsafe {
122            cvt(SSLSetDiffieHellmanParams(
123                self.as_inner(),
124                dh_params.as_ptr().cast(),
125                dh_params.len(),
126            ))
127        }
128    }
129
130    fn certificate_authorities(&self) -> Result<Option<Vec<SecCertificate>>> {
131        unsafe {
132            let mut raw_certs = ptr::null();
133            cvt(SSLCopyCertificateAuthorities(
134                self.as_inner(),
135                &mut raw_certs,
136            ))?;
137            if raw_certs.is_null() {
138                Ok(None)
139            } else {
140                let certs = CFArray::<SecCertificate>::wrap_under_create_rule(raw_certs)
141                    .iter()
142                    .map(|c| c.clone())
143                    .collect();
144                Ok(Some(certs))
145            }
146        }
147    }
148
149    fn set_certificate_authorities(&mut self, certs: &[SecCertificate]) -> Result<()> {
150        unsafe {
151            let certs = CFArray::from_CFTypes(certs);
152            cvt(SSLSetCertificateAuthorities(
153                self.as_inner(),
154                certs.as_CFTypeRef(),
155                1,
156            ))
157        }
158    }
159
160    fn add_certificate_authorities(&mut self, certs: &[SecCertificate]) -> Result<()> {
161        unsafe {
162            let certs = CFArray::from_CFTypes(certs);
163            cvt(SSLSetCertificateAuthorities(
164                self.as_inner(),
165                certs.as_CFTypeRef(),
166                0,
167            ))
168        }
169    }
170
171    impl_options! {
172        const kSSLSessionOptionAllowServerIdentityChange: allow_server_identity_change & set_allow_server_identity_change,
173        const kSSLSessionOptionFallback: fallback & set_fallback,
174        const kSSLSessionOptionBreakOnClientHello: break_on_client_hello & set_break_on_client_hello,
175    }
176}
177
178/// An extension trait adding OSX specific functionality to the
179/// `MidHandshakeSslStream` type.
180pub trait MidHandshakeSslStreamExt {
181    /// Returns `true` iff `break_on_client_hello` was set and the handshake
182    /// has progressed to that point.
183    fn client_hello_received(&self) -> bool;
184}
185
186impl<S> MidHandshakeSslStreamExt for MidHandshakeSslStream<S> {
187    fn client_hello_received(&self) -> bool {
188        self.error().code() == errSSLClientHelloReceived
189    }
190}
191
192#[cfg(test)]
193mod test {
194    use std::io::prelude::*;
195    use std::net::{TcpListener, TcpStream};
196    use std::thread;
197    use tempfile::tempdir;
198
199    use super::*;
200    use crate::cipher_suite::CipherSuite;
201    use crate::os::macos::test::identity;
202    use crate::secure_transport::*;
203    use crate::test::ca_certificate;
204
205    #[test]
206    fn server_client() {
207        let listener = p!(TcpListener::bind("localhost:0"));
208        let port = p!(listener.local_addr()).port();
209
210        let handle = thread::spawn(move || {
211            let dir = p!(tempdir());
212
213            let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
214            let identity = identity(dir.path());
215            p!(ctx.set_certificate(&identity, &[]));
216
217            let stream = p!(listener.accept()).0;
218            let mut stream = p!(ctx.handshake(stream));
219
220            let mut buf = [0; 12];
221            p!(stream.read(&mut buf));
222            assert_eq!(&buf[..], b"hello world!");
223        });
224
225        let mut ctx = p!(SslContext::new(SslProtocolSide::CLIENT, SslConnectionType::STREAM));
226        p!(ctx.set_break_on_server_auth(true));
227        let stream = p!(TcpStream::connect(("localhost", port)));
228
229        let stream = match ctx.handshake(stream) {
230            Ok(_) => panic!("unexpected success"),
231            Err(HandshakeError::Interrupted(stream)) => stream,
232            Err(err) => panic!("unexpected error {err:?}"),
233        };
234
235        assert!(stream.server_auth_completed());
236        let mut peer_trust = p!(stream.context().peer_trust2()).unwrap();
237        p!(peer_trust.set_anchor_certificates(&[ca_certificate()]));
238        p!(peer_trust.evaluate_with_error());
239
240        let mut stream = p!(stream.handshake());
241        p!(stream.write_all(b"hello world!"));
242
243        handle.join().unwrap();
244    }
245
246    #[test]
247    fn server_client_builders() {
248        let listener = p!(TcpListener::bind("localhost:0"));
249        let port = p!(listener.local_addr()).port();
250
251        let handle = thread::spawn(move || {
252            let dir = p!(tempdir());
253
254            let identity = identity(dir.path());
255            let builder = ServerBuilder::new(&identity, &[]);
256
257            let stream = p!(listener.accept()).0;
258            let mut stream = p!(builder.handshake(stream));
259
260            let mut buf = [0; 12];
261            p!(stream.read(&mut buf));
262            assert_eq!(&buf[..], b"hello world!");
263        });
264
265        let stream = p!(TcpStream::connect(("localhost", port)));
266        let mut stream = p!(ClientBuilder::new()
267            .anchor_certificates(&[ca_certificate()])
268            .handshake("foobar.com", stream));
269
270        p!(stream.write_all(b"hello world!"));
271
272        handle.join().unwrap();
273    }
274
275    #[test]
276    fn client_bad_cert() {
277        let _ = env_logger::try_init();
278
279        let listener = p!(TcpListener::bind("localhost:0"));
280        let port = p!(listener.local_addr()).port();
281
282        let handle = thread::spawn(move || {
283            let dir = p!(tempdir());
284
285            let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
286            let identity = identity(dir.path());
287            p!(ctx.set_certificate(&identity, &[]));
288
289            let stream = p!(listener.accept()).0;
290            let _ = ctx.handshake(stream);
291        });
292
293        let stream = p!(TcpStream::connect(("localhost", port)));
294        assert!(ClientBuilder::new().handshake("foobar.com", stream).is_err());
295
296        handle.join().unwrap();
297    }
298
299    #[test]
300    fn client() {
301        let listener = p!(TcpListener::bind("localhost:0"));
302        let port = p!(listener.local_addr()).port();
303
304        let handle = thread::spawn(move || {
305            let dir = p!(tempdir());
306
307            let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
308            let identity = identity(dir.path());
309            p!(ctx.set_certificate(&identity, &[]));
310
311            let stream = p!(listener.accept()).0;
312            let mut stream = p!(ctx.handshake(stream));
313
314            let mut buf = [0; 12];
315            p!(stream.read(&mut buf));
316            assert_eq!(&buf[..], b"hello world!");
317        });
318
319        let stream = p!(TcpStream::connect(("localhost", port)));
320        let mut stream = p!(ClientBuilder::new()
321            .anchor_certificates(&[ca_certificate()])
322            .handshake("foobar.com", stream));
323        p!(stream.write_all(b"hello world!"));
324
325        handle.join().unwrap();
326    }
327
328    #[test]
329    fn negotiated_cipher() {
330        let listener = p!(TcpListener::bind("localhost:0"));
331        let port = p!(listener.local_addr()).port();
332
333        let handle = thread::spawn(move || {
334            let dir = p!(tempdir());
335
336            let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
337            let identity = identity(dir.path());
338            p!(ctx.set_certificate(&identity, &[]));
339            p!(ctx.set_enabled_ciphers(&[
340                CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
341                CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
342            ]));
343
344            let stream = p!(listener.accept()).0;
345            let mut stream = p!(ctx.handshake(stream));
346            assert_eq!(
347                CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
348                p!(stream.context().negotiated_cipher())
349            );
350            let mut buf = [0; 1];
351            p!(stream.read(&mut buf));
352        });
353
354        let mut ctx = p!(SslContext::new(SslProtocolSide::CLIENT, SslConnectionType::STREAM));
355        p!(ctx.set_break_on_server_auth(true));
356        p!(ctx.set_enabled_ciphers(&[
357            CipherSuite::TLS_DHE_PSK_WITH_AES_128_CBC_SHA256,
358            CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
359        ]));
360        let stream = p!(TcpStream::connect(("localhost", port)));
361
362        let stream = match ctx.handshake(stream) {
363            Ok(_) => panic!("unexpected success"),
364            Err(HandshakeError::Interrupted(stream)) => stream,
365            Err(err) => panic!("unexpected error {err:?}"),
366        };
367
368        let mut stream = p!(stream.handshake());
369        assert_eq!(
370            CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
371            p!(stream.context().negotiated_cipher())
372        );
373        p!(stream.write(&[0]));
374
375        handle.join().unwrap();
376    }
377
378    #[test]
379    fn dh_params() {
380        let params = include_bytes!("../../../test/dhparam.der");
381
382        let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
383        assert!(p!(ctx.diffie_hellman_params()).is_none());
384        p!(ctx.set_diffie_hellman_params(params));
385        assert_eq!(p!(ctx.diffie_hellman_params()).unwrap(), &params[..]);
386    }
387
388    #[test]
389    fn try_authenticate_no_cert() {
390        let listener = p!(TcpListener::bind("localhost:0"));
391        let port = p!(listener.local_addr()).port();
392
393        let handle = thread::spawn(move || {
394            let dir = p!(tempdir());
395
396            let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
397            let identity = identity(dir.path());
398            p!(ctx.set_certificate(&identity, &[]));
399            p!(ctx.set_client_side_authenticate(SslAuthenticate::TRY));
400            let cert = ca_certificate();
401            p!(ctx.add_certificate_authorities(&[cert]));
402
403            let stream = p!(listener.accept()).0;
404            let mut stream = p!(ctx.handshake(stream));
405            let mut buf = [0; 1];
406            p!(stream.read(&mut buf));
407        });
408
409        let mut ctx = p!(SslContext::new(SslProtocolSide::CLIENT, SslConnectionType::STREAM));
410        p!(ctx.set_break_on_server_auth(true));
411        let stream = p!(TcpStream::connect(("localhost", port)));
412
413        let stream = match ctx.handshake(stream) {
414            Ok(_) => panic!("unexpected success"),
415            Err(HandshakeError::Interrupted(stream)) => stream,
416            Err(err) => panic!("unexpected error {err:?}"),
417        };
418
419        let mut stream = p!(stream.handshake());
420        p!(stream.write(&[0]));
421
422        handle.join().unwrap();
423    }
424
425    #[test]
426    fn always_authenticate_no_cert() {
427        let listener = p!(TcpListener::bind("localhost:0"));
428        let port = p!(listener.local_addr()).port();
429
430        let handle = thread::spawn(move || {
431            let dir = p!(tempdir());
432
433            let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
434            let identity = identity(dir.path());
435            p!(ctx.set_certificate(&identity, &[]));
436            p!(ctx.set_client_side_authenticate(SslAuthenticate::ALWAYS));
437
438            let stream = p!(listener.accept()).0;
439
440            match ctx.handshake(stream) {
441                Ok(_) => panic!("unexpected success"),
442                Err(HandshakeError::Failure(_)) => {},
443                Err(err) => panic!("unexpected error {err:?}"),
444            }
445        });
446
447        let mut ctx = p!(SslContext::new(SslProtocolSide::CLIENT, SslConnectionType::STREAM));
448        p!(ctx.set_break_on_server_auth(true));
449        let stream = p!(TcpStream::connect(("localhost", port)));
450
451        let stream = match ctx.handshake(stream) {
452            Ok(_) => panic!("unexpected success"),
453            Err(HandshakeError::Interrupted(stream)) => stream,
454            Err(err) => panic!("unexpected error {err:?}"),
455        };
456
457        match stream.handshake() {
458            Ok(_) => panic!("unexpected success"),
459            Err(HandshakeError::Failure(_)) => {},
460            Err(err) => panic!("unexpected error {err:?}"),
461        }
462
463        handle.join().unwrap();
464    }
465
466    #[test]
467    fn always_authenticate_with_cert() {
468        let listener = p!(TcpListener::bind("localhost:0"));
469        let port = p!(listener.local_addr()).port();
470
471        let handle = thread::spawn(move || {
472            let dir = p!(tempdir());
473
474            let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
475            let identity = identity(dir.path());
476            p!(ctx.set_certificate(&identity, &[]));
477            p!(ctx.set_client_side_authenticate(SslAuthenticate::ALWAYS));
478
479            let stream = p!(listener.accept()).0;
480
481            match ctx.handshake(stream) {
482                Ok(_) => panic!("unexpected success"),
483                Err(HandshakeError::Failure(_)) => {},
484                Err(err) => panic!("unexpected error {err:?}"),
485            }
486        });
487
488        let mut ctx = p!(SslContext::new(SslProtocolSide::CLIENT, SslConnectionType::STREAM));
489        p!(ctx.set_break_on_server_auth(true));
490        let dir = p!(tempdir());
491        let identity = identity(dir.path());
492        p!(ctx.set_certificate(&identity, &[]));
493        let stream = p!(TcpStream::connect(("localhost", port)));
494
495        let stream = match ctx.handshake(stream) {
496            Ok(_) => panic!("unexpected success"),
497            Err(HandshakeError::Interrupted(stream)) => stream,
498            Err(err) => panic!("unexpected error {err:?}"),
499        };
500
501        match stream.handshake() {
502            Ok(_) => panic!("unexpected success"),
503            Err(HandshakeError::Failure(_)) => {},
504            Err(err) => panic!("unexpected error {err:?}"),
505        }
506
507        handle.join().unwrap();
508    }
509
510    #[test]
511    fn certificate_authorities() {
512        let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
513        assert!(p!(ctx.certificate_authorities()).is_none());
514        p!(ctx.set_certificate_authorities(&[ca_certificate()]));
515        assert_eq!(p!(ctx.certificate_authorities()).unwrap().len(), 1);
516    }
517
518    #[test]
519    fn close() {
520        let listener = p!(TcpListener::bind("localhost:0"));
521        let port = p!(listener.local_addr()).port();
522
523        let handle = thread::spawn(move || {
524            let dir = p!(tempdir());
525
526            let identity = identity(dir.path());
527            let builder = ServerBuilder::new(&identity, &[]);
528
529            let stream = p!(listener.accept()).0;
530            let mut stream = p!(builder.handshake(stream));
531            p!(stream.close());
532        });
533
534        let stream = p!(TcpStream::connect(("localhost", port)));
535        let mut stream = p!(ClientBuilder::new()
536            .anchor_certificates(&[ca_certificate()])
537            .handshake("foobar.com", stream));
538
539        let mut buf = [0; 1];
540        assert_eq!(p!(stream.read(&mut buf)), 0);
541        p!(stream.close());
542
543        p!(handle.join());
544    }
545
546    #[test]
547    fn short_read() {
548        let listener = p!(TcpListener::bind("localhost:0"));
549        let port = p!(listener.local_addr()).port();
550
551        let handle = thread::spawn(move || {
552            let dir = p!(tempdir());
553
554            let identity = identity(dir.path());
555            let builder = ServerBuilder::new(&identity, &[]);
556
557            let stream = p!(listener.accept()).0;
558            let mut stream = p!(builder.handshake(stream));
559
560            stream.write_all(b"hello").unwrap();
561            // make sure stream doesn't close
562            stream
563        });
564
565        let stream = p!(TcpStream::connect(("localhost", port)));
566        let mut stream = p!(ClientBuilder::new()
567            .anchor_certificates(&[ca_certificate()])
568            .handshake("foobar.com", stream));
569
570        let mut b = [0; 1];
571        stream.read_exact(&mut b).unwrap();
572        assert_eq!(stream.context().buffered_read_size().unwrap(), 4);
573        let mut b = [0; 5];
574        let read = stream.read(&mut b).unwrap();
575        assert_eq!(read, 4);
576
577        p!(handle.join());
578    }
579}