Skip to content

Instantly share code, notes, and snippets.

@rcombs

rcombs/stdin Secret

Created April 30, 2015 07:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rcombs/81f621317bcec3c02306 to your computer and use it in GitHub Desktop.
Save rcombs/81f621317bcec3c02306 to your computer and use it in GitHub Desktop.
commit f594bf8daa35a0d6a0c3383068b403f785135723
Author: Rodger Combs <rodger.combs@gmail.com>
Date: Wed Apr 29 18:41:07 2015 -0500
WIP: lavf/tls: Support Secure Transport
diff --git a/configure b/configure
index 88e0d97..1ef2908 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
@@ -2616,7 +2619,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"
@@ -5186,6 +5189,8 @@ if ! disabled sdl; then
fi
enabled sdl && add_cflags $sdl_cflags && add_extralibs $sdl_libs
+enabled securetransport && require2 "Secure Transport" Security/SecureTransport.h SSLCreateContext "-Wl,-framework,CoreFoundation -Wl,-framework,Security"
+
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..0db471a 100644
--- a/libavformat/tls.c
+++ b/libavformat/tls.c
@@ -52,7 +52,24 @@
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>
+
+#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 +83,9 @@ typedef struct TLSContext {
#elif CONFIG_OPENSSL
SSL_CTX *ctx;
SSL *ssl;
+#elif CONFIG_SECURETRANSPORT
+ SSLContextRef ssl_context;
+ CFArrayRef ca_array;
#endif
int fd;
char *ca_file;
@@ -125,6 +145,18 @@ 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);
+ 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 +195,222 @@ static void set_options(URLContext *h, const char *uri)
c->key_file = av_strdup(buf);
}
+#if CONFIG_SECURETRANSPORT
+static int skip_to_separator(FFTextReader *tr)
+{
+ const char expected[] = "-----\n";
+ int offset = 0;
+ for (;;) {
+ char c = ff_text_r8(tr);
+ if (!c)
+ return 0;
+ if (c == expected[offset]) {
+ if (++offset == sizeof(expected) - 1)
+ return 1;
+ } else {
+ offset = 0;
+ }
+ }
+}
+
+static int get_pem(AVBPrint *buf, FFTextReader *tr)
+{
+ av_bprint_clear(buf);
+ for (;;) {
+ char c = ff_text_r8(tr);
+ if (!c)
+ return 0;
+ if (c == '\n')
+ continue;
+ if (c == '-')
+ goto finish;
+ av_bprint_chars(buf, c, 1);
+ }
+finish:
+ for (;;) {
+ char c = ff_text_r8(tr);
+ if (!c || c == '\n')
+ return 1;
+ }
+}
+
+static int add_cert(char *buf, int len, CFMutableArrayRef array)
+{
+ int ret = 0;
+ SecCertificateRef cert = NULL;
+ CFDataRef data = CFDataCreate(kCFAllocatorDefault, buf, len);
+ if (!data) {
+ ret = AVERROR(ENOMEM);
+ goto end;
+ }
+
+ if (!(cert = SecCertificateCreateWithData(kCFAllocatorDefault, data))) {
+ ret = AVERROR(ENOMEM);
+ goto end;
+ }
+
+ CFArrayAppendValue(array, cert);
+ ret = 1;
+
+end:
+ if (data)
+ CFRelease(data);
+ if (cert)
+ CFRelease(cert);
+ return ret;
+}
+
+static int load_pem(URLContext *h, AVIOContext *s, CFMutableArrayRef array)
+{
+ FFTextReader tr;
+ AVBPrint pem;
+ int ret = 0;
+ char *b64str;
+ char *derstr;
+
+ av_bprint_init(&pem, 0, AV_BPRINT_SIZE_UNLIMITED);
+ ff_text_init_avio(h, &tr, s);
+ if (!skip_to_separator(&tr))
+ goto end;
+
+ if (!get_pem(&pem, &tr))
+ goto end;
+
+ if ((ret = av_bprint_finalize(&pem, &b64str)) < 0)
+ goto end;
+
+ if (!(derstr = av_malloc(pem.len))) {
+ ret = AVERROR(ENOMEM);
+ goto end;
+ }
+
+ if ((ret = av_base64_decode(derstr, b64str, pem.len)) < 0)
+ goto end;
+
+ ret = add_cert(derstr, ret, array);
+
+end:
+ av_freep(&b64str);
+ av_freep(&derstr);
+ return ret;
+}
+
+static int load_ca(URLContext *h)
+{
+ TLSContext *c = h->priv_data;
+ AVIOContext *s = NULL;
+ int ret = 0;
+ CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 0,
+ &kCFTypeArrayCallBacks);
+
+ if (!array) {
+ ret = AVERROR(ENOMEM);
+ goto end;
+ }
+
+ if ((ret = avio_open2(&s, c->ca_file, AVIO_FLAG_READ,
+ &h->interrupt_callback, NULL)) < 0)
+ goto end;
+
+ do {
+ ret = load_pem(h, s, array);
+ } while (ret > 0);
+
+ if (CFArrayGetCount(array) == 0) {
+ ret = AVERROR_INVALIDDATA;
+ goto end;
+ }
+
+ if (!(c->ca_array = CFRetain(array))) {
+ ret = AVERROR(ENOMEM);
+ goto end;
+ }
+
+end:
+ if (array)
+ CFRelease(array);
+ if (s)
+ avio_close(s);
+ return ret;
+}
+
+static int load_cert(URLContext *h)
+{
+ TLSContext *c = h->priv_data;
+ //FIXME: WRITEME
+ return AVERROR(ENOSYS);
+}
+
+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:
+ 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:
+ 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 +591,77 @@ 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;
+ if (c->cert_file)
+ if ((ret = load_cert(h)) < 0)
+ goto fail;
+ if (c->verify && !c->ca_file)
+ CHECK_ERROR(SSLSetPeerDomainName, c->ssl_context, host, strlen(host));
+ else
+ CHECK_ERROR(SSLSetSessionOption, c->ssl_context, kSSLSessionOptionBreakOnServerAuth, true);
+ 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 (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 (ret == 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:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment