1 /*****************************************************************************
3 *****************************************************************************
4 * Copyright (C) 2013 David Fuhrmann
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19 *****************************************************************************/
21 /*****************************************************************************
23 *****************************************************************************/
29 #include <vlc_common.h>
30 #include <vlc_plugin.h>
32 #include <vlc_dialog.h>
34 #include <Security/Security.h>
35 #include <Security/SecureTransport.h>
36 #include <TargetConditionals.h>
38 /* From MacErrors.h (cannot be included because it isn't present in iOS: */
43 /*****************************************************************************
45 *****************************************************************************/
46 static int OpenClient (vlc_tls_creds_t
*);
47 static void CloseClient (vlc_tls_creds_t
*);
50 static int OpenServer (vlc_tls_creds_t
*crd
, const char *cert
, const char *key
);
51 static void CloseServer (vlc_tls_creds_t
*);
55 set_description(N_("TLS support for OS X and iOS"))
56 set_capability("tls client", 2)
57 set_callbacks(OpenClient
, CloseClient
)
58 set_category(CAT_ADVANCED
)
59 set_subcategory(SUBCAT_ADVANCED_NETWORK
)
62 * The server module currently uses an OSX only API, to be compatible with 10.6.
63 If the module is needed on iOS, then the "modern" keychain lookup API need to be
68 set_description(N_("TLS server support for OS X"))
69 set_capability("tls server", 2)
70 set_callbacks(OpenServer
, CloseServer
)
71 set_category(CAT_ADVANCED
)
72 set_subcategory(SUBCAT_ADVANCED_NETWORK
)
73 #endif /* !TARGET_OS_IPHONE */
78 #define cfKeyHost CFSTR("host")
79 #define cfKeyCertificate CFSTR("certificate")
82 CFMutableArrayRef whitelist
;
84 /* valid in server mode */
85 CFArrayRef server_cert_chain
;
86 } vlc_tls_creds_sys_t
;
90 SSLContextRef p_context
;
91 vlc_tls_creds_sys_t
*p_cred
;
92 size_t i_send_buffered_bytes
;
101 static int st_Error (vlc_tls_t
*obj
, int val
)
103 vlc_tls_st_t
*sys
= (vlc_tls_st_t
*)obj
;
107 case errSSLWouldBlock
:
111 case errSSLClosedGraceful
:
112 case errSSLClosedAbort
:
113 msg_Dbg(sys
->obj
, "Connection closed with code %d", val
);
117 msg_Err(sys
->obj
, "Found error %d", val
);
124 * Read function called by secure transport for socket read.
126 * Function is based on Apples SSLSample sample code.
128 static OSStatus
st_SocketReadFunc (SSLConnectionRef connection
,
130 size_t *dataLength
) {
132 vlc_tls_t
*session
= (vlc_tls_t
*)connection
;
133 vlc_tls_st_t
*sys
= (vlc_tls_st_t
*)session
;
136 .iov_len
= *dataLength
,
138 OSStatus retValue
= noErr
;
140 while (iov
.iov_len
> 0) {
141 ssize_t val
= sys
->sock
->readv(sys
->sock
, &iov
, 1);
144 msg_Dbg(sys
->obj
, "found eof");
145 retValue
= errSSLClosedGraceful
;
146 } else { /* do the switch */
149 /* connection closed */
150 retValue
= errSSLClosedGraceful
;
153 retValue
= errSSLClosedAbort
;
156 retValue
= errSSLWouldBlock
;
157 sys
->b_blocking_send
= false;
160 msg_Err(sys
->obj
, "try to read %zu bytes, "
161 "got error %d", iov
.iov_len
, errno
);
169 iov
.iov_base
= (char *)iov
.iov_base
+ val
;
173 *dataLength
-= iov
.iov_len
;
178 * Write function called by secure transport for socket read.
180 * Function is based on Apples SSLSample sample code.
182 static OSStatus
st_SocketWriteFunc (SSLConnectionRef connection
,
184 size_t *dataLength
) {
186 vlc_tls_t
*session
= (vlc_tls_t
*)connection
;
187 vlc_tls_st_t
*sys
= (vlc_tls_st_t
*)session
;
189 .iov_base
= (void *)data
,
190 .iov_len
= *dataLength
,
192 OSStatus retValue
= noErr
;
194 while (iov
.iov_len
> 0) {
195 ssize_t val
= sys
->sock
->writev(sys
->sock
, &iov
, 1);
199 retValue
= errSSLWouldBlock
;
200 sys
->b_blocking_send
= true;
205 retValue
= errSSLClosedAbort
;
209 msg_Err(sys
->obj
, "error while writing: %d", errno
);
216 iov
.iov_base
= (char *)iov
.iov_base
+ val
;
220 *dataLength
-= iov
.iov_len
;
224 static int st_validateServerCertificate (vlc_tls_t
*session
, const char *hostname
) {
226 vlc_tls_st_t
*sys
= (vlc_tls_st_t
*)session
;
228 SecCertificateRef leaf_cert
= NULL
;
230 SecTrustRef trust
= NULL
;
231 OSStatus ret
= SSLCopyPeerTrust(sys
->p_context
, &trust
);
232 if (ret
!= noErr
|| trust
== NULL
) {
233 msg_Err(sys
->obj
, "error getting certifictate chain");
237 CFStringRef cfHostname
= CFStringCreateWithCString(kCFAllocatorDefault
,
239 kCFStringEncodingUTF8
);
242 /* enable default root / anchor certificates */
243 ret
= SecTrustSetAnchorCertificates(trust
, NULL
);
245 msg_Err(sys
->obj
, "error setting anchor certificates");
250 SecTrustResultType trust_eval_result
= 0;
252 ret
= SecTrustEvaluate(trust
, &trust_eval_result
);
254 msg_Err(sys
->obj
, "error calling SecTrustEvaluate");
259 switch (trust_eval_result
) {
260 case kSecTrustResultUnspecified
:
261 case kSecTrustResultProceed
:
262 msg_Dbg(sys
->obj
, "cerfificate verification successful, result is %d", trust_eval_result
);
266 case kSecTrustResultRecoverableTrustFailure
:
267 case kSecTrustResultDeny
:
269 msg_Warn(sys
->obj
, "cerfificate verification failed, result is %d", trust_eval_result
);
272 /* get leaf certificate */
273 /* SSLCopyPeerCertificates is only available on OSX 10.5 or later */
274 #if !TARGET_OS_IPHONE
275 CFArrayRef cert_chain
= NULL
;
276 ret
= SSLCopyPeerCertificates(sys
->p_context
, &cert_chain
);
277 if (ret
!= noErr
|| !cert_chain
) {
282 if (CFArrayGetCount(cert_chain
) == 0) {
283 CFRelease(cert_chain
);
288 leaf_cert
= (SecCertificateRef
)CFArrayGetValueAtIndex(cert_chain
, 0);
290 CFRelease(cert_chain
);
292 /* SecTrustGetCertificateAtIndex is only available on 10.7 or iOS */
293 if (SecTrustGetCertificateCount(trust
) == 0) {
298 leaf_cert
= SecTrustGetCertificateAtIndex(trust
, 0);
303 /* check if leaf already accepted */
304 CFIndex max
= CFArrayGetCount(sys
->p_cred
->whitelist
);
305 for (CFIndex i
= 0; i
< max
; ++i
) {
306 CFDictionaryRef dict
= CFArrayGetValueAtIndex(sys
->p_cred
->whitelist
, i
);
307 CFStringRef knownHost
= (CFStringRef
)CFDictionaryGetValue(dict
, cfKeyHost
);
308 SecCertificateRef knownCert
= (SecCertificateRef
)CFDictionaryGetValue(dict
, cfKeyCertificate
);
310 if (!knownHost
|| !knownCert
)
313 if (CFEqual(knownHost
, cfHostname
) && CFEqual(knownCert
, leaf_cert
)) {
314 msg_Warn(sys
->obj
, "certificate already accepted, continuing");
320 /* We do not show more certificate details yet because there is no proper API to get
321 a summary of the certificate. SecCertificateCopySubjectSummary is the only method
322 available on iOS and 10.6. More promising API functions such as
323 SecCertificateCopyLongDescription also print out the subject only, more or less.
324 But only showing the certificate subject is of no real help for the user.
325 We could use SecCertificateCopyValues, but then we need to parse all OID values for
326 ourself. This is too mad for just printing information the user will never check
330 const char *msg
= N_("You attempted to reach %s. "
331 "However the security certificate presented by the server "
332 "is unknown and could not be authenticated by any trusted "
333 "Certification Authority. "
334 "This problem may be caused by a configuration error "
335 "or an attempt to breach your security or your privacy.\n\n"
336 "If in doubt, abort now.\n");
337 int answer
= vlc_dialog_wait_question(sys
->obj
,
338 VLC_DIALOG_QUESTION_WARNING
, _("Abort"),
339 _("Accept certificate temporarily"),
340 NULL
, _("Insecure site"),
341 vlc_gettext (msg
), hostname
);
343 msg_Warn(sys
->obj
, "Proceeding despite of failed certificate validation");
345 /* save leaf certificate in whitelist */
346 const void *keys
[] = {cfKeyHost
, cfKeyCertificate
};
347 const void *values
[] = {cfHostname
, leaf_cert
};
348 CFDictionaryRef dict
= CFDictionaryCreate(kCFAllocatorDefault
,
350 &kCFTypeDictionaryKeyCallBacks
,
351 &kCFTypeDictionaryValueCallBacks
);
353 msg_Err(sys
->obj
, "error creating dict");
358 CFArrayAppendValue(sys
->p_cred
->whitelist
, dict
);
373 CFRelease(cfHostname
);
375 CFRelease(leaf_cert
);
381 * @return -1 on fatal error, 0 on successful handshake completion,
382 * 1 if more would-be blocking recv is needed,
383 * 2 if more would-be blocking send is required.
385 static int st_Handshake (vlc_tls_creds_t
*crd
, vlc_tls_t
*session
,
386 const char *host
, const char *service
,
387 char **restrict alp
) {
389 vlc_tls_st_t
*sys
= (vlc_tls_st_t
*)session
;
393 OSStatus retValue
= SSLHandshake(sys
->p_context
);
398 if (retValue
== errSSLWouldBlock
) {
399 msg_Dbg(crd
, "handshake is blocked, try again later");
400 return 1 + (sys
->b_blocking_send
? 1 : 0);
405 if (sys
->b_server_mode
== false && st_validateServerCertificate(session
, host
) != 0) {
408 msg_Dbg(crd
, "handshake completed successfully");
409 sys
->b_handshaked
= true;
412 case errSSLServerAuthCompleted
:
413 msg_Dbg(crd
, "SSLHandshake returned errSSLServerAuthCompleted, continuing handshake");
414 return st_Handshake(crd
, session
, host
, service
, alp
);
416 case errSSLConnectionRefused
:
417 msg_Err(crd
, "connection was refused");
419 case errSSLNegotiation
:
420 msg_Err(crd
, "cipher suite negotiation failed");
422 case errSSLFatalAlert
:
423 msg_Err(crd
, "fatal error occurred during handshake");
427 msg_Err(crd
, "handshake returned error %d", (int)retValue
);
432 static int st_GetFD (vlc_tls_t
*session
)
434 vlc_tls_st_t
*sys
= (vlc_tls_st_t
*)session
;
435 vlc_tls_t
*sock
= sys
->sock
;
437 return vlc_tls_GetFD(sock
);
441 * Sends data through a TLS session.
443 static ssize_t
st_Send (vlc_tls_t
*session
, const struct iovec
*iov
,
446 vlc_tls_st_t
*sys
= (vlc_tls_st_t
*)session
;
447 OSStatus ret
= noErr
;
449 if (unlikely(count
== 0))
453 * SSLWrite does not return the number of bytes actually written to
454 * the socket, but the number of bytes written to the internal cache.
456 * If return value is errSSLWouldBlock, the underlying socket cannot
457 * send all data, but the data is already cached. In this situation,
458 * we need to call SSLWrite again. To ensure this call even for the
459 * last bytes, we return EAGAIN. On the next call, we give no new data
460 * to SSLWrite until the error is not errSSLWouldBlock anymore.
462 * This code is adapted the same way as done in curl.
463 * (https://github.com/bagder/curl/blob/master/lib/curl_darwinssl.c#L2067)
466 /* EAGAIN is not expected by net_Write in this situation,
468 int againErr
= sys
->b_server_mode
? EAGAIN
: EINTR
;
471 if (sys
->i_send_buffered_bytes
> 0) {
472 ret
= SSLWrite(sys
->p_context
, NULL
, 0, &actualSize
);
475 /* actualSize remains zero because no new data send */
476 actualSize
= sys
->i_send_buffered_bytes
;
477 sys
->i_send_buffered_bytes
= 0;
479 } else if (ret
== errSSLWouldBlock
) {
485 ret
= SSLWrite(sys
->p_context
, iov
->iov_base
, iov
->iov_len
,
488 if (ret
== errSSLWouldBlock
) {
489 sys
->i_send_buffered_bytes
= iov
->iov_len
;
495 return ret
!= noErr
? st_Error(session
, ret
) : actualSize
;
499 * Receives data through a TLS session.
501 static ssize_t
st_Recv (vlc_tls_t
*session
, struct iovec
*iov
, unsigned count
)
503 vlc_tls_st_t
*sys
= (vlc_tls_st_t
*)session
;
505 if (unlikely(count
== 0))
509 OSStatus ret
= SSLRead(sys
->p_context
, iov
->iov_base
, iov
->iov_len
,
512 if (ret
== errSSLWouldBlock
&& actualSize
)
515 /* peer performed shutdown */
516 if (ret
== errSSLClosedNoNotify
|| ret
== errSSLClosedGraceful
) {
517 msg_Dbg(sys
->obj
, "Got close notification with code %i", (int)ret
);
521 return ret
!= noErr
? st_Error(session
, ret
) : actualSize
;
525 * Closes a TLS session.
528 static int st_SessionShutdown (vlc_tls_t
*session
, bool duplex
) {
530 vlc_tls_st_t
*sys
= (vlc_tls_st_t
*)session
;
532 msg_Dbg(sys
->obj
, "shutdown TLS session");
534 OSStatus ret
= noErr
;
537 if (sys
->b_handshaked
) {
538 ret
= SSLClose(sys
->p_context
);
542 msg_Warn(sys
->obj
, "Cannot close ssl context (%i)", (int)ret
);
549 static void st_SessionClose (vlc_tls_t
*session
) {
551 vlc_tls_st_t
*sys
= (vlc_tls_st_t
*)session
;
552 msg_Dbg(sys
->obj
, "close TLS session");
554 if (sys
->p_context
) {
556 CFRelease(sys
->p_context
);
558 if (SSLDisposeContext(sys
->p_context
) != noErr
) {
559 msg_Err(sys
->obj
, "error deleting context");
567 * Initializes a client-side TLS session.
570 static vlc_tls_t
*st_SessionOpenCommon(vlc_tls_creds_t
*crd
, vlc_tls_t
*sock
,
573 vlc_tls_st_t
*sys
= malloc(sizeof (*sys
));
574 if (unlikely(sys
== NULL
))
577 sys
->p_cred
= crd
->sys
;
578 sys
->b_handshaked
= false;
579 sys
->b_blocking_send
= false;
580 sys
->i_send_buffered_bytes
= 0;
581 sys
->p_context
= NULL
;
583 sys
->b_server_mode
= b_server
;
584 sys
->obj
= VLC_OBJECT(crd
);
586 vlc_tls_t
*tls
= &sys
->tls
;
588 tls
->get_fd
= st_GetFD
;
589 tls
->readv
= st_Recv
;
590 tls
->writev
= st_Send
;
591 tls
->shutdown
= st_SessionShutdown
;
592 tls
->close
= st_SessionClose
;
593 crd
->handshake
= st_Handshake
;
595 SSLContextRef p_context
= NULL
;
597 p_context
= SSLCreateContext(NULL
, b_server
? kSSLServerSide
: kSSLClientSide
, kSSLStreamType
);
598 if (p_context
== NULL
) {
599 msg_Err(crd
, "cannot create ssl context");
603 if (SSLNewContext(b_server
, &p_context
) != noErr
) {
604 msg_Err(crd
, "error calling SSLNewContext");
609 sys
->p_context
= p_context
;
611 OSStatus ret
= SSLSetIOFuncs(p_context
, st_SocketReadFunc
, st_SocketWriteFunc
);
613 msg_Err(crd
, "cannot set io functions");
617 ret
= SSLSetConnection(p_context
, tls
);
619 msg_Err(crd
, "cannot set connection");
626 st_SessionClose(tls
);
630 static vlc_tls_t
*st_ClientSessionOpen(vlc_tls_creds_t
*crd
, vlc_tls_t
*sock
,
631 const char *hostname
, const char *const *alpn
)
634 msg_Warn(crd
, "Ignoring ALPN request due to lack of support in the backend. Proxy behavior potentially undefined.");
635 #warning ALPN support missing, proxy behavior potentially undefined (rdar://29127318, #17721)
638 msg_Dbg(crd
, "open TLS session for %s", hostname
);
640 vlc_tls_t
*tls
= st_SessionOpenCommon(crd
, sock
, false);
644 vlc_tls_st_t
*sys
= (vlc_tls_st_t
*)tls
;
646 OSStatus ret
= SSLSetPeerDomainName(sys
->p_context
, hostname
, strlen(hostname
));
648 msg_Err(crd
, "cannot set peer domain name");
652 /* disable automatic validation. We do so manually to also handle invalid
655 /* this has effect only on iOS 5 and OSX 10.8 or later ... */
656 ret
= SSLSetSessionOption(sys
->p_context
, kSSLSessionOptionBreakOnServerAuth
, true);
658 msg_Err (crd
, "cannot set session option");
661 #if !TARGET_OS_IPHONE
662 /* ... thus calling this for earlier osx versions, which is not available on iOS in turn */
663 ret
= SSLSetEnableCertVerify(sys
->p_context
, false);
665 msg_Err(crd
, "error setting enable cert verify");
673 st_SessionShutdown(tls
, true);
674 st_SessionClose(tls
);
679 * Initializes a client-side TLS credentials.
681 static int OpenClient (vlc_tls_creds_t
*crd
) {
683 msg_Dbg(crd
, "open st client");
685 vlc_tls_creds_sys_t
*sys
= malloc (sizeof (*sys
));
686 if (unlikely(sys
== NULL
))
689 sys
->whitelist
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
690 sys
->server_cert_chain
= NULL
;
693 crd
->open
= st_ClientSessionOpen
;
698 static void CloseClient (vlc_tls_creds_t
*crd
) {
699 msg_Dbg(crd
, "close secure transport client");
701 vlc_tls_creds_sys_t
*sys
= crd
->sys
;
704 CFRelease(sys
->whitelist
);
709 /* Begin of server-side methods */
710 #if !TARGET_OS_IPHONE
713 * Initializes a server-side TLS session.
715 static vlc_tls_t
*st_ServerSessionOpen (vlc_tls_creds_t
*crd
, vlc_tls_t
*sock
,
716 const char *hostname
, const char *const *alpn
) {
718 VLC_UNUSED(hostname
);
720 msg_Dbg(crd
, "open TLS server session");
722 vlc_tls_t
*tls
= st_SessionOpenCommon(crd
, sock
, true);
726 vlc_tls_st_t
*sys
= (vlc_tls_st_t
*)tls
;
727 vlc_tls_creds_sys_t
*p_cred_sys
= crd
->sys
;
729 OSStatus ret
= SSLSetCertificate(sys
->p_context
, p_cred_sys
->server_cert_chain
);
731 msg_Err(crd
, "cannot set server certificate");
738 st_SessionShutdown(tls
, true);
739 st_SessionClose(tls
);
744 * Initializes server-side TLS credentials.
746 static int OpenServer (vlc_tls_creds_t
*crd
, const char *cert
, const char *key
) {
749 * This function expects the label of the certificate in "cert", stored
750 * in the MacOS keychain. The appropriate private key is found automatically.
755 msg_Dbg(crd
, "open st server");
758 * Get the server certificate.
760 * This API is deprecated, but the replacement SecItemCopyMatching
761 * only works on >= 10.7
763 SecKeychainAttribute attrib
= { kSecLabelItemAttr
, strlen(cert
), (void *)cert
};
764 SecKeychainAttributeList attrList
= { 1, &attrib
};
766 SecKeychainSearchRef searchReference
= NULL
;
767 ret
= SecKeychainSearchCreateFromAttributes(NULL
, kSecCertificateItemClass
,
768 &attrList
, &searchReference
);
769 if (ret
!= noErr
|| searchReference
== NULL
) {
770 msg_Err(crd
, "Cannot find certificate with alias %s", cert
);
774 SecKeychainItemRef itemRef
= NULL
;
775 ret
= SecKeychainSearchCopyNext(searchReference
, &itemRef
);
777 msg_Err(crd
, "Cannot get certificate with alias %s, error: %d", cert
, ret
);
780 CFRelease(searchReference
);
782 /* cast allowed according to documentation */
783 SecCertificateRef certificate
= (SecCertificateRef
)itemRef
;
785 SecIdentityRef cert_identity
= NULL
;
786 ret
= SecIdentityCreateWithCertificate(NULL
, certificate
, &cert_identity
);
788 msg_Err(crd
, "Cannot get private key for certificate");
789 CFRelease(certificate
);
794 * We try to validate the server certificate, but do not care about the result.
795 * The only aim is to get the certificate chain.
797 SecPolicyRef policy
= SecPolicyCreateSSL(true, NULL
);
798 SecTrustRef trust_ref
= NULL
;
799 int result
= VLC_SUCCESS
;
801 /* According to docu its fine to pass just one certificate */
802 ret
= SecTrustCreateWithCertificates((CFArrayRef
)certificate
, policy
, &trust_ref
);
804 msg_Err(crd
, "Cannot create trust");
805 result
= VLC_EGENERIC
;
809 SecTrustResultType status
;
810 ret
= SecTrustEvaluate(trust_ref
, &status
);
812 msg_Err(crd
, "Error evaluating trust");
813 result
= VLC_EGENERIC
;
817 CFArrayRef cert_chain
= NULL
;
818 CSSM_TP_APPLE_EVIDENCE_INFO
*status_chain
;
819 ret
= SecTrustGetResult(trust_ref
, &status
, &cert_chain
, &status_chain
);
820 if (ret
!= noErr
|| !cert_chain
) {
821 msg_Err(crd
, "error while getting certificate chain");
822 result
= VLC_EGENERIC
;
826 CFIndex num_cert_chain
= CFArrayGetCount(cert_chain
);
828 /* Build up the certificate chain array expected by SSLSetCertificate */
829 CFMutableArrayRef server_cert_chain
= CFArrayCreateMutable(kCFAllocatorDefault
, num_cert_chain
, &kCFTypeArrayCallBacks
);
830 CFArrayAppendValue(server_cert_chain
, cert_identity
);
832 msg_Dbg(crd
, "Found certificate chain with %ld entries for server certificate", num_cert_chain
);
833 if (num_cert_chain
> 1)
834 CFArrayAppendArray(server_cert_chain
, cert_chain
, CFRangeMake(1, num_cert_chain
- 1));
835 CFRelease(cert_chain
);
837 vlc_tls_creds_sys_t
*sys
= malloc(sizeof(*sys
));
838 if (unlikely(sys
== NULL
)) {
839 CFRelease(server_cert_chain
);
844 sys
->server_cert_chain
= server_cert_chain
;
845 sys
->whitelist
= NULL
;
848 crd
->open
= st_ServerSessionOpen
;
854 CFRelease(trust_ref
);
857 CFRelease(certificate
);
859 CFRelease(cert_identity
);
864 static void CloseServer (vlc_tls_creds_t
*crd
) {
865 msg_Dbg(crd
, "close secure transport server");
867 vlc_tls_creds_sys_t
*sys
= crd
->sys
;
869 if (sys
->server_cert_chain
)
870 CFRelease(sys
->server_cert_chain
);
875 #endif /* !TARGET_OS_IPHONE */