securetransport.c 25 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/*****************************************************************************
 * securetransport.c
 *****************************************************************************
 * Copyright (C) 2013 David Fuhrmann
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Öesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_tls.h>
#include <vlc_dialog.h>

#include <Security/Security.h>
#include <Security/SecureTransport.h>
#include <TargetConditionals.h>

/* From MacErrors.h (cannot be included because it isn't present in iOS: */
#ifndef ioErr
# define ioErr -36
#endif

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
static int  OpenClient  (vlc_tls_creds_t *);
static void CloseClient (vlc_tls_creds_t *);

49
#if !TARGET_OS_IPHONE
50
51
    static int  OpenServer  (vlc_tls_creds_t *crd, const char *cert, const char *key);
    static void CloseServer (vlc_tls_creds_t *);
52
#endif
53

54
55
56
57
58
59
vlc_module_begin ()
    set_description(N_("TLS support for OS X and iOS"))
    set_capability("tls client", 2)
    set_callbacks(OpenClient, CloseClient)
    set_category(CAT_ADVANCED)
    set_subcategory(SUBCAT_ADVANCED_NETWORK)
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

    /*
     * The server module currently uses an OSX only API, to be compatible with 10.6.
     * If the module is needed on iOS, then the "modern" keychain lookup API need to be
     * implemented.
     */
#if !TARGET_OS_IPHONE
    add_submodule()
        set_description(N_("TLS server support for OS X"))
        set_capability("tls server", 2)
        set_callbacks(OpenServer, CloseServer)
        set_category(CAT_ADVANCED)
        set_subcategory(SUBCAT_ADVANCED_NETWORK)
#endif /* !TARGET_OS_IPHONE */

75
76
77
78
79
80
vlc_module_end ()


#define cfKeyHost CFSTR("host")
#define cfKeyCertificate CFSTR("certificate")

81
typedef struct {
82
    CFMutableArrayRef whitelist;
83
84
85

    /* valid in server mode */
    CFArrayRef server_cert_chain;
86
} vlc_tls_creds_sys_t;
87

88
typedef struct {
89
90
91
92
93
94
95
    SSLContextRef p_context;
    vlc_tls_creds_sys_t *p_cred;
    size_t i_send_buffered_bytes;
    int i_fd;

    bool b_blocking_send;
    bool b_handshaked;
96
    bool b_server_mode;
97
} vlc_tls_sys_t;
98
99
100
101
102
103
104
105
106

static int st_Error (vlc_tls_t *obj, int val)
{
    switch (val)
    {
        case errSSLWouldBlock:
            errno = EAGAIN;
            break;

107
108
        case errSSLClosedGraceful:
        case errSSLClosedAbort:
109
            msg_Dbg(obj->obj, "Connection closed with code %d", val);
110
111
            errno = ECONNRESET;
            break;
112
        default:
113
            msg_Err(obj->obj, "Found error %d", val);
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
            errno = ECONNRESET;
    }
    return -1;
}

/*
 * Read function called by secure transport for socket read.
 *
 * Function is based on Apples SSLSample sample code.
 */
static OSStatus st_SocketReadFunc (SSLConnectionRef connection,
                                   void *data,
                                   size_t *dataLength) {

    vlc_tls_t *session = (vlc_tls_t *)connection;
    vlc_tls_sys_t *sys = session->sys;

    size_t bytesToGo = *dataLength;
    size_t initLen = bytesToGo;
    UInt8 *currData = (UInt8 *)data;
    OSStatus retValue = noErr;
    ssize_t val;

137
    for (;;) {
138
139
        val = read(sys->i_fd, currData, bytesToGo);
        if (val <= 0) {
140
            if (val == 0) {
141
                msg_Dbg(session->obj, "found eof");
142
143
                retValue = errSSLClosedGraceful;
            } else { /* do the switch */
144
                switch (errno) {
145
146
147
148
149
150
151
152
153
154
155
156
                    case ENOENT:
                        /* connection closed */
                        retValue = errSSLClosedGraceful;
                        break;
                    case ECONNRESET:
                        retValue = errSSLClosedAbort;
                        break;
                    case EAGAIN:
                        retValue = errSSLWouldBlock;
                        sys->b_blocking_send = false;
                        break;
                    default:
157
                        msg_Err(session->obj, "try to read %d bytes, got error %d",
158
159
160
161
162
163
164
165
166
167
168
                                (int)bytesToGo, errno);
                        retValue = ioErr;
                        break;
                }
            }
            break;
        } else {
            bytesToGo -= val;
            currData += val;
        }

169
        if (bytesToGo == 0) {
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
            /* filled buffer with incoming data, done */
            break;
        }
    }
    *dataLength = initLen - bytesToGo;

    return retValue;
}

/*
 * Write function called by secure transport for socket read.
 *
 * Function is based on Apples SSLSample sample code.
 */
static OSStatus st_SocketWriteFunc (SSLConnectionRef connection,
                                    const void *data,
                                    size_t *dataLength) {

    vlc_tls_t *session = (vlc_tls_t *)connection;
    vlc_tls_sys_t *sys = session->sys;

    size_t bytesSent = 0;
    size_t dataLen = *dataLength;
    OSStatus retValue = noErr;
    ssize_t val;

    do {
        val = write(sys->i_fd, (char *)data + bytesSent, dataLen - bytesSent);
    } while (val >= 0 && (bytesSent += val) < dataLen);

200
    if (val < 0) {
201
202
203
204
205
206
207
208
209
210
211
212
        switch(errno) {
            case EAGAIN:
                retValue = errSSLWouldBlock;
                sys->b_blocking_send = true;
                break;

            case EPIPE:
            case ECONNRESET:
                retValue = errSSLClosedAbort;
                break;

            default:
213
                msg_Err(session->obj, "error while writing: %d", errno);
214
                retValue = ioErr;
215
216
217
218
219
220
221
222
223
224
225
226
227
228
        }
    }

    *dataLength = bytesSent;
    return retValue;
}

static int st_validateServerCertificate (vlc_tls_t *session, const char *hostname) {

    int result = -1;
    vlc_tls_sys_t *sys = session->sys;
    SecCertificateRef leaf_cert = NULL;

    SecTrustRef trust = NULL;
229
    OSStatus ret = SSLCopyPeerTrust(sys->p_context, &trust);
230
    if (ret != noErr || trust == NULL) {
231
        msg_Err(session->obj, "error getting certifictate chain");
232
233
234
235
236
237
238
239
240
        return -1;
    }

    CFStringRef cfHostname = CFStringCreateWithCString(kCFAllocatorDefault,
                                                       hostname,
                                                       kCFStringEncodingUTF8);


    /* enable default root / anchor certificates */
241
    ret = SecTrustSetAnchorCertificates(trust, NULL);
242
    if (ret != noErr) {
243
        msg_Err(session->obj, "error setting anchor certificates");
244
245
246
247
248
249
250
        result = -1;
        goto out;
    }

    SecTrustResultType trust_eval_result = 0;

    ret = SecTrustEvaluate(trust, &trust_eval_result);
251
    if (ret != noErr) {
252
        msg_Err(session->obj, "error calling SecTrustEvaluate");
253
254
255
256
257
258
259
        result = -1;
        goto out;
    }

    switch (trust_eval_result) {
        case kSecTrustResultUnspecified:
        case kSecTrustResultProceed:
260
            msg_Dbg(session->obj, "cerfificate verification successful, result is %d", trust_eval_result);
261
262
263
264
265
266
            result = 0;
            goto out;

        case kSecTrustResultRecoverableTrustFailure:
        case kSecTrustResultDeny:
        default:
267
            msg_Warn(session->obj, "cerfificate verification failed, result is %d", trust_eval_result);
268
269
270
271
272
273
    }

    /* get leaf certificate */
    /* SSLCopyPeerCertificates is only available on OSX 10.5 or later */
#if !TARGET_OS_IPHONE
    CFArrayRef cert_chain = NULL;
274
    ret = SSLCopyPeerCertificates(sys->p_context, &cert_chain);
275
276
277
278
279
    if (ret != noErr || !cert_chain) {
        result = -1;
        goto out;
    }

280
281
    if (CFArrayGetCount(cert_chain) == 0) {
        CFRelease(cert_chain);
282
283
284
285
        result = -1;
        goto out;
    }

286
287
288
    leaf_cert = (SecCertificateRef)CFArrayGetValueAtIndex(cert_chain, 0);
    CFRetain(leaf_cert);
    CFRelease(cert_chain);
289
290
#else
    /* SecTrustGetCertificateAtIndex is only available on 10.7 or iOS */
291
    if (SecTrustGetCertificateCount(trust) == 0) {
292
293
294
295
        result = -1;
        goto out;
    }

296
297
    leaf_cert = SecTrustGetCertificateAtIndex(trust, 0);
    CFRetain(leaf_cert);
298
299
300
301
#endif


    /* check if leaf already accepted */
302
    CFIndex max = CFArrayGetCount(sys->p_cred->whitelist);
303
    for (CFIndex i = 0; i < max; ++i) {
304
305
306
        CFDictionaryRef dict = CFArrayGetValueAtIndex(sys->p_cred->whitelist, i);
        CFStringRef knownHost = (CFStringRef)CFDictionaryGetValue(dict, cfKeyHost);
        SecCertificateRef knownCert = (SecCertificateRef)CFDictionaryGetValue(dict, cfKeyCertificate);
307
308
309
310

        if (!knownHost || !knownCert)
            continue;

311
        if (CFEqual(knownHost, cfHostname) && CFEqual(knownCert, leaf_cert)) {
312
            msg_Warn(session->obj, "certificate already accepted, continuing");
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
            result = 0;
            goto out;
        }
    }

    /* We do not show more certificate details yet because there is no proper API to get
       a summary of the certificate. SecCertificateCopySubjectSummary is the only method
       available on iOS and 10.6. More promising API functions such as
       SecCertificateCopyLongDescription also print out the subject only, more or less.
       But only showing the certificate subject is of no real help for the user.
       We could use SecCertificateCopyValues, but then we need to parse all OID values for
       ourself. This is too mad for just printing information the user will never check
       anyway.
     */

    const char *msg = N_("You attempted to reach %s. "
             "However the security certificate presented by the server "
             "is unknown and could not be authenticated by any trusted "
             "Certification Authority. "
             "This problem may be caused by a configuration error "
             "or an attempt to breach your security or your privacy.\n\n"
             "If in doubt, abort now.\n");
David's avatar
David committed
335
    int answer = dialog_Question(session->obj, _("Insecure site"), vlc_gettext (msg),
336
337
                                  _("Abort"), _("Accept certificate temporarily"), NULL, hostname);

338
    if (answer == 2) {
339
        msg_Warn(session->obj, "Proceeding despite of failed certificate validation");
340
341
342
343

        /* save leaf certificate in whitelist */
        const void *keys[] = {cfKeyHost, cfKeyCertificate};
        const void *values[] = {cfHostname, leaf_cert};
344
        CFDictionaryRef dict = CFDictionaryCreate(kCFAllocatorDefault,
345
346
347
                                                   keys, values, 2,
                                                   &kCFTypeDictionaryKeyCallBacks,
                                                   &kCFTypeDictionaryValueCallBacks);
348
        if (!dict) {
349
            msg_Err(session->obj, "error creating dict");
350
351
352
353
            result = -1;
            goto out;
        }

354
355
        CFArrayAppendValue(sys->p_cred->whitelist, dict);
        CFRelease(dict);
356
357
358
359
360
361
362
363
364
365

        result = 0;
        goto out;

    } else {
        result = -1;
        goto out;
    }

out:
366
    CFRelease(trust);
367
368

    if (cfHostname)
369
        CFRelease(cfHostname);
370
    if (leaf_cert)
371
        CFRelease(leaf_cert);
372
373
374
375
376
377
378
379
380

    return result;
}

/*
 * @return -1 on fatal error, 0 on successful handshake completion,
 * 1 if more would-be blocking recv is needed,
 * 2 if more would-be blocking send is required.
 */
381
382
383
static int st_Handshake (vlc_tls_creds_t *crd, vlc_tls_t *session,
                         const char *host, const char *service,
                         char **restrict alp) {
384
385
386
387
388
    VLC_UNUSED(service);

    vlc_tls_sys_t *sys = session->sys;

    OSStatus retValue = SSLHandshake(sys->p_context);
389
    *alp = NULL;
390
391

    if (retValue == errSSLWouldBlock) {
392
        msg_Dbg(crd, "handshake is blocked, try again later");
393
394
395
396
397
        return 1 + (sys->b_blocking_send ? 1 : 0);
    }

    switch (retValue) {
        case noErr:
398
            if (sys->b_server_mode == false && st_validateServerCertificate(session, host) != 0) {
399
400
                return -1;
            }
401
            msg_Dbg(crd, "handshake completed successfully");
402
403
404
405
            sys->b_handshaked = true;
            return 0;

        case errSSLServerAuthCompleted:
406
            msg_Dbg(crd, "SSLHandshake returned errSSLServerAuthCompleted, continuing handshake");
David's avatar
David committed
407
            return st_Handshake(crd, session, host, service, alp);
408
409

        case errSSLConnectionRefused:
410
            msg_Err(crd, "connection was refused");
411
412
            return -1;
        case errSSLNegotiation:
413
            msg_Err(crd, "cipher suite negotiation failed");
414
415
            return -1;
        case errSSLFatalAlert:
416
            msg_Err(crd, "fatal error occured during handshake");
417
418
419
            return -1;

        default:
420
            msg_Err(crd, "handshake returned error %d", (int)retValue);
421
422
423
424
425
426
427
            return -1;
    }
}

/**
 * Sends data through a TLS session.
 */
428
static ssize_t st_Send (vlc_tls_t *session, const void *buf, size_t length)
429
430
{
    vlc_tls_sys_t *sys = session->sys;
431
    assert(sys);
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
    OSStatus ret = noErr;

    /*
     * SSLWrite does not return the number of bytes actually written to
     * the socket, but the number of bytes written to the internal cache.
     *
     * If return value is errSSLWouldBlock, the underlying socket cannot
     * send all data, but the data is already cached. In this situation,
     * we need to call SSLWrite again. To ensure this call even for the
     * last bytes, we return EAGAIN. On the next call, we give no new data
     * to SSLWrite until the error is not errSSLWouldBlock anymore.
     *
     * This code is adapted the same way as done in curl.
     * (https://github.com/bagder/curl/blob/master/lib/curl_darwinssl.c#L2067)
     */

448
449
450
451
    /* EAGAIN is not expected by net_Write in this situation,
       so use EINTR here */
    int againErr = sys->b_server_mode ? EAGAIN : EINTR;

452
453
454
455
456
457
458
459
460
461
    size_t actualSize;
    if (sys->i_send_buffered_bytes > 0) {
        ret = SSLWrite(sys->p_context, NULL, 0, &actualSize);

        if (ret == noErr) {
            /* actualSize remains zero because no new data send */
            actualSize = sys->i_send_buffered_bytes;
            sys->i_send_buffered_bytes = 0;

        } else if (ret == errSSLWouldBlock) {
462
            errno = againErr;
463
464
465
466
467
468
469
470
            return -1;
        }

    } else {
        ret = SSLWrite(sys->p_context, buf, length, &actualSize);

        if (ret == errSSLWouldBlock) {
            sys->i_send_buffered_bytes = length;
471
            errno = againErr;
472
473
474
475
476
477
478
479
480
481
            return -1;
        }
    }

    return ret != noErr ? st_Error(session, ret) : actualSize;
}

/**
 * Receives data through a TLS session.
 */
482
static ssize_t st_Recv (vlc_tls_t *session, void *buf, size_t length)
483
484
{
    vlc_tls_sys_t *sys = session->sys;
485
    assert(sys);
486

487
488
489
    size_t actualSize;
    OSStatus ret = SSLRead(sys->p_context, buf, length, &actualSize);

490
    if (ret == errSSLWouldBlock && actualSize)
491
492
        return actualSize;

493
494
    /* peer performed shutdown */
    if (ret == errSSLClosedNoNotify || ret == errSSLClosedGraceful) {
495
        msg_Dbg(session->obj, "Got close notification with code %i", (int)ret);
496
497
498
        return 0;
    }

499
500
501
502
    return ret != noErr ? st_Error(session, ret) : actualSize;
}

/**
503
 * Closes a TLS session.
504
 */
505
506

static int st_SessionShutdown (vlc_tls_t *session, bool duplex) {
507
    msg_Dbg(session->obj, "shutdown TLS session");
508
509
510
511
512
513
514
515
516

    vlc_tls_sys_t *sys = session->sys;
    OSStatus ret = noErr;
    VLC_UNUSED(duplex);

    if (sys->b_handshaked) {
        ret = SSLClose(sys->p_context);
    }

517
    if (ret != noErr) {
518
519
520
521
522
523
524
        msg_Warn(session->obj, "Cannot close ssl context (%i)", (int)ret);
        return ret;
    }

    return 0;
}

525
static void st_SessionClose (vlc_tls_t *session) {
526
527

    vlc_tls_sys_t *sys = session->sys;
528
    msg_Dbg(session->obj, "close TLS session");
529

530
    if (sys->p_context) {
531
532
533
#if TARGET_OS_IPHONE
        CFRelease(sys->p_context);
#else
534
        if (SSLDisposeContext(sys->p_context) != noErr) {
David's avatar
David committed
535
            msg_Err(session->obj, "error deleting context");
536
537
538
        }
#endif
    }
539
    free(sys);
540
541
542
543
544
}

/**
 * Initializes a client-side TLS session.
 */
545
546
547

static int st_SessionOpenCommon (vlc_tls_creds_t *crd, vlc_tls_t *session,
                                 int fd, bool b_server) {
548

549
    vlc_tls_sys_t *sys = malloc(sizeof(vlc_tls_sys_t));
550
551
552
553
554
555
556
557
    if (unlikely(sys == NULL))
        return VLC_ENOMEM;

    sys->p_cred = crd->sys;
    sys->i_fd = fd;
    sys->b_handshaked = false;
    sys->b_blocking_send = false;
    sys->i_send_buffered_bytes = 0;
558
    sys->p_context = NULL;
559
560

    session->sys = sys;
561
562
    session->send = st_Send;
    session->recv = st_Recv;
563
    session->shutdown = st_SessionShutdown;
564
    session->close = st_SessionClose;
565
    crd->handshake = st_Handshake;
566
567
568

    SSLContextRef p_context = NULL;
#if TARGET_OS_IPHONE
569
570
    p_context = SSLCreateContext(NULL, b_server ? kSSLServerSide : kSSLClientSide, kSSLStreamType);
    if (p_context == NULL) {
571
        msg_Err(crd, "cannot create ssl context");
572
        return -1;
573
574
    }
#else
575
    if (SSLNewContext(b_server, &p_context) != noErr) {
576
        msg_Err(crd, "error calling SSLNewContext");
577
        return -1;
578
579
580
581
582
    }
#endif

    sys->p_context = p_context;

583
584
    OSStatus ret = SSLSetIOFuncs(p_context, st_SocketReadFunc, st_SocketWriteFunc);
    if (ret != noErr) {
585
        msg_Err(crd, "cannot set io functions");
586
        return -1;
587
588
    }

589
590
    ret = SSLSetConnection(p_context, session);
    if (ret != noErr) {
591
        msg_Err(crd, "cannot set connection");
592
593
594
595
596
597
598
        return -1;
    }

    return 0;
}

static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
David's avatar
David committed
599
600
                                     int fd, const char *hostname, const char *const *alpn) {
    VLC_UNUSED(alpn);
601
    msg_Dbg(crd, "open TLS session for %s", hostname);
602
603
604

    int ret = st_SessionOpenCommon(crd, session, fd, false);
    if (ret != noErr) {
605
606
607
        goto error;
    }

608
609
610
611
612
    vlc_tls_sys_t *sys = session->sys;
    sys->b_server_mode = false;

    ret = SSLSetPeerDomainName(sys->p_context, hostname, strlen(hostname));
    if (ret != noErr) {
613
        msg_Err(crd, "cannot set peer domain name");
614
615
616
617
618
619
620
        goto error;
    }

    /* disable automatic validation. We do so manually to also handle invalid
       certificates */

    /* this has effect only on iOS 5 and OSX 10.8 or later ... */
621
622
    ret = SSLSetSessionOption(sys->p_context, kSSLSessionOptionBreakOnServerAuth, true);
    if (ret != noErr) {
623
        msg_Err (crd, "cannot set session option");
624
625
        goto error;
    }
626
627
#if !TARGET_OS_IPHONE
    /* ... thus calling this for earlier osx versions, which is not available on iOS in turn */
628
629
    ret = SSLSetEnableCertVerify(sys->p_context, false);
    if (ret != noErr) {
630
        msg_Err(crd, "error setting enable cert verify");
631
632
        goto error;
    }
633
634
635
636
637
#endif

    return VLC_SUCCESS;

error:
638
    st_SessionShutdown(session, true);
639
    st_SessionClose(session);
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
    return VLC_EGENERIC;
}

/**
 * Initializes a client-side TLS credentials.
 */
static int OpenClient (vlc_tls_creds_t *crd) {

    msg_Dbg(crd, "open st client");

    vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
    if (unlikely(sys == NULL))
        return VLC_ENOMEM;

    sys->whitelist = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
655
    sys->server_cert_chain = NULL;
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670

    crd->sys = sys;
    crd->open = st_ClientSessionOpen;

    return VLC_SUCCESS;
}

static void CloseClient (vlc_tls_creds_t *crd) {
    msg_Dbg(crd, "close secure transport client");

    vlc_tls_creds_sys_t *sys = crd->sys;

    if (sys->whitelist)
        CFRelease(sys->whitelist);

671
    free(sys);
672
}
673
674
675
676
677
678
679
680

/* Begin of server-side methods */
#if !TARGET_OS_IPHONE

/**
 * Initializes a server-side TLS session.
 */
static int st_ServerSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
David's avatar
David committed
681
                                 int fd, const char *hostname, const char *const *alpn) {
682
683

    VLC_UNUSED(hostname);
David's avatar
David committed
684
    VLC_UNUSED(alpn);
685
    msg_Dbg(crd, "open TLS server session");
686
687
688
689
690
691
692

    int ret = st_SessionOpenCommon(crd, session, fd, true);
    if (ret != noErr) {
        goto error;
    }

    vlc_tls_sys_t *sys = session->sys;
693
    vlc_tls_creds_sys_t *p_cred_sys = crd->sys;
694
695
    sys->b_server_mode = true;

696
    ret = SSLSetCertificate(sys->p_context, p_cred_sys->server_cert_chain);
697
    if (ret != noErr) {
698
        msg_Err(crd, "cannot set server certificate");
699
700
701
702
703
704
        goto error;
    }

    return VLC_SUCCESS;

error:
705
    st_SessionShutdown(session, true);
706
    st_SessionClose(session);
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
    return VLC_EGENERIC;
}

/**
 * Initializes server-side TLS credentials.
 */
static int OpenServer (vlc_tls_creds_t *crd, const char *cert, const char *key) {

    /*
     * This function expects the label of the certificate in "cert", stored
     * in the MacOS keychain. The appropriate private key is found automatically.
     */
    VLC_UNUSED(key);
    OSStatus ret;

    msg_Dbg(crd, "open st server");

    /*
     * Get the server certificate.
     *
     * This API is deprecated, but the replacement SecItemCopyMatching
     * only works on >= 10.7
     */
    SecKeychainAttribute attrib = { kSecLabelItemAttr, strlen(cert), (void *)cert };
    SecKeychainAttributeList attrList = { 1, &attrib };

    SecKeychainSearchRef searchReference = NULL;
    ret = SecKeychainSearchCreateFromAttributes(NULL, kSecCertificateItemClass,
                                                 &attrList, &searchReference);
    if (ret != noErr || searchReference == NULL) {
737
        msg_Err(crd, "Cannot find certificate with alias %s", cert);
738
739
740
741
742
743
        return VLC_EGENERIC;
    }

    SecKeychainItemRef itemRef = NULL;
    ret = SecKeychainSearchCopyNext(searchReference, &itemRef);
    if (ret != noErr) {
744
        msg_Err(crd, "Cannot get certificate with alias %s, error: %d", cert, ret);
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
        return VLC_EGENERIC;
    }
    CFRelease(searchReference);

    /* cast allowed according to documentation */
    SecCertificateRef certificate = (SecCertificateRef)itemRef;

    SecIdentityRef cert_identity = NULL;
    ret = SecIdentityCreateWithCertificate(NULL, certificate, &cert_identity);
    if (ret != noErr) {
        msg_Err(crd, "Cannot get private key for certificate");
        CFRelease(certificate);
        return VLC_EGENERIC;
    }

    /*
     * We try to validate the server certificate, but do not care about the result.
     * The only aim is to get the certificate chain.
     */
    SecPolicyRef policy = SecPolicyCreateSSL(true, NULL);
    SecTrustRef trust_ref = NULL;
    int result = VLC_SUCCESS;

    /* According to docu its fine to pass just one certificate */
    ret = SecTrustCreateWithCertificates((CFArrayRef)certificate, policy, &trust_ref);
    if (ret != noErr) {
        msg_Err(crd, "Cannot create trust");
        result = VLC_EGENERIC;
        goto out;
    }

    SecTrustResultType status;
    ret = SecTrustEvaluate(trust_ref, &status);
    if (ret != noErr) {
        msg_Err(crd, "Error evaluating trust");
        result = VLC_EGENERIC;
        goto out;
    }

    CFArrayRef cert_chain = NULL;
    CSSM_TP_APPLE_EVIDENCE_INFO *status_chain;
    ret = SecTrustGetResult(trust_ref, &status, &cert_chain, &status_chain);
    if (ret != noErr || !cert_chain) {
        msg_Err(crd, "error while getting certificate chain");
        result = VLC_EGENERIC;
        goto out;
    }

    CFIndex num_cert_chain = CFArrayGetCount(cert_chain);

    /* Build up the certificate chain array expected by SSLSetCertificate */
    CFMutableArrayRef server_cert_chain = CFArrayCreateMutable(kCFAllocatorDefault, num_cert_chain, &kCFTypeArrayCallBacks);
    CFArrayAppendValue(server_cert_chain, cert_identity);

    msg_Dbg(crd, "Found certificate chain with %ld entries for server certificate", num_cert_chain);
    if (num_cert_chain > 1)
        CFArrayAppendArray(server_cert_chain, cert_chain, CFRangeMake(1, num_cert_chain - 1));
    CFRelease(cert_chain);

804
805
806
807
808
809
    vlc_tls_creds_sys_t *sys = malloc(sizeof(*sys));
    if (unlikely(sys == NULL)) {
        CFRelease(server_cert_chain);
        result = VLC_ENOMEM;
        goto out;
    }
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842

    sys->server_cert_chain = server_cert_chain;
    sys->whitelist = NULL;

    crd->sys = sys;
    crd->open = st_ServerSessionOpen;

out:
    if (policy)
        CFRelease(policy);
    if (trust_ref)
        CFRelease(trust_ref);

    if (certificate)
        CFRelease(certificate);
    if (cert_identity)
        CFRelease(cert_identity);

    return result;
}

static void CloseServer (vlc_tls_creds_t *crd) {
    msg_Dbg(crd, "close secure transport server");

    vlc_tls_creds_sys_t *sys = crd->sys;

    if (sys->server_cert_chain)
        CFRelease(sys->server_cert_chain);

    free(sys);
}

#endif /* !TARGET_OS_IPHONE */