From patchwork Tue Jun 11 14:16:23 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Rodger Combs X-Patchwork-Id: 13504 Return-Path: X-Original-To: patchwork@ffaux-bg.ffmpeg.org Delivered-To: patchwork@ffaux-bg.ffmpeg.org Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by ffaux.localdomain (Postfix) with ESMTP id 36E264475A7 for ; Tue, 11 Jun 2019 17:23:55 +0300 (EEST) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 168596808EC; Tue, 11 Jun 2019 17:23:55 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-it1-f196.google.com (mail-it1-f196.google.com [209.85.166.196]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 7FB24680691 for ; Tue, 11 Jun 2019 17:23:48 +0300 (EEST) Received: by mail-it1-f196.google.com with SMTP id n189so5063740itd.0 for ; Tue, 11 Jun 2019 07:23:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:subject:date:message-id:in-reply-to:references:mime-version :content-transfer-encoding; bh=EAUba2sO5cuzaOJS5VwYrL1m/cH/EfxfHn9Gfpo6GAI=; b=QSqsDEg1lRgyoitnRvdfTsT7Wbo0laSLvqFD610t8TfSEjGReWx6h137ZJgsRETqvx 3Y3HyN33dC+rBHKEp6R7t+gqHwO0GpWUS+v6PgerWIXkUJNEDN0lbLmP/oNWxSADafN2 iQyJEZw8c6mdp8SgdaAlugTyHHa12mrUlVLdGZNSGnl+uzhhBCsy8nYAAmrbY7fRYXGI bvbFrVutW8Wg2HE81sbcZurfYSs1XQsKz9EuXLx53jjJWMOgzehcDlyEgy2TkS3dmXOH 7Z05y3BEYY+BFbopZXXr0FrykxvvCpSDmyeVcxRKWIDJdovpZJSCzOirtjE49vieNPwk aePQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=EAUba2sO5cuzaOJS5VwYrL1m/cH/EfxfHn9Gfpo6GAI=; b=mYkL2lxm1pjqbOFPZLpk/xF50YkE4n05SwXKTfoRLpTNgH183kBZLn+FY7dA3Mqfdj 4GpLZRDqLxm8ly7XMVMmsfgdDDkPM457WeIIMp3WM1XLP1odkrzDpGfZHm2kA4ZUFied s3w8pexnAQ7AujyhfBv9ZX+rQkKNRkai2jLezoupp9E5YauPdgL8Pvc+SlQpM1nZ3AVy QbIFNUKE/ADXs8NTj8BG81G2rQHTAKSYwRLv2/AVM4Uq90kxTpNEimh5ez+bqq5TqQ37 54bDDFJ5Qtd54SCMKZkggYt6/DasfY1sxqEsengrN/RSw0wacTta6OJc5TK9U8uTk220 4oPg== X-Gm-Message-State: APjAAAXbElECAgdpMvmNCXkJQqtzDxxVK51v/9XhWMKIdDt8MdpUgG+e fVcGvNmtU3H2OtnSiNbdGkjfhnoVSaU= X-Google-Smtp-Source: APXvYqzlo0i/Gs9ZKAj3hA1h0OByfOxWvQsRW6gP4uy/CvX/xOwY/RyAxotjWo/FQ1O6PIXlsW7wWQ== X-Received: by 2002:a24:1710:: with SMTP id 16mr17882902ith.116.1560262598089; Tue, 11 Jun 2019 07:16:38 -0700 (PDT) Received: from Rodgers-MBP.localdomain ([71.201.155.37]) by smtp.gmail.com with ESMTPSA id w23sm5005046ioa.51.2019.06.11.07.16.36 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Tue, 11 Jun 2019 07:16:37 -0700 (PDT) From: Rodger Combs To: ffmpeg-devel@ffmpeg.org Date: Tue, 11 Jun 2019 09:16:23 -0500 Message-Id: <20190611141623.59440-6-rodger.combs@gmail.com> X-Mailer: git-send-email 2.21.0 In-Reply-To: <20190611141623.59440-1-rodger.combs@gmail.com> References: <20190611141623.59440-1-rodger.combs@gmail.com> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH 6/6] lavf/tls_apple: add support for the new Network framework X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Network.framework was added in macOS 10.14/iOS 12, replacing Secure Transport. Its TLS functionality is (currently) implemented on top of BoringSSL. Secure Transport is deprecated as of macOS 10.15/iOS 13. It'll likely remain available for the forseeable future, but it's considered "legacy" and won't receive new features; most significantly, TLS 1.3. Network.framework also adds additional functionality (like multipath TCP) that ffmpeg may eventually want to support. The new code is behind compile-time checks for header availability, as well as runtime checks for OS version. The framework is linked weakly, so ffmpeg built with Network.framework support will continue to work normally on OS versions prior to macOS 10.14/iOS 12, with the Secure Transport code path being taken. Currently, it's not possible to build tls_apple without support for Secure Transport. I'm not sure if that's a useful case currently, though I suppose if its headers are ever removed then we'll need to add some compile-time conditionals. --- configure | 11 +- libavformat/Makefile | 1 + libavformat/tls_apple.c | 401 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 404 insertions(+), 9 deletions(-) diff --git a/configure b/configure index 32fc26356c..81be23a54f 100755 --- a/configure +++ b/configure @@ -301,6 +301,8 @@ External library support: --enable-mbedtls enable mbedTLS, needed for https support if openssl, gnutls or libtls is not used [no] --enable-mediacodec enable Android MediaCodec support [no] + --disable-nwf disable Apple Network.framework, needed for TLS support + on macOS if openssl and gnutls are not used [autodetect] --enable-libmysofa enable libmysofa, needed for sofalizer filter [no] --enable-openal enable OpenAL 1.1 capture support [no] --enable-opencl enable OpenCL processing [no] @@ -1693,6 +1695,7 @@ EXTERNAL_AUTODETECT_LIBRARY_LIST=" libxcb_shape libxcb_xfixes lzma + nwf schannel sdl2 securetransport @@ -3357,6 +3360,7 @@ https_protocol_suggest="zlib" icecast_protocol_select="http_protocol" mmsh_protocol_select="http_protocol" mmst_protocol_select="network" +nwf_conflict="openssl gnutls libtls mbedtls" rtmp_protocol_conflict="librtmp_protocol" rtmp_protocol_select="tcp_protocol" rtmp_protocol_suggest="zlib" @@ -3378,7 +3382,7 @@ sctp_protocol_select="network" securetransport_conflict="openssl gnutls libtls mbedtls" srtp_protocol_select="rtp_protocol srtp" tcp_protocol_select="network" -tls_protocol_deps_any="gnutls openssl schannel securetransport libtls mbedtls" +tls_protocol_deps_any="gnutls nwf openssl schannel securetransport libtls mbedtls" tls_protocol_select="tcp_protocol" udp_protocol_select="network" udplite_protocol_select="network" @@ -6353,8 +6357,11 @@ if enabled decklink; then esac fi +enabled nwf && + check_lib nwf "Network/Network.h" "nw_connection_create" "-Wl,-framework,CoreFoundation -Wl,-framework,Security -Wl,-weak_framework,Network" || + disable nwf + enabled securetransport && - check_func SecIdentityCreate "-Wl,-framework,CoreFoundation -Wl,-framework,Security" && check_lib securetransport "Security/SecureTransport.h Security/Security.h" "SSLCreateContext" "-Wl,-framework,CoreFoundation -Wl,-framework,Security" || disable securetransport diff --git a/libavformat/Makefile b/libavformat/Makefile index 358d4abf49..fdf981bedc 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -613,6 +613,7 @@ OBJS-$(CONFIG_TCP_PROTOCOL) += tcp.o TLS-OBJS-$(CONFIG_GNUTLS) += tls_gnutls.o TLS-OBJS-$(CONFIG_LIBTLS) += tls_libtls.o TLS-OBJS-$(CONFIG_MBEDTLS) += tls_mbedtls.o +TLS-OBJS-$(CONFIG_NWF) += tls_apple.o TLS-OBJS-$(CONFIG_OPENSSL) += tls_openssl.o TLS-OBJS-$(CONFIG_SECURETRANSPORT) += tls_apple.o TLS-OBJS-$(CONFIG_SCHANNEL) += tls_schannel.o diff --git a/libavformat/tls_apple.c b/libavformat/tls_apple.c index 23042eb8ee..dd202eb9c0 100644 --- a/libavformat/tls_apple.c +++ b/libavformat/tls_apple.c @@ -19,7 +19,7 @@ */ #include - +#include #include "avformat.h" #include "avio_internal.h" @@ -37,18 +37,88 @@ #include #include +#if CONFIG_NWF +#include +#endif + // We use a private API call here; it's good enough for WebKit. SecIdentityRef __attribute__((weak)) SecIdentityCreate(CFAllocatorRef allocator, SecCertificateRef certificate, SecKeyRef privateKey); #define ioErr -36 +#define NWF_CHECK __builtin_available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *) + typedef struct TLSContext { const AVClass *class; TLSShared tls_shared; SSLContextRef ssl_context; CFArrayRef ca_array; int lastErr; +#if CONFIG_NWF + nw_connection_t nw_conn; + dispatch_semaphore_t semaphore; + dispatch_semaphore_t state_semaphore; + pthread_mutex_t state_lock; + int at_eof; + nw_listener_state_t nw_listen_state; + nw_connection_state_t nw_state; + nw_error_t nw_state_error; + int tcp_nodelay; +#endif } TLSContext; +#if CONFIG_NWF +static int nwf_init = 0; +static dispatch_queue_t nwf_queue = NULL; + +static int print_nwf_error(void *log_ctx, nw_error_t error, int successRet, const char *func) +{ + if (!error) + return successRet; + + switch (nw_error_get_error_domain(error)) { + case nw_error_domain_posix: + return AVERROR(nw_error_get_error_code(error)); + default: + av_log(log_ctx, AV_LOG_ERROR, "[%s] Error domain %u, code %i\n", func, + nw_error_get_error_domain(error), + nw_error_get_error_code(error)); + return AVERROR(EIO); + } +} + +#define PRINT_NWF_ERROR(err, successRet) print_nwf_error(h, err, successRet, __FUNCTION__) + +static int ff_nwf_init(void) +{ + if (NWF_CHECK) { + int ret = 0; + ff_lock_avformat(); + if (!nwf_init) + nwf_queue = dispatch_queue_create("org.ffmpeg.nwf", DISPATCH_QUEUE_SERIAL); + if (nwf_queue) + nwf_init++; + else + ret = AVERROR(ENOMEM); + ff_unlock_avformat(); + + return ret; + } + + return AVERROR(EINVAL); +} + +static void ff_nwf_deinit(void) +{ + if (NWF_CHECK) { + ff_lock_avformat(); + nwf_init--; + if (!nwf_init) + dispatch_release(nwf_queue); + ff_unlock_avformat(); + } +} +#endif + static int print_tls_error(URLContext *h, int ret) { TLSContext *c = h->priv_data; @@ -272,6 +342,35 @@ static OSStatus tls_write_cb(SSLConnectionRef connection, const void *data, size static int tls_close(URLContext *h) { TLSContext *c = h->priv_data; + +#if CONFIG_NWF + if (NWF_CHECK) { + if (c->nw_conn) { + nw_connection_cancel(c->nw_conn); + nw_release(c->nw_conn); + } + + pthread_mutex_lock(&c->state_lock); + while (c->nw_state != nw_connection_state_cancelled && + c->nw_state != nw_connection_state_failed && + c->nw_state != nw_connection_state_invalid) { + pthread_mutex_unlock(&c->state_lock); + dispatch_semaphore_wait(c->state_semaphore, DISPATCH_TIME_FOREVER); + pthread_mutex_lock(&c->state_lock); + } + pthread_mutex_unlock(&c->state_lock); + + if (c->semaphore) + dispatch_release(c->semaphore); + if (c->state_semaphore) + dispatch_release(c->state_semaphore); + + pthread_mutex_destroy(&c->state_lock); + + ff_nwf_deinit(); + } +#endif + if (c->ssl_context) { SSLClose(c->ssl_context); CFRelease(c->ssl_context); @@ -296,7 +395,235 @@ static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **op { TLSContext *c = h->priv_data; TLSShared *s = &c->tls_shared; - int ret; + int ret = 0; + + if (s->ca_file) { + if ((ret = load_ca(h)) < 0) + goto fail; + } + +#if CONFIG_NWF + if (NWF_CHECK) { + int port; + char portStr[8]; + SecIdentityRef identity = NULL; + CFArrayRef certArray = NULL; + nw_endpoint_t endpoint = NULL; + nw_parameters_t parameters = NULL; + bool finished = false; + + if ((ret = ff_nwf_init()) < 0) + return ret; + + if ((ret = AVERROR(pthread_mutex_init(&c->state_lock, NULL))) < 0) + goto nwf_fail; + + if ((ret = ff_tls_process_underlying(s, h, uri, &port)) < 0) + goto nwf_fail; + + snprintf(portStr, sizeof(portStr), "%i", port); + + if (!(c->semaphore = dispatch_semaphore_create(0))) { + ret = AVERROR(ENOMEM); + goto nwf_fail; + } + + if (!(c->state_semaphore = dispatch_semaphore_create(0))) { + ret = AVERROR(ENOMEM); + goto nwf_fail; + } + + if (!(endpoint = nw_endpoint_create_host(s->underlying_host, portStr))) { + ret = AVERROR(ENOMEM); + goto nwf_fail; + } + + if (s->cert_file && (ret = load_identity(h, &identity, &certArray)) < 0) + goto nwf_fail; + + if (!(parameters = nw_parameters_create_secure_tcp(^(nw_protocol_options_t tls_options) { + sec_protocol_options_t options = nw_tls_copy_sec_protocol_options(tls_options); + sec_protocol_options_set_tls_server_name(options, s->host); + sec_protocol_options_set_peer_authentication_required(options, s->verify); + if (s->ca_file || !s->verify) + sec_protocol_options_set_verify_block(options, ^(sec_protocol_metadata_t metadata, sec_trust_t trust_ref, sec_protocol_verify_complete_t complete) { + bool succ = false; + SecTrustRef peerTrust = NULL; + SecTrustResultType trustResult; + + // set_peer_authentication_required is buggy; seems to no-op + if (!s->verify) + complete(true); + + if (!(peerTrust = sec_trust_copy_ref(trust_ref))) + goto verify_fail; + + if (SecTrustSetAnchorCertificates(peerTrust, c->ca_array) != noErr) + goto verify_fail; + + if (SecTrustEvaluate(peerTrust, &trustResult) != noErr) + goto verify_fail; + + succ = (trustResult == kSecTrustResultProceed || + trustResult == kSecTrustResultUnspecified); + +verify_fail: + if (peerTrust) + CFRelease(peerTrust); + complete(succ); + }, nwf_queue); + if (identity) { + sec_identity_t sec_id = sec_identity_create_with_certificates(identity, certArray); + if (sec_id) { + sec_protocol_options_set_local_identity(options, sec_id); + nw_release(sec_id); + } + } + }, ^(nw_protocol_options_t tcp_options) { + nw_tcp_options_set_no_delay(tcp_options, c->tcp_nodelay); + }))) { + ret = AVERROR(ENOMEM); + goto nwf_fail; + } + + if (s->listen) { + nw_listener_t listener; + + nw_parameters_set_local_endpoint(parameters, endpoint); + + listener = nw_listener_create(parameters); + if (!listener) { + ret = AVERROR(ENOMEM); + goto nwf_fail; + } + + nw_listener_set_queue(listener, nwf_queue); + + nw_listener_set_new_connection_handler(listener, ^(nw_connection_t connection) { + pthread_mutex_lock(&c->state_lock); + if (!c->nw_conn) { + nw_retain(connection); + c->nw_conn = connection; + dispatch_semaphore_signal(c->state_semaphore); + } + pthread_mutex_unlock(&c->state_lock); + }); + nw_listener_set_state_changed_handler(listener, ^(nw_listener_state_t state, nw_error_t error) { + pthread_mutex_lock(&c->state_lock); + c->nw_listen_state = state; + c->nw_state_error = error; + dispatch_semaphore_signal(c->state_semaphore); + pthread_mutex_unlock(&c->state_lock); + }); + + nw_listener_start(listener); + + while (!finished) { + dispatch_semaphore_wait(c->state_semaphore, DISPATCH_TIME_FOREVER); + pthread_mutex_lock(&c->state_lock); + switch (c->nw_listen_state) { + case nw_listener_state_invalid: + ret = AVERROR_UNKNOWN; + finished = true; + break; + case nw_listener_state_waiting: + break; + case nw_listener_state_ready: + ret = 0; + if (c->nw_conn) + finished = true; + break; + case nw_listener_state_failed: + default: + ret = PRINT_NWF_ERROR(c->nw_state_error, AVERROR_UNKNOWN); + finished = true; + break; + case nw_listener_state_cancelled: + ret = AVERROR(EIO); + finished = true; + break; + } + pthread_mutex_unlock(&c->state_lock); + } + + finished = false; + + nw_listener_cancel(listener); + nw_release(listener); + + pthread_mutex_lock(&c->state_lock); + while (c->nw_listen_state != nw_listener_state_cancelled && + c->nw_listen_state != nw_listener_state_failed && + c->nw_listen_state != nw_listener_state_invalid) { + pthread_mutex_unlock(&c->state_lock); + dispatch_semaphore_wait(c->state_semaphore, DISPATCH_TIME_FOREVER); + pthread_mutex_lock(&c->state_lock); + } + pthread_mutex_unlock(&c->state_lock); + + if (!c->nw_conn) + goto nwf_fail; + } else { + if (!(c->nw_conn = nw_connection_create(endpoint, parameters))) { + ret = AVERROR(ENOMEM); + goto nwf_fail; + } + } + + nw_connection_set_state_changed_handler(c->nw_conn, ^(nw_connection_state_t state, nw_error_t error) { + pthread_mutex_lock(&c->state_lock); + c->nw_state = state; + c->nw_state_error = error; + dispatch_semaphore_signal(c->state_semaphore); + pthread_mutex_unlock(&c->state_lock); + }); + + nw_connection_set_queue(c->nw_conn, nwf_queue); + + nw_connection_start(c->nw_conn); + + while (!finished) { + dispatch_semaphore_wait(c->state_semaphore, DISPATCH_TIME_FOREVER); + pthread_mutex_lock(&c->state_lock); + switch (c->nw_state) { + case nw_connection_state_invalid: + ret = AVERROR_UNKNOWN; + finished = true; + break; + case nw_connection_state_waiting: + case nw_connection_state_preparing: + break; + case nw_connection_state_ready: + ret = 0; + finished = true; + break; + case nw_connection_state_failed: + default: + ret = PRINT_NWF_ERROR(c->nw_state_error, AVERROR_UNKNOWN); + finished = true; + break; + case nw_connection_state_cancelled: + ret = AVERROR(EIO); + finished = true; + break; + } + pthread_mutex_unlock(&c->state_lock); + } + +nwf_fail: + if (endpoint) + nw_release(endpoint); + if (parameters) + nw_release(parameters); + if (certArray) + CFRelease(certArray); + if (identity) + CFRelease(identity); + if (ret < 0) + tls_close(h); + return ret; + } +#endif if ((ret = ff_tls_open_underlying(s, h, uri, options)) < 0) goto fail; @@ -307,10 +634,6 @@ static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **op ret = AVERROR(ENOMEM); goto fail; } - if (s->ca_file) { - if ((ret = load_ca(h)) < 0) - goto fail; - } if (s->ca_file || !s->verify) CHECK_ERROR(SSLSetSessionOption, c->ssl_context, kSSLSessionOptionBreakOnServerAuth, true); if (s->cert_file) @@ -393,6 +716,41 @@ static int tls_read(URLContext *h, uint8_t *buf, int size) TLSContext *c = h->priv_data; size_t available = 0, processed = 0; int ret; + +#if CONFIG_NWF + if (NWF_CHECK) { + __block nw_error_t error; + __block int gotSize = 0; + + if (c->at_eof) + return AVERROR_EOF; + + nw_connection_receive(c->nw_conn, 1, size, ^(dispatch_data_t content, nw_content_context_t context, bool is_complete, nw_error_t inError) { + if (is_complete) + c->at_eof = 1; + + if (content) { + gotSize = dispatch_data_get_size(content); + + dispatch_data_apply(content, ^(dispatch_data_t region, size_t offset, const void *buffer, size_t inSize) { + memcpy(buf + offset, buffer, inSize); + return (bool)true; + }); + } + + error = inError; + dispatch_semaphore_signal(c->semaphore); + }); + + dispatch_semaphore_wait(c->semaphore, DISPATCH_TIME_FOREVER); + + if (c->at_eof && !gotSize && !error) + return AVERROR_EOF; + + return PRINT_NWF_ERROR(error, gotSize); + } +#endif + SSLGetBufferedReadSize(c->ssl_context, &available); if (available) size = FFMIN(available, size); @@ -409,7 +767,25 @@ static int tls_write(URLContext *h, const uint8_t *buf, int size) { TLSContext *c = h->priv_data; size_t processed = 0; - int ret = SSLWrite(c->ssl_context, buf, size, &processed); + int ret; + +#if CONFIG_NWF + if (NWF_CHECK) { + __block nw_error_t error; + dispatch_data_t content = dispatch_data_create(buf, size, nwf_queue, DISPATCH_DATA_DESTRUCTOR_DEFAULT); + nw_connection_send(c->nw_conn, content, NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, true, ^(nw_error_t inError) { + error = inError; + dispatch_semaphore_signal(c->semaphore); + }); + dispatch_release(content); + + dispatch_semaphore_wait(c->semaphore, DISPATCH_TIME_FOREVER); + + return PRINT_NWF_ERROR(error, size); + } +#endif + + ret = SSLWrite(c->ssl_context, buf, size, &processed); ret = map_ssl_error(ret, processed); if (ret > 0) return ret; @@ -421,11 +797,22 @@ static int tls_write(URLContext *h, const uint8_t *buf, int size) static int tls_get_file_handle(URLContext *h) { TLSContext *c = h->priv_data; + +#if CONFIG_NWF + if (NWF_CHECK) // Network.framework doesn't expose file handles + return -1; +#endif + return ffurl_get_file_handle(c->tls_shared.tcp); } +#define OFFSET(v) offsetof(TLSContext, v) + static const AVOption options[] = { TLS_COMMON_OPTIONS(TLSContext, tls_shared), +#if CONFIG_NWF + { "tcp_nodelay", "Use TCP_NODELAY to disable nagle's algorithm", OFFSET(tcp_nodelay), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, .flags = TLS_OPTFL }, +#endif { NULL } };