-
-
Save rcombs/81f621317bcec3c02306 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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