[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