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

Rodger Combs rodger.combs at gmail.com
Sat May 2 11:11:22 CEST 2015


---
 configure         |   7 +-
 libavformat/tls.c | 302 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 308 insertions(+), 1 deletion(-)

diff --git a/configure b/configure
index 88e0d97..1ef2908 100755
--- a/configure
+++ b/configure
@@ -274,6 +274,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]
@@ -1422,6 +1424,7 @@ EXTERNAL_LIBRARY_LIST="
     opengl
     openssl
     sdl
+    securetransport
     x11grab
     xlib
     zlib
@@ -2616,7 +2619,7 @@ sctp_protocol_deps="struct_sctp_event_subscribe"
 sctp_protocol_select="network"
 srtp_protocol_select="rtp_protocol"
 tcp_protocol_select="network"
-tls_protocol_deps_any="openssl gnutls"
+tls_protocol_deps_any="openssl gnutls securetransport"
 tls_protocol_select="tcp_protocol"
 udp_protocol_select="network"
 udplite_protocol_select="network"
@@ -5186,6 +5189,8 @@ if ! disabled sdl; then
 fi
 enabled sdl && add_cflags $sdl_cflags && add_extralibs $sdl_libs
 
+enabled securetransport && require2 "Secure Transport" Security/SecureTransport.h SSLCreateContext "-Wl,-framework,CoreFoundation -Wl,-framework,Security"
+
 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/tls.c b/libavformat/tls.c
index 2a415c9..baad8f5 100644
--- a/libavformat/tls.c
+++ b/libavformat/tls.c
@@ -52,7 +52,26 @@
         if ((c)->ctx) \
             SSL_CTX_free((c)->ctx); \
     } while (0)
+#elif CONFIG_SECURETRANSPORT
+#include "libavutil/base64.h"
+#include "libavformat/subtitles.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
+#define TLS_shutdown(c) SSLClose((c)->ssl_context)
+#define TLS_free(c) do { \
+        if ((c)->ssl_context) \
+            CFRelease((c)->ssl_context); \
+        if ((c)->ca_array) \
+            CFRelease((c)->ca_array); \
+    } while (0)
 #endif
+
 #if HAVE_POLL_H
 #include <poll.h>
 #endif
@@ -66,6 +85,9 @@ typedef struct TLSContext {
 #elif CONFIG_OPENSSL
     SSL_CTX *ctx;
     SSL *ssl;
+#elif CONFIG_SECURETRANSPORT
+    SSLContextRef ssl_context;
+    CFArrayRef ca_array;
 #endif
     int fd;
     char *ca_file;
@@ -125,6 +147,18 @@ static int do_tls_poll(URLContext *h, int ret)
         av_log(h, AV_LOG_ERROR, "%s\n", ERR_error_string(ERR_get_error(), NULL));
         return AVERROR(EIO);
     }
+#elif CONFIG_SECURETRANSPORT
+    switch (ret) {
+    case errSSLWouldBlock:
+        break;
+    case errSSLXCertChainInvalid:
+        av_log(h, AV_LOG_ERROR, "Invalid certificate chain\n");
+        return AVERROR(EIO);
+    default:
+        av_log(h, AV_LOG_ERROR, "IO Error: %i\n", ret);
+        return AVERROR(EIO);
+    }
+    p.events = POLLIN | POLLOUT;
 #endif
     if (h->flags & AVIO_FLAG_NONBLOCK)
         return AVERROR(EAGAIN);
@@ -163,6 +197,198 @@ static void set_options(URLContext *h, const char *uri)
         c->key_file = av_strdup(buf);
 }
 
+#if CONFIG_SECURETRANSPORT
+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->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 array = NULL;
+    CFArrayRef keyArray = NULL;
+    SecIdentityRef id = NULL;
+    CFMutableArrayRef outArray = NULL;
+
+    if ((ret = import_pem(h, c->cert_file, &array)) < 0)
+        goto end;
+
+    if ((ret = import_pem(h, c->key_file, &keyArray)) < 0)
+        goto end;
+
+    if (!(id = SecIdentityCreate(kCFAllocatorDefault,
+                                 CFArrayGetValueAtIndex(array, 0),
+                                 CFArrayGetValueAtIndex(keyArray, 0)))) {
+        ret = AVERROR_UNKNOWN;
+        goto end;
+    }
+
+    if (!(outArray = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, array))) {
+        ret = AVERROR(ENOMEM);
+        goto end;
+    }
+
+    CFArraySetValueAtIndex(outArray, 0, id);
+
+    SSLSetCertificate(c->ssl_context, outArray);
+
+end:
+    if (array)
+        CFRelease(array);
+    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->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:
+                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->tcp, data, *dataLength);
+    if (written <= 0) {
+        *dataLength = 0;
+        switch(AVUNERROR(written)) {
+            case EAGAIN:
+                return errSSLWouldBlock;
+            default:
+                return ioErr;
+        }
+    } else {
+        *dataLength = written;
+        return noErr;
+    }
+}
+
+static int TLS_read(TLSContext *c, uint8_t *buf, int size)
+{
+    size_t processed;
+    OSStatus status = SSLRead(c->ssl_context, buf, size, &processed);
+    switch (status) {
+    case noErr:
+        return processed;
+    case errSSLClosedGraceful:
+    case errSSLClosedNoNotify:
+        return 0;
+    default:
+        return (int)status;
+    }
+}
+static int TLS_write(TLSContext *c, const uint8_t *buf, int size)
+{
+    size_t processed;
+    OSStatus status = SSLWrite(c->ssl_context, buf, size, &processed);
+    switch (status) {
+    case noErr:
+        return processed;
+    default:
+        return (int)status;
+    }
+}
+#endif
+
 static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **options)
 {
     TLSContext *c = h->priv_data;
@@ -343,6 +569,82 @@ static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **op
         if ((ret = do_tls_poll(h, ret)) < 0)
             goto fail;
     }
+#elif CONFIG_SECURETRANSPORT
+    #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)
+    c->ssl_context = SSLCreateContext(NULL, c->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;
+    }
+    set_options(h, uri);
+    if (c->ca_file) {
+        if ((ret = load_ca(h)) < 0)
+            goto fail;
+        CHECK_ERROR(SSLSetSessionOption, c->ssl_context, kSSLSessionOptionBreakOnServerAuth, true);
+    }
+    if (c->cert_file)
+        if ((ret = load_cert(h)) < 0)
+            goto fail;
+    if (c->verify)
+        CHECK_ERROR(SSLSetPeerDomainName, c->ssl_context, host, strlen(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 (!c->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;
+        if (status != errSSLWouldBlock) {
+            av_log(h, AV_LOG_ERROR, "Unable to negotiate TLS/SSL session: %i\n", (int)status);
+            ret = AVERROR(EIO);
+            goto fail;
+        }
+        if ((ret = do_tls_poll(h, status)) < 0)
+            goto fail;
+    }
 #endif
     return 0;
 fail:
-- 
2.3.5



More information about the ffmpeg-devel mailing list