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")
79 struct vlc_tls_creds_sys
81 CFMutableArrayRef whitelist;
83 /* valid in server mode */
84 CFArrayRef server_cert_chain;
88 SSLContextRef p_context;
89 vlc_tls_creds_sys_t *p_cred;
90 size_t i_send_buffered_bytes;
98 static int st_Error (vlc_tls_t *obj, int val)
102 case errSSLWouldBlock:
106 case errSSLClosedGraceful:
107 case errSSLClosedAbort:
108 msg_Dbg(obj, "Connection closed with code %d", val);
112 msg_Err(obj, "Found error %d", val);
119 * Read function called by secure transport for socket read.
121 * Function is based on Apples SSLSample sample code.
123 static OSStatus st_SocketReadFunc (SSLConnectionRef connection,
125 size_t *dataLength) {
127 vlc_tls_t *session = (vlc_tls_t *)connection;
128 vlc_tls_sys_t *sys = session->sys;
130 size_t bytesToGo = *dataLength;
131 size_t initLen = bytesToGo;
132 UInt8 *currData = (UInt8 *)data;
133 OSStatus retValue = noErr;
137 val = read(sys->i_fd, currData, bytesToGo);
140 msg_Dbg(session, "found eof");
141 retValue = errSSLClosedGraceful;
142 } else { /* do the switch */
145 /* connection closed */
146 retValue = errSSLClosedGraceful;
149 retValue = errSSLClosedAbort;
152 retValue = errSSLWouldBlock;
153 sys->b_blocking_send = false;
156 msg_Err(session, "try to read %d bytes, got error %d",
157 (int)bytesToGo, errno);
168 if (bytesToGo == 0) {
169 /* filled buffer with incoming data, done */
173 *dataLength = initLen - bytesToGo;
179 * Write function called by secure transport for socket read.
181 * Function is based on Apples SSLSample sample code.
183 static OSStatus st_SocketWriteFunc (SSLConnectionRef connection,
185 size_t *dataLength) {
187 vlc_tls_t *session = (vlc_tls_t *)connection;
188 vlc_tls_sys_t *sys = session->sys;
190 size_t bytesSent = 0;
191 size_t dataLen = *dataLength;
192 OSStatus retValue = noErr;
196 val = write(sys->i_fd, (char *)data + bytesSent, dataLen - bytesSent);
197 } while (val >= 0 && (bytesSent += val) < dataLen);
202 retValue = errSSLWouldBlock;
203 sys->b_blocking_send = true;
208 retValue = errSSLClosedAbort;
212 msg_Err(session, "error while writing: %d", errno);
217 *dataLength = bytesSent;
221 static int st_validateServerCertificate (vlc_tls_t *session, const char *hostname) {
224 vlc_tls_sys_t *sys = session->sys;
225 SecCertificateRef leaf_cert = NULL;
227 SecTrustRef trust = NULL;
228 OSStatus ret = SSLCopyPeerTrust(sys->p_context, &trust);
229 if (ret != noErr || trust == NULL) {
230 msg_Err(session, "error getting certifictate chain");
234 CFStringRef cfHostname = CFStringCreateWithCString(kCFAllocatorDefault,
236 kCFStringEncodingUTF8);
239 /* enable default root / anchor certificates */
240 ret = SecTrustSetAnchorCertificates(trust, NULL);
242 msg_Err(session, "error setting anchor certificates");
247 SecTrustResultType trust_eval_result = 0;
249 ret = SecTrustEvaluate(trust, &trust_eval_result);
251 msg_Err(session, "error calling SecTrustEvaluate");
256 switch (trust_eval_result) {
257 case kSecTrustResultUnspecified:
258 case kSecTrustResultProceed:
259 msg_Dbg(session, "cerfificate verification successful, result is %d", trust_eval_result);
263 case kSecTrustResultRecoverableTrustFailure:
264 case kSecTrustResultDeny:
266 msg_Warn(session, "cerfificate verification failed, result is %d", trust_eval_result);
269 /* get leaf certificate */
270 /* SSLCopyPeerCertificates is only available on OSX 10.5 or later */
271 #if !TARGET_OS_IPHONE
272 CFArrayRef cert_chain = NULL;
273 ret = SSLCopyPeerCertificates(sys->p_context, &cert_chain);
274 if (ret != noErr || !cert_chain) {
279 if (CFArrayGetCount(cert_chain) == 0) {
280 CFRelease(cert_chain);
285 leaf_cert = (SecCertificateRef)CFArrayGetValueAtIndex(cert_chain, 0);
287 CFRelease(cert_chain);
289 /* SecTrustGetCertificateAtIndex is only available on 10.7 or iOS */
290 if (SecTrustGetCertificateCount(trust) == 0) {
295 leaf_cert = SecTrustGetCertificateAtIndex(trust, 0);
300 /* check if leaf already accepted */
301 CFIndex max = CFArrayGetCount(sys->p_cred->whitelist);
302 for (CFIndex i = 0; i < max; ++i) {
303 CFDictionaryRef dict = CFArrayGetValueAtIndex(sys->p_cred->whitelist, i);
304 CFStringRef knownHost = (CFStringRef)CFDictionaryGetValue(dict, cfKeyHost);
305 SecCertificateRef knownCert = (SecCertificateRef)CFDictionaryGetValue(dict, cfKeyCertificate);
307 if (!knownHost || !knownCert)
310 if (CFEqual(knownHost, cfHostname) && CFEqual(knownCert, leaf_cert)) {
311 msg_Warn(session, "certificate already accepted, continuing");
317 /* We do not show more certificate details yet because there is no proper API to get
318 a summary of the certificate. SecCertificateCopySubjectSummary is the only method
319 available on iOS and 10.6. More promising API functions such as
320 SecCertificateCopyLongDescription also print out the subject only, more or less.
321 But only showing the certificate subject is of no real help for the user.
322 We could use SecCertificateCopyValues, but then we need to parse all OID values for
323 ourself. This is too mad for just printing information the user will never check
327 const char *msg = N_("You attempted to reach %s. "
328 "However the security certificate presented by the server "
329 "is unknown and could not be authenticated by any trusted "
330 "Certification Authority. "
331 "This problem may be caused by a configuration error "
332 "or an attempt to breach your security or your privacy.\n\n"
333 "If in doubt, abort now.\n");
334 int answer = dialog_Question(session, _("Insecure site"), vlc_gettext (msg),
335 _("Abort"), _("Accept certificate temporarily"), NULL, hostname);
338 msg_Warn(session, "Proceeding despite of failed certificate validation");
340 /* save leaf certificate in whitelist */
341 const void *keys[] = {cfKeyHost, cfKeyCertificate};
342 const void *values[] = {cfHostname, leaf_cert};
343 CFDictionaryRef dict = CFDictionaryCreate(kCFAllocatorDefault,
345 &kCFTypeDictionaryKeyCallBacks,
346 &kCFTypeDictionaryValueCallBacks);
348 msg_Err(session, "error creating dict");
353 CFArrayAppendValue(sys->p_cred->whitelist, dict);
368 CFRelease(cfHostname);
370 CFRelease(leaf_cert);
376 * @return -1 on fatal error, 0 on successful handshake completion,
377 * 1 if more would-be blocking recv is needed,
378 * 2 if more would-be blocking send is required.
380 static int st_Handshake (vlc_tls_t *session, const char *host,
381 const char *service) {
384 vlc_tls_sys_t *sys = session->sys;
386 OSStatus retValue = SSLHandshake(sys->p_context);
388 if (retValue == errSSLWouldBlock) {
389 msg_Dbg(session, "handshake is blocked, try again later");
390 return 1 + (sys->b_blocking_send ? 1 : 0);
395 if (sys->b_server_mode == false && st_validateServerCertificate(session, host) != 0) {
398 msg_Dbg(session, "handshake completed successfully");
399 sys->b_handshaked = true;
402 case errSSLServerAuthCompleted:
403 return st_Handshake(session, host, service);
405 case errSSLConnectionRefused:
406 msg_Err(session, "connection was refused");
408 case errSSLNegotiation:
409 msg_Err(session, "cipher suite negotiation failed");
411 case errSSLFatalAlert:
412 msg_Err(session, "fatal error occured during handshake");
416 msg_Err(session, "handshake returned error %d", (int)retValue);
422 * Sends data through a TLS session.
424 static int st_Send (void *opaque, const void *buf, size_t length)
426 vlc_tls_t *session = opaque;
427 vlc_tls_sys_t *sys = session->sys;
428 OSStatus ret = noErr;
431 * SSLWrite does not return the number of bytes actually written to
432 * the socket, but the number of bytes written to the internal cache.
434 * If return value is errSSLWouldBlock, the underlying socket cannot
435 * send all data, but the data is already cached. In this situation,
436 * we need to call SSLWrite again. To ensure this call even for the
437 * last bytes, we return EAGAIN. On the next call, we give no new data
438 * to SSLWrite until the error is not errSSLWouldBlock anymore.
440 * This code is adapted the same way as done in curl.
441 * (https://github.com/bagder/curl/blob/master/lib/curl_darwinssl.c#L2067)
444 /* EAGAIN is not expected by net_Write in this situation,
446 int againErr = sys->b_server_mode ? EAGAIN : EINTR;
449 if (sys->i_send_buffered_bytes > 0) {
450 ret = SSLWrite(sys->p_context, NULL, 0, &actualSize);
453 /* actualSize remains zero because no new data send */
454 actualSize = sys->i_send_buffered_bytes;
455 sys->i_send_buffered_bytes = 0;
457 } else if (ret == errSSLWouldBlock) {
463 ret = SSLWrite(sys->p_context, buf, length, &actualSize);
465 if (ret == errSSLWouldBlock) {
466 sys->i_send_buffered_bytes = length;
472 return ret != noErr ? st_Error(session, ret) : actualSize;
476 * Receives data through a TLS session.
478 static int st_Recv (void *opaque, void *buf, size_t length)
480 vlc_tls_t *session = opaque;
481 vlc_tls_sys_t *sys = session->sys;
484 OSStatus ret = SSLRead(sys->p_context, buf, length, &actualSize);
486 if (ret == errSSLWouldBlock && actualSize)
489 /* peer performed shutdown */
490 if (ret == errSSLClosedNoNotify || ret == errSSLClosedGraceful) {
491 msg_Dbg(session, "Got close notification with code %d", ret);
495 return ret != noErr ? st_Error(session, ret) : actualSize;
499 * Closes a TLS session.
501 static void st_SessionClose (vlc_tls_creds_t *crd, vlc_tls_t *session) {
505 vlc_tls_sys_t *sys = session->sys;
506 msg_Dbg(session, "close TLS session");
508 if (sys->p_context) {
509 if (sys->b_handshaked) {
510 OSStatus ret = SSLClose(sys->p_context);
512 msg_Warn(session, "Cannot close ssl context");
517 CFRelease(sys->p_context);
519 if (SSLDisposeContext(sys->p_context) != noErr) {
520 msg_Err(session, "error deleting context");
528 * Initializes a client-side TLS session.
531 static int st_SessionOpenCommon (vlc_tls_creds_t *crd, vlc_tls_t *session,
532 int fd, bool b_server) {
534 vlc_tls_sys_t *sys = malloc(sizeof(*session->sys));
535 if (unlikely(sys == NULL))
538 sys->p_cred = crd->sys;
540 sys->b_handshaked = false;
541 sys->b_blocking_send = false;
542 sys->i_send_buffered_bytes = 0;
543 sys->p_context = NULL;
546 session->sock.p_sys = session;
547 session->sock.pf_send = st_Send;
548 session->sock.pf_recv = st_Recv;
549 session->handshake = st_Handshake;
551 SSLContextRef p_context = NULL;
553 p_context = SSLCreateContext(NULL, b_server ? kSSLServerSide : kSSLClientSide, kSSLStreamType);
554 if (p_context == NULL) {
555 msg_Err(session, "cannot create ssl context");
559 if (SSLNewContext(b_server, &p_context) != noErr) {
560 msg_Err(session, "error calling SSLNewContext");
565 sys->p_context = p_context;
567 OSStatus ret = SSLSetIOFuncs(p_context, st_SocketReadFunc, st_SocketWriteFunc);
569 msg_Err(session, "cannot set io functions");
573 ret = SSLSetConnection(p_context, session);
575 msg_Err(session, "cannot set connection");
582 static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
583 int fd, const char *hostname) {
584 msg_Dbg(session, "open TLS session for %s", hostname);
586 int ret = st_SessionOpenCommon(crd, session, fd, false);
591 vlc_tls_sys_t *sys = session->sys;
592 sys->b_server_mode = false;
594 ret = SSLSetPeerDomainName(sys->p_context, hostname, strlen(hostname));
596 msg_Err(session, "cannot set peer domain name");
600 /* disable automatic validation. We do so manually to also handle invalid
603 /* this has effect only on iOS 5 and OSX 10.8 or later ... */
604 ret = SSLSetSessionOption(sys->p_context, kSSLSessionOptionBreakOnServerAuth, true);
606 msg_Err (session, "cannot set session option");
609 #if !TARGET_OS_IPHONE
610 /* ... thus calling this for earlier osx versions, which is not available on iOS in turn */
611 ret = SSLSetEnableCertVerify(sys->p_context, false);
613 msg_Err(session, "error setting enable cert verify");
621 st_SessionClose(crd, session);
626 * Initializes a client-side TLS credentials.
628 static int OpenClient (vlc_tls_creds_t *crd) {
630 msg_Dbg(crd, "open st client");
632 vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
633 if (unlikely(sys == NULL))
636 sys->whitelist = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
637 sys->server_cert_chain = NULL;
640 crd->open = st_ClientSessionOpen;
641 crd->close = st_SessionClose;
646 static void CloseClient (vlc_tls_creds_t *crd) {
647 msg_Dbg(crd, "close secure transport client");
649 vlc_tls_creds_sys_t *sys = crd->sys;
652 CFRelease(sys->whitelist);
657 /* Begin of server-side methods */
658 #if !TARGET_OS_IPHONE
661 * Initializes a server-side TLS session.
663 static int st_ServerSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
664 int fd, const char *hostname) {
666 VLC_UNUSED(hostname);
667 msg_Dbg(session, "open TLS server session");
669 int ret = st_SessionOpenCommon(crd, session, fd, true);
674 vlc_tls_sys_t *sys = session->sys;
675 sys->b_server_mode = true;
677 ret = SSLSetCertificate(sys->p_context, crd->sys->server_cert_chain);
679 msg_Err(session, "cannot set server certificate");
686 st_SessionClose(crd, 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");
704 vlc_tls_creds_sys_t *sys = malloc(sizeof(*sys));
705 if (unlikely(sys == NULL))
709 * Get the server certificate.
711 * This API is deprecated, but the replacement SecItemCopyMatching
712 * only works on >= 10.7
714 SecKeychainAttribute attrib = { kSecLabelItemAttr, strlen(cert), (void *)cert };
715 SecKeychainAttributeList attrList = { 1, &attrib };
717 SecKeychainSearchRef searchReference = NULL;
718 ret = SecKeychainSearchCreateFromAttributes(NULL, kSecCertificateItemClass,
719 &attrList, &searchReference);
720 if (ret != noErr || searchReference == NULL) {
721 msg_Err(crd, "Cannot find certificate with alias %s", cert);
725 SecKeychainItemRef itemRef = NULL;
726 ret = SecKeychainSearchCopyNext(searchReference, &itemRef);
728 msg_Err(crd, "Cannot get certificate with alias %s, error: %d", cert, ret);
731 CFRelease(searchReference);
733 /* cast allowed according to documentation */
734 SecCertificateRef certificate = (SecCertificateRef)itemRef;
736 SecIdentityRef cert_identity = NULL;
737 ret = SecIdentityCreateWithCertificate(NULL, certificate, &cert_identity);
739 msg_Err(crd, "Cannot get private key for certificate");
740 CFRelease(certificate);
745 * We try to validate the server certificate, but do not care about the result.
746 * The only aim is to get the certificate chain.
748 SecPolicyRef policy = SecPolicyCreateSSL(true, NULL);
749 SecTrustRef trust_ref = NULL;
750 int result = VLC_SUCCESS;
752 /* According to docu its fine to pass just one certificate */
753 ret = SecTrustCreateWithCertificates((CFArrayRef)certificate, policy, &trust_ref);
755 msg_Err(crd, "Cannot create trust");
756 result = VLC_EGENERIC;
760 SecTrustResultType status;
761 ret = SecTrustEvaluate(trust_ref, &status);
763 msg_Err(crd, "Error evaluating trust");
764 result = VLC_EGENERIC;
768 CFArrayRef cert_chain = NULL;
769 CSSM_TP_APPLE_EVIDENCE_INFO *status_chain;
770 ret = SecTrustGetResult(trust_ref, &status, &cert_chain, &status_chain);
771 if (ret != noErr || !cert_chain) {
772 msg_Err(crd, "error while getting certificate chain");
773 result = VLC_EGENERIC;
777 CFIndex num_cert_chain = CFArrayGetCount(cert_chain);
779 /* Build up the certificate chain array expected by SSLSetCertificate */
780 CFMutableArrayRef server_cert_chain = CFArrayCreateMutable(kCFAllocatorDefault, num_cert_chain, &kCFTypeArrayCallBacks);
781 CFArrayAppendValue(server_cert_chain, cert_identity);
783 msg_Dbg(crd, "Found certificate chain with %ld entries for server certificate", num_cert_chain);
784 if (num_cert_chain > 1)
785 CFArrayAppendArray(server_cert_chain, cert_chain, CFRangeMake(1, num_cert_chain - 1));
786 CFRelease(cert_chain);
789 sys->server_cert_chain = server_cert_chain;
790 sys->whitelist = NULL;
793 crd->open = st_ServerSessionOpen;
794 crd->close = st_SessionClose;
800 CFRelease(trust_ref);
803 CFRelease(certificate);
805 CFRelease(cert_identity);
810 static void CloseServer (vlc_tls_creds_t *crd) {
811 msg_Dbg(crd, "close secure transport server");
813 vlc_tls_creds_sys_t *sys = crd->sys;
815 if (sys->server_cert_chain)
816 CFRelease(sys->server_cert_chain);
821 #endif /* !TARGET_OS_IPHONE */