From 59114b88850dec9b49c52260262ae497836364eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Montgomery=20Edwards=E2=81=B4=E2=81=B4=E2=81=B8?= <57072051+x448@users.noreply.github.com> Date: Fri, 17 Apr 2020 11:12:49 -0500 Subject: [PATCH] Force strong TLS cipher suites in rustup-init.sh Force curl and wget to use strong TLS cipher suites if supported by local tools. If RUSTUP_TLS_CIPHERSUITES variable is set by user then use it. Closes #2284. curl and wget TLS backends supported for strong TLS cipher suites: GnuTLS, OpenSSL, LibreSSL and BoringSSL. Other backends (NSS, WolfSSL, etc.) fall back to prior behavior but can also handle user-specified cipher suites in any syntax required (syntax is not checked by script). curl and wget (if support is detected) will use the same strong TLS 1.2-1.3 cipher suite as Firefox 68 ESR with all weak cipher suites disabled using about:config. Sequence of all 9 cipher suites for OpenSSL is identical to Firefox (slightly different for GnuTLS). DHE is excluded from TLS 1.2 because servers often use bad DH params (see RFC 7919). GnuTLS priority string produces: TLS_AES_128_GCM_SHA256 0x13, 0x01 TLS1.3 TLS_CHACHA20_POLY1305_SHA256 0x13, 0x03 TLS1.3 TLS_AES_256_GCM_SHA384 0x13, 0x02 TLS1.3 TLS_ECDHE_ECDSA_AES_128_GCM_SHA256 0xc0, 0x2b TLS1.2 TLS_ECDHE_ECDSA_CHACHA20_POLY1305 0xcc, 0xa9 TLS1.2 TLS_ECDHE_ECDSA_AES_256_GCM_SHA384 0xc0, 0x2c TLS1.2 TLS_ECDHE_RSA_AES_128_GCM_SHA256 0xc0, 0x2f TLS1.2 TLS_ECDHE_RSA_CHACHA20_POLY1305 0xcc, 0xa8 TLS1.2 TLS_ECDHE_RSA_AES_256_GCM_SHA384 0xc0, 0x30 TLS1.2 GnuTLS priority string could be hardened more but it isn't forgiving of unknown/unsupported values, so the bare minimum was specified. --- rustup-init.sh | 118 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 110 insertions(+), 8 deletions(-) diff --git a/rustup-init.sh b/rustup-init.sh index 0020276a10..26e28e706c 100755 --- a/rustup-init.sh +++ b/rustup-init.sh @@ -376,6 +376,7 @@ ignore() { # use wget instead. downloader() { local _dld + local _ciphersuites if check_cmd curl; then _dld=curl elif check_cmd wget; then @@ -387,18 +388,32 @@ downloader() { if [ "$1" = --check ]; then need_cmd "$_dld" elif [ "$_dld" = curl ]; then - if ! check_help_for "$3" curl --proto --tlsv1.2; then - echo "Warning: Not forcing TLS v1.2, this is potentially less secure" - curl --silent --show-error --fail --location "$1" --output "$2" + get_ciphersuites_for_curl + _ciphersuites="$RETVAL" + if [ -n "$_ciphersuites" ]; then + curl --proto '=https' --tlsv1.2 --ciphers "$_ciphersuites" --silent --show-error --fail --location "$1" --output "$2" else - curl --proto '=https' --tlsv1.2 --silent --show-error --fail --location "$1" --output "$2" + echo "Warning: Not forcing strong cipher suites for TLS, this is potentially less secure" + if ! check_help_for "$3" curl --proto --tlsv1.2; then + echo "Warning: Not forcing TLS v1.2, this is potentially less secure" + curl --silent --show-error --fail --location "$1" --output "$2" + else + curl --proto '=https' --tlsv1.2 --silent --show-error --fail --location "$1" --output "$2" + fi fi elif [ "$_dld" = wget ]; then - if ! check_help_for "$3" wget --https-only --secure-protocol; then - echo "Warning: Not forcing TLS v1.2, this is potentially less secure" - wget "$1" -O "$2" + get_ciphersuites_for_wget + _ciphersuites="$RETVAL" + if [ -n "$_ciphersuites" ]; then + wget --https-only --secure-protocol=TLSv1_2 --ciphers "$_ciphersuites" "$1" -O "$2" else - wget --https-only --secure-protocol=TLSv1_2 "$1" -O "$2" + echo "Warning: Not forcing strong cipher suites for TLS, this is potentially less secure" + if ! check_help_for "$3" wget --https-only --secure-protocol; then + echo "Warning: Not forcing TLS v1.2, this is potentially less secure" + wget "$1" -O "$2" + else + wget --https-only --secure-protocol=TLSv1_2 "$1" -O "$2" + fi fi else err "Unknown downloader" # should not reach here @@ -441,4 +456,91 @@ check_help_for() { test "$_ok" = "y" } +# Return cipher suite string specified by user, otherwise return strong TLS 1.2-1.3 cipher suites +# if support by local tools is detected. Detection currently supports these curl backends: +# GnuTLS and OpenSSL (possibly also LibreSSL and BoringSSL). Return value can be empty. +get_ciphersuites_for_curl() { + if [ -n "${RUSTUP_TLS_CIPHERSUITES-}" ]; then + # user specified custom cipher suites, assume they know what they're doing + RETVAL="$RUSTUP_TLS_CIPHERSUITES" + return + fi + + local _openssl_syntax="no" + local _gnutls_syntax="no" + local _backend_supported="yes" + if curl -V | grep -q ' OpenSSL/'; then + _openssl_syntax="yes" + elif curl -V | grep -iq ' LibreSSL/'; then + _openssl_syntax="yes" + elif curl -V | grep -iq ' BoringSSL/'; then + _openssl_syntax="yes" + elif curl -V | grep -iq ' GnuTLS/'; then + _gnutls_syntax="yes" + else + _backend_supported="no" + fi + + local _args_supported="no" + if [ "$_backend_supported" = "yes" ]; then + # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc. + if check_help_for "notspecified" "curl" "--tlsv1.2" "--ciphers" "--proto"; then + _args_supported="yes" + fi + fi + + local _cs="" + if [ "$_args_supported" = "yes" ]; then + if [ "$_openssl_syntax" = "yes" ]; then + _cs=$(get_strong_ciphersuites_for "openssl") + elif [ "$_gnutls_syntax" = "yes" ]; then + _cs=$(get_strong_ciphersuites_for "gnutls") + fi + fi + + RETVAL="$_cs" +} + +# Return cipher suite string specified by user, otherwise return strong TLS 1.2-1.3 cipher suites +# if support by local tools is detected. Detection currently supports these wget backends: +# GnuTLS and OpenSSL (possibly also LibreSSL and BoringSSL). Return value can be empty. +get_ciphersuites_for_wget() { + if [ -n "${RUSTUP_TLS_CIPHERSUITES-}" ]; then + # user specified custom cipher suites, assume they know what they're doing + RETVAL="$RUSTUP_TLS_CIPHERSUITES" + return + fi + + local _cs="" + if wget -V | grep -q '\-DHAVE_LIBSSL'; then + # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc. + if check_help_for "notspecified" "wget" "TLSv1_2" "--ciphers" "--https-only" "--secure-protocol"; then + _cs=$(get_strong_ciphersuites_for "openssl") + fi + elif wget -V | grep -q '\-DHAVE_LIBGNUTLS'; then + # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc. + if check_help_for "notspecified" "wget" "TLSv1_2" "--ciphers" "--https-only" "--secure-protocol"; then + _cs=$(get_strong_ciphersuites_for "gnutls") + fi + fi + + RETVAL="$_cs" +} + +# Return strong TLS 1.2-1.3 cipher suites in OpenSSL or GnuTLS syntax. TLS 1.2 +# excludes non-ECDHE and non-AEAD cipher suites. DHE is excluded due to bad +# DH params often found on servers (see RFC 7919). Sequence matches or is +# similar to Firefox 68 ESR with weak cipher suites disabled via about:config. +# $1 must be openssl or gnutls. +get_strong_ciphersuites_for() { + if [ "$1" = "openssl" ]; then + # OpenSSL is forgiving of unknown values, no problems with TLS 1.3 values on versions that don't support it yet. + echo "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384" + elif [ "$1" = "gnutls" ]; then + # GnuTLS isn't forgiving of unknown values, so this may require a GnuTLS version that supports TLS 1.3 even if wget doesn't. + # Begin with SECURE128 (and higher) then remove/add to build cipher suites. Produces same 9 cipher suites as OpenSSL but in slightly different order. + echo "SECURE128:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-DTLS-ALL:-CIPHER-ALL:-MAC-ALL:-KX-ALL:+AEAD:+ECDHE-ECDSA:+ECDHE-RSA:+AES-128-GCM:+CHACHA20-POLY1305:+AES-256-GCM" + fi +} + main "$@" || exit 1