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

Chris Ballinger chrisballinger at gmail.com
Wed May 20 23:53:01 CEST 2015


Tested the latest version of your patch and now the configure autodetection
works properly. I tested it on a few https streams and it works great,
thank you!

On Wed, May 20, 2015 at 2:29 PM, Rodger Combs <rodger.combs at gmail.com>
wrote:

> ---
>  configure         |  10 +-
>  libavformat/tls.c | 307
> ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 316 insertions(+), 1 deletion(-)
>
> diff --git a/configure b/configure
> index a02fe4a..d4429f1 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
> @@ -2617,7 +2620,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"
> @@ -5185,6 +5188,11 @@ if ! disabled sdl; then
>  fi
>  enabled sdl && add_cflags $sdl_cflags && add_extralibs $sdl_libs
>
> +{ enabled openssl || enabled gnutls; } && disable securetransport
> +
> +disabled securetransport || 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/tls.c b/libavformat/tls.c
> index 2a415c9..70596f8 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,10 @@ typedef struct TLSContext {
>  #elif CONFIG_OPENSSL
>      SSL_CTX *ctx;
>      SSL *ssl;
> +#elif CONFIG_SECURETRANSPORT
> +    SSLContextRef ssl_context;
> +    CFArrayRef ca_array;
> +    int lastErr;
>  #endif
>      int fd;
>      char *ca_file;
> @@ -125,6 +148,20 @@ 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);
> +    case ioErr:
> +        return c->lastErr;
> +    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 +200,200 @@ 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:
> +                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->tcp, data, *dataLength);
> +    if (written <= 0) {
> +        *dataLength = 0;
> +        switch(AVUNERROR(written)) {
> +            case EAGAIN:
> +                return errSSLWouldBlock;
> +            default:
> +                c->lastErr = read;
> +                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 +574,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
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>


More information about the ffmpeg-devel mailing list