securetransport: Fix compilation
[vlc.git] / modules / misc / securetransport.c
1 /*****************************************************************************
2  * securetransport.c
3  *****************************************************************************
4  * Copyright (C) 2013 David Fuhrmann
5  *
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.
10  *
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.
15  *
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  *****************************************************************************/
20
21 /*****************************************************************************
22  * Preamble
23  *****************************************************************************/
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include <vlc_common.h>
30 #include <vlc_plugin.h>
31 #include <vlc_tls.h>
32 #include <vlc_dialog.h>
33
34 #include <Security/Security.h>
35 #include <Security/SecureTransport.h>
36 #include <TargetConditionals.h>
37
38 /* From MacErrors.h (cannot be included because it isn't present in iOS: */
39 #ifndef ioErr
40 # define ioErr -36
41 #endif
42
43 /*****************************************************************************
44  * Module descriptor
45  *****************************************************************************/
46 static int  OpenClient  (vlc_tls_creds_t *);
47 static void CloseClient (vlc_tls_creds_t *);
48
49 #if !TARGET_OS_IPHONE
50     static int  OpenServer  (vlc_tls_creds_t *crd, const char *cert, const char *key);
51     static void CloseServer (vlc_tls_creds_t *);
52 #endif
53
54 vlc_module_begin ()
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)
60
61     /*
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
64      * implemented.
65      */
66 #if !TARGET_OS_IPHONE
67     add_submodule()
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 */
74
75 vlc_module_end ()
76
77
78 #define cfKeyHost CFSTR("host")
79 #define cfKeyCertificate CFSTR("certificate")
80
81 typedef struct {
82     CFMutableArrayRef whitelist;
83
84     /* valid in server mode */
85     CFArrayRef server_cert_chain;
86 } vlc_tls_creds_sys_t;
87
88 typedef struct {
89     vlc_tls_t tls;
90     SSLContextRef p_context;
91     vlc_tls_creds_sys_t *p_cred;
92     size_t i_send_buffered_bytes;
93     vlc_tls_t *sock;
94     vlc_object_t *obj;
95
96     bool b_blocking_send;
97     bool b_handshaked;
98     bool b_server_mode;
99 } vlc_tls_st_t;
100
101 static int st_Error (vlc_tls_t *obj, int val)
102 {
103     vlc_tls_st_t *sys = (vlc_tls_st_t *)obj;
104
105     switch (val)
106     {
107         case errSSLWouldBlock:
108             errno = EAGAIN;
109             break;
110
111         case errSSLClosedGraceful:
112         case errSSLClosedAbort:
113             msg_Dbg(sys->obj, "Connection closed with code %d", val);
114             errno = ECONNRESET;
115             break;
116         default:
117             msg_Err(sys->obj, "Found error %d", val);
118             errno = ECONNRESET;
119     }
120     return -1;
121 }
122
123 /*
124  * Read function called by secure transport for socket read.
125  *
126  * Function is based on Apples SSLSample sample code.
127  */
128 static OSStatus st_SocketReadFunc (SSLConnectionRef connection,
129                                    void *data,
130                                    size_t *dataLength) {
131
132     vlc_tls_t *session = (vlc_tls_t *)connection;
133     vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
134     struct iovec iov = {
135         .iov_base = data,
136         .iov_len = *dataLength,
137     };
138     OSStatus retValue = noErr;
139
140     while (iov.iov_len > 0) {
141         ssize_t val = sys->sock->readv(sys->sock, &iov, 1);
142         if (val <= 0) {
143             if (val == 0) {
144                 msg_Dbg(sys->obj, "found eof");
145                 retValue = errSSLClosedGraceful;
146             } else { /* do the switch */
147                 switch (errno) {
148                     case ENOENT:
149                         /* connection closed */
150                         retValue = errSSLClosedGraceful;
151                         break;
152                     case ECONNRESET:
153                         retValue = errSSLClosedAbort;
154                         break;
155                     case EAGAIN:
156                         retValue = errSSLWouldBlock;
157                         sys->b_blocking_send = false;
158                         break;
159                     default:
160                         msg_Err(sys->obj, "try to read %zu bytes, "
161                                 "got error %d", iov.iov_len, errno);
162                         retValue = ioErr;
163                         break;
164                 }
165             }
166             break;
167         }
168
169         iov.iov_base = (char *)iov.iov_base + val;
170         iov.iov_len -= val;
171     }
172
173     *dataLength -= iov.iov_len;
174     return retValue;
175 }
176
177 /*
178  * Write function called by secure transport for socket read.
179  *
180  * Function is based on Apples SSLSample sample code.
181  */
182 static OSStatus st_SocketWriteFunc (SSLConnectionRef connection,
183                                     const void *data,
184                                     size_t *dataLength) {
185
186     vlc_tls_t *session = (vlc_tls_t *)connection;
187     vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
188     struct iovec iov = {
189         .iov_base = (void *)data,
190         .iov_len = *dataLength,
191     };
192     OSStatus retValue = noErr;
193
194     while (iov.iov_len > 0) {
195         ssize_t val = sys->sock->writev(sys->sock, &iov, 1);
196         if (val < 0) {
197             switch (errno) {
198                 case EAGAIN:
199                     retValue = errSSLWouldBlock;
200                     sys->b_blocking_send = true;
201                     break;
202
203                 case EPIPE:
204                 case ECONNRESET:
205                     retValue = errSSLClosedAbort;
206                     break;
207
208                 default:
209                     msg_Err(sys->obj, "error while writing: %d", errno);
210                     retValue = ioErr;
211                     break;
212             }
213             break;
214         }
215
216         iov.iov_base = (char *)iov.iov_base + val;
217         iov.iov_len -= val;
218     }
219
220     *dataLength -= iov.iov_len;
221     return retValue;
222 }
223
224 static int st_validateServerCertificate (vlc_tls_t *session, const char *hostname) {
225
226     vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
227     int result = -1;
228     SecCertificateRef leaf_cert = NULL;
229
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");
234         return -1;
235     }
236
237     CFStringRef cfHostname = CFStringCreateWithCString(kCFAllocatorDefault,
238                                                        hostname,
239                                                        kCFStringEncodingUTF8);
240
241
242     /* enable default root / anchor certificates */
243     ret = SecTrustSetAnchorCertificates(trust, NULL);
244     if (ret != noErr) {
245         msg_Err(sys->obj, "error setting anchor certificates");
246         result = -1;
247         goto out;
248     }
249
250     SecTrustResultType trust_eval_result = 0;
251
252     ret = SecTrustEvaluate(trust, &trust_eval_result);
253     if (ret != noErr) {
254         msg_Err(sys->obj, "error calling SecTrustEvaluate");
255         result = -1;
256         goto out;
257     }
258
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);
263             result = 0;
264             goto out;
265
266         case kSecTrustResultRecoverableTrustFailure:
267         case kSecTrustResultDeny:
268         default:
269             msg_Warn(sys->obj, "cerfificate verification failed, result is %d", trust_eval_result);
270     }
271
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) {
278         result = -1;
279         goto out;
280     }
281
282     if (CFArrayGetCount(cert_chain) == 0) {
283         CFRelease(cert_chain);
284         result = -1;
285         goto out;
286     }
287
288     leaf_cert = (SecCertificateRef)CFArrayGetValueAtIndex(cert_chain, 0);
289     CFRetain(leaf_cert);
290     CFRelease(cert_chain);
291 #else
292     /* SecTrustGetCertificateAtIndex is only available on 10.7 or iOS */
293     if (SecTrustGetCertificateCount(trust) == 0) {
294         result = -1;
295         goto out;
296     }
297
298     leaf_cert = SecTrustGetCertificateAtIndex(trust, 0);
299     CFRetain(leaf_cert);
300 #endif
301
302
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);
309
310         if (!knownHost || !knownCert)
311             continue;
312
313         if (CFEqual(knownHost, cfHostname) && CFEqual(knownCert, leaf_cert)) {
314             msg_Warn(sys->obj, "certificate already accepted, continuing");
315             result = 0;
316             goto out;
317         }
318     }
319
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
327        anyway.
328      */
329
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);
342     if (answer == 1) {
343         msg_Warn(sys->obj, "Proceeding despite of failed certificate validation");
344
345         /* save leaf certificate in whitelist */
346         const void *keys[] = {cfKeyHost, cfKeyCertificate};
347         const void *values[] = {cfHostname, leaf_cert};
348         CFDictionaryRef dict = CFDictionaryCreate(kCFAllocatorDefault,
349                                                    keys, values, 2,
350                                                    &kCFTypeDictionaryKeyCallBacks,
351                                                    &kCFTypeDictionaryValueCallBacks);
352         if (!dict) {
353             msg_Err(sys->obj, "error creating dict");
354             result = -1;
355             goto out;
356         }
357
358         CFArrayAppendValue(sys->p_cred->whitelist, dict);
359         CFRelease(dict);
360
361         result = 0;
362         goto out;
363
364     } else {
365         result = -1;
366         goto out;
367     }
368
369 out:
370     CFRelease(trust);
371
372     if (cfHostname)
373         CFRelease(cfHostname);
374     if (leaf_cert)
375         CFRelease(leaf_cert);
376
377     return result;
378 }
379
380 /*
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.
384  */
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) {
388
389     vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
390
391     VLC_UNUSED(service);
392
393     OSStatus retValue = SSLHandshake(sys->p_context);
394     if (alp != NULL) {
395         *alp = NULL;
396     }
397
398     if (retValue == errSSLWouldBlock) {
399         msg_Dbg(crd, "handshake is blocked, try again later");
400         return 1 + (sys->b_blocking_send ? 1 : 0);
401     }
402
403     switch (retValue) {
404         case noErr:
405             if (sys->b_server_mode == false && st_validateServerCertificate(session, host) != 0) {
406                 return -1;
407             }
408             msg_Dbg(crd, "handshake completed successfully");
409             sys->b_handshaked = true;
410             return 0;
411
412         case errSSLServerAuthCompleted:
413             msg_Dbg(crd, "SSLHandshake returned errSSLServerAuthCompleted, continuing handshake");
414             return st_Handshake(crd, session, host, service, alp);
415
416         case errSSLConnectionRefused:
417             msg_Err(crd, "connection was refused");
418             return -1;
419         case errSSLNegotiation:
420             msg_Err(crd, "cipher suite negotiation failed");
421             return -1;
422         case errSSLFatalAlert:
423             msg_Err(crd, "fatal error occurred during handshake");
424             return -1;
425
426         default:
427             msg_Err(crd, "handshake returned error %d", (int)retValue);
428             return -1;
429     }
430 }
431
432 static int st_GetFD (vlc_tls_t *session)
433 {
434     vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
435     vlc_tls_t *sock = sys->sock;
436
437     return vlc_tls_GetFD(sock);
438 }
439
440 /**
441  * Sends data through a TLS session.
442  */
443 static ssize_t st_Send (vlc_tls_t *session, const struct iovec *iov,
444                         unsigned count)
445 {
446     vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
447     OSStatus ret = noErr;
448
449     if (unlikely(count == 0))
450         return 0;
451
452     /*
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.
455      *
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.
461      *
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)
464      */
465
466     /* EAGAIN is not expected by net_Write in this situation,
467        so use EINTR here */
468     int againErr = sys->b_server_mode ? EAGAIN : EINTR;
469
470     size_t actualSize;
471     if (sys->i_send_buffered_bytes > 0) {
472         ret = SSLWrite(sys->p_context, NULL, 0, &actualSize);
473
474         if (ret == noErr) {
475             /* actualSize remains zero because no new data send */
476             actualSize = sys->i_send_buffered_bytes;
477             sys->i_send_buffered_bytes = 0;
478
479         } else if (ret == errSSLWouldBlock) {
480             errno = againErr;
481             return -1;
482         }
483
484     } else {
485         ret = SSLWrite(sys->p_context, iov->iov_base, iov->iov_len,
486                        &actualSize);
487
488         if (ret == errSSLWouldBlock) {
489             sys->i_send_buffered_bytes = iov->iov_len;
490             errno = againErr;
491             return -1;
492         }
493     }
494
495     return ret != noErr ? st_Error(session, ret) : actualSize;
496 }
497
498 /**
499  * Receives data through a TLS session.
500  */
501 static ssize_t st_Recv (vlc_tls_t *session, struct iovec *iov, unsigned count)
502 {
503     vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
504
505     if (unlikely(count == 0))
506         return 0;
507
508     size_t actualSize;
509     OSStatus ret = SSLRead(sys->p_context, iov->iov_base, iov->iov_len,
510                            &actualSize);
511
512     if (ret == errSSLWouldBlock && actualSize)
513         return actualSize;
514
515     /* peer performed shutdown */
516     if (ret == errSSLClosedNoNotify || ret == errSSLClosedGraceful) {
517         msg_Dbg(sys->obj, "Got close notification with code %i", (int)ret);
518         return 0;
519     }
520
521     return ret != noErr ? st_Error(session, ret) : actualSize;
522 }
523
524 /**
525  * Closes a TLS session.
526  */
527
528 static int st_SessionShutdown (vlc_tls_t *session, bool duplex) {
529
530     vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
531
532     msg_Dbg(sys->obj, "shutdown TLS session");
533
534     OSStatus ret = noErr;
535     VLC_UNUSED(duplex);
536
537     if (sys->b_handshaked) {
538         ret = SSLClose(sys->p_context);
539     }
540
541     if (ret != noErr) {
542         msg_Warn(sys->obj, "Cannot close ssl context (%i)", (int)ret);
543         return ret;
544     }
545
546     return 0;
547 }
548
549 static void st_SessionClose (vlc_tls_t *session) {
550
551     vlc_tls_st_t *sys = (vlc_tls_st_t *)session;
552     msg_Dbg(sys->obj, "close TLS session");
553
554     if (sys->p_context) {
555 #if TARGET_OS_IPHONE
556         CFRelease(sys->p_context);
557 #else
558         if (SSLDisposeContext(sys->p_context) != noErr) {
559             msg_Err(sys->obj, "error deleting context");
560         }
561 #endif
562     }
563     free(sys);
564 }
565
566 /**
567  * Initializes a client-side TLS session.
568  */
569
570 static vlc_tls_t *st_SessionOpenCommon(vlc_tls_creds_t *crd, vlc_tls_t *sock,
571                                        bool b_server)
572 {
573     vlc_tls_st_t *sys = malloc(sizeof (*sys));
574     if (unlikely(sys == NULL))
575         return NULL;
576
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;
582     sys->sock = sock;
583     sys->b_server_mode = b_server;
584     sys->obj = VLC_OBJECT(crd);
585
586     vlc_tls_t *tls = &sys->tls;
587
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;
594
595     SSLContextRef p_context = NULL;
596 #if TARGET_OS_IPHONE
597     p_context = SSLCreateContext(NULL, b_server ? kSSLServerSide : kSSLClientSide, kSSLStreamType);
598     if (p_context == NULL) {
599         msg_Err(crd, "cannot create ssl context");
600         goto error;
601     }
602 #else
603     if (SSLNewContext(b_server, &p_context) != noErr) {
604         msg_Err(crd, "error calling SSLNewContext");
605         goto error;
606     }
607 #endif
608
609     sys->p_context = p_context;
610
611     OSStatus ret = SSLSetIOFuncs(p_context, st_SocketReadFunc, st_SocketWriteFunc);
612     if (ret != noErr) {
613         msg_Err(crd, "cannot set io functions");
614         goto error;
615     }
616
617     ret = SSLSetConnection(p_context, tls);
618     if (ret != noErr) {
619         msg_Err(crd, "cannot set connection");
620         goto error;
621     }
622
623     return tls;
624
625 error:
626     st_SessionClose(tls);
627     return NULL;
628 }
629
630 static vlc_tls_t *st_ClientSessionOpen(vlc_tls_creds_t *crd, vlc_tls_t *sock,
631                                  const char *hostname, const char *const *alpn)
632 {
633     if (alpn != NULL) {
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)
636     }
637
638     msg_Dbg(crd, "open TLS session for %s", hostname);
639
640     vlc_tls_t *tls = st_SessionOpenCommon(crd, sock, false);
641     if (tls == NULL)
642         return NULL;
643
644     vlc_tls_st_t *sys = (vlc_tls_st_t *)tls;
645
646     OSStatus ret = SSLSetPeerDomainName(sys->p_context, hostname, strlen(hostname));
647     if (ret != noErr) {
648         msg_Err(crd, "cannot set peer domain name");
649         goto error;
650     }
651
652     /* disable automatic validation. We do so manually to also handle invalid
653        certificates */
654
655     /* this has effect only on iOS 5 and OSX 10.8 or later ... */
656     ret = SSLSetSessionOption(sys->p_context, kSSLSessionOptionBreakOnServerAuth, true);
657     if (ret != noErr) {
658         msg_Err (crd, "cannot set session option");
659         goto error;
660     }
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);
664     if (ret != noErr) {
665         msg_Err(crd, "error setting enable cert verify");
666         goto error;
667     }
668 #endif
669
670     return tls;
671
672 error:
673     st_SessionShutdown(tls, true);
674     st_SessionClose(tls);
675     return NULL;
676 }
677
678 /**
679  * Initializes a client-side TLS credentials.
680  */
681 static int OpenClient (vlc_tls_creds_t *crd) {
682
683     msg_Dbg(crd, "open st client");
684
685     vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
686     if (unlikely(sys == NULL))
687         return VLC_ENOMEM;
688
689     sys->whitelist = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
690     sys->server_cert_chain = NULL;
691
692     crd->sys = sys;
693     crd->open = st_ClientSessionOpen;
694
695     return VLC_SUCCESS;
696 }
697
698 static void CloseClient (vlc_tls_creds_t *crd) {
699     msg_Dbg(crd, "close secure transport client");
700
701     vlc_tls_creds_sys_t *sys = crd->sys;
702
703     if (sys->whitelist)
704         CFRelease(sys->whitelist);
705
706     free(sys);
707 }
708
709 /* Begin of server-side methods */
710 #if !TARGET_OS_IPHONE
711
712 /**
713  * Initializes a server-side TLS session.
714  */
715 static vlc_tls_t *st_ServerSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *sock,
716                                const char *hostname, const char *const *alpn) {
717
718     VLC_UNUSED(hostname);
719     VLC_UNUSED(alpn);
720     msg_Dbg(crd, "open TLS server session");
721
722     vlc_tls_t *tls = st_SessionOpenCommon(crd, sock, true);
723     if (tls != NULL)
724         return NULL;
725
726     vlc_tls_st_t *sys = (vlc_tls_st_t *)tls;
727     vlc_tls_creds_sys_t *p_cred_sys = crd->sys;
728
729     OSStatus ret = SSLSetCertificate(sys->p_context, p_cred_sys->server_cert_chain);
730     if (ret != noErr) {
731         msg_Err(crd, "cannot set server certificate");
732         goto error;
733     }
734
735     return tls;
736
737 error:
738     st_SessionShutdown(tls, true);
739     st_SessionClose(tls);
740     return NULL;
741 }
742
743 /**
744  * Initializes server-side TLS credentials.
745  */
746 static int OpenServer (vlc_tls_creds_t *crd, const char *cert, const char *key) {
747
748     /*
749      * This function expects the label of the certificate in "cert", stored
750      * in the MacOS keychain. The appropriate private key is found automatically.
751      */
752     VLC_UNUSED(key);
753     OSStatus ret;
754
755     msg_Dbg(crd, "open st server");
756
757     /*
758      * Get the server certificate.
759      *
760      * This API is deprecated, but the replacement SecItemCopyMatching
761      * only works on >= 10.7
762      */
763     SecKeychainAttribute attrib = { kSecLabelItemAttr, strlen(cert), (void *)cert };
764     SecKeychainAttributeList attrList = { 1, &attrib };
765
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);
771         return VLC_EGENERIC;
772     }
773
774     SecKeychainItemRef itemRef = NULL;
775     ret = SecKeychainSearchCopyNext(searchReference, &itemRef);
776     if (ret != noErr) {
777         msg_Err(crd, "Cannot get certificate with alias %s, error: %d", cert, ret);
778         return VLC_EGENERIC;
779     }
780     CFRelease(searchReference);
781
782     /* cast allowed according to documentation */
783     SecCertificateRef certificate = (SecCertificateRef)itemRef;
784
785     SecIdentityRef cert_identity = NULL;
786     ret = SecIdentityCreateWithCertificate(NULL, certificate, &cert_identity);
787     if (ret != noErr) {
788         msg_Err(crd, "Cannot get private key for certificate");
789         CFRelease(certificate);
790         return VLC_EGENERIC;
791     }
792
793     /*
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.
796      */
797     SecPolicyRef policy = SecPolicyCreateSSL(true, NULL);
798     SecTrustRef trust_ref = NULL;
799     int result = VLC_SUCCESS;
800
801     /* According to docu its fine to pass just one certificate */
802     ret = SecTrustCreateWithCertificates((CFArrayRef)certificate, policy, &trust_ref);
803     if (ret != noErr) {
804         msg_Err(crd, "Cannot create trust");
805         result = VLC_EGENERIC;
806         goto out;
807     }
808
809     SecTrustResultType status;
810     ret = SecTrustEvaluate(trust_ref, &status);
811     if (ret != noErr) {
812         msg_Err(crd, "Error evaluating trust");
813         result = VLC_EGENERIC;
814         goto out;
815     }
816
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;
823         goto out;
824     }
825
826     CFIndex num_cert_chain = CFArrayGetCount(cert_chain);
827
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);
831
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);
836
837     vlc_tls_creds_sys_t *sys = malloc(sizeof(*sys));
838     if (unlikely(sys == NULL)) {
839         CFRelease(server_cert_chain);
840         result = VLC_ENOMEM;
841         goto out;
842     }
843
844     sys->server_cert_chain = server_cert_chain;
845     sys->whitelist = NULL;
846
847     crd->sys = sys;
848     crd->open = st_ServerSessionOpen;
849
850 out:
851     if (policy)
852         CFRelease(policy);
853     if (trust_ref)
854         CFRelease(trust_ref);
855
856     if (certificate)
857         CFRelease(certificate);
858     if (cert_identity)
859         CFRelease(cert_identity);
860
861     return result;
862 }
863
864 static void CloseServer (vlc_tls_creds_t *crd) {
865     msg_Dbg(crd, "close secure transport server");
866
867     vlc_tls_creds_sys_t *sys = crd->sys;
868
869     if (sys->server_cert_chain)
870         CFRelease(sys->server_cert_chain);
871
872     free(sys);
873 }
874
875 #endif /* !TARGET_OS_IPHONE */