diff options
author | Rodger Combs <rodger.combs@gmail.com> | 2015-05-28 22:46:16 +0300 |
---|---|---|
committer | Michael Niedermayer <michaelni@gmx.at> | 2015-05-29 22:52:27 +0300 |
commit | f24d92badadc365f90b5737d6b0234779b6c33bf (patch) | |
tree | 8a5a4e4c61b790578e364ea430d1c37044a33f90 /libavformat | |
parent | bedb5d587b5f1fc962e2786c0ea8274ddea6c684 (diff) |
lavf/tls: Support Secure Transport
Signed-off-by: Michael Niedermayer <michaelni@gmx.at>
Diffstat (limited to 'libavformat')
-rw-r--r-- | libavformat/Makefile | 1 | ||||
-rw-r--r-- | libavformat/allformats.c | 1 | ||||
-rw-r--r-- | libavformat/avio.c | 4 | ||||
-rw-r--r-- | libavformat/tls.h | 2 | ||||
-rw-r--r-- | libavformat/tls_securetransport.c | 393 | ||||
-rw-r--r-- | libavformat/version.h | 4 |
6 files changed, 401 insertions, 4 deletions
diff --git a/libavformat/Makefile b/libavformat/Makefile index 142d69cd1e..aab3b3b32e 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -523,6 +523,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 3938bfa8fe..e58ddd5410 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -377,6 +377,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 a990ea2229..deeb87f9ec 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 558676fc8c..959badaf53 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 0000000000..10fc96721c --- /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, +}; diff --git a/libavformat/version.h b/libavformat/version.h index e16d316286..625025e657 100644 --- a/libavformat/version.h +++ b/libavformat/version.h @@ -30,8 +30,8 @@ #include "libavutil/version.h" #define LIBAVFORMAT_VERSION_MAJOR 56 -#define LIBAVFORMAT_VERSION_MINOR 33 -#define LIBAVFORMAT_VERSION_MICRO 101 +#define LIBAVFORMAT_VERSION_MINOR 34 +#define LIBAVFORMAT_VERSION_MICRO 100 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ LIBAVFORMAT_VERSION_MINOR, \ |