[FFmpeg-devel] [PATCH] lavf/tls: Support Secure Transport

Rodger Combs rodger.combs at gmail.com
Thu May 28 21:46:16 CEST 2015


---
 configure                         |  14 +-
 libavformat/Makefile              |   1 +
 libavformat/allformats.c          |   1 +
 libavformat/avio.c                |   4 +-
 libavformat/tls.h                 |   2 +-
 libavformat/tls_securetransport.c | 393 ++++++++++++++++++++++++++++++++++++++
 6 files changed, 410 insertions(+), 5 deletions(-)
 create mode 100644 libavformat/tls_securetransport.c

diff --git a/configure b/configure
index a90fef1..04aa7f3 100755
--- a/configure
+++ b/configure
@@ -275,6 +275,8 @@ External library support:
   --enable-openssl         enable openssl, needed for https support
                            if gnutls is not used [no]
   --disable-sdl            disable sdl [autodetect]
+  --disable-securetransport disable Secure Transport, needed for TLS support
+                           on OSX if openssl and gnutls are not used [autodetect]
   --enable-x11grab         enable X11 grabbing (legacy) [no]
   --disable-xlib           disable xlib [autodetect]
   --disable-zlib           disable zlib [autodetect]
@@ -1423,6 +1425,7 @@ EXTERNAL_LIBRARY_LIST="
     opengl
     openssl
     sdl
+    securetransport
     x11grab
     xlib
     zlib
@@ -2634,9 +2637,10 @@ sctp_protocol_deps="struct_sctp_event_subscribe"
 sctp_protocol_select="network"
 srtp_protocol_select="rtp_protocol"
 tcp_protocol_select="network"
-tls_gnutls_protocol_deps="gnutls"
-tls_openssl_protocol_deps="openssl !tls_gnutls_protocol"
-tls_protocol_deps_any="tls_gnutls_protocol tls_openssl_protocol"
+tls_securetransport_protocol_deps="securetransport"
+tls_gnutls_protocol_deps="gnutls !tls_securetransport_protocol"
+tls_openssl_protocol_deps="openssl !tls_securetransport_protocol !tls_gnutls_protocol"
+tls_protocol_deps_any="tls_securetransport_protocol tls_gnutls_protocol tls_openssl_protocol"
 tls_protocol_select="tcp_protocol"
 udp_protocol_select="network"
 udplite_protocol_select="network"
@@ -5208,6 +5212,10 @@ if ! disabled sdl; then
 fi
 enabled sdl && add_cflags $sdl_cflags && add_extralibs $sdl_libs
 
+disabled securetransport || { check_func SecIdentityCreate "-Wl,-framework,CoreFoundation -Wl,-framework,Security" &&
+    check_lib2 Security/SecureTransport.h SSLCreateContext "-Wl,-framework,CoreFoundation -Wl,-framework,Security" &&
+    enable securetransport; }
+
 makeinfo --version > /dev/null 2>&1 && enable makeinfo  || disable makeinfo
 enabled makeinfo && (makeinfo --version | \
                      grep -q 'makeinfo (GNU texinfo) 5' > /dev/null 2>&1) \
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 6f2de4d..63233e6 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -524,6 +524,7 @@ OBJS-$(CONFIG_SUBFILE_PROTOCOL)          += subfile.o
 OBJS-$(CONFIG_TCP_PROTOCOL)              += tcp.o
 OBJS-$(CONFIG_TLS_GNUTLS_PROTOCOL)       += tls_gnutls.o tls.o
 OBJS-$(CONFIG_TLS_OPENSSL_PROTOCOL)      += tls_openssl.o tls.o
+OBJS-$(CONFIG_TLS_SECURETRANSPORT_PROTOCOL) += tls_securetransport.o tls.o
 OBJS-$(CONFIG_UDP_PROTOCOL)              += udp.o
 OBJS-$(CONFIG_UDPLITE_PROTOCOL)          += udp.o
 OBJS-$(CONFIG_UNIX_PROTOCOL)             += unix.o
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index 45f174e..efdca2a 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -378,6 +378,7 @@ void av_register_all(void)
     REGISTER_PROTOCOL(SRTP,             srtp);
     REGISTER_PROTOCOL(SUBFILE,          subfile);
     REGISTER_PROTOCOL(TCP,              tcp);
+    REGISTER_PROTOCOL(TLS_SECURETRANSPORT, tls_securetransport);
     REGISTER_PROTOCOL(TLS_GNUTLS,       tls_gnutls);
     REGISTER_PROTOCOL(TLS_OPENSSL,      tls_openssl);
     REGISTER_PROTOCOL(UDP,              udp);
diff --git a/libavformat/avio.c b/libavformat/avio.c
index a990ea2..deeb87f 100644
--- a/libavformat/avio.c
+++ b/libavformat/avio.c
@@ -263,7 +263,9 @@ int ffurl_alloc(URLContext **puc, const char *filename, int flags,
 
     *puc = NULL;
     if (av_strstart(filename, "https:", NULL))
-        av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile with openssl or gnutls enabled.\n");
+        av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile with "
+                                     "openssl, gnutls,\n"
+                                     "or securetransport enabled.\n");
     return AVERROR_PROTOCOL_NOT_FOUND;
 }
 
diff --git a/libavformat/tls.h b/libavformat/tls.h
index 558676f..959bada 100644
--- a/libavformat/tls.h
+++ b/libavformat/tls.h
@@ -26,7 +26,7 @@
 #include "url.h"
 #include "libavutil/opt.h"
 
-#define CONFIG_TLS_PROTOCOL (CONFIG_TLS_GNUTLS_PROTOCOL | CONFIG_TLS_OPENSSL_PROTOCOL)
+#define CONFIG_TLS_PROTOCOL (CONFIG_TLS_GNUTLS_PROTOCOL | CONFIG_TLS_OPENSSL_PROTOCOL | CONFIG_TLS_SECURETRANSPORT_PROTOCOL)
 
 typedef struct TLSShared {
     char *ca_file;
diff --git a/libavformat/tls_securetransport.c b/libavformat/tls_securetransport.c
new file mode 100644
index 0000000..10fc967
--- /dev/null
+++ b/libavformat/tls_securetransport.c
@@ -0,0 +1,393 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg 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.
+ *
+ * FFmpeg 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 Lesser General Public License
+ * along with FFmpeg; if not, write to the Free Software * Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <errno.h>
+
+
+#include "avformat.h"
+#include "internal.h"
+#include "network.h"
+#include "os_support.h"
+#include "url.h"
+#include "tls.h"
+#include "libavcodec/internal.h"
+#include "libavutil/avstring.h"
+#include "libavutil/opt.h"
+#include "libavutil/parseutils.h"
+
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include <CoreFoundation/CoreFoundation.h>
+
+// We use a private API call here; it's good enough for WebKit.
+SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator, SecCertificateRef certificate, SecKeyRef privateKey);
+#define ioErr -36
+
+typedef struct TLSContext {
+    const AVClass *class;
+    TLSShared tls_shared;
+    SSLContextRef ssl_context;
+    CFArrayRef ca_array;
+    int lastErr;
+} TLSContext;
+
+static int print_tls_error(URLContext *h, int ret)
+{
+    TLSContext *c = h->priv_data;
+    switch (ret) {
+    case errSSLWouldBlock:
+        break;
+    case errSSLXCertChainInvalid:
+        av_log(h, AV_LOG_ERROR, "Invalid certificate chain\n");
+        return AVERROR(EIO);
+    case ioErr:
+        return c->lastErr;
+    default:
+        av_log(h, AV_LOG_ERROR, "IO Error: %i\n", ret);
+        return AVERROR(EIO);
+    }
+    return AVERROR(EIO);
+}
+
+static int import_pem(URLContext *h, char *path, CFArrayRef *array)
+{
+    AVIOContext *s = NULL;
+    CFDataRef data = NULL;
+    int64_t ret = 0;
+    char *buf = NULL;
+    SecExternalFormat format = kSecFormatPEMSequence;
+    SecExternalFormat type = kSecItemTypeAggregate;
+    CFStringRef pathStr = CFStringCreateWithCString(NULL, path, 0x08000100);
+    if (!pathStr) {
+        ret = AVERROR(ENOMEM);
+        goto end;
+    }
+
+    if ((ret = avio_open2(&s, path, AVIO_FLAG_READ,
+                          &h->interrupt_callback, NULL)) < 0)
+        goto end;
+
+    if ((ret = avio_size(s)) < 0)
+        goto end;
+
+    if (ret == 0) {
+        ret = AVERROR_INVALIDDATA;
+        goto end;
+    }
+
+    if (!(buf = av_malloc(ret))) {
+        ret = AVERROR(ENOMEM);
+        goto end;
+    }
+
+    if ((ret = avio_read(s, buf, ret)) < 0)
+        goto end;
+
+    data = CFDataCreate(kCFAllocatorDefault, buf, ret);
+
+    if (SecItemImport(data, pathStr, &format, &type,
+                      0, NULL, NULL, array) != noErr || !array) {
+        ret = AVERROR_UNKNOWN;
+        goto end;
+    }
+
+    if (CFArrayGetCount(*array) == 0) {
+        ret = AVERROR_INVALIDDATA;
+        goto end;
+    }
+
+end:
+    av_free(buf);
+    if (pathStr)
+        CFRelease(pathStr);
+    if (data)
+        CFRelease(data);
+    if (s)
+        avio_close(s);
+    return ret;
+}
+
+static int load_ca(URLContext *h)
+{
+    TLSContext *c = h->priv_data;
+    int ret = 0;
+    CFArrayRef array = NULL;
+
+    if ((ret = import_pem(h, c->tls_shared.ca_file, &array)) < 0)
+        goto end;
+
+    if (!(c->ca_array = CFRetain(array))) {
+        ret = AVERROR(ENOMEM);
+        goto end;
+    }
+
+end:
+    if (array)
+        CFRelease(array);
+    return ret;
+}
+
+static int load_cert(URLContext *h)
+{
+    TLSContext *c = h->priv_data;
+    int ret = 0;
+    CFArrayRef certArray = NULL;
+    CFArrayRef keyArray = NULL;
+    SecIdentityRef id = NULL;
+    CFMutableArrayRef outArray = NULL;
+
+    if ((ret = import_pem(h, c->tls_shared.cert_file, &certArray)) < 0)
+        goto end;
+
+    if ((ret = import_pem(h, c->tls_shared.key_file, &keyArray)) < 0)
+        goto end;
+
+    if (!(id = SecIdentityCreate(kCFAllocatorDefault,
+                                 (SecCertificateRef)CFArrayGetValueAtIndex(certArray, 0),
+                                 (SecKeyRef)CFArrayGetValueAtIndex(keyArray, 0)))) {
+        ret = AVERROR_UNKNOWN;
+        goto end;
+    }
+
+    if (!(outArray = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, certArray))) {
+        ret = AVERROR(ENOMEM);
+        goto end;
+    }
+
+    CFArraySetValueAtIndex(outArray, 0, id);
+
+    SSLSetCertificate(c->ssl_context, outArray);
+
+end:
+    if (certArray)
+        CFRelease(certArray);
+    if (keyArray)
+        CFRelease(keyArray);
+    if (outArray)
+        CFRelease(outArray);
+    if (id)
+        CFRelease(id);
+    return ret;
+}
+
+static OSStatus tls_read_cb(SSLConnectionRef connection, void *data, size_t *dataLength)
+{
+    URLContext *h = (URLContext*)connection;
+    TLSContext *c = h->priv_data;
+    int read = ffurl_read_complete(c->tls_shared.tcp, data, *dataLength);
+    if (read <= 0) {
+        *dataLength = 0;
+        switch(AVUNERROR(read)) {
+            case ENOENT:
+            case 0:
+                return errSSLClosedGraceful;
+            case ECONNRESET:
+                return errSSLClosedAbort;
+            case EAGAIN:
+                return errSSLWouldBlock;
+            default:
+                c->lastErr = read;
+                return ioErr;
+        }
+    } else {
+        *dataLength = read;
+        return noErr;
+    }
+}
+
+static OSStatus tls_write_cb(SSLConnectionRef connection, const void *data, size_t *dataLength)
+{
+    URLContext *h = (URLContext*)connection;
+    TLSContext *c = h->priv_data;
+    int written = ffurl_write(c->tls_shared.tcp, data, *dataLength);
+    if (written <= 0) {
+        *dataLength = 0;
+        switch(AVUNERROR(written)) {
+            case EAGAIN:
+                return errSSLWouldBlock;
+            default:
+                c->lastErr = written;
+                return ioErr;
+        }
+    } else {
+        *dataLength = written;
+        return noErr;
+    }
+}
+
+static int tls_close(URLContext *h)
+{
+    TLSContext *c = h->priv_data;
+    if (c->ssl_context) {
+        SSLClose(c->ssl_context);
+        CFRelease(c->ssl_context);
+    }
+    if (c->ca_array)
+        CFRelease(c->ca_array);
+    if (c->tls_shared.tcp)
+        ffurl_close(c->tls_shared.tcp);
+    return 0;
+}
+
+#define CHECK_ERROR(func, ...) do {                                     \
+        OSStatus status = func(__VA_ARGS__);                            \
+        if (status != noErr) {                                          \
+            ret = AVERROR_UNKNOWN;                                      \
+            av_log(h, AV_LOG_ERROR, #func ": Error %i\n", (int)status); \
+            goto fail;                                                  \
+        }                                                               \
+    } while (0)
+
+static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **options)
+{
+    TLSContext *c = h->priv_data;
+    TLSShared *s = &c->tls_shared;
+    int ret;
+
+    if ((ret = ff_tls_open_underlying(s, h, uri, options)) < 0)
+        goto fail;
+
+    c->ssl_context = SSLCreateContext(NULL, s->listen ? kSSLServerSide : kSSLClientSide, kSSLStreamType);
+    if (!c->ssl_context) {
+        av_log(h, AV_LOG_ERROR, "Unable to create SSL context\n");
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+    if (s->ca_file) {
+        if ((ret = load_ca(h)) < 0)
+            goto fail;
+        CHECK_ERROR(SSLSetSessionOption, c->ssl_context, kSSLSessionOptionBreakOnServerAuth, true);
+    }
+    if (s->cert_file)
+        if ((ret = load_cert(h)) < 0)
+            goto fail;
+    if (s->verify)
+        CHECK_ERROR(SSLSetPeerDomainName, c->ssl_context, s->host, strlen(s->host));
+    CHECK_ERROR(SSLSetIOFuncs, c->ssl_context, tls_read_cb, tls_write_cb);
+    CHECK_ERROR(SSLSetConnection, c->ssl_context, h);
+    while (1) {
+        OSStatus status = SSLHandshake(c->ssl_context);
+        if (status == errSSLServerAuthCompleted) {
+            SecTrustRef peerTrust;
+            SecTrustResultType trustResult;
+            if (!s->verify)
+                continue;
+
+            if (SSLCopyPeerTrust(c->ssl_context, &peerTrust) != noErr) {
+                ret = AVERROR(ENOMEM);
+                goto fail;
+            }
+
+            if (SecTrustSetAnchorCertificates(peerTrust, c->ca_array) != noErr) {
+                ret = AVERROR_UNKNOWN;
+                goto fail;
+            }
+
+            if (SecTrustEvaluate(peerTrust, &trustResult) != noErr) {
+                ret = AVERROR_UNKNOWN;
+                goto fail;
+            }
+
+            if (trustResult == kSecTrustResultProceed ||
+                trustResult == kSecTrustResultUnspecified) {
+                // certificate is trusted
+                status = errSSLWouldBlock; // so we call SSLHandshake again
+            } else if (trustResult == kSecTrustResultRecoverableTrustFailure) {
+                // not trusted, for some reason other than being expired
+                status = errSSLXCertChainInvalid;
+            } else {
+                // cannot use this certificate (fatal)
+                status = errSSLBadCert;
+            }
+
+            if (peerTrust)
+                CFRelease(peerTrust);
+        }
+        if (status == noErr)
+            break;
+
+        av_log(h, AV_LOG_ERROR, "Unable to negotiate TLS/SSL session: %i\n", (int)status);
+        ret = AVERROR(EIO);
+        goto fail;
+    }
+
+    return 0;
+fail:
+    tls_close(h);
+    return ret;
+}
+
+static int map_ssl_error(OSStatus status, size_t processed)
+{
+    switch (status) {
+    case noErr:
+        return processed;
+    case errSSLClosedGraceful:
+    case errSSLClosedNoNotify:
+        return 0;
+    default:
+        return (int)status;
+    }
+}
+
+static int tls_read(URLContext *h, uint8_t *buf, int size)
+{
+    TLSContext *c = h->priv_data;
+    size_t processed;
+    int ret = map_ssl_error(SSLRead(c->ssl_context, buf, size, &processed), processed);
+    if (ret > 0)
+        return ret;
+    if (ret == 0)
+        return AVERROR_EOF;
+    return print_tls_error(h, ret);
+}
+
+static int tls_write(URLContext *h, const uint8_t *buf, int size)
+{
+    TLSContext *c = h->priv_data;
+    size_t processed;
+    int ret = map_ssl_error(SSLWrite(c->ssl_context, buf, size, &processed), processed);
+    if (ret > 0)
+        return ret;
+    if (ret == 0)
+        return AVERROR_EOF;
+    return print_tls_error(h, ret);
+}
+
+static const AVOption options[] = {
+    TLS_COMMON_OPTIONS(TLSContext, tls_shared),
+    { NULL }
+};
+
+static const AVClass tls_class = {
+    .class_name = "tls",
+    .item_name  = av_default_item_name,
+    .option     = options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+URLProtocol ff_tls_securetransport_protocol = {
+    .name           = "tls",
+    .url_open2      = tls_open,
+    .url_read       = tls_read,
+    .url_write      = tls_write,
+    .url_close      = tls_close,
+    .priv_data_size = sizeof(TLSContext),
+    .flags          = URL_PROTOCOL_FLAG_NETWORK,
+    .priv_data_class = &tls_class,
+};
-- 
2.4.1



More information about the ffmpeg-devel mailing list