1 /*****************************************************************************
3 *****************************************************************************
4 * Copyright (C) 2013 David Fuhrmann
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
16 * You should have received a copy of the GNU Ă–esser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19 *****************************************************************************/
21 /*****************************************************************************
23 *****************************************************************************/
29 #include <vlc_common.h>
30 #include <vlc_plugin.h>
32 #include <vlc_dialog.h>
34 #include <Security/Security.h>
35 #include <Security/SecureTransport.h>
36 #include <TargetConditionals.h>
38 /* From MacErrors.h (cannot be included because it isn't present in iOS: */
43 /*****************************************************************************
45 *****************************************************************************/
46 static int OpenClient (vlc_tls_creds_t *);
47 static void CloseClient (vlc_tls_creds_t *);
50 set_description(N_("TLS support for OS X and iOS"))
51 set_capability("tls client", 2)
52 set_callbacks(OpenClient, CloseClient)
53 set_category(CAT_ADVANCED)
54 set_subcategory(SUBCAT_ADVANCED_NETWORK)
58 #define cfKeyHost CFSTR("host")
59 #define cfKeyCertificate CFSTR("certificate")
61 struct vlc_tls_creds_sys
63 CFMutableArrayRef whitelist;
67 SSLContextRef p_context;
68 vlc_tls_creds_sys_t *p_cred;
69 size_t i_send_buffered_bytes;
76 static int st_Error (vlc_tls_t *obj, int val)
80 /* peer performed shutdown */
81 case errSSLClosedNoNotify:
82 case errSSLClosedGraceful:
83 msg_Dbg(obj, "Got shutdown notification");
86 case errSSLWouldBlock:
91 msg_Err (obj, "Found error %d", val);
98 * Read function called by secure transport for socket read.
100 * Function is based on Apples SSLSample sample code.
102 static OSStatus st_SocketReadFunc (SSLConnectionRef connection,
104 size_t *dataLength) {
106 vlc_tls_t *session = (vlc_tls_t *)connection;
107 vlc_tls_sys_t *sys = session->sys;
109 size_t bytesToGo = *dataLength;
110 size_t initLen = bytesToGo;
111 UInt8 *currData = (UInt8 *)data;
112 OSStatus retValue = noErr;
116 val = read(sys->i_fd, currData, bytesToGo);
119 msg_Dbg(session, "found eof");
120 retValue = errSSLClosedGraceful;
121 } else { /* do the switch */
124 /* connection closed */
125 retValue = errSSLClosedGraceful;
128 retValue = errSSLClosedAbort;
131 retValue = errSSLWouldBlock;
132 sys->b_blocking_send = false;
135 msg_Err(session, "try to read %d bytes, got error %d",
136 (int)bytesToGo, errno);
148 /* filled buffer with incoming data, done */
152 *dataLength = initLen - bytesToGo;
158 * Write function called by secure transport for socket read.
160 * Function is based on Apples SSLSample sample code.
162 static OSStatus st_SocketWriteFunc (SSLConnectionRef connection,
164 size_t *dataLength) {
166 vlc_tls_t *session = (vlc_tls_t *)connection;
167 vlc_tls_sys_t *sys = session->sys;
169 size_t bytesSent = 0;
170 size_t dataLen = *dataLength;
171 OSStatus retValue = noErr;
175 val = write(sys->i_fd, (char *)data + bytesSent, dataLen - bytesSent);
176 } while (val >= 0 && (bytesSent += val) < dataLen);
179 if(errno == EAGAIN) {
180 retValue = errSSLWouldBlock;
181 sys->b_blocking_send = true;
183 msg_Err(session, "error while writing: %d", errno);
188 *dataLength = bytesSent;
192 static int st_validateServerCertificate (vlc_tls_t *session, const char *hostname) {
195 vlc_tls_sys_t *sys = session->sys;
196 SecCertificateRef leaf_cert = NULL;
198 SecTrustRef trust = NULL;
199 OSStatus ret = SSLCopyPeerTrust (sys->p_context, &trust);
200 if (ret != noErr || trust == NULL) {
201 msg_Err(session, "error getting certifictate chain");
205 CFStringRef cfHostname = CFStringCreateWithCString(kCFAllocatorDefault,
207 kCFStringEncodingUTF8);
210 /* enable default root / anchor certificates */
211 ret = SecTrustSetAnchorCertificates (trust, NULL);
213 msg_Err(session, "error setting anchor certificates");
218 SecTrustResultType trust_eval_result = 0;
220 ret = SecTrustEvaluate(trust, &trust_eval_result);
222 msg_Err(session, "error calling SecTrustEvaluate");
227 switch (trust_eval_result) {
228 case kSecTrustResultUnspecified:
229 case kSecTrustResultProceed:
230 msg_Dbg(session, "cerfificate verification successful, result is %d", trust_eval_result);
234 case kSecTrustResultRecoverableTrustFailure:
235 case kSecTrustResultDeny:
237 msg_Warn(session, "cerfificate verification failed, result is %d", trust_eval_result);
240 /* get leaf certificate */
241 /* SSLCopyPeerCertificates is only available on OSX 10.5 or later */
242 #if !TARGET_OS_IPHONE
243 CFArrayRef cert_chain = NULL;
244 ret = SSLCopyPeerCertificates (sys->p_context, &cert_chain);
245 if (ret != noErr || !cert_chain) {
250 if (CFArrayGetCount (cert_chain) == 0) {
251 CFRelease (cert_chain);
256 leaf_cert = (SecCertificateRef)CFArrayGetValueAtIndex (cert_chain, 0);
257 CFRetain (leaf_cert);
258 CFRelease (cert_chain);
260 /* SecTrustGetCertificateAtIndex is only available on 10.7 or iOS */
261 if (SecTrustGetCertificateCount (trust) == 0) {
266 leaf_cert = SecTrustGetCertificateAtIndex (trust, 0);
267 CFRetain (leaf_cert);
271 /* check if leaf already accepted */
272 CFIndex max = CFArrayGetCount (sys->p_cred->whitelist);
273 for (CFIndex i = 0; i < max; ++i) {
274 CFDictionaryRef dict = CFArrayGetValueAtIndex (sys->p_cred->whitelist, i);
275 CFStringRef knownHost = (CFStringRef)CFDictionaryGetValue (dict, cfKeyHost);
276 SecCertificateRef knownCert = (SecCertificateRef)CFDictionaryGetValue (dict, cfKeyCertificate);
278 if (!knownHost || !knownCert)
281 if (CFEqual (knownHost, cfHostname) && CFEqual (knownCert, leaf_cert)) {
282 msg_Warn(session, "certificate already accepted, continuing");
288 /* We do not show more certificate details yet because there is no proper API to get
289 a summary of the certificate. SecCertificateCopySubjectSummary is the only method
290 available on iOS and 10.6. More promising API functions such as
291 SecCertificateCopyLongDescription also print out the subject only, more or less.
292 But only showing the certificate subject is of no real help for the user.
293 We could use SecCertificateCopyValues, but then we need to parse all OID values for
294 ourself. This is too mad for just printing information the user will never check
298 const char *msg = N_("You attempted to reach %s. "
299 "However the security certificate presented by the server "
300 "is unknown and could not be authenticated by any trusted "
301 "Certification Authority. "
302 "This problem may be caused by a configuration error "
303 "or an attempt to breach your security or your privacy.\n\n"
304 "If in doubt, abort now.\n");
305 int answer = dialog_Question (session, _("Insecure site"), vlc_gettext (msg),
306 _("Abort"), _("Accept certificate temporarily"), NULL, hostname);
309 msg_Warn(session, "Proceeding despite of failed certificate validation");
311 /* save leaf certificate in whitelist */
312 const void *keys[] = {cfKeyHost, cfKeyCertificate};
313 const void *values[] = {cfHostname, leaf_cert};
314 CFDictionaryRef dict = CFDictionaryCreate (kCFAllocatorDefault,
316 &kCFTypeDictionaryKeyCallBacks,
317 &kCFTypeDictionaryValueCallBacks);
319 msg_Err (session, "error creating dict");
324 CFArrayAppendValue (sys->p_cred->whitelist, dict);
339 CFRelease (cfHostname);
341 CFRelease (leaf_cert);
347 * @return -1 on fatal error, 0 on successful handshake completion,
348 * 1 if more would-be blocking recv is needed,
349 * 2 if more would-be blocking send is required.
351 static int st_Handshake (vlc_tls_t *session, const char *host,
352 const char *service) {
355 vlc_tls_sys_t *sys = session->sys;
357 OSStatus retValue = SSLHandshake(sys->p_context);
359 if (retValue == errSSLWouldBlock) {
360 msg_Dbg(session, "handshake is blocked, try again later");
361 return 1 + (sys->b_blocking_send ? 1 : 0);
366 if(st_validateServerCertificate(session, host) != 0) {
369 msg_Dbg(session, "handshake completed successfully");
370 sys->b_handshaked = true;
373 case errSSLServerAuthCompleted:
374 return st_Handshake (session, host, service);
376 case errSSLConnectionRefused:
377 msg_Err(session, "connection was refused");
379 case errSSLNegotiation:
380 msg_Err(session, "cipher suite negotiation failed");
382 case errSSLFatalAlert:
383 msg_Err(session, "fatal error occured during handshake");
387 msg_Err(session, "handshake returned error %d", (int)retValue);
393 * Sends data through a TLS session.
395 static int st_Send (void *opaque, const void *buf, size_t length)
397 vlc_tls_t *session = opaque;
398 vlc_tls_sys_t *sys = session->sys;
399 OSStatus ret = noErr;
402 * SSLWrite does not return the number of bytes actually written to
403 * the socket, but the number of bytes written to the internal cache.
405 * If return value is errSSLWouldBlock, the underlying socket cannot
406 * send all data, but the data is already cached. In this situation,
407 * we need to call SSLWrite again. To ensure this call even for the
408 * last bytes, we return EAGAIN. On the next call, we give no new data
409 * to SSLWrite until the error is not errSSLWouldBlock anymore.
411 * This code is adapted the same way as done in curl.
412 * (https://github.com/bagder/curl/blob/master/lib/curl_darwinssl.c#L2067)
416 if (sys->i_send_buffered_bytes > 0) {
417 ret = SSLWrite(sys->p_context, NULL, 0, &actualSize);
420 /* actualSize remains zero because no new data send */
421 actualSize = sys->i_send_buffered_bytes;
422 sys->i_send_buffered_bytes = 0;
424 } else if (ret == errSSLWouldBlock) {
425 /* EAGAIN is not expected by the core in this situation,
432 ret = SSLWrite(sys->p_context, buf, length, &actualSize);
434 if (ret == errSSLWouldBlock) {
435 sys->i_send_buffered_bytes = length;
436 /* EAGAIN is not expected by the core in this situation,
443 return ret != noErr ? st_Error(session, ret) : actualSize;
447 * Receives data through a TLS session.
449 static int st_Recv (void *opaque, void *buf, size_t length)
451 vlc_tls_t *session = opaque;
452 vlc_tls_sys_t *sys = session->sys;
455 OSStatus ret = SSLRead(sys->p_context, buf, length, &actualSize);
457 if(ret == errSSLWouldBlock && actualSize)
460 return ret != noErr ? st_Error(session, ret) : actualSize;
464 * Closes a client-side TLS credentials.
466 static void st_ClientSessionClose (vlc_tls_creds_t *crd, vlc_tls_t *session) {
470 vlc_tls_sys_t *sys = session->sys;
471 msg_Dbg(session, "close TLS session");
473 if(sys->b_handshaked) {
474 OSStatus ret = SSLClose(sys->p_context);
476 msg_Err(session, "error closing ssl context");
480 if (sys->p_context) {
482 CFRelease(sys->p_context);
484 if(SSLDisposeContext(sys->p_context) != noErr) {
485 msg_Err(session, "error deleting context");
493 * Initializes a client-side TLS session.
495 static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
496 int fd, const char *hostname) {
497 msg_Dbg(session, "open TLS session for %s", hostname);
499 vlc_tls_sys_t *sys = malloc (sizeof (*session->sys));
500 if (unlikely(sys == NULL))
503 sys->p_cred = crd->sys;
505 sys->b_handshaked = false;
506 sys->b_blocking_send = false;
507 sys->i_send_buffered_bytes = 0;
510 session->sock.p_sys = session;
511 session->sock.pf_send = st_Send;
512 session->sock.pf_recv = st_Recv;
513 session->handshake = st_Handshake;
515 SSLContextRef p_context = NULL;
517 p_context = SSLCreateContext (NULL, kSSLClientSide, kSSLStreamType);
518 if(p_context == NULL) {
519 msg_Err(session, "cannot create ssl context");
523 if (SSLNewContext (false, &p_context) != noErr) {
524 msg_Err(session, "error calling SSLNewContext");
529 sys->p_context = p_context;
531 OSStatus ret = SSLSetIOFuncs (p_context, st_SocketReadFunc, st_SocketWriteFunc);
533 msg_Err(session, "cannot set io functions");
537 ret = SSLSetConnection (p_context, session);
539 msg_Err(session, "cannot set connection");
543 ret = SSLSetPeerDomainName (p_context, hostname, strlen(hostname));
545 msg_Err(session, "cannot set peer domain name");
549 /* disable automatic validation. We do so manually to also handle invalid
552 /* this has effect only on iOS 5 and OSX 10.8 or later ... */
553 ret = SSLSetSessionOption (sys->p_context, kSSLSessionOptionBreakOnServerAuth, true);
555 msg_Err (session, "cannot set session option");
558 #if !TARGET_OS_IPHONE
559 /* ... thus calling this for earlier osx versions, which is not available on iOS in turn */
560 ret = SSLSetEnableCertVerify (sys->p_context, false);
562 msg_Err (session, "error setting enable cert verify");
570 st_ClientSessionClose(crd, session);
575 * Initializes a client-side TLS credentials.
577 static int OpenClient (vlc_tls_creds_t *crd) {
579 msg_Dbg(crd, "open st client");
581 vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
582 if (unlikely(sys == NULL))
585 sys->whitelist = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
588 crd->open = st_ClientSessionOpen;
589 crd->close = st_ClientSessionClose;
595 static void CloseClient (vlc_tls_creds_t *crd) {
596 msg_Dbg(crd, "close secure transport client");
598 vlc_tls_creds_sys_t *sys = crd->sys;
601 CFRelease(sys->whitelist);