misc: medialibrary: ctx does not need dynamic lifetime
[vlc.git] / modules / misc / securetransport.c
blob4cafc30d3aa22f3386cf532ee20e035d3e0b103a
1 /*****************************************************************************
2 * securetransport.c
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 /*****************************************************************************
22 * Preamble
23 *****************************************************************************/
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
29 #include <vlc_common.h>
30 #include <vlc_plugin.h>
31 #include <vlc_tls.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: */
39 #ifndef ioErr
40 # define ioErr -36
41 #endif
43 /*****************************************************************************
44 * ALPN helper functions
45 *****************************************************************************/
47 /* Converts the VLC ALPN C array (null-terminated) to a ALPN
48 * CFMutableArray as expected by the Secure Transport API
49 * Returns CFMutableArrayRef on success, else NULL.
51 static CFMutableArrayRef alpnToCFArray(const char *const *alpn)
53 CFMutableArrayRef alpnValues =
54 CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
56 for (size_t i = 0; alpn[i] != NULL; i++) {
57 CFStringRef alpnVal =
58 CFStringCreateWithCString(kCFAllocatorDefault, alpn[i], kCFStringEncodingASCII);
59 if (alpnVal == NULL) {
60 // Failed to convert the ALPN value to CFString, error out.
61 CFRelease(alpnValues);
62 return NULL;
64 CFArrayAppendValue(alpnValues, alpnVal);
65 CFRelease(alpnVal);
67 return alpnValues;
70 /* Obtains a copy of the contents of a CFString in ASCII encoding.
71 * Returns char* (must be freed by caller) or NULL on failure.
73 static char* CFStringCopyASCIICString(CFStringRef cfString)
75 // Try the quick way to obtain the buffer
76 const char *tmpBuffer = CFStringGetCStringPtr(cfString, kCFStringEncodingASCII);
78 if (tmpBuffer != NULL) {
79 return strdup(tmpBuffer);
82 // The quick way did not work, try the long way
83 CFIndex length = CFStringGetLength(cfString);
84 CFIndex maxSize =
85 CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingASCII);
87 // If result would exceed LONG_MAX, kCFNotFound is returned
88 if (unlikely(maxSize == kCFNotFound)) {
89 return NULL;
92 // Account for the null terminator
93 maxSize++;
95 char *buffer = (char *)malloc(maxSize);
97 if (unlikely(buffer == NULL)) {
98 return NULL;
101 // Copy CFString in requested encoding to buffer
102 Boolean success = CFStringGetCString(cfString, buffer, maxSize, kCFStringEncodingASCII);
104 if (!success)
105 FREENULL(buffer);
106 return buffer;
109 /* Returns the first entry copy of the ALPN array as char*
110 * or NULL on failure.
112 static char* CFArrayALPNCopyFirst(CFArrayRef alpnArray)
114 CFIndex count = CFArrayGetCount(alpnArray);
116 if (count <= 0)
117 return NULL;
119 CFStringRef alpnVal = CFArrayGetValueAtIndex(alpnArray, 0);
120 return CFStringCopyASCIICString(alpnVal);
123 /*****************************************************************************
124 * Module descriptor
125 *****************************************************************************/
126 static int OpenClient (vlc_tls_creds_t *);
127 static void CloseClient (vlc_tls_creds_t *);
129 #if !TARGET_OS_IPHONE
130 static int OpenServer (vlc_tls_creds_t *crd, const char *cert, const char *key);
131 static void CloseServer (vlc_tls_creds_t *);
132 #endif
134 vlc_module_begin ()
135 set_description(N_("TLS support for OS X and iOS"))
136 set_capability("tls client", 2)
137 set_callbacks(OpenClient, CloseClient)
138 set_category(CAT_ADVANCED)
139 set_subcategory(SUBCAT_ADVANCED_NETWORK)
142 * The server module currently uses an OSX only API, to be compatible with 10.6.
143 If the module is needed on iOS, then the "modern" keychain lookup API need to be
144 * implemented.
146 #if !TARGET_OS_IPHONE
147 add_submodule()
148 set_description(N_("TLS server support for OS X"))
149 set_capability("tls server", 2)
150 set_callbacks(OpenServer, CloseServer)
151 set_category(CAT_ADVANCED)
152 set_subcategory(SUBCAT_ADVANCED_NETWORK)
153 #endif /* !TARGET_OS_IPHONE */
155 vlc_module_end ()
158 #define cfKeyHost CFSTR("host")
159 #define cfKeyCertificate CFSTR("certificate")
161 typedef struct {
162 CFMutableArrayRef whitelist;
164 /* valid in server mode */
165 CFArrayRef server_cert_chain;
166 } vlc_tls_creds_sys_t;
168 typedef struct {
169 vlc_tls_t tls;
170 SSLContextRef p_context;
171 vlc_tls_creds_sys_t *p_cred;
172 size_t i_send_buffered_bytes;
173 vlc_tls_t *sock;
174 vlc_object_t *obj;
176 bool b_blocking_send;
177 bool b_handshaked;
178 bool b_server_mode;
180 vlc_mutex_t lock;
181 } vlc_tls_st_t;
183 static int st_Error (vlc_tls_t *obj, int val)
185 vlc_tls_st_t *sys = (vlc_tls_st_t *)obj;
187 switch (val)
189 case errSSLWouldBlock:
190 errno = EAGAIN;
191 break;
193 case errSSLClosedGraceful:
194 case errSSLClosedAbort:
195 msg_Dbg(sys->obj, "Connection closed with code %d", val);
196 errno = ECONNRESET;
197 break;
198 default:
199 msg_Err(sys->obj, "Found error %d", val);
200 errno = ECONNRESET;
202 return -1;
206 * Read function called by secure transport for socket read.
208 * Function is based on Apples SSLSample sample code.
210 static OSStatus st_SocketReadFunc (SSLConnectionRef connection,
211 void *data,
212 size_t *dataLength) {
214 vlc_tls_t *session = (vlc_tls_t *)connection;
215 vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
216 struct iovec iov = {
217 .iov_base = data,
218 .iov_len = *dataLength,
220 OSStatus retValue = noErr;
222 while (iov.iov_len > 0) {
223 ssize_t val = sys->sock->readv(sys->sock, &iov, 1);
224 if (val <= 0) {
225 if (val == 0) {
226 msg_Dbg(sys->obj, "found eof");
227 retValue = errSSLClosedGraceful;
228 } else { /* do the switch */
229 switch (errno) {
230 case ENOENT:
231 /* connection closed */
232 retValue = errSSLClosedGraceful;
233 break;
234 case ECONNRESET:
235 retValue = errSSLClosedAbort;
236 break;
237 case EAGAIN:
238 retValue = errSSLWouldBlock;
239 sys->b_blocking_send = false;
240 break;
241 default:
242 msg_Err(sys->obj, "try to read %zu bytes, "
243 "got error %d", iov.iov_len, errno);
244 retValue = ioErr;
245 break;
248 break;
251 iov.iov_base = (char *)iov.iov_base + val;
252 iov.iov_len -= val;
255 *dataLength -= iov.iov_len;
256 return retValue;
260 * Write function called by secure transport for socket read.
262 * Function is based on Apples SSLSample sample code.
264 static OSStatus st_SocketWriteFunc (SSLConnectionRef connection,
265 const void *data,
266 size_t *dataLength) {
268 vlc_tls_t *session = (vlc_tls_t *)connection;
269 vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
270 struct iovec iov = {
271 .iov_base = (void *)data,
272 .iov_len = *dataLength,
274 OSStatus retValue = noErr;
276 while (iov.iov_len > 0) {
277 ssize_t val = sys->sock->writev(sys->sock, &iov, 1);
278 if (val < 0) {
279 switch (errno) {
280 case EAGAIN:
281 retValue = errSSLWouldBlock;
282 sys->b_blocking_send = true;
283 break;
285 case EPIPE:
286 case ECONNRESET:
287 retValue = errSSLClosedAbort;
288 break;
290 default:
291 msg_Err(sys->obj, "error while writing: %d", errno);
292 retValue = ioErr;
293 break;
295 break;
298 iov.iov_base = (char *)iov.iov_base + val;
299 iov.iov_len -= val;
302 *dataLength -= iov.iov_len;
303 return retValue;
306 static int st_validateServerCertificate (vlc_tls_t *session, const char *hostname) {
308 vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
309 int result = -1;
310 SecCertificateRef leaf_cert = NULL;
312 SecTrustRef trust = NULL;
313 OSStatus ret = SSLCopyPeerTrust(sys->p_context, &trust);
314 if (ret != noErr || trust == NULL) {
315 msg_Err(sys->obj, "error getting certifictate chain");
316 return -1;
319 CFStringRef cfHostname = CFStringCreateWithCString(kCFAllocatorDefault,
320 hostname,
321 kCFStringEncodingUTF8);
324 /* enable default root / anchor certificates */
325 ret = SecTrustSetAnchorCertificates(trust, NULL);
326 if (ret != noErr) {
327 msg_Err(sys->obj, "error setting anchor certificates");
328 result = -1;
329 goto out;
332 SecTrustResultType trust_eval_result = 0;
334 ret = SecTrustEvaluate(trust, &trust_eval_result);
335 if (ret != noErr) {
336 msg_Err(sys->obj, "error calling SecTrustEvaluate");
337 result = -1;
338 goto out;
341 switch (trust_eval_result) {
342 case kSecTrustResultUnspecified:
343 case kSecTrustResultProceed:
344 msg_Dbg(sys->obj, "cerfificate verification successful, result is %d", trust_eval_result);
345 result = 0;
346 goto out;
348 case kSecTrustResultRecoverableTrustFailure:
349 case kSecTrustResultDeny:
350 default:
351 msg_Warn(sys->obj, "cerfificate verification failed, result is %d", trust_eval_result);
354 /* get leaf certificate */
355 /* SSLCopyPeerCertificates is only available on OSX 10.5 or later */
356 #if !TARGET_OS_IPHONE
357 CFArrayRef cert_chain = NULL;
358 ret = SSLCopyPeerCertificates(sys->p_context, &cert_chain);
359 if (ret != noErr || !cert_chain) {
360 result = -1;
361 goto out;
364 if (CFArrayGetCount(cert_chain) == 0) {
365 CFRelease(cert_chain);
366 result = -1;
367 goto out;
370 leaf_cert = (SecCertificateRef)CFArrayGetValueAtIndex(cert_chain, 0);
371 CFRetain(leaf_cert);
372 CFRelease(cert_chain);
373 #else
374 /* SecTrustGetCertificateAtIndex is only available on 10.7 or iOS */
375 if (SecTrustGetCertificateCount(trust) == 0) {
376 result = -1;
377 goto out;
380 leaf_cert = SecTrustGetCertificateAtIndex(trust, 0);
381 CFRetain(leaf_cert);
382 #endif
385 /* check if leaf already accepted */
386 CFIndex max = CFArrayGetCount(sys->p_cred->whitelist);
387 for (CFIndex i = 0; i < max; ++i) {
388 CFDictionaryRef dict = CFArrayGetValueAtIndex(sys->p_cred->whitelist, i);
389 CFStringRef knownHost = (CFStringRef)CFDictionaryGetValue(dict, cfKeyHost);
390 SecCertificateRef knownCert = (SecCertificateRef)CFDictionaryGetValue(dict, cfKeyCertificate);
392 if (!knownHost || !knownCert)
393 continue;
395 if (CFEqual(knownHost, cfHostname) && CFEqual(knownCert, leaf_cert)) {
396 msg_Warn(sys->obj, "certificate already accepted, continuing");
397 result = 0;
398 goto out;
402 /* We do not show more certificate details yet because there is no proper API to get
403 a summary of the certificate. SecCertificateCopySubjectSummary is the only method
404 available on iOS and 10.6. More promising API functions such as
405 SecCertificateCopyLongDescription also print out the subject only, more or less.
406 But only showing the certificate subject is of no real help for the user.
407 We could use SecCertificateCopyValues, but then we need to parse all OID values for
408 ourself. This is too mad for just printing information the user will never check
409 anyway.
412 const char *msg = N_("You attempted to reach %s. "
413 "However the security certificate presented by the server "
414 "is unknown and could not be authenticated by any trusted "
415 "Certification Authority. "
416 "This problem may be caused by a configuration error "
417 "or an attempt to breach your security or your privacy.\n\n"
418 "If in doubt, abort now.\n");
419 int answer = vlc_dialog_wait_question(sys->obj,
420 VLC_DIALOG_QUESTION_WARNING, _("Abort"),
421 _("Accept certificate temporarily"),
422 NULL, _("Insecure site"),
423 vlc_gettext (msg), hostname);
424 if (answer == 1) {
425 msg_Warn(sys->obj, "Proceeding despite of failed certificate validation");
427 /* save leaf certificate in whitelist */
428 const void *keys[] = {cfKeyHost, cfKeyCertificate};
429 const void *values[] = {cfHostname, leaf_cert};
430 CFDictionaryRef dict = CFDictionaryCreate(kCFAllocatorDefault,
431 keys, values, 2,
432 &kCFTypeDictionaryKeyCallBacks,
433 &kCFTypeDictionaryValueCallBacks);
434 if (!dict) {
435 msg_Err(sys->obj, "error creating dict");
436 result = -1;
437 goto out;
440 CFArrayAppendValue(sys->p_cred->whitelist, dict);
441 CFRelease(dict);
443 result = 0;
444 goto out;
446 } else {
447 result = -1;
448 goto out;
451 out:
452 CFRelease(trust);
454 if (cfHostname)
455 CFRelease(cfHostname);
456 if (leaf_cert)
457 CFRelease(leaf_cert);
459 return result;
463 * @return -1 on fatal error, 0 on successful handshake completion,
464 * 1 if more would-be blocking recv is needed,
465 * 2 if more would-be blocking send is required.
467 static int st_Handshake (vlc_tls_creds_t *crd, vlc_tls_t *session,
468 const char *host, const char *service,
469 char **restrict alp) {
471 vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
473 VLC_UNUSED(service);
475 OSStatus retValue = SSLHandshake(sys->p_context);
477 // Only try to use ALPN on recent enough SDKs
478 // macOS 10.13.2, iOS 11, tvOS 11, watchOS 4
479 #if (TARGET_OS_OSX && MAC_OS_X_VERSION_MAX_ALLOWED >= 101302) || \
480 (TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000) || \
481 (TARGET_OS_TV && __TV_OS_VERSION_MAX_ALLOWED >= 110000) || \
482 (TARGET_OS_WATCH && __WATCH_OS_VERSION_MAX_ALLOWED >= 40000)
483 #pragma clang diagnostic push
484 #pragma clang diagnostic ignored "-Wpartial-availability"
486 /* Handle ALPN data */
487 if (alp != NULL) {
488 if (SSLCopyALPNProtocols != NULL) {
489 CFArrayRef alpnArray = NULL;
490 OSStatus res = SSLCopyALPNProtocols(sys->p_context, &alpnArray);
491 if (res == noErr && alpnArray) {
492 *alp = CFArrayALPNCopyFirst(alpnArray);
493 if (unlikely(*alp == NULL))
494 return -1;
495 } else {
496 *alp = NULL;
498 } else {
499 *alp = NULL;
503 #pragma clang diagnostic pop
504 #else
506 /* No ALPN support */
507 if (alp != NULL) {
508 *alp = NULL;
511 #endif
513 if (retValue == errSSLWouldBlock) {
514 msg_Dbg(crd, "handshake is blocked, try again later");
515 return 1 + (sys->b_blocking_send ? 1 : 0);
518 switch (retValue) {
519 case noErr:
520 if (sys->b_server_mode == false && st_validateServerCertificate(session, host) != 0) {
521 return -1;
523 msg_Dbg(crd, "handshake completed successfully");
524 sys->b_handshaked = true;
525 return 0;
527 case errSSLServerAuthCompleted:
528 msg_Dbg(crd, "SSLHandshake returned errSSLServerAuthCompleted, continuing handshake");
529 return st_Handshake(crd, session, host, service, alp);
531 case errSSLConnectionRefused:
532 msg_Err(crd, "connection was refused");
533 return -1;
534 case errSSLNegotiation:
535 msg_Err(crd, "cipher suite negotiation failed");
536 return -1;
537 case errSSLFatalAlert:
538 msg_Err(crd, "fatal error occurred during handshake");
539 return -1;
541 default:
542 msg_Err(crd, "handshake returned error %d", (int)retValue);
543 return -1;
547 static int st_GetFD (vlc_tls_t *session)
549 vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
550 vlc_tls_t *sock = sys->sock;
552 return vlc_tls_GetFD(sock);
556 * Sends data through a TLS session.
558 static ssize_t st_Send (vlc_tls_t *session, const struct iovec *iov,
559 unsigned count)
561 vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
562 OSStatus ret = noErr;
564 if (unlikely(count == 0))
565 return 0;
567 vlc_mutex_lock(&sys->lock);
570 * SSLWrite does not return the number of bytes actually written to
571 * the socket, but the number of bytes written to the internal cache.
573 * If return value is errSSLWouldBlock, the underlying socket cannot
574 * send all data, but the data is already cached. In this situation,
575 * we need to call SSLWrite again. To ensure this call even for the
576 * last bytes, we return EAGAIN. On the next call, we give no new data
577 * to SSLWrite until the error is not errSSLWouldBlock anymore.
579 * This code is adapted the same way as done in curl.
580 * (https://github.com/bagder/curl/blob/master/lib/curl_darwinssl.c#L2067)
583 /* EAGAIN is not expected by net_Write in this situation,
584 so use EINTR here */
585 int againErr = sys->b_server_mode ? EAGAIN : EINTR;
587 size_t actualSize;
588 if (sys->i_send_buffered_bytes > 0) {
589 ret = SSLWrite(sys->p_context, NULL, 0, &actualSize);
591 if (ret == noErr) {
592 /* actualSize remains zero because no new data send */
593 actualSize = sys->i_send_buffered_bytes;
594 sys->i_send_buffered_bytes = 0;
596 } else if (ret == errSSLWouldBlock) {
597 vlc_mutex_unlock(&sys->lock);
598 errno = againErr;
599 return -1;
602 } else {
603 ret = SSLWrite(sys->p_context, iov->iov_base, iov->iov_len,
604 &actualSize);
606 if (ret == errSSLWouldBlock) {
607 sys->i_send_buffered_bytes = iov->iov_len;
608 errno = againErr;
609 vlc_mutex_unlock(&sys->lock);
610 return -1;
614 vlc_mutex_unlock(&sys->lock);
615 return ret != noErr ? st_Error(session, ret) : actualSize;
619 * Receives data through a TLS session.
621 static ssize_t st_Recv (vlc_tls_t *session, struct iovec *iov, unsigned count)
623 vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
625 if (unlikely(count == 0))
626 return 0;
628 vlc_mutex_lock(&sys->lock);
630 size_t actualSize;
631 OSStatus ret = SSLRead(sys->p_context, iov->iov_base, iov->iov_len,
632 &actualSize);
634 if (ret == errSSLWouldBlock && actualSize) {
635 vlc_mutex_unlock(&sys->lock);
636 return actualSize;
639 /* peer performed shutdown */
640 if (ret == errSSLClosedNoNotify || ret == errSSLClosedGraceful) {
641 msg_Dbg(sys->obj, "Got close notification with code %i", (int)ret);
642 vlc_mutex_unlock(&sys->lock);
643 return 0;
646 vlc_mutex_unlock(&sys->lock);
647 return ret != noErr ? st_Error(session, ret) : actualSize;
651 * Closes a TLS session.
654 static int st_SessionShutdown (vlc_tls_t *session, bool duplex) {
656 vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
658 msg_Dbg(sys->obj, "shutdown TLS session");
660 vlc_mutex_destroy(&sys->lock);
662 OSStatus ret = noErr;
663 VLC_UNUSED(duplex);
665 if (sys->b_handshaked) {
666 ret = SSLClose(sys->p_context);
669 if (ret != noErr) {
670 msg_Warn(sys->obj, "Cannot close ssl context (%i)", (int)ret);
671 return ret;
674 return 0;
677 static void st_SessionClose (vlc_tls_t *session) {
679 vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
680 msg_Dbg(sys->obj, "close TLS session");
682 if (sys->p_context) {
683 #if TARGET_OS_IPHONE
684 CFRelease(sys->p_context);
685 #else
686 if (SSLDisposeContext(sys->p_context) != noErr) {
687 msg_Err(sys->obj, "error deleting context");
689 #endif
691 free(sys);
695 * Initializes a client-side TLS session.
698 static vlc_tls_t *st_SessionOpenCommon(vlc_tls_creds_t *crd, vlc_tls_t *sock,
699 bool b_server)
701 vlc_tls_st_t *sys = malloc(sizeof (*sys));
702 if (unlikely(sys == NULL))
703 return NULL;
705 sys->p_cred = crd->sys;
706 sys->b_handshaked = false;
707 sys->b_blocking_send = false;
708 sys->i_send_buffered_bytes = 0;
709 sys->p_context = NULL;
710 sys->sock = sock;
711 sys->b_server_mode = b_server;
712 vlc_mutex_init(&sys->lock);
713 sys->obj = VLC_OBJECT(crd);
715 vlc_tls_t *tls = &sys->tls;
717 tls->get_fd = st_GetFD;
718 tls->readv = st_Recv;
719 tls->writev = st_Send;
720 tls->shutdown = st_SessionShutdown;
721 tls->close = st_SessionClose;
722 crd->handshake = st_Handshake;
724 SSLContextRef p_context = NULL;
725 #if TARGET_OS_IPHONE
726 p_context = SSLCreateContext(NULL, b_server ? kSSLServerSide : kSSLClientSide, kSSLStreamType);
727 if (p_context == NULL) {
728 msg_Err(crd, "cannot create ssl context");
729 goto error;
731 #else
732 if (SSLNewContext(b_server, &p_context) != noErr) {
733 msg_Err(crd, "error calling SSLNewContext");
734 goto error;
736 #endif
738 sys->p_context = p_context;
740 OSStatus ret = SSLSetIOFuncs(p_context, st_SocketReadFunc, st_SocketWriteFunc);
741 if (ret != noErr) {
742 msg_Err(crd, "cannot set io functions");
743 goto error;
746 ret = SSLSetConnection(p_context, tls);
747 if (ret != noErr) {
748 msg_Err(crd, "cannot set connection");
749 goto error;
752 return tls;
754 error:
755 st_SessionClose(tls);
756 return NULL;
759 static vlc_tls_t *st_ClientSessionOpen(vlc_tls_creds_t *crd, vlc_tls_t *sock,
760 const char *hostname, const char *const *alpn)
762 msg_Dbg(crd, "open TLS session for %s", hostname);
764 vlc_tls_t *tls = st_SessionOpenCommon(crd, sock, false);
765 if (tls == NULL)
766 return NULL;
768 vlc_tls_st_t *sys = (vlc_tls_st_t *)tls;
770 OSStatus ret = SSLSetPeerDomainName(sys->p_context, hostname, strlen(hostname));
771 if (ret != noErr) {
772 msg_Err(crd, "cannot set peer domain name");
773 goto error;
776 // Only try to use ALPN on recent enough SDKs
777 // macOS 10.13.2, iOS 11, tvOS 11, watchOS 4
778 #if (TARGET_OS_OSX && MAC_OS_X_VERSION_MAX_ALLOWED >= 101302) || \
779 (TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000) || \
780 (TARGET_OS_TV && __TV_OS_VERSION_MAX_ALLOWED >= 110000) || \
781 (TARGET_OS_WATCH && __WATCH_OS_VERSION_MAX_ALLOWED >= 40000)
782 #pragma clang diagnostic push
783 #pragma clang diagnostic ignored "-Wpartial-availability"
785 /* Handle ALPN */
786 if (alpn != NULL) {
787 if (SSLSetALPNProtocols != NULL) {
788 CFMutableArrayRef alpnValues = alpnToCFArray(alpn);
790 if (alpnValues == NULL) {
791 msg_Err(crd, "cannot create CFMutableArray for ALPN values");
792 goto error;
795 OSStatus ret = SSLSetALPNProtocols(sys->p_context, alpnValues);
796 if (ret != noErr){
797 msg_Err(crd, "failed setting ALPN protocols (%i)", ret);
799 CFRelease(alpnValues);
800 } else {
801 msg_Warn(crd, "Ignoring ALPN request due to lack of support in the backend. Proxy behavior potentially undefined.");
805 #pragma clang diagnostic pop
806 #else
808 /* No ALPN support */
809 if (alpn != NULL) {
810 // Fallback if SDK does not has SSLSetALPNProtocols
811 msg_Warn(crd, "Compiled in SDK without ALPN support. Proxy behavior potentially undefined.");
812 #warning ALPN support in your SDK version missing (need 10.13.2), proxy behavior potentially undefined (rdar://29127318, #17721)
815 #endif
817 /* disable automatic validation. We do so manually to also handle invalid
818 certificates */
820 /* this has effect only on iOS 5 and OSX 10.8 or later ... */
821 ret = SSLSetSessionOption(sys->p_context, kSSLSessionOptionBreakOnServerAuth, true);
822 if (ret != noErr) {
823 msg_Err (crd, "cannot set session option");
824 goto error;
826 #if !TARGET_OS_IPHONE
827 /* ... thus calling this for earlier osx versions, which is not available on iOS in turn */
828 ret = SSLSetEnableCertVerify(sys->p_context, false);
829 if (ret != noErr) {
830 msg_Err(crd, "error setting enable cert verify");
831 goto error;
833 #endif
835 return tls;
837 error:
838 st_SessionShutdown(tls, true);
839 st_SessionClose(tls);
840 return NULL;
844 * Initializes a client-side TLS credentials.
846 static int OpenClient (vlc_tls_creds_t *crd) {
848 msg_Dbg(crd, "open st client");
850 vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
851 if (unlikely(sys == NULL))
852 return VLC_ENOMEM;
854 sys->whitelist = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
855 sys->server_cert_chain = NULL;
857 crd->sys = sys;
858 crd->open = st_ClientSessionOpen;
860 return VLC_SUCCESS;
863 static void CloseClient (vlc_tls_creds_t *crd) {
864 msg_Dbg(crd, "close secure transport client");
866 vlc_tls_creds_sys_t *sys = crd->sys;
868 if (sys->whitelist)
869 CFRelease(sys->whitelist);
871 free(sys);
874 /* Begin of server-side methods */
875 #if !TARGET_OS_IPHONE
878 * Initializes a server-side TLS session.
880 static vlc_tls_t *st_ServerSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *sock,
881 const char *hostname, const char *const *alpn) {
883 VLC_UNUSED(hostname);
884 VLC_UNUSED(alpn);
885 msg_Dbg(crd, "open TLS server session");
887 vlc_tls_t *tls = st_SessionOpenCommon(crd, sock, true);
888 if (tls != NULL)
889 return NULL;
891 vlc_tls_st_t *sys = (vlc_tls_st_t *)tls;
892 vlc_tls_creds_sys_t *p_cred_sys = crd->sys;
894 OSStatus ret = SSLSetCertificate(sys->p_context, p_cred_sys->server_cert_chain);
895 if (ret != noErr) {
896 msg_Err(crd, "cannot set server certificate");
897 goto error;
900 return tls;
902 error:
903 st_SessionShutdown(tls, true);
904 st_SessionClose(tls);
905 return NULL;
909 * Initializes server-side TLS credentials.
911 static int OpenServer (vlc_tls_creds_t *crd, const char *cert, const char *key) {
914 * This function expects the label of the certificate in "cert", stored
915 * in the MacOS keychain. The appropriate private key is found automatically.
917 VLC_UNUSED(key);
918 OSStatus ret;
920 msg_Dbg(crd, "open st server");
923 * Get the server certificate.
925 * This API is deprecated, but the replacement SecItemCopyMatching
926 * only works on >= 10.7
928 SecKeychainAttribute attrib = { kSecLabelItemAttr, strlen(cert), (void *)cert };
929 SecKeychainAttributeList attrList = { 1, &attrib };
931 SecKeychainSearchRef searchReference = NULL;
932 ret = SecKeychainSearchCreateFromAttributes(NULL, kSecCertificateItemClass,
933 &attrList, &searchReference);
934 if (ret != noErr || searchReference == NULL) {
935 msg_Err(crd, "Cannot find certificate with alias %s", cert);
936 return VLC_EGENERIC;
939 SecKeychainItemRef itemRef = NULL;
940 ret = SecKeychainSearchCopyNext(searchReference, &itemRef);
941 if (ret != noErr) {
942 msg_Err(crd, "Cannot get certificate with alias %s, error: %d", cert, ret);
943 return VLC_EGENERIC;
945 CFRelease(searchReference);
947 /* cast allowed according to documentation */
948 SecCertificateRef certificate = (SecCertificateRef)itemRef;
950 SecIdentityRef cert_identity = NULL;
951 ret = SecIdentityCreateWithCertificate(NULL, certificate, &cert_identity);
952 if (ret != noErr) {
953 msg_Err(crd, "Cannot get private key for certificate");
954 CFRelease(certificate);
955 return VLC_EGENERIC;
959 * We try to validate the server certificate, but do not care about the result.
960 * The only aim is to get the certificate chain.
962 SecPolicyRef policy = SecPolicyCreateSSL(true, NULL);
963 SecTrustRef trust_ref = NULL;
964 int result = VLC_SUCCESS;
966 /* According to docu its fine to pass just one certificate */
967 ret = SecTrustCreateWithCertificates((CFArrayRef)certificate, policy, &trust_ref);
968 if (ret != noErr) {
969 msg_Err(crd, "Cannot create trust");
970 result = VLC_EGENERIC;
971 goto out;
974 SecTrustResultType status;
975 ret = SecTrustEvaluate(trust_ref, &status);
976 if (ret != noErr) {
977 msg_Err(crd, "Error evaluating trust");
978 result = VLC_EGENERIC;
979 goto out;
982 CFArrayRef cert_chain = NULL;
983 CSSM_TP_APPLE_EVIDENCE_INFO *status_chain;
984 ret = SecTrustGetResult(trust_ref, &status, &cert_chain, &status_chain);
985 if (ret != noErr || !cert_chain) {
986 msg_Err(crd, "error while getting certificate chain");
987 result = VLC_EGENERIC;
988 goto out;
991 CFIndex num_cert_chain = CFArrayGetCount(cert_chain);
993 /* Build up the certificate chain array expected by SSLSetCertificate */
994 CFMutableArrayRef server_cert_chain = CFArrayCreateMutable(kCFAllocatorDefault, num_cert_chain, &kCFTypeArrayCallBacks);
995 CFArrayAppendValue(server_cert_chain, cert_identity);
997 msg_Dbg(crd, "Found certificate chain with %ld entries for server certificate", num_cert_chain);
998 if (num_cert_chain > 1)
999 CFArrayAppendArray(server_cert_chain, cert_chain, CFRangeMake(1, num_cert_chain - 1));
1000 CFRelease(cert_chain);
1002 vlc_tls_creds_sys_t *sys = malloc(sizeof(*sys));
1003 if (unlikely(sys == NULL)) {
1004 CFRelease(server_cert_chain);
1005 result = VLC_ENOMEM;
1006 goto out;
1009 sys->server_cert_chain = server_cert_chain;
1010 sys->whitelist = NULL;
1012 crd->sys = sys;
1013 crd->open = st_ServerSessionOpen;
1015 out:
1016 if (policy)
1017 CFRelease(policy);
1018 if (trust_ref)
1019 CFRelease(trust_ref);
1021 if (certificate)
1022 CFRelease(certificate);
1023 if (cert_identity)
1024 CFRelease(cert_identity);
1026 return result;
1029 static void CloseServer (vlc_tls_creds_t *crd) {
1030 msg_Dbg(crd, "close secure transport server");
1032 vlc_tls_creds_sys_t *sys = crd->sys;
1034 if (sys->server_cert_chain)
1035 CFRelease(sys->server_cert_chain);
1037 free(sys);
1040 #endif /* !TARGET_OS_IPHONE */