[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