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 Ă–esser 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 *);
49 static int OpenServer (vlc_tls_creds_t *crd, const char *cert, const char *key);
50 static void CloseServer (vlc_tls_creds_t *);
53 set_description(N_("TLS support for OS X and iOS"))
54 set_capability("tls client", 2)
55 set_callbacks(OpenClient, CloseClient)
56 set_category(CAT_ADVANCED)
57 set_subcategory(SUBCAT_ADVANCED_NETWORK)
60 * The server module currently uses an OSX only API, to be compatible with 10.6.
61 * If the module is needed on iOS, then the "modern" keychain lookup API need to be
66 set_description(N_("TLS server support for OS X"))
67 set_capability("tls server", 2)
68 set_callbacks(OpenServer, CloseServer)
69 set_category(CAT_ADVANCED)
70 set_subcategory(SUBCAT_ADVANCED_NETWORK)
71 #endif /* !TARGET_OS_IPHONE */
76 #define cfKeyHost CFSTR("host")
77 #define cfKeyCertificate CFSTR("certificate")
80 CFMutableArrayRef whitelist;
82 /* valid in server mode */
83 CFArrayRef server_cert_chain;
84 } vlc_tls_creds_sys_t;
87 SSLContextRef p_context;
88 vlc_tls_creds_sys_t *p_cred;
89 size_t i_send_buffered_bytes;
97 static int st_Error (vlc_tls_t *obj, int val)
101 case errSSLWouldBlock:
105 case errSSLClosedGraceful:
106 case errSSLClosedAbort:
107 msg_Dbg(obj, "Connection closed with code %d", val);
111 msg_Err(obj, "Found error %d", val);
118 * Read function called by secure transport for socket read.
120 * Function is based on Apples SSLSample sample code.
122 static OSStatus st_SocketReadFunc (SSLConnectionRef connection,
124 size_t *dataLength) {
126 vlc_tls_t *session = (vlc_tls_t *)connection;
127 vlc_tls_sys_t *sys = session->sys;
129 size_t bytesToGo = *dataLength;
130 size_t initLen = bytesToGo;
131 UInt8 *currData = (UInt8 *)data;
132 OSStatus retValue = noErr;
136 val = read(sys->i_fd, currData, bytesToGo);
139 msg_Dbg(session, "found eof");
140 retValue = errSSLClosedGraceful;
141 } else { /* do the switch */
144 /* connection closed */
145 retValue = errSSLClosedGraceful;
148 retValue = errSSLClosedAbort;
151 retValue = errSSLWouldBlock;
152 sys->b_blocking_send = false;
155 msg_Err(session, "try to read %d bytes, got error %d",
156 (int)bytesToGo, errno);
167 if (bytesToGo == 0) {
168 /* filled buffer with incoming data, done */
172 *dataLength = initLen - bytesToGo;
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_sys_t *sys = session->sys;
189 size_t bytesSent = 0;
190 size_t dataLen = *dataLength;
191 OSStatus retValue = noErr;
195 val = write(sys->i_fd, (char *)data + bytesSent, dataLen - bytesSent);
196 } while (val >= 0 && (bytesSent += val) < dataLen);
201 retValue = errSSLWouldBlock;
202 sys->b_blocking_send = true;
207 retValue = errSSLClosedAbort;
211 msg_Err(session, "error while writing: %d", errno);
216 *dataLength = bytesSent;
220 static int st_validateServerCertificate (vlc_tls_t *session, const char *hostname) {
223 vlc_tls_sys_t *sys = session->sys;
224 SecCertificateRef leaf_cert = NULL;
226 SecTrustRef trust = NULL;
227 OSStatus ret = SSLCopyPeerTrust(sys->p_context, &trust);
228 if (ret != noErr || trust == NULL) {
229 msg_Err(session, "error getting certifictate chain");
233 CFStringRef cfHostname = CFStringCreateWithCString(kCFAllocatorDefault,
235 kCFStringEncodingUTF8);
238 /* enable default root / anchor certificates */
239 ret = SecTrustSetAnchorCertificates(trust, NULL);
241 msg_Err(session, "error setting anchor certificates");
246 SecTrustResultType trust_eval_result = 0;
248 ret = SecTrustEvaluate(trust, &trust_eval_result);
250 msg_Err(session, "error calling SecTrustEvaluate");
255 switch (trust_eval_result) {
256 case kSecTrustResultUnspecified:
257 case kSecTrustResultProceed:
258 msg_Dbg(session, "cerfificate verification successful, result is %d", trust_eval_result);
262 case kSecTrustResultRecoverableTrustFailure:
263 case kSecTrustResultDeny:
265 msg_Warn(session, "cerfificate verification failed, result is %d", trust_eval_result);
268 /* get leaf certificate */
269 /* SSLCopyPeerCertificates is only available on OSX 10.5 or later */
270 #if !TARGET_OS_IPHONE
271 CFArrayRef cert_chain = NULL;
272 ret = SSLCopyPeerCertificates(sys->p_context, &cert_chain);
273 if (ret != noErr || !cert_chain) {
278 if (CFArrayGetCount(cert_chain) == 0) {
279 CFRelease(cert_chain);
284 leaf_cert = (SecCertificateRef)CFArrayGetValueAtIndex(cert_chain, 0);
286 CFRelease(cert_chain);
288 /* SecTrustGetCertificateAtIndex is only available on 10.7 or iOS */
289 if (SecTrustGetCertificateCount(trust) == 0) {
294 leaf_cert = SecTrustGetCertificateAtIndex(trust, 0);
299 /* check if leaf already accepted */
300 CFIndex max = CFArrayGetCount(sys->p_cred->whitelist);
301 for (CFIndex i = 0; i < max; ++i) {
302 CFDictionaryRef dict = CFArrayGetValueAtIndex(sys->p_cred->whitelist, i);
303 CFStringRef knownHost = (CFStringRef)CFDictionaryGetValue(dict, cfKeyHost);
304 SecCertificateRef knownCert = (SecCertificateRef)CFDictionaryGetValue(dict, cfKeyCertificate);
306 if (!knownHost || !knownCert)
309 if (CFEqual(knownHost, cfHostname) && CFEqual(knownCert, leaf_cert)) {
310 msg_Warn(session, "certificate already accepted, continuing");
316 /* We do not show more certificate details yet because there is no proper API to get
317 a summary of the certificate. SecCertificateCopySubjectSummary is the only method
318 available on iOS and 10.6. More promising API functions such as
319 SecCertificateCopyLongDescription also print out the subject only, more or less.
320 But only showing the certificate subject is of no real help for the user.
321 We could use SecCertificateCopyValues, but then we need to parse all OID values for
322 ourself. This is too mad for just printing information the user will never check
326 const char *msg = N_("You attempted to reach %s. "
327 "However the security certificate presented by the server "
328 "is unknown and could not be authenticated by any trusted "
329 "Certification Authority. "
330 "This problem may be caused by a configuration error "
331 "or an attempt to breach your security or your privacy.\n\n"
332 "If in doubt, abort now.\n");
333 int answer = dialog_Question(session, _("Insecure site"), vlc_gettext (msg),
334 _("Abort"), _("Accept certificate temporarily"), NULL, hostname);
337 msg_Warn(session, "Proceeding despite of failed certificate validation");
339 /* save leaf certificate in whitelist */
340 const void *keys[] = {cfKeyHost, cfKeyCertificate};
341 const void *values[] = {cfHostname, leaf_cert};
342 CFDictionaryRef dict = CFDictionaryCreate(kCFAllocatorDefault,
344 &kCFTypeDictionaryKeyCallBacks,
345 &kCFTypeDictionaryValueCallBacks);
347 msg_Err(session, "error creating dict");
352 CFArrayAppendValue(sys->p_cred->whitelist, dict);
367 CFRelease(cfHostname);
369 CFRelease(leaf_cert);
375 * @return -1 on fatal error, 0 on successful handshake completion,
376 * 1 if more would-be blocking recv is needed,
377 * 2 if more would-be blocking send is required.
379 static int st_Handshake (vlc_tls_t *session, const char *host,
380 const char *service, char **restrict alp) {
383 vlc_tls_sys_t *sys = session->sys;
385 OSStatus retValue = SSLHandshake(sys->p_context);
387 if (retValue == errSSLWouldBlock) {
388 msg_Dbg(session, "handshake is blocked, try again later");
389 return 1 + (sys->b_blocking_send ? 1 : 0);
394 if (sys->b_server_mode == false && st_validateServerCertificate(session, host) != 0) {
397 msg_Dbg(session, "handshake completed successfully");
398 sys->b_handshaked = true;
401 case errSSLServerAuthCompleted:
402 return st_Handshake(session, host, service, alp);
404 case errSSLConnectionRefused:
405 msg_Err(session, "connection was refused");
407 case errSSLNegotiation:
408 msg_Err(session, "cipher suite negotiation failed");
410 case errSSLFatalAlert:
411 msg_Err(session, "fatal error occured during handshake");
415 msg_Err(session, "handshake returned error %d", (int)retValue);
421 * Sends data through a TLS session.
423 static int st_Send (void *opaque, const void *buf, size_t length)
425 vlc_tls_t *session = opaque;
426 vlc_tls_sys_t *sys = session->sys;
427 OSStatus ret = noErr;
430 * SSLWrite does not return the number of bytes actually written to
431 * the socket, but the number of bytes written to the internal cache.
433 * If return value is errSSLWouldBlock, the underlying socket cannot
434 * send all data, but the data is already cached. In this situation,
435 * we need to call SSLWrite again. To ensure this call even for the
436 * last bytes, we return EAGAIN. On the next call, we give no new data
437 * to SSLWrite until the error is not errSSLWouldBlock anymore.
439 * This code is adapted the same way as done in curl.
440 * (https://github.com/bagder/curl/blob/master/lib/curl_darwinssl.c#L2067)
443 /* EAGAIN is not expected by net_Write in this situation,
445 int againErr = sys->b_server_mode ? EAGAIN : EINTR;
448 if (sys->i_send_buffered_bytes > 0) {
449 ret = SSLWrite(sys->p_context, NULL, 0, &actualSize);
452 /* actualSize remains zero because no new data send */
453 actualSize = sys->i_send_buffered_bytes;
454 sys->i_send_buffered_bytes = 0;
456 } else if (ret == errSSLWouldBlock) {
462 ret = SSLWrite(sys->p_context, buf, length, &actualSize);
464 if (ret == errSSLWouldBlock) {
465 sys->i_send_buffered_bytes = length;
471 return ret != noErr ? st_Error(session, ret) : actualSize;
475 * Receives data through a TLS session.
477 static int st_Recv (void *opaque, void *buf, size_t length)
479 vlc_tls_t *session = opaque;
480 vlc_tls_sys_t *sys = session->sys;
483 OSStatus ret = SSLRead(sys->p_context, buf, length, &actualSize);
485 if (ret == errSSLWouldBlock && actualSize)
488 /* peer performed shutdown */
489 if (ret == errSSLClosedNoNotify || ret == errSSLClosedGraceful) {
490 msg_Dbg(session, "Got close notification with code %d", ret);
494 return ret != noErr ? st_Error(session, ret) : actualSize;
498 * Closes a TLS session.
500 static void st_SessionClose (vlc_tls_t *session) {
502 vlc_tls_sys_t *sys = session->sys;
503 msg_Dbg(session, "close TLS session");
505 if (sys->p_context) {
506 if (sys->b_handshaked) {
507 OSStatus ret = SSLClose(sys->p_context);
509 msg_Warn(session, "Cannot close ssl context");
514 CFRelease(sys->p_context);
516 if (SSLDisposeContext(sys->p_context) != noErr) {
517 msg_Err(session, "error deleting context");
525 * Initializes a client-side TLS session.
528 static int st_SessionOpenCommon (vlc_tls_creds_t *crd, vlc_tls_t *session,
529 int fd, bool b_server) {
531 vlc_tls_sys_t *sys = malloc(sizeof(vlc_tls_sys_t));
532 if (unlikely(sys == NULL))
535 sys->p_cred = crd->sys;
537 sys->b_handshaked = false;
538 sys->b_blocking_send = false;
539 sys->i_send_buffered_bytes = 0;
540 sys->p_context = NULL;
543 session->sock.p_sys = session;
544 session->sock.pf_send = st_Send;
545 session->sock.pf_recv = st_Recv;
546 crd->handshake = st_Handshake;
548 SSLContextRef p_context = NULL;
550 p_context = SSLCreateContext(NULL, b_server ? kSSLServerSide : kSSLClientSide, kSSLStreamType);
551 if (p_context == NULL) {
552 msg_Err(session, "cannot create ssl context");
556 if (SSLNewContext(b_server, &p_context) != noErr) {
557 msg_Err(session, "error calling SSLNewContext");
562 sys->p_context = p_context;
564 OSStatus ret = SSLSetIOFuncs(p_context, st_SocketReadFunc, st_SocketWriteFunc);
566 msg_Err(session, "cannot set io functions");
570 ret = SSLSetConnection(p_context, session);
572 msg_Err(session, "cannot set connection");
579 static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
580 int fd, const char *hostname, const char *const *alpn) {
582 msg_Dbg(session, "open TLS session for %s", hostname);
584 int ret = st_SessionOpenCommon(crd, session, fd, false);
589 vlc_tls_sys_t *sys = session->sys;
590 sys->b_server_mode = false;
592 ret = SSLSetPeerDomainName(sys->p_context, hostname, strlen(hostname));
594 msg_Err(session, "cannot set peer domain name");
598 /* disable automatic validation. We do so manually to also handle invalid
601 /* this has effect only on iOS 5 and OSX 10.8 or later ... */
602 ret = SSLSetSessionOption(sys->p_context, kSSLSessionOptionBreakOnServerAuth, true);
604 msg_Err (session, "cannot set session option");
607 #if !TARGET_OS_IPHONE
608 /* ... thus calling this for earlier osx versions, which is not available on iOS in turn */
609 ret = SSLSetEnableCertVerify(sys->p_context, false);
611 msg_Err(session, "error setting enable cert verify");
619 st_SessionClose(session);
624 * Initializes a client-side TLS credentials.
626 static int OpenClient (vlc_tls_creds_t *crd) {
628 msg_Dbg(crd, "open st client");
630 vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
631 if (unlikely(sys == NULL))
634 sys->whitelist = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
635 sys->server_cert_chain = NULL;
638 crd->open = st_ClientSessionOpen;
639 crd->close = st_SessionClose;
644 static void CloseClient (vlc_tls_creds_t *crd) {
645 msg_Dbg(crd, "close secure transport client");
647 vlc_tls_creds_sys_t *sys = crd->sys;
650 CFRelease(sys->whitelist);
655 /* Begin of server-side methods */
656 #if !TARGET_OS_IPHONE
659 * Initializes a server-side TLS session.
661 static int st_ServerSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
662 int fd, const char *hostname, const char *const *alpn) {
664 VLC_UNUSED(hostname);
666 msg_Dbg(session, "open TLS server session");
668 int ret = st_SessionOpenCommon(crd, session, fd, true);
673 vlc_tls_sys_t *sys = session->sys;
674 vlc_tls_creds_sys_t *p_cred_sys = crd->sys;
675 sys->b_server_mode = true;
677 ret = SSLSetCertificate(sys->p_context, p_cred_sys->server_cert_chain);
679 msg_Err(session, "cannot set server certificate");
686 st_SessionClose(session);
691 * Initializes server-side TLS credentials.
693 static int OpenServer (vlc_tls_creds_t *crd, const char *cert, const char *key) {
696 * This function expects the label of the certificate in "cert", stored
697 * in the MacOS keychain. The appropriate private key is found automatically.
702 msg_Dbg(crd, "open st server");
705 * Get the server certificate.
707 * This API is deprecated, but the replacement SecItemCopyMatching
708 * only works on >= 10.7
710 SecKeychainAttribute attrib = { kSecLabelItemAttr, strlen(cert), (void *)cert };
711 SecKeychainAttributeList attrList = { 1, &attrib };
713 SecKeychainSearchRef searchReference = NULL;
714 ret = SecKeychainSearchCreateFromAttributes(NULL, kSecCertificateItemClass,
715 &attrList, &searchReference);
716 if (ret != noErr || searchReference == NULL) {
717 msg_Err(crd, "Cannot find certificate with alias %s", cert);
721 SecKeychainItemRef itemRef = NULL;
722 ret = SecKeychainSearchCopyNext(searchReference, &itemRef);
724 msg_Err(crd, "Cannot get certificate with alias %s, error: %d", cert, ret);
727 CFRelease(searchReference);
729 /* cast allowed according to documentation */
730 SecCertificateRef certificate = (SecCertificateRef)itemRef;
732 SecIdentityRef cert_identity = NULL;
733 ret = SecIdentityCreateWithCertificate(NULL, certificate, &cert_identity);
735 msg_Err(crd, "Cannot get private key for certificate");
736 CFRelease(certificate);
741 * We try to validate the server certificate, but do not care about the result.
742 * The only aim is to get the certificate chain.
744 SecPolicyRef policy = SecPolicyCreateSSL(true, NULL);
745 SecTrustRef trust_ref = NULL;
746 int result = VLC_SUCCESS;
748 /* According to docu its fine to pass just one certificate */
749 ret = SecTrustCreateWithCertificates((CFArrayRef)certificate, policy, &trust_ref);
751 msg_Err(crd, "Cannot create trust");
752 result = VLC_EGENERIC;
756 SecTrustResultType status;
757 ret = SecTrustEvaluate(trust_ref, &status);
759 msg_Err(crd, "Error evaluating trust");
760 result = VLC_EGENERIC;
764 CFArrayRef cert_chain = NULL;
765 CSSM_TP_APPLE_EVIDENCE_INFO *status_chain;
766 ret = SecTrustGetResult(trust_ref, &status, &cert_chain, &status_chain);
767 if (ret != noErr || !cert_chain) {
768 msg_Err(crd, "error while getting certificate chain");
769 result = VLC_EGENERIC;
773 CFIndex num_cert_chain = CFArrayGetCount(cert_chain);
775 /* Build up the certificate chain array expected by SSLSetCertificate */
776 CFMutableArrayRef server_cert_chain = CFArrayCreateMutable(kCFAllocatorDefault, num_cert_chain, &kCFTypeArrayCallBacks);
777 CFArrayAppendValue(server_cert_chain, cert_identity);
779 msg_Dbg(crd, "Found certificate chain with %ld entries for server certificate", num_cert_chain);
780 if (num_cert_chain > 1)
781 CFArrayAppendArray(server_cert_chain, cert_chain, CFRangeMake(1, num_cert_chain - 1));
782 CFRelease(cert_chain);
784 vlc_tls_creds_sys_t *sys = malloc(sizeof(*sys));
785 if (unlikely(sys == NULL)) {
786 CFRelease(server_cert_chain);
791 sys->server_cert_chain = server_cert_chain;
792 sys->whitelist = NULL;
795 crd->open = st_ServerSessionOpen;
796 crd->close = st_SessionClose;
802 CFRelease(trust_ref);
805 CFRelease(certificate);
807 CFRelease(cert_identity);
812 static void CloseServer (vlc_tls_creds_t *crd) {
813 msg_Dbg(crd, "close secure transport server");
815 vlc_tls_creds_sys_t *sys = crd->sys;
817 if (sys->server_cert_chain)
818 CFRelease(sys->server_cert_chain);
823 #endif /* !TARGET_OS_IPHONE */