[vlc-devel] [PATCH] Add secure transport TLS module

Jean-Baptiste Kempf jb at videolan.org
Mon Dec 23 19:14:32 CET 2013


LGTM.

On 18 Dec, david.fuhrmann at gmail.com wrote :
> From: David Fuhrmann <david.fuhrmann at googlemail.com>
> 
> 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.
> ---
>  modules/misc/Modules.am        |   8 +
>  modules/misc/securetransport.c | 597 +++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 605 insertions(+)
>  create mode 100644 modules/misc/securetransport.c
> 
> diff --git a/modules/misc/Modules.am b/modules/misc/Modules.am
> index 4858c09..f0b69d4 100644
> --- a/modules/misc/Modules.am
> +++ b/modules/misc/Modules.am
> @@ -32,6 +32,14 @@ endif
>  EXTRA_LTLIBRARIES += libgnutls_plugin.la
>  misc_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) -rpath '$(miscdir)' -Wl,-framework,Security,-framework,CoreFoundation
> +misc_LTLIBRARIES += libsecuretransport_plugin.la
> +endif
> +
>  libxdg_screensaver_plugin_la_SOURCES = inhibit/xdg.c
>  if HAVE_XCB
>  misc_LTLIBRARIES += libxdg_screensaver_plugin.la
> diff --git a/modules/misc/securetransport.c b/modules/misc/securetransport.c
> new file mode 100644
> index 0000000..8fd3dd6
> --- /dev/null
> +++ b/modules/misc/securetransport.c
> @@ -0,0 +1,597 @@
> +/*****************************************************************************
> + * 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_shortname("Secure Transport TLS")
> +    set_description(N_("TLS implementation using Secure Transport"))
> +    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_MAC
> +    CFArrayRef cert_chain = NULL;
> +    ret = SSLCopyPeerCertificates (sys->p_context, &cert_chain); // todo need replacement for ios
> +    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", 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_MAC
> +    /* ... 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)
> 
> _______________________________________________
> vlc-devel mailing list
> To unsubscribe or modify your subscription options:
> https://mailman.videolan.org/listinfo/vlc-devel

-- 
With my kindest regards,

-- 
Jean-Baptiste Kempf
http://www.jbkempf.com/ - +33 672 704 734
Sent from my Electronic Device



More information about the vlc-devel mailing list