[FFmpeg-devel] [PATCH 1/2] lavf/tls: Support Secure Transport
Rodger Combs
rodger.combs at gmail.com
Wed May 20 23:59:46 CEST 2015
---
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
More information about the ffmpeg-devel
mailing list