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