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 /*****************************************************************************
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
++) {
58 CFStringCreateWithCString(kCFAllocatorDefault
, alpn
[i
], kCFStringEncodingASCII
);
59 if (alpnVal
== NULL
) {
60 // Failed to convert the ALPN value to CFString, error out.
61 CFRelease(alpnValues
);
64 CFArrayAppendValue(alpnValues
, alpnVal
);
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
);
85 CFStringGetMaximumSizeForEncoding(length
, kCFStringEncodingASCII
);
87 // If result would exceed LONG_MAX, kCFNotFound is returned
88 if (unlikely(maxSize
== kCFNotFound
)) {
92 // Account for the null terminator
95 char *buffer
= (char *)malloc(maxSize
);
97 if (unlikely(buffer
== NULL
)) {
101 // Copy CFString in requested encoding to buffer
102 Boolean success
= CFStringGetCString(cfString
, buffer
, maxSize
, kCFStringEncodingASCII
);
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
);
119 CFStringRef alpnVal
= CFArrayGetValueAtIndex(alpnArray
, 0);
120 return CFStringCopyASCIICString(alpnVal
);
123 /*****************************************************************************
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
*);
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
146 #if !TARGET_OS_IPHONE
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 */
158 #define cfKeyHost CFSTR("host")
159 #define cfKeyCertificate CFSTR("certificate")
162 CFMutableArrayRef whitelist
;
164 /* valid in server mode */
165 CFArrayRef server_cert_chain
;
166 } vlc_tls_creds_sys_t
;
170 SSLContextRef p_context
;
171 vlc_tls_creds_sys_t
*p_cred
;
172 size_t i_send_buffered_bytes
;
176 bool b_blocking_send
;
183 static int st_Error (vlc_tls_t
*obj
, int val
)
185 vlc_tls_st_t
*sys
= (vlc_tls_st_t
*)obj
;
189 case errSSLWouldBlock
:
193 case errSSLClosedGraceful
:
194 case errSSLClosedAbort
:
195 msg_Dbg(sys
->obj
, "Connection closed with code %d", val
);
199 msg_Err(sys
->obj
, "Found error %d", val
);
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
,
212 size_t *dataLength
) {
214 vlc_tls_t
*session
= (vlc_tls_t
*)connection
;
215 vlc_tls_st_t
*sys
= (vlc_tls_st_t
*)session
;
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);
226 msg_Dbg(sys
->obj
, "found eof");
227 retValue
= errSSLClosedGraceful
;
228 } else { /* do the switch */
231 /* connection closed */
232 retValue
= errSSLClosedGraceful
;
235 retValue
= errSSLClosedAbort
;
238 retValue
= errSSLWouldBlock
;
239 sys
->b_blocking_send
= false;
242 msg_Err(sys
->obj
, "try to read %zu bytes, "
243 "got error %d", iov
.iov_len
, errno
);
251 iov
.iov_base
= (char *)iov
.iov_base
+ val
;
255 *dataLength
-= iov
.iov_len
;
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
,
266 size_t *dataLength
) {
268 vlc_tls_t
*session
= (vlc_tls_t
*)connection
;
269 vlc_tls_st_t
*sys
= (vlc_tls_st_t
*)session
;
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);
281 retValue
= errSSLWouldBlock
;
282 sys
->b_blocking_send
= true;
287 retValue
= errSSLClosedAbort
;
291 msg_Err(sys
->obj
, "error while writing: %d", errno
);
298 iov
.iov_base
= (char *)iov
.iov_base
+ val
;
302 *dataLength
-= iov
.iov_len
;
306 static int st_validateServerCertificate (vlc_tls_t
*session
, const char *hostname
) {
308 vlc_tls_st_t
*sys
= (vlc_tls_st_t
*)session
;
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");
319 CFStringRef cfHostname
= CFStringCreateWithCString(kCFAllocatorDefault
,
321 kCFStringEncodingUTF8
);
324 /* enable default root / anchor certificates */
325 ret
= SecTrustSetAnchorCertificates(trust
, NULL
);
327 msg_Err(sys
->obj
, "error setting anchor certificates");
332 SecTrustResultType trust_eval_result
= 0;
334 ret
= SecTrustEvaluate(trust
, &trust_eval_result
);
336 msg_Err(sys
->obj
, "error calling SecTrustEvaluate");
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
);
348 case kSecTrustResultRecoverableTrustFailure
:
349 case kSecTrustResultDeny
:
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
) {
364 if (CFArrayGetCount(cert_chain
) == 0) {
365 CFRelease(cert_chain
);
370 leaf_cert
= (SecCertificateRef
)CFArrayGetValueAtIndex(cert_chain
, 0);
372 CFRelease(cert_chain
);
374 /* SecTrustGetCertificateAtIndex is only available on 10.7 or iOS */
375 if (SecTrustGetCertificateCount(trust
) == 0) {
380 leaf_cert
= SecTrustGetCertificateAtIndex(trust
, 0);
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
)
395 if (CFEqual(knownHost
, cfHostname
) && CFEqual(knownCert
, leaf_cert
)) {
396 msg_Warn(sys
->obj
, "certificate already accepted, continuing");
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
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
);
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
,
432 &kCFTypeDictionaryKeyCallBacks
,
433 &kCFTypeDictionaryValueCallBacks
);
435 msg_Err(sys
->obj
, "error creating dict");
440 CFArrayAppendValue(sys
->p_cred
->whitelist
, dict
);
455 CFRelease(cfHostname
);
457 CFRelease(leaf_cert
);
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
;
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 */
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
))
503 #pragma clang diagnostic pop
506 /* No ALPN support */
513 if (retValue
== errSSLWouldBlock
) {
514 msg_Dbg(crd
, "handshake is blocked, try again later");
515 return 1 + (sys
->b_blocking_send
? 1 : 0);
520 if (sys
->b_server_mode
== false && st_validateServerCertificate(session
, host
) != 0) {
523 msg_Dbg(crd
, "handshake completed successfully");
524 sys
->b_handshaked
= true;
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");
534 case errSSLNegotiation
:
535 msg_Err(crd
, "cipher suite negotiation failed");
537 case errSSLFatalAlert
:
538 msg_Err(crd
, "fatal error occurred during handshake");
542 msg_Err(crd
, "handshake returned error %d", (int)retValue
);
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
,
561 vlc_tls_st_t
*sys
= (vlc_tls_st_t
*)session
;
562 OSStatus ret
= noErr
;
564 if (unlikely(count
== 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,
585 int againErr
= sys
->b_server_mode
? EAGAIN
: EINTR
;
588 if (sys
->i_send_buffered_bytes
> 0) {
589 ret
= SSLWrite(sys
->p_context
, NULL
, 0, &actualSize
);
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
);
603 ret
= SSLWrite(sys
->p_context
, iov
->iov_base
, iov
->iov_len
,
606 if (ret
== errSSLWouldBlock
) {
607 sys
->i_send_buffered_bytes
= iov
->iov_len
;
609 vlc_mutex_unlock(&sys
->lock
);
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))
628 vlc_mutex_lock(&sys
->lock
);
631 OSStatus ret
= SSLRead(sys
->p_context
, iov
->iov_base
, iov
->iov_len
,
634 if (ret
== errSSLWouldBlock
&& actualSize
) {
635 vlc_mutex_unlock(&sys
->lock
);
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
);
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
;
665 if (sys
->b_handshaked
) {
666 ret
= SSLClose(sys
->p_context
);
670 msg_Warn(sys
->obj
, "Cannot close ssl context (%i)", (int)ret
);
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
) {
684 CFRelease(sys
->p_context
);
686 if (SSLDisposeContext(sys
->p_context
) != noErr
) {
687 msg_Err(sys
->obj
, "error deleting context");
695 * Initializes a client-side TLS session.
698 static vlc_tls_t
*st_SessionOpenCommon(vlc_tls_creds_t
*crd
, vlc_tls_t
*sock
,
701 vlc_tls_st_t
*sys
= malloc(sizeof (*sys
));
702 if (unlikely(sys
== 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
;
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
;
726 p_context
= SSLCreateContext(NULL
, b_server
? kSSLServerSide
: kSSLClientSide
, kSSLStreamType
);
727 if (p_context
== NULL
) {
728 msg_Err(crd
, "cannot create ssl context");
732 if (SSLNewContext(b_server
, &p_context
) != noErr
) {
733 msg_Err(crd
, "error calling SSLNewContext");
738 sys
->p_context
= p_context
;
740 OSStatus ret
= SSLSetIOFuncs(p_context
, st_SocketReadFunc
, st_SocketWriteFunc
);
742 msg_Err(crd
, "cannot set io functions");
746 ret
= SSLSetConnection(p_context
, tls
);
748 msg_Err(crd
, "cannot set connection");
755 st_SessionClose(tls
);
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);
768 vlc_tls_st_t
*sys
= (vlc_tls_st_t
*)tls
;
770 OSStatus ret
= SSLSetPeerDomainName(sys
->p_context
, hostname
, strlen(hostname
));
772 msg_Err(crd
, "cannot set peer domain name");
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"
787 if (SSLSetALPNProtocols
!= NULL
) {
788 CFMutableArrayRef alpnValues
= alpnToCFArray(alpn
);
790 if (alpnValues
== NULL
) {
791 msg_Err(crd
, "cannot create CFMutableArray for ALPN values");
795 OSStatus ret
= SSLSetALPNProtocols(sys
->p_context
, alpnValues
);
797 msg_Err(crd
, "failed setting ALPN protocols (%i)", ret
);
799 CFRelease(alpnValues
);
801 msg_Warn(crd
, "Ignoring ALPN request due to lack of support in the backend. Proxy behavior potentially undefined.");
805 #pragma clang diagnostic pop
808 /* No ALPN support */
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)
817 /* disable automatic validation. We do so manually to also handle invalid
820 /* this has effect only on iOS 5 and OSX 10.8 or later ... */
821 ret
= SSLSetSessionOption(sys
->p_context
, kSSLSessionOptionBreakOnServerAuth
, true);
823 msg_Err (crd
, "cannot set session option");
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);
830 msg_Err(crd
, "error setting enable cert verify");
838 st_SessionShutdown(tls
, true);
839 st_SessionClose(tls
);
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
))
854 sys
->whitelist
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
855 sys
->server_cert_chain
= NULL
;
858 crd
->open
= st_ClientSessionOpen
;
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
;
869 CFRelease(sys
->whitelist
);
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
);
885 msg_Dbg(crd
, "open TLS server session");
887 vlc_tls_t
*tls
= st_SessionOpenCommon(crd
, sock
, true);
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
);
896 msg_Err(crd
, "cannot set server certificate");
903 st_SessionShutdown(tls
, true);
904 st_SessionClose(tls
);
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.
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
);
939 SecKeychainItemRef itemRef
= NULL
;
940 ret
= SecKeychainSearchCopyNext(searchReference
, &itemRef
);
942 msg_Err(crd
, "Cannot get certificate with alias %s, error: %d", cert
, ret
);
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
);
953 msg_Err(crd
, "Cannot get private key for certificate");
954 CFRelease(certificate
);
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
);
969 msg_Err(crd
, "Cannot create trust");
970 result
= VLC_EGENERIC
;
974 SecTrustResultType status
;
975 ret
= SecTrustEvaluate(trust_ref
, &status
);
977 msg_Err(crd
, "Error evaluating trust");
978 result
= VLC_EGENERIC
;
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
;
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
;
1009 sys
->server_cert_chain
= server_cert_chain
;
1010 sys
->whitelist
= NULL
;
1013 crd
->open
= st_ServerSessionOpen
;
1019 CFRelease(trust_ref
);
1022 CFRelease(certificate
);
1024 CFRelease(cert_identity
);
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
);
1040 #endif /* !TARGET_OS_IPHONE */