[FFmpeg-cvslog] lavf/tls: Support Secure Transport
Rodger Combs
git at videolan.org
Fri May 29 22:05:43 CEST 2015
ffmpeg | branch: master | Rodger Combs <rodger.combs at gmail.com> | Thu May 28 14:46:16 2015 -0500| [f24d92badadc365f90b5737d6b0234779b6c33bf] | committer: Michael Niedermayer
lavf/tls: Support Secure Transport
Signed-off-by: Michael Niedermayer <michaelni at gmx.at>
> http://git.videolan.org/gitweb.cgi/ffmpeg.git/?a=commit;h=f24d92badadc365f90b5737d6b0234779b6c33bf
---
Changelog | 1 +
configure | 14 +-
libavformat/Makefile | 1 +
libavformat/allformats.c | 1 +
libavformat/avio.c | 4 +-
libavformat/tls.h | 2 +-
libavformat/tls_securetransport.c | 393 +++++++++++++++++++++++++++++++++++++
libavformat/version.h | 4 +-
8 files changed, 413 insertions(+), 7 deletions(-)
diff --git a/Changelog b/Changelog
index 3f47490..c73f1dd 100644
--- a/Changelog
+++ b/Changelog
@@ -23,6 +23,7 @@ version <next>:
- VP9 high bit-depth and extended colorspaces decoding support
- WebPAnimEncoder API when available for encoding and muxing WebP
- Direct3D11-accelerated decoding
+- Support Secure Transport
version 2.6:
diff --git a/configure b/configure
index afa3b64..fb7ec68 100755
--- a/configure
+++ b/configure
@@ -276,6 +276,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]
@@ -1424,6 +1426,7 @@ EXTERNAL_LIBRARY_LIST="
opengl
openssl
sdl
+ securetransport
x11grab
xlib
zlib
@@ -2636,9 +2639,10 @@ sctp_protocol_deps="struct_sctp_event_subscribe"
sctp_protocol_select="network"
srtp_protocol_select="rtp_protocol"
tcp_protocol_select="network"
-tls_gnutls_protocol_deps="gnutls"
-tls_openssl_protocol_deps="openssl !tls_gnutls_protocol"
-tls_protocol_deps_any="tls_gnutls_protocol tls_openssl_protocol"
+tls_securetransport_protocol_deps="securetransport"
+tls_gnutls_protocol_deps="gnutls !tls_securetransport_protocol"
+tls_openssl_protocol_deps="openssl !tls_securetransport_protocol !tls_gnutls_protocol"
+tls_protocol_deps_any="tls_securetransport_protocol tls_gnutls_protocol tls_openssl_protocol"
tls_protocol_select="tcp_protocol"
udp_protocol_select="network"
udplite_protocol_select="network"
@@ -5213,6 +5217,10 @@ if ! disabled sdl; then
fi
enabled sdl && add_cflags $sdl_cflags && add_extralibs $sdl_libs
+disabled securetransport || { check_func SecIdentityCreate "-Wl,-framework,CoreFoundation -Wl,-framework,Security" &&
+ 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/Makefile b/libavformat/Makefile
index 142d69c..aab3b3b 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 3938bfa..e58ddd5 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 a990ea2..deeb87f 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 558676f..959bada 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 0000000..10fc967
--- /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 e16d316..625025e 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, \
More information about the ffmpeg-cvslog
mailing list