apple_security_framework/os/macos/
secure_transport.rs

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