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::certificate;
204
205    #[test]
206    #[ignore = "needs certs re-generated"]
207    fn server_client() {
208        let listener = p!(TcpListener::bind("localhost:0"));
209        let port = p!(listener.local_addr()).port();
210
211        let handle = thread::spawn(move || {
212            let dir = p!(tempdir());
213
214            let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
215            let identity = identity(dir.path());
216            p!(ctx.set_certificate(&identity, &[]));
217
218            let stream = p!(listener.accept()).0;
219            let mut stream = p!(ctx.handshake(stream));
220
221            let mut buf = [0; 12];
222            p!(stream.read(&mut buf));
223            assert_eq!(&buf[..], b"hello world!");
224        });
225
226        let mut ctx = p!(SslContext::new(SslProtocolSide::CLIENT, SslConnectionType::STREAM));
227        p!(ctx.set_break_on_server_auth(true));
228        let stream = p!(TcpStream::connect(("localhost", port)));
229
230        let stream = match ctx.handshake(stream) {
231            Ok(_) => panic!("unexpected success"),
232            Err(HandshakeError::Interrupted(stream)) => stream,
233            Err(err) => panic!("unexpected error {err:?}"),
234        };
235
236        assert!(stream.server_auth_completed());
237        let mut peer_trust = p!(stream.context().peer_trust2()).unwrap();
238        p!(peer_trust.set_anchor_certificates(&[certificate()]));
239        p!(peer_trust.evaluate_with_error());
240
241        let mut stream = p!(stream.handshake());
242        p!(stream.write_all(b"hello world!"));
243
244        handle.join().unwrap();
245    }
246
247    #[test]
248    #[ignore]
249    fn server_client_builders() {
250        let listener = p!(TcpListener::bind("localhost:0"));
251        let port = p!(listener.local_addr()).port();
252
253        let handle = thread::spawn(move || {
254            let dir = p!(tempdir());
255
256            let identity = identity(dir.path());
257            let builder = ServerBuilder::new(&identity, &[]);
258
259            let stream = p!(listener.accept()).0;
260            let mut stream = p!(builder.handshake(stream));
261
262            let mut buf = [0; 12];
263            p!(stream.read(&mut buf));
264            assert_eq!(&buf[..], b"hello world!");
265        });
266
267        let stream = p!(TcpStream::connect(("localhost", port)));
268        let mut stream = p!(ClientBuilder::new()
269            .anchor_certificates(&[certificate()])
270            .handshake("foobar.com", stream));
271
272        p!(stream.write_all(b"hello world!"));
273
274        handle.join().unwrap();
275    }
276
277    #[test]
278    fn client_bad_cert() {
279        let _ = env_logger::try_init();
280
281        let listener = p!(TcpListener::bind("localhost:0"));
282        let port = p!(listener.local_addr()).port();
283
284        let handle = thread::spawn(move || {
285            let dir = p!(tempdir());
286
287            let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
288            let identity = identity(dir.path());
289            p!(ctx.set_certificate(&identity, &[]));
290
291            let stream = p!(listener.accept()).0;
292            let _ = ctx.handshake(stream);
293        });
294
295        let stream = p!(TcpStream::connect(("localhost", port)));
296        assert!(ClientBuilder::new().handshake("foobar.com", stream).is_err());
297
298        handle.join().unwrap();
299    }
300
301    #[test]
302    #[ignore]
303    fn client() {
304        let listener = p!(TcpListener::bind("localhost:0"));
305        let port = p!(listener.local_addr()).port();
306
307        let handle = thread::spawn(move || {
308            let dir = p!(tempdir());
309
310            let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
311            let identity = identity(dir.path());
312            p!(ctx.set_certificate(&identity, &[]));
313
314            let stream = p!(listener.accept()).0;
315            let mut stream = p!(ctx.handshake(stream));
316
317            let mut buf = [0; 12];
318            p!(stream.read(&mut buf));
319            assert_eq!(&buf[..], b"hello world!");
320        });
321
322        let stream = p!(TcpStream::connect(("localhost", port)));
323        let mut stream = p!(ClientBuilder::new()
324            .anchor_certificates(&[certificate()])
325            .handshake("foobar.com", stream));
326        p!(stream.write_all(b"hello world!"));
327
328        handle.join().unwrap();
329    }
330
331    #[test]
332    fn negotiated_cipher() {
333        let listener = p!(TcpListener::bind("localhost:0"));
334        let port = p!(listener.local_addr()).port();
335
336        let handle = thread::spawn(move || {
337            let dir = p!(tempdir());
338
339            let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
340            let identity = identity(dir.path());
341            p!(ctx.set_certificate(&identity, &[]));
342            p!(ctx.set_enabled_ciphers(&[
343                CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
344                CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
345            ]));
346
347            let stream = p!(listener.accept()).0;
348            let mut stream = p!(ctx.handshake(stream));
349            assert_eq!(
350                CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
351                p!(stream.context().negotiated_cipher())
352            );
353            let mut buf = [0; 1];
354            p!(stream.read(&mut buf));
355        });
356
357        let mut ctx = p!(SslContext::new(SslProtocolSide::CLIENT, SslConnectionType::STREAM));
358        p!(ctx.set_break_on_server_auth(true));
359        p!(ctx.set_enabled_ciphers(&[
360            CipherSuite::TLS_DHE_PSK_WITH_AES_128_CBC_SHA256,
361            CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
362        ]));
363        let stream = p!(TcpStream::connect(("localhost", port)));
364
365        let stream = match ctx.handshake(stream) {
366            Ok(_) => panic!("unexpected success"),
367            Err(HandshakeError::Interrupted(stream)) => stream,
368            Err(err) => panic!("unexpected error {err:?}"),
369        };
370
371        let mut stream = p!(stream.handshake());
372        assert_eq!(
373            CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
374            p!(stream.context().negotiated_cipher())
375        );
376        p!(stream.write(&[0]));
377
378        handle.join().unwrap();
379    }
380
381    #[test]
382    fn dh_params() {
383        let params = include_bytes!("../../../test/dhparam.der");
384
385        let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
386        assert!(p!(ctx.diffie_hellman_params()).is_none());
387        p!(ctx.set_diffie_hellman_params(params));
388        assert_eq!(p!(ctx.diffie_hellman_params()).unwrap(), &params[..]);
389    }
390
391    #[test]
392    fn try_authenticate_no_cert() {
393        let listener = p!(TcpListener::bind("localhost:0"));
394        let port = p!(listener.local_addr()).port();
395
396        let handle = thread::spawn(move || {
397            let dir = p!(tempdir());
398
399            let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
400            let identity = identity(dir.path());
401            p!(ctx.set_certificate(&identity, &[]));
402            p!(ctx.set_client_side_authenticate(SslAuthenticate::TRY));
403            let cert = certificate();
404            p!(ctx.add_certificate_authorities(&[cert]));
405
406            let stream = p!(listener.accept()).0;
407            let mut stream = p!(ctx.handshake(stream));
408            let mut buf = [0; 1];
409            p!(stream.read(&mut buf));
410        });
411
412        let mut ctx = p!(SslContext::new(SslProtocolSide::CLIENT, SslConnectionType::STREAM));
413        p!(ctx.set_break_on_server_auth(true));
414        let stream = p!(TcpStream::connect(("localhost", port)));
415
416        let stream = match ctx.handshake(stream) {
417            Ok(_) => panic!("unexpected success"),
418            Err(HandshakeError::Interrupted(stream)) => stream,
419            Err(err) => panic!("unexpected error {err:?}"),
420        };
421
422        let mut stream = p!(stream.handshake());
423        p!(stream.write(&[0]));
424
425        handle.join().unwrap();
426    }
427
428    #[test]
429    fn always_authenticate_no_cert() {
430        let listener = p!(TcpListener::bind("localhost:0"));
431        let port = p!(listener.local_addr()).port();
432
433        let handle = thread::spawn(move || {
434            let dir = p!(tempdir());
435
436            let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
437            let identity = identity(dir.path());
438            p!(ctx.set_certificate(&identity, &[]));
439            p!(ctx.set_client_side_authenticate(SslAuthenticate::ALWAYS));
440
441            let stream = p!(listener.accept()).0;
442
443            match ctx.handshake(stream) {
444                Ok(_) => panic!("unexpected success"),
445                Err(HandshakeError::Failure(_)) => {},
446                Err(err) => panic!("unexpected error {err:?}"),
447            }
448        });
449
450        let mut ctx = p!(SslContext::new(SslProtocolSide::CLIENT, SslConnectionType::STREAM));
451        p!(ctx.set_break_on_server_auth(true));
452        let stream = p!(TcpStream::connect(("localhost", port)));
453
454        let stream = match ctx.handshake(stream) {
455            Ok(_) => panic!("unexpected success"),
456            Err(HandshakeError::Interrupted(stream)) => stream,
457            Err(err) => panic!("unexpected error {err:?}"),
458        };
459
460        match stream.handshake() {
461            Ok(_) => panic!("unexpected success"),
462            Err(HandshakeError::Failure(_)) => {},
463            Err(err) => panic!("unexpected error {err:?}"),
464        }
465
466        handle.join().unwrap();
467    }
468
469    #[test]
470    fn always_authenticate_with_cert() {
471        let listener = p!(TcpListener::bind("localhost:0"));
472        let port = p!(listener.local_addr()).port();
473
474        let handle = thread::spawn(move || {
475            let dir = p!(tempdir());
476
477            let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
478            let identity = identity(dir.path());
479            p!(ctx.set_certificate(&identity, &[]));
480            p!(ctx.set_client_side_authenticate(SslAuthenticate::ALWAYS));
481
482            let stream = p!(listener.accept()).0;
483
484            match ctx.handshake(stream) {
485                Ok(_) => panic!("unexpected success"),
486                Err(HandshakeError::Failure(_)) => {},
487                Err(err) => panic!("unexpected error {err:?}"),
488            }
489        });
490
491        let mut ctx = p!(SslContext::new(SslProtocolSide::CLIENT, SslConnectionType::STREAM));
492        p!(ctx.set_break_on_server_auth(true));
493        let dir = p!(tempdir());
494        let identity = identity(dir.path());
495        p!(ctx.set_certificate(&identity, &[]));
496        let stream = p!(TcpStream::connect(("localhost", port)));
497
498        let stream = match ctx.handshake(stream) {
499            Ok(_) => panic!("unexpected success"),
500            Err(HandshakeError::Interrupted(stream)) => stream,
501            Err(err) => panic!("unexpected error {err:?}"),
502        };
503
504        match stream.handshake() {
505            Ok(_) => panic!("unexpected success"),
506            Err(HandshakeError::Failure(_)) => {},
507            Err(err) => panic!("unexpected error {err:?}"),
508        }
509
510        handle.join().unwrap();
511    }
512
513    #[test]
514    fn certificate_authorities() {
515        let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
516        assert!(p!(ctx.certificate_authorities()).is_none());
517        p!(ctx.set_certificate_authorities(&[certificate()]));
518        assert_eq!(p!(ctx.certificate_authorities()).unwrap().len(), 1);
519    }
520
521    #[test]
522    #[ignore]
523    fn close() {
524        let listener = p!(TcpListener::bind("localhost:0"));
525        let port = p!(listener.local_addr()).port();
526
527        let handle = thread::spawn(move || {
528            let dir = p!(tempdir());
529
530            let identity = identity(dir.path());
531            let builder = ServerBuilder::new(&identity, &[]);
532
533            let stream = p!(listener.accept()).0;
534            let mut stream = p!(builder.handshake(stream));
535            p!(stream.close());
536        });
537
538        let stream = p!(TcpStream::connect(("localhost", port)));
539        let mut stream = p!(ClientBuilder::new()
540            .anchor_certificates(&[certificate()])
541            .handshake("foobar.com", stream));
542
543        let mut buf = [0; 1];
544        assert_eq!(p!(stream.read(&mut buf)), 0);
545        p!(stream.close());
546
547        p!(handle.join());
548    }
549
550    #[test]
551    #[ignore]
552    fn short_read() {
553        let listener = p!(TcpListener::bind("localhost:0"));
554        let port = p!(listener.local_addr()).port();
555
556        let handle = thread::spawn(move || {
557            let dir = p!(tempdir());
558
559            let identity = identity(dir.path());
560            let builder = ServerBuilder::new(&identity, &[]);
561
562            let stream = p!(listener.accept()).0;
563            let mut stream = p!(builder.handshake(stream));
564
565            stream.write_all(b"hello").unwrap();
566            // make sure stream doesn't close
567            stream
568        });
569
570        let stream = p!(TcpStream::connect(("localhost", port)));
571        let mut stream = p!(ClientBuilder::new()
572            .anchor_certificates(&[certificate()])
573            .handshake("foobar.com", stream));
574
575        let mut b = [0; 1];
576        stream.read_exact(&mut b).unwrap();
577        assert_eq!(stream.context().buffered_read_size().unwrap(), 4);
578        let mut b = [0; 5];
579        let read = stream.read(&mut b).unwrap();
580        assert_eq!(read, 4);
581
582        p!(handle.join());
583    }
584}