--- configure | 10 +- libavformat/tls.c | 307 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 316 insertions(+), 1 deletion(-)
diff --git a/configure b/configure index a02fe4a..fed69ec 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@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel