Browse Source

MVK: add packported securetransport module patch

David Fuhrmann 11 years ago
parent
commit
8474794880
1 changed files with 646 additions and 0 deletions
  1. 646 0
      MobileVLCKit/patches/0001-Add-secure-transport-TLS-module.patch

+ 646 - 0
MobileVLCKit/patches/0001-Add-secure-transport-TLS-module.patch

@@ -0,0 +1,646 @@
+From d9a48c551fc478c8e35dc67bf9efe112c518261f Mon Sep 17 00:00:00 2001
+From: David Fuhrmann <david.fuhrmann@googlemail.com>
+Date: Tue, 17 Dec 2013 23:03:06 +0100
+Subject: [PATCH 1/2] Add secure transport TLS module
+
+Secure Transport is a TLS library part of the Security framework
+(preinstalled on every iOS and MacOS device). This library does
+certificate validation during handshake automatically using the
+root certificates from the preinstalled certificate store.
+
+The main reason for this module is proper certificate validation
+on iOS devices. This is not possible with gnutls, because there is
+no access to the root certificates for external applications.
+The module is also intended for use on OSX.
+
+(manually backported from 673d45d0d54ac89bd553a8e35242426d14ba4212)
+---
+ modules/misc/Modules.am        |   8 +
+ modules/misc/securetransport.c | 596 +++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 604 insertions(+)
+ create mode 100644 modules/misc/securetransport.c
+
+diff --git a/modules/misc/Modules.am b/modules/misc/Modules.am
+index 08fe043..97690e5 100644
+--- a/modules/misc/Modules.am
++++ b/modules/misc/Modules.am
+@@ -30,6 +30,14 @@ endif
+ EXTRA_LTLIBRARIES += libgnutls_plugin.la
+ libvlc_LTLIBRARIES += $(LTLIBgnutls)
+ 
++if HAVE_DARWIN
++libsecuretransport_plugin_la_SOURCES = securetransport.c
++libsecuretransport_plugin_la_CFLAGS = $(AM_CFLAGS) $(SECURETRANSPORT_CFLAGS)
++libsecuretransport_plugin_la_LIBADD = $(SECURETRANSPORT_LIBS)
++libsecuretransport_plugin_la_LDFLAGS = $(AM_LDFLAGS) -Wl,-framework,Security,-framework,CoreFoundation
++libvlc_LTLIBRARIES += libsecuretransport_plugin.la
++endif
++
+ libxdg_screensaver_plugin_la_SOURCES = inhibit/xdg.c
+ libxdg_screensaver_plugin_la_CFLAGS = $(AM_CFLAGS)
+ libxdg_screensaver_plugin_la_LIBADD = $(AM_LIBADD)
+diff --git a/modules/misc/securetransport.c b/modules/misc/securetransport.c
+new file mode 100644
+index 0000000..f0b12c8
+--- /dev/null
++++ b/modules/misc/securetransport.c
+@@ -0,0 +1,596 @@
++/*****************************************************************************
++ * 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 *);
++
++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)
++vlc_module_end ()
++
++
++#define cfKeyHost CFSTR("host")
++#define cfKeyCertificate CFSTR("certificate")
++
++struct vlc_tls_creds_sys
++{
++    CFMutableArrayRef whitelist;
++};
++
++struct vlc_tls_sys {
++    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;
++};
++
++static int st_Error (vlc_tls_t *obj, int val)
++{
++    switch (val)
++    {
++        /* peer performed shutdown */
++        case errSSLClosedNoNotify:
++        case errSSLClosedGraceful:
++            msg_Dbg(obj, "Got shutdown notification");
++            return 0;
++
++        case errSSLWouldBlock:
++            errno = EAGAIN;
++            break;
++
++        default:
++            msg_Err (obj, "Found error %d", val);
++            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;
++
++    for(;;) {
++        val = read(sys->i_fd, currData, bytesToGo);
++        if (val <= 0) {
++            if(val == 0) {
++                msg_Dbg(session, "found eof");
++                retValue = errSSLClosedGraceful;
++            } else { /* do the switch */
++                switch(errno) {
++                    case ENOENT:
++                        /* connection closed */
++                        retValue = errSSLClosedGraceful;
++                        break;
++                    case ECONNRESET:
++                        retValue = errSSLClosedAbort;
++                        break;
++                    case EAGAIN:
++                        retValue = errSSLWouldBlock;
++                        sys->b_blocking_send = false;
++                        break;
++                    default:
++                        msg_Err(session, "try to read %d bytes, got error %d",
++                                (int)bytesToGo, errno);
++                        retValue = ioErr;
++                        break;
++                }
++            }
++            break;
++        } else {
++            bytesToGo -= val;
++            currData += val;
++        }
++
++        if(bytesToGo == 0) {
++            /* 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);
++
++    if(val < 0) {
++        if(errno == EAGAIN) {
++            retValue = errSSLWouldBlock;
++            sys->b_blocking_send = true;
++        } else {
++            msg_Err(session, "error while writing: %d", errno);
++            retValue = ioErr;
++        }
++    }
++
++    *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;
++    OSStatus ret = SSLCopyPeerTrust (sys->p_context, &trust);
++    if (ret != noErr || trust == NULL) {
++        msg_Err(session, "error getting certifictate chain");
++        return -1;
++    }
++
++    CFStringRef cfHostname = CFStringCreateWithCString(kCFAllocatorDefault,
++                                                       hostname,
++                                                       kCFStringEncodingUTF8);
++
++
++    /* enable default root / anchor certificates */
++    ret = SecTrustSetAnchorCertificates (trust, NULL);
++    if (ret != noErr) {
++        msg_Err(session, "error setting anchor certificates");
++        result = -1;
++        goto out;
++    }
++
++    SecTrustResultType trust_eval_result = 0;
++
++    ret = SecTrustEvaluate(trust, &trust_eval_result);
++    if(ret != noErr) {
++        msg_Err(session, "error calling SecTrustEvaluate");
++        result = -1;
++        goto out;
++    }
++
++    switch (trust_eval_result) {
++        case kSecTrustResultUnspecified:
++        case kSecTrustResultProceed:
++            msg_Dbg(session, "cerfificate verification successful, result is %d", trust_eval_result);
++            result = 0;
++            goto out;
++
++        case kSecTrustResultRecoverableTrustFailure:
++        case kSecTrustResultDeny:
++        default:
++            msg_Warn(session, "cerfificate verification failed, result is %d", trust_eval_result);
++    }
++
++    /* get leaf certificate */
++    /* SSLCopyPeerCertificates is only available on OSX 10.5 or later */
++#if !TARGET_OS_IPHONE
++    CFArrayRef cert_chain = NULL;
++    ret = SSLCopyPeerCertificates (sys->p_context, &cert_chain);
++    if (ret != noErr || !cert_chain) {
++        result = -1;
++        goto out;
++    }
++
++    if (CFArrayGetCount (cert_chain) == 0) {
++        CFRelease (cert_chain);
++        result = -1;
++        goto out;
++    }
++
++    leaf_cert = (SecCertificateRef)CFArrayGetValueAtIndex (cert_chain, 0);
++    CFRetain (leaf_cert);
++    CFRelease (cert_chain);
++#else
++    /* SecTrustGetCertificateAtIndex is only available on 10.7 or iOS */
++    if (SecTrustGetCertificateCount (trust) == 0) {
++        result = -1;
++        goto out;
++    }
++
++    leaf_cert = SecTrustGetCertificateAtIndex (trust, 0);
++    CFRetain (leaf_cert);
++#endif
++
++
++    /* check if leaf already accepted */
++    CFIndex max = CFArrayGetCount (sys->p_cred->whitelist);
++    for (CFIndex i = 0; i < max; ++i) {
++        CFDictionaryRef dict = CFArrayGetValueAtIndex (sys->p_cred->whitelist, i);
++        CFStringRef knownHost = (CFStringRef)CFDictionaryGetValue (dict, cfKeyHost);
++        SecCertificateRef knownCert = (SecCertificateRef)CFDictionaryGetValue (dict, cfKeyCertificate);
++
++        if (!knownHost || !knownCert)
++            continue;
++
++        if (CFEqual (knownHost, cfHostname) && CFEqual (knownCert, leaf_cert)) {
++            msg_Warn(session, "certificate already accepted, continuing");
++            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");
++    int answer = dialog_Question (session, _("Insecure site"), vlc_gettext (msg),
++                                  _("Abort"), _("Accept certificate temporarily"), NULL, hostname);
++
++    if(answer == 2) {
++        msg_Warn(session, "Proceeding despite of failed certificate validation");
++
++        /* save leaf certificate in whitelist */
++        const void *keys[] = {cfKeyHost, cfKeyCertificate};
++        const void *values[] = {cfHostname, leaf_cert};
++        CFDictionaryRef dict = CFDictionaryCreate (kCFAllocatorDefault,
++                                                   keys, values, 2,
++                                                   &kCFTypeDictionaryKeyCallBacks,
++                                                   &kCFTypeDictionaryValueCallBacks);
++        if(!dict) {
++            msg_Err (session, "error creating dict");
++            result = -1;
++            goto out;
++        }
++
++        CFArrayAppendValue (sys->p_cred->whitelist, dict);
++        CFRelease (dict);
++
++        result = 0;
++        goto out;
++
++    } else {
++        result = -1;
++        goto out;
++    }
++
++out:
++    CFRelease (trust);
++
++    if (cfHostname)
++        CFRelease (cfHostname);
++    if (leaf_cert)
++        CFRelease (leaf_cert);
++
++    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.
++ */
++static int st_Handshake (vlc_tls_t *session, const char *host,
++                                        const char *service) {
++    VLC_UNUSED(service);
++
++    vlc_tls_sys_t *sys = session->sys;
++
++    OSStatus retValue = SSLHandshake(sys->p_context);
++
++    if (retValue == errSSLWouldBlock) {
++        msg_Dbg(session, "handshake is blocked, try again later");
++        return 1 + (sys->b_blocking_send ? 1 : 0);
++    }
++
++    switch (retValue) {
++        case noErr:
++            if(st_validateServerCertificate(session, host) != 0) {
++                return -1;
++            }
++            msg_Dbg(session, "handshake completed successfully");
++            sys->b_handshaked = true;
++            return 0;
++
++        case errSSLServerAuthCompleted:
++            return st_Handshake (session, host, service);
++
++        case errSSLConnectionRefused:
++            msg_Err(session, "connection was refused");
++            return -1;
++        case errSSLNegotiation:
++            msg_Err(session, "cipher suite negotiation failed");
++            return -1;
++        case errSSLFatalAlert:
++            msg_Err(session, "fatal error occured during handshake");
++            return -1;
++
++        default:
++            msg_Err(session, "handshake returned error %d", (int)retValue);
++            return -1;
++    }
++}
++
++/**
++ * Sends data through a TLS session.
++ */
++static int st_Send (void *opaque, const void *buf, size_t length)
++{
++    vlc_tls_t *session = opaque;
++    vlc_tls_sys_t *sys = session->sys;
++    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)
++     */
++
++    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) {
++            /* EAGAIN is not expected by the core in this situation,
++             so use EINTR here */
++            errno = EINTR;
++            return -1;
++        }
++
++    } else {
++        ret = SSLWrite(sys->p_context, buf, length, &actualSize);
++
++        if (ret == errSSLWouldBlock) {
++            sys->i_send_buffered_bytes = length;
++            /* EAGAIN is not expected by the core in this situation,
++               so use EINTR here */
++            errno = EINTR;
++            return -1;
++        }
++    }
++
++    return ret != noErr ? st_Error(session, ret) : actualSize;
++}
++
++/**
++ * Receives data through a TLS session.
++ */
++static int st_Recv (void *opaque, void *buf, size_t length)
++{
++    vlc_tls_t *session = opaque;
++    vlc_tls_sys_t *sys = session->sys;
++
++    size_t actualSize;
++    OSStatus ret = SSLRead(sys->p_context, buf, length, &actualSize);
++
++    if(ret == errSSLWouldBlock && actualSize)
++        return actualSize;
++
++    return ret != noErr ? st_Error(session, ret) : actualSize;
++}
++
++/**
++ * Closes a client-side TLS credentials.
++ */
++static void st_ClientSessionClose (vlc_tls_creds_t *crd, vlc_tls_t *session) {
++
++    VLC_UNUSED(crd);
++
++    vlc_tls_sys_t *sys = session->sys;
++    msg_Dbg(session, "close TLS session");
++
++    if(sys->b_handshaked) {
++        OSStatus ret = SSLClose(sys->p_context);
++        if(ret != noErr) {
++            msg_Err(session, "error closing ssl context");
++        }
++    }
++
++    if (sys->p_context) {
++#if TARGET_OS_IPHONE
++        CFRelease(sys->p_context);
++#else
++        if(SSLDisposeContext(sys->p_context) != noErr) {
++            msg_Err(session, "error deleting context");
++        }
++#endif
++    }
++    free (sys);
++}
++
++/**
++ * Initializes a client-side TLS session.
++ */
++static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
++                                     int fd, const char *hostname) {
++    msg_Dbg(session, "open TLS session for %s", hostname);
++
++    vlc_tls_sys_t *sys = malloc (sizeof (*session->sys));
++    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;
++
++    session->sys = sys;
++    session->sock.p_sys = session;
++    session->sock.pf_send = st_Send;
++    session->sock.pf_recv = st_Recv;
++    session->handshake = st_Handshake;
++
++    SSLContextRef p_context = NULL;
++#if TARGET_OS_IPHONE
++    p_context = SSLCreateContext (NULL, kSSLClientSide, kSSLStreamType);
++    if(p_context == NULL) {
++        msg_Err(session, "cannot create ssl context");
++        goto error;
++    }
++#else
++    if (SSLNewContext (false, &p_context) != noErr) {
++        msg_Err(session, "error calling SSLNewContext");
++        goto error;
++    }
++#endif
++
++    sys->p_context = p_context;
++
++    OSStatus ret = SSLSetIOFuncs (p_context, st_SocketReadFunc, st_SocketWriteFunc);
++    if(ret != noErr) {
++        msg_Err(session, "cannot set io functions");
++        goto error;
++    }
++
++    ret = SSLSetConnection (p_context, session);
++    if(ret != noErr) {
++        msg_Err(session, "cannot set connection");
++        goto error;
++    }
++
++    ret = SSLSetPeerDomainName (p_context, hostname, strlen(hostname));
++    if(ret != noErr) {
++        msg_Err(session, "cannot set peer domain name");
++        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 ... */
++    SSLSetSessionOption (sys->p_context, kSSLSessionOptionBreakOnServerAuth, true);
++#if !TARGET_OS_IPHONE
++    /* ... thus calling this for earlier osx versions, which is not available on iOS in turn */
++    SSLSetEnableCertVerify (sys->p_context, false);
++#endif
++
++    return VLC_SUCCESS;
++
++error:
++    st_ClientSessionClose(crd, session);
++    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);
++
++    crd->sys = sys;
++    crd->open = st_ClientSessionOpen;
++    crd->close = st_ClientSessionClose;
++
++    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);
++
++    free (sys);
++}
+-- 
+1.8.3.4 (Apple Git-47)
+