diff options
Diffstat (limited to 'deluge/tests/data/testssl.sh')
-rwxr-xr-x | deluge/tests/data/testssl.sh | 20256 |
1 files changed, 0 insertions, 20256 deletions
diff --git a/deluge/tests/data/testssl.sh b/deluge/tests/data/testssl.sh deleted file mode 100755 index c04d055..0000000 --- a/deluge/tests/data/testssl.sh +++ /dev/null @@ -1,20256 +0,0 @@ -#!/usr/bin/env bash -# -# vim:ts=5:sw=5:expandtab -# we have a spaces softtab, that ensures readability with other editors too - -# testssl.sh is a program for spotting weak SSL/TLS encryption, ciphers, protocols and some -# vulnerabilities or features. It may or may be not distributed by your distribution. -# The upstream versions are available (please leave the links intact): -# -# Development version https://github.com/drwetter/testssl.sh -# Stable version https://testssl.sh -# File bugs at github https://github.com/drwetter/testssl.sh/issues -# -# Project lead and initiator: Dirk Wetter, copyleft: 2007-today. -# Main contributions from David Cooper. Further contributors see CREDITS.md . -# -# License: GPLv2, see https://www.fsf.org/licensing/licenses/info/GPLv2.html -# and accompanying license "LICENSE.txt". Redistribution + modification under this -# license permitted. -# If you enclose this program or parts of it in your software, it has to be -# accompanied by the same license (see link). Do not violate the license. -# If you do not agree to these terms, do not use it in the first place! -# -# OpenSSL, which is being used and maybe distributed via one of this projects' -# web sites, is subject to their licensing: https://www.openssl.org/source/license.txt -# -# The client simulation data comes from SSLlabs and is licensed to the 'Qualys SSL Labs -# Terms of Use' (v2.2), see https://www.ssllabs.com/downloads/Qualys_SSL_Labs_Terms_of_Use.pdf, -# stating a CC BY 3.0 US license: https://creativecommons.org/licenses/by/3.0/us/ -# -# Please note: USAGE WITHOUT ANY WARRANTY, THE SOFTWARE IS PROVIDED "AS IS". -# USE IT AT your OWN RISK! -# Seriously! The threat is you run this code on your computer and untrusted input e.g. -# could be supplied from a server you are querying. -# -# HISTORY: -# Back in 2006 it all started with a few openssl commands... -# That's because openssl is a such a good swiss army knife (see e.g. -# https://wiki.openssl.org/index.php/Command_Line_Utilities) that it was difficult to resist -# wrapping some shell commands around it, which I used for my pen tests. This is how -# everything started. -# Now it has grown up, it has bash socket support for most features, which has been basically -# replacing more and more functions of OpenSSL and some sockets functions serve as some kind -# of central functions. -# -# WHY BASH? -# Cross-platform is one of the three main goals of this script. Second: Ease of installation. -# No compiling, install gems, go to CPAN, use pip etc. Third: Easy to use and to interpret -# the results. -# /bin/bash including the builtin sockets fulfill all that. The socket checks in bash may sound -# cool and unique -- they are -- but probably you can achieve e.g. the same result with my favorite -# interactive shell: zsh (zmodload zsh/net/socket -- checkout zsh/net/tcp) too! Oh, and btw. -# ksh93 has socket support too. -# /bin/bash though is way more often used within Linux and it's perfect for cross platform support. -# MacOS X has it and also under Windows the MSYS2 extension or Cygwin as well as Bash on Windows (WSL) -# has /bin/bash. -# -# Q: So what's the difference to www.ssllabs.com/ssltest/ or sslcheck.globalsign.com/ ? -# A: As of now ssllabs only check 1) webservers 2) on standard ports, 3) reachable from the -# internet. And those examples above 4) are 3rd parties. If these restrictions are all fine -# with you and you need a management compatible rating -- go ahead and use those. -# -# But also if your fine with those restrictions: testssl.sh is meant as a tool in your hand -# and it's way more flexible. Oh, and did I mention testssl.sh is open source? -# -#################### Stop talking, action now #################### - - -########### Definition of error codes -# -declare -r ERR_BASH=255 # Bash version incorrect -declare -r ERR_CMDLINE=254 # Cmd line couldn't be parsed -declare -r ERR_FCREATE=253 # Output file couldn't be created -declare -r ERR_FNAMEPARSE=252 # Input file couldn't be parsed -declare -r ERR_NOSUPPORT=251 # Feature requested is not supported -declare -r ERR_OSSLBIN=250 # Problem with OpenSSL binary -declare -r ERR_DNSBIN=249 # Problem with DNS lookup binaries -declare -r ERR_OTHERCLIENT=248 # Other client problem -declare -r ERR_DNSLOOKUP=247 # Problem with resolving IP addresses or names -declare -r ERR_CONNECT=246 # Connectivity problem -declare -r ERR_CLUELESS=245 # Weird state, either though user options or testssl.sh -declare -r ERR_RESOURCE=244 # Resources testssl.sh needs couldn't be read -declare -r ERR_CHILD=242 # Child received a signal from master -declare -r ALLOK=0 # All is fine - - -[ -z "${BASH_VERSINFO[0]}" ] && printf "\n\033[1;35m Please make sure you're using \"bash\"! Bye...\033[m\n\n" >&2 && exit $ERR_BASH -[ $(kill -l | grep -c SIG) -eq 0 ] && printf "\n\033[1;35m Please make sure you're calling me without leading \"sh\"! Bye...\033[m\n\n" >&2 && exit $ERR_BASH -[ ${BASH_VERSINFO[0]} -lt 3 ] && printf "\n\033[1;35m Minimum requirement is bash 3.2. You have $BASH_VERSION \033[m\n\n" >&2 && exit $ERR_BASH -[ ${BASH_VERSINFO[0]} -le 3 ] && [ ${BASH_VERSINFO[1]} -le 1 ] && printf "\n\033[1;35m Minimum requirement is bash 3.2. You have $BASH_VERSION \033[m\n\n" >&2 && exit $ERR_BASH - -########### Debugging helpers + profiling -# -declare -r PS4='|${LINENO}> \011${FUNCNAME[0]:+${FUNCNAME[0]}(): }' -DEBUGTIME=${DEBUGTIME:-false} # https://stackoverflow.com/questions/5014823/how-to-profile-a-bash-shell-script-slow-startup#20855353, profiling bash -DEBUG_ALLINONE=${DEBUG_ALLINONE:-false} # true: do debugging in one screen (old behavior for testssl.sh and bash3's default - # false: needed for performance analysis or useful for just having an extra file -DEBUG_ALLINONE=${SETX:-false} # SETX as a shortcut for old style debugging, overriding DEBUG_ALLINONE -if [[ "$SHELLOPTS" =~ xtrace ]]; then - if "$DEBUGTIME"; then - # separate debugging, doesn't mess up the screen, $DEBUGTIME determines whether we also do performance analysis - exec 42>&2 2> >(tee /tmp/testssl-$$.log | sed -u 's/^.*$/now/' | date -f - +%s.%N >/tmp/testssl-$$.time) - # BASH_XTRACEFD=42 - else - if ! "$DEBUG_ALLINONE"; then - exec 42>| /tmp/testssl-$$.log - BASH_XTRACEFD=42 - fi - fi -fi - -########### Traps! Make sure that temporary files are cleaned up after use in ANY case -# -trap "cleanup" QUIT EXIT -trap "child_error" USR1 - - -########### Internal definitions -# -declare -r VERSION="3.0.6" -declare -r SWCONTACT="dirk aet testssl dot sh" -grep -E -q "dev|rc|beta" <<< "$VERSION" && \ - SWURL="https://testssl.sh/dev/" || - SWURL="https://testssl.sh/" -if git log &>/dev/null; then - declare -r GIT_REL="$(git log --format='%h %ci' -1 2>/dev/null | awk '{ print $1" "$2" "$3 }')" - declare -r GIT_REL_SHORT="$(git log --format='%h %ci' -1 2>/dev/null | awk '{ print $1 }')" - declare -r REL_DATE="$(git log --format='%h %ci' -1 2>/dev/null | awk '{ print $2 }')" -fi -declare -r PROG_NAME="$(basename "$0")" -declare -r RUN_DIR="$(dirname "$0")" -declare -r SYSTEM="$(uname -s)" -declare -r SYSTEMREV="$(uname -r)" -SYSTEM2="" # currently only being used for WSL = bash on windows -TESTSSL_INSTALL_DIR="${TESTSSL_INSTALL_DIR:-""}" # If you run testssl.sh and it doesn't find it necessary file automagically set TESTSSL_INSTALL_DIR -CA_BUNDLES_PATH="${CA_BUNDLES_PATH:-""}" # You can have your stores some place else -ADDITIONAL_CA_FILES="${ADDITIONAL_CA_FILES:-""}" # single file with a CA in PEM format or comma separated lists of them -CIPHERS_BY_STRENGTH_FILE="" -TLS_DATA_FILE="" # mandatory file for socket-based handshakes -OPENSSL_LOCATION="" -HNAME="$(uname -n)" -HNAME="${HNAME%%.*}" - -declare CMDLINE -CMDLINE_PARSED="" # This makes sure we don't let early fatal() write into files when files aren't created yet -declare -r -a CMDLINE_ARRAY=("$@") # When performing mass testing, the child processes need to be sent the -declare -a MASS_TESTING_CMDLINE # command line in the form of an array (see #702 and https://mywiki.wooledge.org/BashFAQ/050). - - -########### Some predefinitions: date, sed (we always use test and NOT try to determine -# capabilities by querying the OS) -# -HAS_GNUDATE=false -HAS_FREEBSDDATE=false -HAS_OPENBSDDATE=false -if date -d @735275209 >/dev/null 2>&1; then - if date -r @735275209 >/dev/null 2>&1; then - # It can't do any conversion from a plain date output. - HAS_OPENBSDDATE=true - else - HAS_GNUDATE=true - fi -fi -# FreeBSD and OS X date(1) accept "-f inputformat", so do newer OpenBSD versions >~ 6.6. -date -j -f '%s' 1234567 >/dev/null 2>&1 && \ - HAS_FREEBSDDATE=true - -echo A | sed -E 's/A//' >/dev/null 2>&1 && \ - declare -r HAS_SED_E=true || \ - declare -r HAS_SED_E=false - -########### Terminal definitions -tty -s && \ - declare -r INTERACTIVE=true || \ - declare -r INTERACTIVE=false - -if [[ -z $TERM_WIDTH ]]; then # no batch file and no otherwise predefined TERM_WIDTH - if ! tput cols &>/dev/null || ! "$INTERACTIVE";then # Prevent tput errors if running non interactive - export TERM_WIDTH=${COLUMNS:-80} - else - export TERM_WIDTH=${COLUMNS:-$(tput cols)} # for custom line wrapping and dashes - fi -fi -TERM_CURRPOS=0 # custom line wrapping needs alter the current horizontal cursor pos - - -########### Defining (and presetting) variables which can be changed -# -# Following variables make use of $ENV and can be used like "OPENSSL=<myprivate_path_to_openssl> ./testssl.sh <URI>" -declare -x OPENSSL -OPENSSL_TIMEOUT=${OPENSSL_TIMEOUT:-""} # Default connect timeout with openssl before we call the server side unreachable -CONNECT_TIMEOUT=${CONNECT_TIMEOUT:-""} # Default connect timeout with sockets before we call the server side unreachable -PHONE_OUT=${PHONE_OUT:-false} # Whether testssl can retrieve CRLs and OCSP -FAST_SOCKET=${FAST_SOCKET:-false} # EXPERIMENTAL feature to accelerate sockets -- DO NOT USE it for production -COLOR=${COLOR:-2} # 3: Extra color (ciphers, curves), 2: Full color, 1: B/W only 0: No ESC at all -COLORBLIND=${COLORBLIND:-false} # if true, swap blue and green in the output -SHOW_EACH_C=${SHOW_EACH_C:-false} # where individual ciphers are tested show just the positively ones tested -SHOW_SIGALGO=${SHOW_SIGALGO:-false} # "secret" switch whether testssl.sh shows the signature algorithm for -E / -e -SNEAKY=${SNEAKY:-false} # is the referer and useragent we leave behind just usual? -QUIET=${QUIET:-false} # don't output the banner. By doing this you acknowledge usage term appearing in the banner -SSL_NATIVE=${SSL_NATIVE:-false} # we do per default bash sockets where possible "true": switch back to "openssl native" -ASSUME_HTTP=${ASSUME_HTTP:-false} # in seldom cases (WAF, old servers, grumpy SSL) service detection fails. "True" enforces HTTP checks -BASICAUTH=${BASICAUTH:-""} # HTTP basic auth credentials can be set here like user:pass -BUGS=${BUGS:-""} # -bugs option from openssl, needed for some BIG IP F5 -WARNINGS=${WARNINGS:-""} # can be either off or batch -DEBUG=${DEBUG:-0} # 1: normal output the files in /tmp/ are kept for further debugging purposes - # 2: list more what's going on , also lists some errors of connections - # 3: slight hexdumps + other info, - # 4: display bytes sent via sockets - # 5: display bytes received via sockets - # 6: whole 9 yards -FAST=${FAST:-false} # preference: show only first cipher, run_allciphers with openssl instead of sockets -WIDE=${WIDE:-false} # whether to display for some options just ciphers or a table w hexcode/KX,Enc,strength etc. -MASS_TESTING_MODE=${MASS_TESTING_MODE:-serial} # can be serial or parallel. Subject to change -LOGFILE="${LOGFILE:-""}" # logfile if used -JSONFILE="${JSONFILE:-""}" # jsonfile if used -CSVFILE="${CSVFILE:-""}" # csvfile if used -HTMLFILE="${HTMLFILE:-""}" # HTML if used -FNAME=${FNAME:-""} # file name to read commands from -FNAME_PREFIX=${FNAME_PREFIX:-""} # output filename prefix, see --outprefix -APPEND=${APPEND:-false} # append to csv/json file instead of overwriting it -[[ -z "$NODNS" ]] && declare NODNS # If unset it does all DNS lookups per default. "min" only for hosts or "none" at all -HAS_IPv6=${HAS_IPv6:-false} # if you have OpenSSL with IPv6 support AND IPv6 networking set it to yes -ALL_CLIENTS=${ALL_CLIENTS:-false} # do you want to run all client simulation form all clients supplied by SSLlabs? -OFFENSIVE=${OFFENSIVE:-true} # do you want to include offensive vulnerability tests which may cause blocking by an IDS? - -########### Tuning vars which cannot be set by a cmd line switch. Use instead e.g "HEADER_MAXSLEEP=10 ./testssl.sh <your_args_here>" -# -EXPERIMENTAL=${EXPERIMENTAL:-false} # a development hook which allows us to disable code -PROXY_WAIT=${PROXY_WAIT:-20} # waiting at max 20 seconds for socket reply through proxy -DNS_VIA_PROXY=${DNS_VIA_PROXY:-true} # do DNS lookups via proxy. --ip=proxy reverses this -IGN_OCSP_PROXY=${IGN_OCSP_PROXY:-false} # Also when --proxy is supplied it is ignored when testing for revocation via OCSP via --phone-out -HEADER_MAXSLEEP=${HEADER_MAXSLEEP:-5} # we wait this long before killing the process to retrieve a service banner / http header -MAX_SOCKET_FAIL=${MAX_SOCKET_FAIL:-2} # If this many failures for TCP socket connects are reached we terminate -MAX_OSSL_FAIL=${MAX_OSSL_FAIL:-2} # If this many failures for s_client connects are reached we terminate -MAX_HEADER_FAIL=${MAX_HEADER_FAIL:-2} # If this many failures for HTTP GET are encountered we don't try again to get the header -MAX_WAITSOCK=${MAX_WAITSOCK:-10} # waiting at max 10 seconds for socket reply. There shouldn't be any reason to change this. -CCS_MAX_WAITSOCK=${CCS_MAX_WAITSOCK:-5} # for the two CCS payload (each). There shouldn't be any reason to change this. -HEARTBLEED_MAX_WAITSOCK=${HEARTBLEED_MAX_WAITSOCK:-8} # for the heartbleed payload. There shouldn't be any reason to change this. -STARTTLS_SLEEP=${STARTTLS_SLEEP:-10} # max time wait on a socket for STARTTLS. MySQL has a fixed value of 1 which can't be overwritten (#914) -FAST_STARTTLS=${FAST_STARTTLS:-true} # at the cost of reliability decrease the handshakes for STARTTLS -USLEEP_SND=${USLEEP_SND:-0.1} # sleep time for general socket send -USLEEP_REC=${USLEEP_REC:-0.2} # sleep time for general socket receive -HSTS_MIN=${HSTS_MIN:-180} # >=180 days is ok for HSTS - HSTS_MIN=$((HSTS_MIN * 86400)) # correct to seconds -HPKP_MIN=${HPKP_MIN:-30} # >=30 days should be ok for HPKP_MIN, practical hints? - HPKP_MIN=$((HPKP_MIN * 86400)) # correct to seconds -DAYS2WARN1=${DAYS2WARN1:-60} # days to warn before cert expires, threshold 1 -DAYS2WARN2=${DAYS2WARN2:-30} # days to warn before cert expires, threshold 2 -VULN_THRESHLD=${VULN_THRESHLD:-1} # if vulnerabilities to check >$VULN_THRESHLD we DON'T show a separate header line in the output each vuln. check -UNBRACKTD_IPV6=${UNBRACKTD_IPV6:-false} # some versions of OpenSSL (like Gentoo) don't support [bracketed] IPv6 addresses -NO_ENGINE=${NO_ENGINE:-false} # if there are problems finding the (external) openssl engine set this to true -declare -r CLIENT_MIN_PFS=5 # number of ciphers needed to run a test for PFS -CAPATH="${CAPATH:-/etc/ssl/certs/}" # Does nothing yet (FC has only a CA bundle per default, ==> openssl version -d) -GOOD_CA_BUNDLE="" # A bundle of CA certificates that can be used to validate the server's certificate -CERTIFICATE_LIST_ORDERING_PROBLEM=false # Set to true if server sends a certificate list that contains a certificate - # that does not certify the one immediately preceding it. (See RFC 8446, Section 4.4.2) -STAPLED_OCSP_RESPONSE="" -HAS_DNS_SANS=false # Whether the certificate includes a subjectAltName extension with a DNS name or an application-specific identifier type. -MEASURE_TIME_FILE=${MEASURE_TIME_FILE:-""} -if [[ -n "$MEASURE_TIME_FILE" ]] && [[ -z "$MEASURE_TIME" ]]; then - MEASURE_TIME=true -else - MEASURE_TIME=${MEASURE_TIME:-false} -fi -DISPLAY_CIPHERNAMES="openssl" # display OpenSSL ciphername (but both OpenSSL and RFC ciphernames in wide mode) -declare -r UA_STD="TLS tester from $SWURL" -declare -r UA_SNEAKY="Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0" - -########### Initialization part, further global vars just being declared here -# -LC_COLLATE=en_US.UTF-8 # ensures certain regex patterns work as expected and aren't localized, see #1860 -PRINTF="" # which external printf to use. Empty presets the internal one, see #1130 -IKNOW_FNAME=false -FIRST_FINDING=true # is this the first finding we are outputting to file? -JSONHEADER=true # include JSON headers and footers in HTML file, if one is being created -CSVHEADER=true # same for CSV -HTMLHEADER=true # same for HTML -SECTION_FOOTER_NEEDED=false # kludge for tracking whether we need to close the JSON section object -GIVE_HINTS=false # give an additional info to findings -SERVER_SIZE_LIMIT_BUG=false # Some servers have either a ClientHello total size limit or a 128 cipher limit (e.g. old ASAs) -MULTIPLE_CHECKS=false # need to know whether an MX record or a hostname resolves to multiple IPs to check -CHILD_MASS_TESTING=${CHILD_MASS_TESTING:-false} -TIMEOUT_CMD="" -HAD_SLEPT=0 -NR_SOCKET_FAIL=0 # Counter for socket failures -NR_OSSL_FAIL=0 # .. for OpenSSL connects -NR_HEADER_FAIL=0 # .. for HTTP_GET -PROTOS_OFFERED="" # This keeps which protocol is being offered. See has_server_protocol(). -TLS12_CIPHER_OFFERED="" # This contains the hexcode of a cipher known to be supported by the server with TLS 1.2 -CURVES_OFFERED="" # This keeps which curves have been detected. Just for error handling -KNOWN_OSSL_PROB=false # We need OpenSSL a few times. This variable is an indicator if we can't connect. Eases handling -DETECTED_TLS_VERSION="" # .. as hex string, e.g. 0300 or 0303 -TLS13_ONLY=false # Does the server support TLS 1.3 ONLY? -OSSL_SHORTCUT=${OSSL_SHORTCUT:-false} # Hack: if during the scan turns out the OpenSSL binary supports TLS 1.3 would be a better choice, this enables it. -TLS_EXTENSIONS="" -declare -r NPN_PROTOs="spdy/4a2,spdy/3,spdy/3.1,spdy/2,spdy/1,http/1.1" -# alpn_protos needs to be space-separated, not comma-seperated, including odd ones observed @ facebook and others, old ones like h2-17 omitted as they could not be found -declare -r ALPN_PROTOs="h2 spdy/3.1 http/1.1 grpc-exp h2-fb spdy/1 spdy/2 spdy/3 stun.turn stun.nat-discovery webrtc c-webrtc ftp" -declare -a SESS_RESUMPTION -TEMPDIR="" -TMPFILE="" -ERRFILE="" -CLIENT_AUTH=false -TLS_TICKETS=false -NO_SSL_SESSIONID=false -CERT_COMPRESSION=${CERT_COMPRESSION:-false} # secret flag to set in addition to --devel for certificate compression -HOSTCERT="" # File with host certificate, without intermediate certificate -HEADERFILE="" -HEADERVALUE="" -HTTP_STATUS_CODE="" -DH_GROUP_OFFERED="" -DH_GROUP_LEN_P=0 -KEY_SHARE_EXTN_NR="33" # The extension number for key_share was changed from 40 to 51 in TLSv1.3 draft 23. - # In order to support draft 23 and later in addition to earlier drafts, need to - # know which extension number to use. Note that it appears that a single - # ClientHello cannot advertise both draft 23 and later and earlier drafts. - # Preset may help to deal with STARTTLS + TLS 1.3 draft 23 and later but not earlier. -BAD_SERVER_HELLO_CIPHER=false # reserved for cases where a ServerHello doesn't contain a cipher offered in the ClientHello -GOST_STATUS_PROBLEM=false -PATTERN2SHOW="" -SOCK_REPLY_FILE="" -NW_STR="" -LEN_STR="" -SNI="" -POODLE="" # keep vulnerability status for TLS_FALLBACK_SCSV -OSSL_NAME="" # openssl name, in case of LibreSSL it's LibreSSL -OSSL_VER="" # openssl version, will be auto-determined -OSSL_VER_MAJOR=0 -OSSL_VER_MINOR=0 -OSSL_VER_APPENDIX="none" -CLIENT_PROB_NO=1 -HAS_DH_BITS=${HAS_DH_BITS:-false} # initialize openssl variables -HAS_CURVES=false -OSSL_SUPPORTED_CURVES="" -HAS_SSL2=false -HAS_SSL3=false -HAS_TLS13=false -HAS_X448=false -HAS_X25519=false -HAS_PKUTIL=false -HAS_PKEY=false -HAS_NO_SSL2=false -HAS_NOSERVERNAME=false -HAS_CIPHERSUITES=false -HAS_COMP=false -HAS_NO_COMP=false -HAS_ALPN=false -HAS_NPN=false -HAS_FALLBACK_SCSV=false -HAS_PROXY=false -HAS_XMPP=false -HAS_POSTGRES=false -HAS_MYSQL=false -HAS_LMTP=false -HAS_NNTP=false -HAS_IRC=false -HAS_CHACHA20=false -HAS_AES128_GCM=false -HAS_AES256_GCM=false -HAS_ZLIB=false -HAS_DIG=false -HAS_DIG_R=true -DIG_R="-r" -HAS_HOST=false -HAS_DRILL=false -HAS_NSLOOKUP=false -HAS_IDN=false -HAS_IDN2=false -HAS_AVAHIRESOLVE=false -HAS_DIG_NOIDNOUT=false - -OSSL_CIPHERS_S="" -PORT=443 # unless otherwise auto-determined, see below -NODE="" -NODEIP="" -rDNS="" -CORRECT_SPACES="" # Used for IPv6 and proper output formatting -IPADDRs="" -IP46ADDRs="" -LOCAL_A=false # Does the $NODEIP come from /etc/hosts? -LOCAL_AAAA=false # Does the IPv6 IP come from /etc/hosts? -XMPP_HOST="" -PROXYIP="" # $PROXYIP:$PROXPORT is your proxy if --proxy is defined ... -PROXYPORT="" # ... and openssl has proxy support -PROXY="" # Once check_proxy() executed it contains $PROXYIP:$PROXPORT -VULN_COUNT=0 -SERVICE="" # Is the server running an HTTP server, SMTP, POP or IMAP? -URI="" -CERT_FINGERPRINT_SHA2="" -RSA_CERT_FINGERPRINT_SHA2="" -STARTTLS_PROTOCOL="" -OPTIMAL_PROTO="" # Need this for IIS6 (sigh) + OpenSSL 1.0.2, otherwise some handshakes will fail see - # https://github.com/PeterMosmans/openssl/issues/19#issuecomment-100897892 -STARTTLS_OPTIMAL_PROTO="" # Same for STARTTLS, see https://github.com/drwetter/testssl.sh/issues/188 -OPTIMAL_SOCKETS_PROTO="" # Same for tls_sockets(). -- not yet used -ALL_FAILED_SOCKETS=true # Set to true if all attempts to connect to server using tls_sockets/sslv2_sockets failed -TLS_TIME="" # To keep the value of TLS server timestamp -TLS_NOW="" # Similar -TLS_DIFFTIME_SET=false # Tells TLS functions to measure the TLS difftime or not -NOW_TIME="" -HTTP_TIME="" -GET_REQ11="" -START_TIME=0 # time in epoch when the action started -END_TIME=0 # .. ended -SCAN_TIME=0 # diff of both: total scan time -LAST_TIME=0 # only used for performance measurements (MEASURE_TIME=true) -SERVER_COUNTER=0 # Counter for multiple servers - -TLS_LOW_BYTE="" # For "secret" development stuff, see -q below -HEX_CIPHER="" # " - - -########### Global variables for parallel mass testing -# -declare -r PARALLEL_SLEEP=1 # Time to sleep after starting each test -MAX_WAIT_TEST=${MAX_WAIT_TEST:-1200} # Maximum time (in seconds) to wait for a test to complete -MAX_PARALLEL=${MAX_PARALLEL:-20} # Maximum number of tests to run in parallel - # This value may be made larger on systems with faster processors -declare -a -i PARALLEL_TESTING_PID=() # process id for each child test (or 0 to indicate test has already completed) -declare -a PARALLEL_TESTING_CMDLINE=() # command line for each child test -declare -i NR_PARALLEL_TESTS=0 # number of parallel tests run -declare -i NEXT_PARALLEL_TEST_TO_FINISH=0 # number of parallel tests that have completed and have been processed -declare FIRST_JSON_OUTPUT=true # true if no output has been added to $JSONFILE yet. - - -########### Cipher suite information -# -declare -i TLS_NR_CIPHERS=0 -declare TLS_CIPHER_HEXCODE=() -declare TLS_CIPHER_OSSL_NAME=() -declare TLS_CIPHER_RFC_NAME=() -declare TLS_CIPHER_SSLVERS=() -declare TLS_CIPHER_KX=() -declare TLS_CIPHER_AUTH=() -declare TLS_CIPHER_ENC=() -declare TLS_CIPHER_EXPORT=() -declare TLS_CIPHER_OSSL_SUPPORTED=() -declare TLS13_OSSL_CIPHERS="TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_CCM_SHA256:TLS_AES_128_CCM_8_SHA256" - -########### Severity functions and globals -# -INFO=0 -OK=0 -LOW=1 -MEDIUM=2 -HIGH=3 -CRITICAL=4 -SEVERITY_LEVEL=0 - -set_severity_level() { - local severity=$1 - - if [[ "$severity" == LOW ]]; then - SEVERITY_LEVEL=$LOW - elif [[ "$severity" == MEDIUM ]]; then - SEVERITY_LEVEL=$MEDIUM - elif [[ "$severity" == HIGH ]]; then - SEVERITY_LEVEL=$HIGH - elif [[ "$severity" == CRITICAL ]]; then - SEVERITY_LEVEL=$CRITICAL - else - # WARN and FATAL will always be logged as the represent scanning problems - echo "Supported severity levels are LOW, MEDIUM, HIGH, CRITICAL!" - help 1 - fi -} - -show_finding() { - local severity=$1 - - ( [[ "$severity" == DEBUG ]] ) || - ( [[ "$severity" == INFO ]] && [[ $SEVERITY_LEVEL -le $INFO ]] ) || - ( [[ "$severity" == OK ]] && [[ $SEVERITY_LEVEL -le $OK ]] ) || - ( [[ "$severity" == LOW ]] && [[ $SEVERITY_LEVEL -le $LOW ]] ) || - ( [[ "$severity" == MEDIUM ]] && [[ $SEVERITY_LEVEL -le $MEDIUM ]] ) || - ( [[ "$severity" == HIGH ]] && [[ $SEVERITY_LEVEL -le $HIGH ]] ) || - ( [[ "$severity" == CRITICAL ]] && [[ $SEVERITY_LEVEL -le $CRITICAL ]] ) || - ( [[ "$severity" == WARN ]] ) || - ( [[ "$severity" == FATAL ]] ) -} - -########### Output functions - -# For HTML output, replace any HTML reserved characters with the entity name -html_reserved(){ - local output - "$do_html" || return 0 - #sed -e 's/\&/\&/g' -e 's/</\</g' -e 's/>/\>/g' -e 's/"/\"/g' -e "s/'/\'/g" <<< "$1" - output="${1//&/&}" - output="${output//</<}" - output="${output//>/>}" - output="${output//\"/"}" - output="${output//\'/'}" - printf -- "%s" "$output" - return 0 -} - -html_out() { - "$do_html" || return 0 - [[ -n "$HTMLFILE" ]] && [[ ! -d "$HTMLFILE" ]] && printf -- "%b" "$1" >> "$HTMLFILE" -} - -# This is intentionally the same. -safe_echo() { printf -- "%b" "$1"; } -tm_out() { printf -- "%b" "$1"; } -tmln_out() { printf -- "%b" "$1\n"; } - -out() { printf -- "%b" "$1"; html_out "$(html_reserved "$1")"; } -outln() { printf -- "%b" "$1\n"; html_out "$(html_reserved "$1")\n"; } - - -#TODO: Still no shell injection safe but if just run it from the cmd line: that's fine - -# Color print functions, see also https://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html -tm_liteblue() { [[ "$COLOR" -ge 2 ]] && ( "$COLORBLIND" && tm_out "\033[0;32m$1" || tm_out "\033[0;34m$1" ) || tm_out "$1"; tm_off; } # not yet used -pr_liteblue() { tm_liteblue "$1"; [[ "$COLOR" -ge 2 ]] && ( "$COLORBLIND" && html_out "<span style=\"color:#00cd00;\">$(html_reserved "$1")</span>" || html_out "<span style=\"color:#0000ee;\">$(html_reserved "$1")</span>" ) || html_out "$(html_reserved "$1")"; } -tmln_liteblue() { tm_liteblue "$1"; tmln_out; } -prln_liteblue() { pr_liteblue "$1"; outln; } - -tm_blue() { [[ "$COLOR" -ge 2 ]] && ( "$COLORBLIND" && tm_out "\033[1;32m$1" || tm_out "\033[1;34m$1" ) || tm_out "$1"; tm_off; } # used for head lines of single tests -pr_blue() { tm_blue "$1"; [[ "$COLOR" -ge 2 ]] && ( "$COLORBLIND" && html_out "<span style=\"color:lime;font-weight:bold;\">$(html_reserved "$1")</span>" || html_out "<span style=\"color:#5c5cff;font-weight:bold;\">$(html_reserved "$1")</span>" ) || html_out "$(html_reserved "$1")"; } -tmln_blue() { tm_blue "$1"; tmln_out; } -prln_blue() { pr_blue "$1"; outln; } - -# we should be able to use aliases here -tm_warning() { [[ "$COLOR" -ge 2 ]] && tm_out "\033[0;35m$1" || tm_underline "$1"; tm_off; } # some local problem: one test cannot be done -tmln_warning() { tm_warning "$1"; tmln_out; } # litemagenta -pr_warning() { tm_warning "$1"; [[ "$COLOR" -ge 2 ]] && html_out "<span style=\"color:#cd00cd;\">$(html_reserved "$1")</span>" || ( [[ "$COLOR" -eq 1 ]] && html_out "<u>$(html_reserved "$1")</u>" || html_out "$(html_reserved "$1")" ); } -prln_warning() { pr_warning "$1"; outln; } - -tm_magenta() { [[ "$COLOR" -ge 2 ]] && tm_out "\033[1;35m$1" || tm_underline "$1"; tm_off; } # fatal error: quitting because of this! -tmln_magenta() { tm_magenta "$1"; tmln_out; } -# different as warning above? -pr_magenta() { tm_magenta "$1"; [[ "$COLOR" -ge 2 ]] && html_out "<span style=\"color:magenta;font-weight:bold;\">$(html_reserved "$1")</span>" || ( [[ "$COLOR" -eq 1 ]] && html_out "<u>$(html_reserved "$1")</u>" || html_out "$(html_reserved "$1")" ); } -prln_magenta() { pr_magenta "$1"; outln; } - -tm_litecyan() { [[ "$COLOR" -ge 2 ]] && tm_out "\033[0;36m$1" || tm_out "$1"; tm_off; } # not yet used -tmln_litecyan() { tm_litecyan "$1"; tmln_out; } -pr_litecyan() { tm_litecyan "$1"; [[ "$COLOR" -ge 2 ]] && html_out "<span style=\"color:#00cdcd;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")"; } -prln_litecyan() { pr_litecyan "$1"; outln; } - -tm_cyan() { [[ "$COLOR" -ge 2 ]] && tm_out "\033[1;36m$1" || tm_out "$1"; tm_off; } # additional hint -tmln_cyan() { tm_cyan "$1"; tmln_out; } -pr_cyan() { tm_cyan "$1"; [[ "$COLOR" -ge 2 ]] && html_out "<span style=\"color:cyan;font-weight:bold;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")"; } -prln_cyan() { pr_cyan "$1"; outln; } - -tm_litegrey() { [[ "$COLOR" -ne 0 ]] && tm_out "\033[0;37m$1" || tm_out "$1"; tm_off; } # ... https://github.com/drwetter/testssl.sh/pull/600#issuecomment-276129876 -tmln_litegrey() { tm_litegrey "$1"; tmln_out; } # not really usable on a black background, see .. -prln_litegrey() { pr_litegrey "$1"; outln; } -pr_litegrey() { tm_litegrey "$1"; [[ "$COLOR" -ne 0 ]] && html_out "<span style=\"color:darkgray;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")"; } - -tm_grey() { [[ "$COLOR" -ne 0 ]] && tm_out "\033[1;30m$1" || tm_out "$1"; tm_off; } -pr_grey() { tm_grey "$1"; [[ "$COLOR" -ne 0 ]] && html_out "<span style=\"color:#7f7f7f;font-weight:bold;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")"; } -tmln_grey() { tm_grey "$1"; tmln_out; } -prln_grey() { pr_grey "$1"; outln; } - -tm_svrty_good() { [[ "$COLOR" -ge 2 ]] && ( "$COLORBLIND" && tm_out "\033[0;34m$1" || tm_out "\033[0;32m$1" ) || tm_out "$1"; tm_off; } # litegreen (liteblue), This is good -tmln_svrty_good() { tm_svrty_good "$1"; tmln_out; } -pr_svrty_good() { tm_svrty_good "$1"; [[ "$COLOR" -ge 2 ]] && ( "$COLORBLIND" && html_out "<span style=\"color:#0000ee;\">$(html_reserved "$1")</span>" || html_out "<span style=\"color:#00cd00;\">$(html_reserved "$1")</span>" ) || html_out "$(html_reserved "$1")"; } -prln_svrty_good() { pr_svrty_good "$1"; outln; } - -tm_svrty_best() { [[ "$COLOR" -ge 2 ]] && ( "$COLORBLIND" && tm_out "\033[1;34m$1" || tm_out "\033[1;32m$1" ) || tm_out "$1"; tm_off; } # green (blue), This is the best -tmln_svrty_best() { tm_svrty_best "$1"; tmln_out; } -pr_svrty_best() { tm_svrty_best "$1"; [[ "$COLOR" -ge 2 ]] && ( "$COLORBLIND" && html_out "<span style=\"color:#5c5cff;font-weight:bold;\">$(html_reserved "$1")</span>" || html_out "<span style=\"color:lime;font-weight:bold;\">$(html_reserved "$1")</span>" ) || html_out "$(html_reserved "$1")"; } -prln_svrty_best() { pr_svrty_best "$1"; outln; } - -tm_svrty_low() { [[ "$COLOR" -ge 2 ]] && tm_out "\033[1;33m$1" || tm_out "$1"; tm_off; } # yellow brown | academic or minor problem -tmln_svrty_low() { tm_svrty_low "$1"; tmln_out; } -pr_svrty_low() { tm_svrty_low "$1"; [[ "$COLOR" -ge 2 ]] && html_out "<span style=\"color:#cdcd00;font-weight:bold;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")"; } -prln_svrty_low() { pr_svrty_low "$1"; outln; } - -tm_svrty_medium() { [[ "$COLOR" -ge 2 ]] && tm_out "\033[0;33m$1" || tm_out "$1"; tm_off; } # brown | it is not a bad problem but you shouldn't do this -pr_svrty_medium() { tm_svrty_medium "$1"; [[ "$COLOR" -ge 2 ]] && html_out "<span style=\"color:#cd8000;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")"; } -tmln_svrty_medium(){ tm_svrty_medium "$1"; tmln_out; } -prln_svrty_medium(){ pr_svrty_medium "$1"; outln; } - -tm_svrty_high() { [[ "$COLOR" -ge 2 ]] && tm_out "\033[0;31m$1" || tm_bold "$1"; tm_off; } # litered -pr_svrty_high() { tm_svrty_high "$1"; [[ "$COLOR" -ge 2 ]] && html_out "<span style=\"color:#cd0000;\">$(html_reserved "$1")</span>" || ( [[ "$COLOR" -eq 1 ]] && html_out "<span style=\"font-weight:bold;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")" ); } -tmln_svrty_high() { tm_svrty_high "$1"; tmln_out; } -prln_svrty_high() { pr_svrty_high "$1"; outln; } - -tm_svrty_critical() { [[ "$COLOR" -ge 2 ]] && tm_out "\033[1;31m$1" || tm_bold "$1"; tm_off; } # red -pr_svrty_critical() { tm_svrty_critical "$1"; [[ "$COLOR" -ge 2 ]] && html_out "<span style=\"color:red;font-weight:bold;\">$(html_reserved "$1")</span>" || ( [[ "$COLOR" -eq 1 ]] && html_out "<span style=\"font-weight:bold;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")" ); } -tmln_svrty_critical() { tm_svrty_critical "$1"; tmln_out; } -prln_svrty_critical() { pr_svrty_critical "$1"; outln; } - -tm_deemphasize() { tm_out "$1"; } # hook for a weakened screen output, see #600 -pr_deemphasize() { tm_deemphasize "$1"; html_out "<span style=\"color:darkgray;\">$(html_reserved "$1")</span>"; } -tmln_deemphasize() { tm_deemphasize "$1"; tmln_out; } -prln_deemphasize() { pr_deemphasize "$1"; outln; } - -# color=1 functions -tm_off() { [[ "$COLOR" -ne 0 ]] && tm_out "\033[m"; } - -tm_bold() { [[ "$COLOR" -ne 0 ]] && tm_out "\033[1m$1" || tm_out "$1"; tm_off; } -tmln_bold() { tm_bold "$1"; tmln_out; } -pr_bold() { tm_bold "$1"; [[ "$COLOR" -ne 0 ]] && html_out "<span style=\"font-weight:bold;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")"; } -prln_bold() { pr_bold "$1" ; outln; } - -NO_ITALICS=false -if [[ $TERM == screen ]]; then - NO_ITALICS=true -elif [[ $SYSTEM == OpenBSD ]]; then - NO_ITALICS=true -elif [[ $SYSTEM == FreeBSD ]]; then - if [[ ${SYSTEMREV%\.*} -le 9 ]]; then - NO_ITALICS=true - fi -fi -tm_italic() { ( [[ "$COLOR" -ne 0 ]] && ! "$NO_ITALICS" ) && tm_out "\033[3m$1" || tm_out "$1"; tm_off; } -tmln_italic() { tm_italic "$1" ; tmln_out; } -pr_italic() { tm_italic "$1"; [[ "$COLOR" -ne 0 ]] && html_out "<i>$(html_reserved "$1")</i>" || html_out "$(html_reserved "$1")"; } -prln_italic() { pr_italic "$1"; outln; } - -tm_strikethru() { [[ "$COLOR" -ne 0 ]] && tm_out "\033[9m$1" || tm_out "$1"; tm_off; } # ugly! -tmln_strikethru() { tm_strikethru "$1"; tmln_out; } -pr_strikethru() { tm_strikethru "$1"; [[ "$COLOR" -ne 0 ]] && html_out "<strike>$(html_reserved "$1")</strike>" || html_out "$(html_reserved "$1")"; } -prln_strikethru() { pr_strikethru "$1" ; outln; } - -tm_underline() { [[ "$COLOR" -ne 0 ]] && tm_out "\033[4m$1" || tm_out "$1"; tm_off; } -tmln_underline() { tm_underline "$1"; tmln_out; } -pr_underline() { tm_underline "$1"; [[ "$COLOR" -ne 0 ]] && html_out "<u>$(html_reserved "$1")</u>" || html_out "$(html_reserved "$1")"; } -prln_underline() { pr_underline "$1"; outln; } - -tm_reverse() { [[ "$COLOR" -ne 0 ]] && tm_out "\033[7m$1" || tm_out "$1"; tm_off; } -tm_reverse_bold() { [[ "$COLOR" -ne 0 ]] && tm_out "\033[7m\033[1m$1" || tm_out "$1"; tm_off; } -pr_reverse() { tm_reverse "$1"; [[ "$COLOR" -ne 0 ]] && html_out "<span style=\"color:white;background-color:black;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")"; } -pr_reverse_bold() { tm_reverse_bold "$1"; [[ "$COLOR" -ne 0 ]] && html_out "<span style=\"color:white;background-color:black;font-weight:bold;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")"; } - -#pr_headline() { pr_blue "$1"; } -# https://misc.flogisoft.com/bash/tip_colors_and_formatting - -#pr_headline() { [[ "$COLOR" -ge 2 ]] && out "\033[1;30m\033[47m$1" || out "$1"; tm_off; } -tm_headline() { [[ "$COLOR" -ne 0 ]] && tm_out "\033[1m\033[4m$1" || tm_out "$1"; tm_off; } -tmln_headline() { tm_headline "$1"; tmln_out; } -pr_headline() { tm_headline "$1"; [[ "$COLOR" -ne 0 ]] && html_out "<span style=\"text-decoration:underline;font-weight:bold;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")"; } -pr_headlineln() { pr_headline "$1" ; outln; } - -tm_squoted() { tm_out "'$1'"; } -pr_squoted() { out "'$1'"; } -tm_dquoted() { tm_out "\"$1\""; } -pr_dquoted() { out "\"$1\""; } - -# either files couldn't be found or openssl isn't good enough (which shouldn't happen anymore) -tm_local_problem() { tm_warning "Local problem: $1"; } -tmln_local_problem() { tmln_warning "Local problem: $1"; } -pr_local_problem() { pr_warning "Local problem: $1"; } -prln_local_problem() { prln_warning "Local problem: $1"; } - -# general failure -tm_fixme() { tm_warning "Fixme: $1"; } -tmln_fixme() { tmln_warning "Fixme: $1"; } -pr_fixme() { pr_warning "Fixme: $1"; } -prln_fixme() { prln_warning "Fixme: $1"; } - -pr_url() { tm_out "$1"; html_out "<a href=\"$1\" style=\"color:black;text-decoration:none;\">$1</a>"; } -pr_boldurl() { tm_bold "$1"; html_out "<a href=\"$1\" style=\"font-weight:bold;color:black;text-decoration:none;\">$1</a>"; } - -### color switcher (see e.g. https://linuxtidbits.wordpress.com/2008/08/11/output-color-on-bash-scripts/ -### https://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html -### no output support for HTML! -set_color_functions() { - local ncurses_tput=true - - if [[ $SYSTEM == OpenBSD ]] && [[ "$TERM" =~ xterm-256 ]]; then - export TERM=xterm - # OpenBSD can't handle 256 colors (yet) in xterm which might lead to ugly errors - # like "tput: not enough arguments (3) for capability `AF'". Not our fault but - # before we get blamed we fix it here. - fi - - # Empty all vars if we have COLOR=0 equals no escape code -- these are globals: - red="" - green="" - brown="" - blue="" - magenta="" - cyan="" - grey="" - yellow="" - off="" - bold="" - underline="" - italic="" - - type -p tput &>/dev/null || return 0 # Hey wait, do we actually have tput / ncurses ? - tput cols &>/dev/null || return 0 # tput under BSDs and GNUs doesn't work either (TERM undefined?) - tput sgr0 &>/dev/null || ncurses_tput=false - if [[ "$COLOR" -ge 2 ]]; then - if $ncurses_tput; then - red=$(tput setaf 1) - green=$(tput setaf 2) - brown=$(tput setaf 3) - blue=$(tput setaf 4) - magenta=$(tput setaf 5) - cyan=$(tput setaf 6) - grey=$(tput setaf 7) - yellow=$(tput setaf 3; tput bold) - else # this is a try for old BSD, see terminfo(5) - red=$(tput AF 1) - green=$(tput AF 2) - brown=$(tput AF 3) - blue=$(tput AF 4) - magenta=$(tput AF 5) - cyan=$(tput AF 6) - grey=$(tput AF 7) - yellow=$(tput AF 3; tput md) - fi - fi - if [[ "$COLOR" -ge 1 ]]; then - if $ncurses_tput; then - bold=$(tput bold) - underline=$(tput sgr 0 1 2>/dev/null) - italic=$(tput sitm) # This doesn't work on FreeBSDi (9,10) and OpenBSD ... - italic_end=$(tput ritm) # ... and this, too - off=$(tput sgr0) - else # this is a try for old BSD, see terminfo(5) - bold=$(tput md) - underline=$(tput us) - italic=$(tput ZH 2>/dev/null) # This doesn't work on FreeBSDi (9,10) and OpenBSD - italic_end=$(tput ZR 2>/dev/null) # ... probably entry missing in /etc/termcap - reverse=$(tput mr) - off=$(tput me) - fi - fi - # FreeBSD 10 understands ESC codes like 'echo -e "\e[3mfoobar\e[23m"', but also no tput for italics -} - -strip_quote() { - # remove color codes (see https://www.commandlinefu.com/commands/view/3584/remove-color-codes-special-characters-with-sed) - # \', leading and all trailing spaces - sed -e "s,$(echo -e "\033")\[[0-9;]*[a-zA-Z],,g" \ - -e "s/\"/\\'/g" \ - -e 's/^ *//g' \ - -e 's/ *$//g' <<< "$1" -} - -# " deconfuse vim\'s syntax highlighting ;-) - -#################### JSON FILE FORMATTING #################### - -fileout_json_footer() { - if "$do_json"; then - if [[ "$SCAN_TIME" -eq 0 ]]; then - fileout_json_finding "scanTime" "WARN" "Scan interrupted" "" "" "" - elif [[ $SEVERITY_LEVEL -lt $LOW ]] ; then - # no scan time in --severity=low and above, also needed for Travis, hackish... - fileout_json_finding "scanTime" "INFO" $SCAN_TIME "" "" "" - fi - printf "]\n" >> "$JSONFILE" - fi - if "$do_pretty_json"; then - if [[ "$SCAN_TIME" -eq 0 ]]; then - echo -e " ],\n \"scanTime\" : \"Scan interrupted\"\n}" >> "$JSONFILE" - else - echo -e " ],\n \"scanTime\" : ${SCAN_TIME}\n}" >> "$JSONFILE" - fi - fi -} - -fileout_json_section() { - case $1 in - 0) echo -e " \"pretest\" : [" ;; - 1) echo -e " \"singleCipher\" : [" ;; - 2) echo -e ",\n \"protocols\" : [" ;; - 3) echo -e ",\n \"grease\" : [" ;; - 4) echo -e ",\n \"ciphers\" : [" ;; - 5) echo -e ",\n \"pfs\" : [" ;; - 6) echo -e ",\n \"serverPreferences\" : [" ;; - 7) echo -e ",\n \"serverDefaults\" : [" ;; - 8) echo -e ",\n \"headerResponse\" : [" ;; - 9) echo -e ",\n \"vulnerabilities\" : [" ;; - 10) echo -e ",\n \"cipherTests\" : [" ;; - 11) echo -e ",\n \"browserSimulations\": [" ;; - *) echo "invalid section" ;; - esac -} - -fileout_section_header() { - local str="" - "$2" && str="$(fileout_section_footer false)" - "$do_pretty_json" && FIRST_FINDING=true && (printf "%s%s\n" "$str" "$(fileout_json_section "$1")") >> "$JSONFILE" - SECTION_FOOTER_NEEDED=true -} - -# arg1: whether to end object too -fileout_section_footer() { - "$do_pretty_json" && printf "\n ]" >> "$JSONFILE" - "$do_pretty_json" && "$1" && echo -e "\n }" >> "$JSONFILE" - SECTION_FOOTER_NEEDED=false -} - -fileout_json_print_parameter() { - local parameter="$1" - local filler="$2" - local value="$3" - local not_last="$4" - local spaces="" - - "$do_json" && \ - spaces=" " || \ - spaces=" " - if [[ -n "$value" ]] || [[ "$parameter" == finding ]]; then - printf "%s%s%s%s" "$spaces" "\"$parameter\"" "$filler" ": \"$value\"" >> "$JSONFILE" - "$not_last" && printf ",\n" >> "$JSONFILE" - fi -} - -fileout_json_finding() { - local target - local finding="$3" - local cve="$4" - local cwe="$5" - local hint="$6" - - if "$do_json"; then - "$FIRST_FINDING" || echo -n "," >> "$JSONFILE" - echo -e " {" >> "$JSONFILE" - fileout_json_print_parameter "id" " " "$1" true - fileout_json_print_parameter "ip" " " "$NODE/$NODEIP" true - fileout_json_print_parameter "port" " " "$PORT" true - fileout_json_print_parameter "severity" " " "$2" true - fileout_json_print_parameter "cve" " " "$cve" true - fileout_json_print_parameter "cwe" " " "$cwe" true - "$GIVE_HINTS" && fileout_json_print_parameter "hint" " " "$hint" true - fileout_json_print_parameter "finding" " " "$finding" false - echo -e "\n }" >> "$JSONFILE" - fi - if "$do_pretty_json"; then - if [[ "$1" == service ]]; then - if [[ $SERVER_COUNTER -gt 1 ]]; then - echo " ," >> "$JSONFILE" - fi - target="$NODE" - $do_mx_all_ips && target="$URI" - echo -e " { - \"targetHost\" : \"$target\", - \"ip\" : \"$NODEIP\", - \"port\" : \"$PORT\", - \"rDNS\" : \"$rDNS\", - \"service\" : \"$finding\"," >> "$JSONFILE" - $do_mx_all_ips && echo -e " \"hostname\" : \"$NODE\"," >> "$JSONFILE" - else - ("$FIRST_FINDING" && echo -n " {" >> "$JSONFILE") || echo -n ",{" >> "$JSONFILE" - echo -e -n "\n" >> "$JSONFILE" - fileout_json_print_parameter "id" " " "$1" true - fileout_json_print_parameter "severity" " " "$2" true - fileout_json_print_parameter "cve" " " "$cve" true - fileout_json_print_parameter "cwe" " " "$cwe" true - "$GIVE_HINTS" && fileout_json_print_parameter "hint" " " "$hint" true - fileout_json_print_parameter "finding" " " "$finding" false - echo -e -n "\n }" >> "$JSONFILE" - fi - fi -} - -##################### FILE FORMATTING ######################### - -fileout_pretty_json_banner() { - local target - - if ! "$do_mass_testing"; then - [[ -z "$NODE" ]] && parse_hn_port "${URI}" - # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now --> wrong place - target="$NODE" - $do_mx_all_ips && target="$URI" - fi - - echo -e " \"Invocation\" : \"$PROG_NAME $CMDLINE\", - \"at\" : \"$HNAME:$OPENSSL_LOCATION\", - \"version\" : \"$VERSION ${GIT_REL_SHORT} from $REL_DATE\", - \"openssl\" : \"$OSSL_NAME $OSSL_VER from $OSSL_BUILD_DATE\", - \"startTime\" : \"$START_TIME\", - \"scanResult\" : [" -} - -fileout_banner() { - if "$JSONHEADER"; then - # "$do_json" && # here we maybe should add a banner, too - "$do_pretty_json" && FIRST_FINDING=true && (printf "%s\n" "$(fileout_pretty_json_banner)") >> "$JSONFILE" - fi -} - -fileout_separator() { - if "$JSONHEADER"; then - "$do_pretty_json" && echo " ," >> "$JSONFILE" - "$do_json" && echo -n "," >> "$JSONFILE" - fi -} - -fileout_footer() { - if "$JSONHEADER"; then - fileout_json_footer - fi - # CSV: no footer - return 0 -} - -fileout_insert_warning() { - # See #815. Make sure we don't mess up the JSON PRETTY format if we complain with a client side warning. - # This should only be called if an *extra* warning will be printed (previously: 'fileout <extra_warning_ID> "WARN" ' - # arg1: json identifier, arg2: normally "WARN", arg3: finding - # - # Also, we have to be careful with any form of mass testing so that a warning won't lead to an invalid JSON - # file. As any child will do any check as well (to be reconsidered later), we don't need also the parent to issue - # warnings upfront, see #1169. As a detection we'll use --file/-iL as in the children jobs it'll be removed: - [[ "$CMDLINE=" =~ --file ]] && return 0 - [[ "$CMDLINE=" =~ -iL ]] && return 0 - # Note we still have the message on screen + in HTML which is not as optimal as it could be - - if "$do_pretty_json"; then - echo -e " \"clientProblem${CLIENT_PROB_NO}\" : [" >>"$JSONFILE" - CLIENT_PROB_NO=$((CLIENT_PROB_NO + 1)) - FIRST_FINDING=true # make sure we don't have a comma here - fi - fileout "$1" "$2" "$3" - if "$do_pretty_json"; then - echo -e "\n ]," >>"$JSONFILE" - fi -} - -fileout_csv_finding() { - safe_echo "\"$1\"," >> "$CSVFILE" - safe_echo "\"$2\"," >> "$CSVFILE" - safe_echo "\"$3\"," >> "$CSVFILE" - safe_echo "\"$4\"," >> "$CSVFILE" - safe_echo "\"$5\"," >> "$CSVFILE" - safe_echo "\"$6\"," >> "$CSVFILE" - if "$GIVE_HINTS"; then - safe_echo "\"$7\"," >> "$CSVFILE" - safe_echo "\"$8\"\n" >> "$CSVFILE" - else - safe_echo "\"$7\"\n" >> "$CSVFILE" - fi -} - - -# ID, SEVERITY, FINDING, CVE, CWE, HINT -fileout() { - local severity="$2" - local cve="$4" - local cwe="$5" - local hint="$6" - - if ( "$do_pretty_json" && [[ "$1" == service ]] ) || show_finding "$severity"; then - local finding=$(strip_lf "$(newline_to_spaces "$(strip_quote "$3")")") # additional quotes will mess up screen output - [[ -e "$JSONFILE" ]] && [[ ! -d "$JSONFILE" ]] && fileout_json_finding "$1" "$severity" "$finding" "$cve" "$cwe" "$hint" - "$do_csv" && [[ -n "$CSVFILE" ]] && [[ ! -d "$CSVFILE" ]] && \ - fileout_csv_finding "$1" "$NODE/$NODEIP" "$PORT" "$severity" "$finding" "$cve" "$cwe" "$hint" - "$FIRST_FINDING" && FIRST_FINDING=false - fi -} - - -json_header() { - local fname_prefix - local filename_provided=false - - [[ -n "$JSONFILE" ]] && [[ ! -d "$JSONFILE" ]] && filename_provided=true - # Similar to HTML: Don't create headers and footers in the following scenarios: - # * no JSON/CSV output is being created. - # * mass testing is being performed and each test will have its own file. - # * this is an individual test within a mass test and all output is being placed in a single file. - ! "$do_json" && ! "$do_pretty_json" && JSONHEADER=false && return 0 - "$do_mass_testing" && ! "$filename_provided" && JSONHEADER=false && return 0 - "$CHILD_MASS_TESTING" && "$filename_provided" && JSONHEADER=false && return 0 - - if "$do_display_only"; then - fname_prefix="local-ciphers" - elif "$do_mass_testing"; then - : - elif "$do_mx_all_ips"; then - fname_prefix="${FNAME_PREFIX}mx-${URI}" - else - # ensure NODE, URL_PATH, PORT, IPADDR and IP46ADDR are set - ! "$filename_provided" && [[ -z "$NODE" ]] && parse_hn_port "${URI}" - fname_prefix="${FNAME_PREFIX}${NODE}_p${PORT}" - fi - if [[ -z "$JSONFILE" ]]; then - JSONFILE="$fname_prefix-$(date +"%Y%m%d-%H%M".json)" - elif [[ -d "$JSONFILE" ]]; then - JSONFILE="$JSONFILE/${fname_prefix}-$(date +"%Y%m%d-%H%M".json)" - fi - # Silently reset APPEND var if the file doesn't exist as otherwise it won't be created - if "$APPEND" && [[ ! -s "$JSONFILE" ]]; then - APPEND=false - fi - if "$APPEND"; then - JSONHEADER=false - else - [[ -s "$JSONFILE" ]] && fatal "non-empty \"$JSONFILE\" exists. Either use \"--append\" or (re)move it" $ERR_FCREATE - "$do_json" && echo "[" > "$JSONFILE" - "$do_pretty_json" && echo "{" > "$JSONFILE" - fi - return 0 -} - - -csv_header() { - local fname_prefix - local filename_provided=false - - [[ -n "$CSVFILE" ]] && [[ ! -d "$CSVFILE" ]] && filename_provided=true - # CSV similar to JSON - ! "$do_csv" && CSVHEADER=false && return 0 - "$do_mass_testing" && ! "$filename_provided" && CSVHEADER=false && return 0 - "$CHILD_MASS_TESTING" && "$filename_provided" && CSVHEADER=false && return 0 - - if "$do_display_only"; then - fname_prefix="local-ciphers" - elif "$do_mass_testing"; then - : - elif "$do_mx_all_ips"; then - fname_prefix="${FNAME_PREFIX}mx-${URI}" - else - # ensure NODE, URL_PATH, PORT, IPADDR and IP46ADDR are set - ! "$filename_provided" && [[ -z "$NODE" ]] && parse_hn_port "${URI}" - fname_prefix="${FNAME_PREFIX}${NODE}_p${PORT}" - fi - if [[ -z "$CSVFILE" ]]; then - CSVFILE="${fname_prefix}-$(date +"%Y%m%d-%H%M".csv)" - elif [[ -d "$CSVFILE" ]]; then - CSVFILE="$CSVFILE/${fname_prefix}-$(date +"%Y%m%d-%H%M".csv)" - fi - # Silently reset APPEND var if the file doesn't exist as otherwise it won't be created - if "$APPEND" && [[ ! -s "$CSVFILE" ]]; then - APPEND=false - fi - if "$APPEND"; then - CSVHEADER=false - else - [[ -s "$CSVFILE" ]] && fatal "non-empty \"$CSVFILE\" exists. Either use \"--append\" or (re)move it" $ERR_FCREATE - touch "$CSVFILE" - if "$GIVE_HINTS"; then - fileout_csv_finding "id" "fqdn/ip" "port" "severity" "finding" "cve" "cwe" "hint" - else - fileout_csv_finding "id" "fqdn/ip" "port" "severity" "finding" "cve" "cwe" - fi - fi - return 0 -} - - -################# JSON FILE FORMATTING END. HTML START #################### - -html_header() { - local fname_prefix - local filename_provided=false - - [[ -n "$HTMLFILE" ]] && [[ ! -d "$HTMLFILE" ]] && filename_provided=true - # Don't create HTML headers and footers in the following scenarios: - # * HTML output is not being created. - # * mass testing is being performed and each test will have its own HTML file. - # * this is an individual test within a mass test and all HTML output is being placed in a single file. - ! "$do_html" && HTMLHEADER=false && return 0 - "$do_mass_testing" && ! "$filename_provided" && HTMLHEADER=false && return 0 - "$CHILD_MASS_TESTING" && "$filename_provided" && HTMLHEADER=false && return 0 - - if "$do_display_only"; then - fname_prefix="local-ciphers" - elif "$do_mass_testing"; then - : - elif "$do_mx_all_ips"; then - fname_prefix="${FNAME_PREFIX}mx-${URI}" - else - # ensure NODE, URL_PATH, PORT, IPADDR and IP46ADDR are set - ! "$filename_provided" && [[ -z "$NODE" ]] && parse_hn_port "${URI}" - fname_prefix="${FNAME_PREFIX}${NODE}_p${PORT}" - fi - if [[ -z "$HTMLFILE" ]]; then - HTMLFILE="$fname_prefix-$(date +"%Y%m%d-%H%M".html)" - elif [[ -d "$HTMLFILE" ]]; then - HTMLFILE="$HTMLFILE/$fname_prefix-$(date +"%Y%m%d-%H%M".html)" - fi - # Silently reset APPEND var if the file doesn't exist as otherwise it won't be created - if "$APPEND" && [[ ! -s "$HTMLFILE" ]]; then - APPEND=false - fi - if "$APPEND"; then - HTMLHEADER=false - else - [[ -s "$HTMLFILE" ]] && fatal "non-empty \"$HTMLFILE\" exists. Either use \"--append\" or (re)move it" $ERR_FCREATE - html_out "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" - html_out "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" - html_out "<!-- This file was created with testssl.sh. https://testssl.sh -->\n" - html_out "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" - html_out "<head>\n" - html_out "<meta http-equiv=\"Content-Type\" content=\"application/xml+xhtml; charset=UTF-8\" />\n" - html_out "<title>testssl.sh</title>\n" - html_out "</head>\n" - html_out "<body>\n" - html_out "<pre>\n" - fi - return 0 -} - -html_banner() { - if "$CHILD_MASS_TESTING" && "$HTMLHEADER"; then - html_out "## Scan started as: \"$PROG_NAME $CMDLINE\"\n" - html_out "## at $HNAME:$OPENSSL_LOCATION\n" - html_out "## version testssl: $VERSION ${GIT_REL_SHORT} from $REL_DATE\n" - html_out "## version openssl: \"$OSSL_NAME $OSSL_VER\" from \"$OSSL_BUILD_DATE\")\n\n" - fi -} - -html_footer() { - if "$HTMLHEADER"; then - html_out "</pre>\n" - html_out "</body>\n" - html_out "</html>\n" - fi - return 0 -} - -################# HTML FILE FORMATTING END #################### - -prepare_logging() { - # arg1: for testing mx records name we put a name of logfile in here, otherwise we get strange file names - local fname_prefix="$1" - local filename_provided=false - - [[ -n "$LOGFILE" ]] && [[ ! -d "$LOGFILE" ]] && filename_provided=true - - # Similar to html_header(): - ! "$do_logging" && return 0 - "$do_mass_testing" && ! "$filename_provided" && return 0 - "$CHILD_MASS_TESTING" && "$filename_provided" && return 0 - - [[ -z "$fname_prefix" ]] && fname_prefix="${FNAME_PREFIX}${NODE}_p${PORT}" - - if [[ -z "$LOGFILE" ]]; then - LOGFILE="$fname_prefix-$(date +"%Y%m%d-%H%M".log)" - elif [[ -d "$LOGFILE" ]]; then - # actually we were instructed to place all files in a DIR instead of the current working dir - LOGFILE="$LOGFILE/$fname_prefix-$(date +"%Y%m%d-%H%M".log)" - else - : # just for clarity: a log file was specified, no need to do anything else - fi - - if ! "$APPEND"; then - [[ -s "$LOGFILE" ]] && fatal "non-empty \"$LOGFILE\" exists. Either use \"--append\" or (re)move it" $ERR_FCREATE - fi - tmln_out "## Scan started as: \"$PROG_NAME $CMDLINE\"" >>"$LOGFILE" - tmln_out "## at $HNAME:$OPENSSL_LOCATION" >>"$LOGFILE" - tmln_out "## version testssl: $VERSION ${GIT_REL_SHORT} from $REL_DATE" >>"$LOGFILE" - tmln_out "## version openssl: \"$OSSL_VER\" from \"$OSSL_BUILD_DATE\")\n" >>"$LOGFILE" - exec > >(tee -a -i "$LOGFILE") -} - -################### FILE FORMATTING END ######################### - -###### START helper function definitions ###### - -if [[ "${BASH_VERSINFO[0]}" == 3 ]]; then - # older bash can do this only (MacOS X), even SLES 11, see #697 - toupper() { tr 'a-z' 'A-Z' <<< "$1"; } - tolower() { tr 'A-Z' 'a-z' <<< "$1"; } -else - toupper() { echo -n "${1^^}"; } - tolower() { echo -n "${1,,}"; } -fi - -get_last_char() { - echo "${1:~0}" # "${string: -1}" would work too (both also in bash 3.2) -} - # Checking for last char. If already a separator supplied, we don't need an additional one -debugme() { - [[ "$DEBUG" -ge 2 ]] && "$@" >&2 - return 0 -} - -hex2dec() { - echo $((16#$1)) -} - -# convert 414243 into ABC -hex2ascii() { - for (( i=0; i<${#1}; i+=2 )); do - # 2>/dev/null added because 'warning: command substitution: ignored null byte in input' - # --> didn't help though - printf "\x${1:$i:2}" 2>/dev/null - done -} - -# convert decimal number < 256 to hex -dec02hex() { - printf "x%02x" "$1" -} - -# convert decimal number between 256 and < 256*256 to hex -dec04hex() { - local a=$(printf "%04x" "$1") - printf "x%02s, x%02s" "${a:0:2}" "${a:2:2}" -} - - -# trim spaces for BSD and old sed -count_lines() { - #echo "${$(wc -l <<< "$1")// /}" - # ^^ bad substitution under bash, zsh ok. For some reason this does the trick: - echo $(wc -l <<< "$1") -} - -count_words() { - #echo "${$(wc -w <<< "$1")// /}" - # ^^ bad substitution under bash, zsh ok. For some reason this does the trick: - echo $(wc -w <<< "$1") -} - -count_ciphers() { - echo $(wc -w <<< "${1//:/ }") -} - -#arg1: TLS 1.2 and below ciphers -#arg2: TLS 1.3 ciphers -#arg3: options (e.g., -V) -actually_supported_osslciphers() { - local tls13_ciphers="$TLS13_OSSL_CIPHERS" - - [[ "$2" != ALL ]] && tls13_ciphers="$2" - if "$HAS_CIPHERSUITES"; then - $OPENSSL ciphers $3 $OSSL_CIPHERS_S -ciphersuites "$tls13_ciphers" "$1" 2>/dev/null || echo "" - elif [[ -n "$tls13_ciphers" ]]; then - $OPENSSL ciphers $3 $OSSL_CIPHERS_S "$tls13_ciphers:$1" 2>/dev/null || echo "" - else - $OPENSSL ciphers $OSSL_CIPHERS_S $3 "$1" 2>/dev/null || echo "" - fi -} - -# Given a protocol (arg1) and a list of ciphers (arg2) that is formatted as -# ", xx,xx, xx,xx, xx,xx, xx,xx" remove any TLSv1.3 ciphers if the protocol -# is less than 04 and remove any TLSv1.2-only ciphers if the protocol is less -# than 03. -strip_inconsistent_ciphers() { - local -i proto=0x$1 - local cipherlist="$2" - - [[ $proto -lt 4 ]] && cipherlist="${cipherlist//, 13,0[0-9a-fA-F]/}" - if [[ $proto -lt 3 ]]; then - cipherlist="${cipherlist//, 00,3[b-fB-F]/}" - cipherlist="${cipherlist//, 00,40/}" - cipherlist="${cipherlist//, 00,6[7-9a-dA-D]/}" - cipherlist="${cipherlist//, 00,9[c-fC-F]/}" - cipherlist="${cipherlist//, 00,[abAB][0-9a-fA-F]/}" - cipherlist="${cipherlist//, 00,[cC][0-5]/}" - cipherlist="${cipherlist//, 16,[bB][7-9aA]/}" - cipherlist="${cipherlist//, [cC]0,2[3-9a-fA-F]/}" - cipherlist="${cipherlist//, [cC]0,3[01278a-fA-F]/}" - cipherlist="${cipherlist//, [cC]0,[4-9aA][0-9a-fA-F]/}" - cipherlist="${cipherlist//, [cC][cC],1[345]/}" - cipherlist="${cipherlist//, [cC][cC],[aA][89a-eA-E]/}" - fi - echo "$cipherlist" - return 0 -} - -newline_to_spaces() { - tr '\n' ' ' <<< "$1" | sed 's/ $//' -} - -colon_to_spaces() { - echo "${1//:/ }" -} - -strip_lf() { - tr -d '\n' <<< "$1" | tr -d '\r' -} - -strip_spaces() { - echo "${1// /}" -} - -# https://web.archive.org/web/20121022051228/http://codesnippets.joyent.com/posts/show/1816 -strip_leading_space() { - printf "%s" "${1#"${1%%[![:space:]]*}"}" -} -strip_trailing_space() { - printf "%s" "${1%"${1##*[![:space:]]}"}" -} - - -# retrieve cipher from ServerHello (via openssl) -get_cipher() { - local cipher="" - local server_hello="$(cat -v "$1")" - # This and two other following instances are not best practice and normally a useless use of "cat", see - # https://web.archive.org/web/20160711205930/http://porkmail.org/era/unix/award.html#uucaletter - # However there seem to be cases where the preferred $(< "$1") logic has a problem. - # Esepcially with bash 3.2 (Mac OS X) and when on the server side binary chars - # are returned, see https://stackoverflow.com/questions/7427262/how-to-read-a-file-into-a-variable-in-shell#22607352 - # and https://github.com/drwetter/testssl.sh/issues/1292 - # Performance measurements showed no to barely measureable penalty (1s displayed in 9 tries). - - if [[ "$server_hello" =~ Cipher\ *:\ ([A-Z0-9]+-[A-Za-z0-9\-]+|TLS_[A-Za-z0-9_]+|SSL_[A-Za-z0-9_]+) ]]; then - cipher="${BASH_REMATCH##* }" - elif [[ "$server_hello" =~ (New|Reused)", "(SSLv[23]|TLSv1(\.[0-3])?(\/SSLv3)?)", Cipher is "([A-Z0-9]+-[A-Za-z0-9\-]+|TLS_[A-Za-z0-9_]+) ]]; then - cipher="${BASH_REMATCH##* }" - fi - tm_out "$cipher" -} - -# retrieve protocol from ServerHello (via openssl) -get_protocol() { - local protocol="" - local server_hello="$(cat -v "$1")" - - if [[ "$server_hello" =~ Protocol\ *:\ (SSLv[23]|TLSv1(\.[0-3])?) ]]; then - protocol="${BASH_REMATCH##* }" - elif [[ "$server_hello" =~ (New|Reused)", TLSv1.3, Cipher is "TLS_[A-Z0-9_]+ ]]; then - # Note: When OpenSSL prints "New, <protocol>, Cipher is <cipher>", <cipher> is the - # negotiated cipher, but <protocol> is not the negotiated protocol. Instead, it is - # the SSL/TLS protocol that first defined <cipher>. Since the ciphers that were - # first defined for TLSv1.3 may only be used with TLSv1.3, this line may be used - # to determine whether TLSv1.3 was negotiated, but if another protocol is specified - # on this line, then this line does not indicate the actual protocol negotiated. Also, - # only TLSv1.3 cipher suites have names that begin with TLS_, which provides additional - # assurance that the above match will only succeed if TLSv1.3 was negotiated. - protocol="TLSv1.3" - fi - tm_out "$protocol" -} - -is_number() { - [[ "$1" =~ ^[1-9][0-9]*$ ]] && \ - return 0 || \ - return 1 -} - -is_ipv4addr() { - local octet="(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])" - local ipv4address="$octet\\.$octet\\.$octet\\.$octet" - - [[ -z "$1" ]] && return 1 - # more than numbers, important for hosts like AAA.BBB.CCC.DDD.in-addr.arpa.DOMAIN.TLS - [[ -n $(tr -d '0-9\.' <<< "$1") ]] && return 1 - - grep -Eq "$ipv4address" <<< "$1" && \ - return 0 || \ - return 1 -} - -# a bit easier -is_ipv6addr() { - [[ -z "$1" ]] && return 1 - # less than 2x ":" - [[ $(count_lines "$(tr ':' '\n' <<< "$1")") -le 1 ]] && \ - return 1 - #check on chars allowed: - [[ -n "$(tr -d '0-9:a-fA-F ' <<< "$1" | sed -e '/^$/d')" ]] && \ - return 1 - return 0 -} - -# now some function for the integrated BIGIP F5 Cookie detector (see https://github.com/drwetter/F5-BIGIP-Decoder) - -f5_hex2ip() { - debugme echo "$1" - echo $((16#${1:0:2})).$((16#${1:2:2})).$((16#${1:4:2})).$((16#${1:6:2})) -} -f5_hex2ip6() { - debugme echo "$1" - echo "[${1:0:4}:${1:4:4}:${1:8:4}:${1:12:4}.${1:16:4}:${1:20:4}:${1:24:4}:${1:28:4}]" -} - -f5_determine_routeddomain() { - local tmp - tmp="${1%%o*}" - echo "${tmp/rd/}" -} - -f5_ip_oldstyle() { - local tmp - local a b c d - - tmp="${1/%.*}" # until first dot - tmp="$(printf "%08x" "$tmp")" # convert the whole thing to hex, now back to ip (reversed notation: - tmp="$(f5_hex2ip $tmp)" # transform to ip with reversed notation - IFS="." read -r a b c d <<< "$tmp" # reverse it - echo $d.$c.$b.$a -} - -f5_port_decode() { - local tmp - - tmp="$(strip_lf "$1")" # remove lf if there is one - tmp="${tmp/.0000/}" # to be sure remove trailing zeros with a dot - tmp="${tmp#*.}" # get the port - tmp="$(printf "%04x" "${tmp}")" # to hex - if [[ ${#tmp} -eq 4 ]]; then - : - elif [[ ${#tmp} -eq 3 ]]; then # fill it up with leading zeros if needed - tmp=0${tmp} - elif [[ ${#tmp} -eq 2 ]]; then - tmp=00${tmp} - fi - echo $((16#${tmp:2:2}${tmp:0:2})) # reverse order and convert it from hex to dec -} - - - -###### END helper function definitions ###### - -# prints out multiple lines in $1, left aligned by spaces in $2 -out_row_aligned() { - local first=true - - while read line; do - "$first" && \ - first=false || \ - out "$2" - outln "$line" - done <<< "$1" -} - -# prints text over multiple lines, trying to make no line longer than $max_width. -# Each line is indented with $spaces. -out_row_aligned_max_width() { - local text="$1" - local spaces="$2" - local -i max_width="$3" - local -i i len - local cr=$'\n' - local line - local first=true - - max_width=$max_width-${#spaces} - len=${#text} - while true; do - if [[ $len -lt $max_width ]]; then - # If the remaining text to print is shorter than $max_width, - # then just print it. - i=$len - else - # Find the final space character in the text that is less than - # $max_width characters into the remaining text, and make the - # text up to that space character the next line to print. - line="${text:0:max_width}" - line="${line% *}" - i="${#line}" - if [[ $i -eq $max_width ]]; then - # If there are no space characters in the first $max_width - # characters of the remaining text, then make the text up - # to the first space the next line to print. If there are - # no space characters in the remaining text, make the - # remaining text the next line to print. - line="${text#* }" - i=$len-${#line} - [[ $i -eq 0 ]] && i=$len - fi - fi - if ! "$first"; then - tm_out "${cr}${spaces}" - fi - tm_out "${text:0:i}" - [[ $i -eq $len ]] && break - len=$len-$i-1 - i=$i+1 - text="${text:i:len}" - first=false - [[ $len -eq 0 ]] && break - done - return 0 -} - -out_row_aligned_max_width_by_entry() { - local text="$1" - local spaces="$2" - local -i max_width="$3" - local print_function="$4" - local resp entry prev_entry=" " - - resp="$(out_row_aligned_max_width "$text" "$spaces" "$max_width")" - while read -d " " entry; do - if [[ -n "$entry" ]]; then - $print_function "$entry" - elif [[ -n "$prev_entry" ]]; then - outln; out " " - fi - out " " - prev_entry="$entry" - done <<< "$resp" -} - -print_fixed_width() { - local text="$1" - local -i i len width="$2" - local print_function="$3" - - len=${#text} - $print_function "$text" - for (( i=len; i <= width; i++ )); do - out " " - done -} - -# saves $TMPFILE or file supplied in $2 under name "$TEMPDIR/$NODEIP.$1". -# Note: after finishing $TEMPDIR will be removed unless DEBUG >=1 -tmpfile_handle() { - local savefile="$2" - [[ -z "$savefile" ]] && savefile=$TMPFILE -#FIXME: make sure/find out if we do not need $TEMPDIR/$NODEIP.$1" if debug=0. We would save fs access here - mv $savefile "$TEMPDIR/$NODEIP.$1" 2>/dev/null - [[ $ERRFILE =~ dev.null ]] && return 0 || \ - mv $ERRFILE "$TEMPDIR/$NODEIP.${1//.txt/}.errorlog" 2>/dev/null - return 0 -} - -# arg1: line with comment sign, tabs and so on -filter_input() { - sed -e 's/#.*$//' -e '/^$/d' <<< "$1" | tr -d '\n' | tr -d '\t' | tr -d '\r' -} - -# Dl's any URL (arg1) via HTTP 1.1 GET from port 80, arg2: file to store http body. -# Proxy is not honored yet (see cmd line switches) -- except when using curl or wget. -# There the environment variable is used automatically -# Currently it is being used by check_revocation_crl() only. -http_get() { - local proto z - local node="" query="" - local dl="$2" - local useragent="$UA_STD" - local jsonID="http_get" - - "$SNEAKY" && useragent="$UA_SNEAKY" - - if type -p curl &>/dev/null; then - if [[ -z "$PROXY" ]]; then - curl -s --noproxy '*' -A $''"$useragent"'' -o $dl "$1" - else - # for the sake of simplicity assume the proxy is using http - curl -s -x $PROXYIP:$PROXYPORT -A $''"$useragent"'' -o $dl "$1" - fi - return $? - elif type -p wget &>/dev/null; then - # wget has no proxy command line. We need to use http_proxy instead. And for the sake of simplicity - # assume the GET protocol we query is using http -- http_proxy is the $ENV not for the connection TO - # the proxy, but for the protocol we query THROUGH the proxy - if [[ -z "$PROXY" ]]; then - wget --no-proxy -q -U $''"$useragent"'' -O $dl "$1" - else - if [[ -z "$http_proxy" ]]; then - http_proxy=http://$PROXYIP:$PROXYPORT wget -q -U $''"$useragent"'' -O $dl "$1" - else - wget -q -U $''"$useragent"'' -O $dl "$1" - fi - fi - return $? - else - # Worst option: slower and hiccups with chunked transfers. Workaround for the - # latter is using HTTP/1.0. We do not support https here, yet. - # First the URL will be split - IFS=/ read -r proto z node query <<< "$1" - proto=${proto%:} - if [[ "$proto" != http ]]; then - pr_warning "protocol $proto not supported yet" - fileout "$jsonID" "DEBUG" "protocol $proto not supported yet" - return 6 - fi - if [[ -n $PROXY ]]; then - # PROXYNODE works better than PROXYIP on modern versions of squid. \ - # We don't reuse the code in fd_socket() as there's initial CONNECT which makes problems - if ! exec 33<> /dev/tcp/${PROXYNODE}/${PROXYPORT}; then - outln - pr_warning "$PROG_NAME: unable to open a socket to proxy $PROXYNODE:$PROXYPORT" - fileout "$jsonID" "DEBUG" "$PROG_NAME: unable to open a socket to proxy $PROXYNODE:$PROXYPORT" - return 6 - else - printf -- "%b" "GET $proto://$node/$query HTTP/1.0\r\nUser-Agent: $useragent\r\nHost: $node\r\nAccept: */*\r\n\r\n" >&33 - fi - else - IFS=/ read -r proto z node query <<< "$1" - exec 33<>/dev/tcp/$node/80 - printf -- "%b" "GET /$query HTTP/1.0\r\nUser-Agent: $useragent\r\nHost: $node\r\nAccept: */*\r\n\r\n" >&33 - fi - # Strip HTTP header. When in Debug Mode we leave the raw data in place - if [[ $DEBUG -ge 1 ]]; then - cat <&33 >${dl}.raw - cat ${dl}.raw | sed '1,/^[[:space:]]*$/d' >${dl} - else - cat <&33 | sed '1,/^[[:space:]]*$/d' >${dl} - fi - exec 33<&- - exec 33>&- - [[ -s "$dl" ]] && return 0 || return 1 - fi -} - -# Outputs the headers when downloading any URL (arg1) via HTTP 1.1 GET from port 80. -# Only works if curl or wget is available. -# There the environment variable is used automatically -# Currently it is being used by check_pwnedkeys() only. -http_get_header() { - local proto z - local node="" query="" - local dl="$2" - local useragent="$UA_STD" - local jsonID="http_get_header" - local headers - local -i ret - - "$SNEAKY" && useragent="$UA_SNEAKY" - - if type -p curl &>/dev/null; then - if [[ -z "$PROXY" ]]; then - headers="$(curl --head -s --noproxy '*' -A $''"$useragent"'' "$1")" - else - # for the sake of simplicity assume the proxy is using http - headers="$(curl --head -s -x $PROXYIP:$PROXYPORT -A $''"$useragent"'' "$1")" - fi - ret=$? - [[ $ret -eq 0 ]] && tm_out "$headers" - return $ret - elif type -p wget &>/dev/null; then - # wget has no proxy command line. We need to use http_proxy instead. And for the sake of simplicity - # assume the GET protocol we query is using http -- http_proxy is the $ENV not for the connection TO - # the proxy, but for the protocol we query THROUGH the proxy - if [[ -z "$PROXY" ]]; then - headers="$(wget --no-proxy -q -S -U $''"$useragent"'' -O /dev/null "$1" 2>&1)" - else - if [[ -z "$http_proxy" ]]; then - headers="$(http_proxy=http://$PROXYIP:$PROXYPORT wget -q -S -U $''"$useragent"'' -O /dev/null "$1" 2>&1)" - else - headers="$(wget -q -S -U $''"$useragent"'' -O /dev/null "$1" 2>&1)" - fi - fi - ret=$? - [[ $ret -eq 0 ]] && tm_out "$headers" - # wget(1): "8: Server issued an error response.". Happens e.g. when 404 is returned. However also if the call wasn't correct (400) - # So we assume for now that everything is submitted correctly. We parse the error code too later - [[ $ret -eq 8 ]] && ret=0 && tm_out "$headers" - return $ret - else - return 1 - fi -} - -ldap_get() { - local ldif - local -i success - local crl="$1" - local tmpfile="$2" - local jsonID="$3" - - if type -p curl &>/dev/null; then - # proxy handling? - ldif="$(curl -s "$crl")" - [[ $? -eq 0 ]] || return 1 - awk '/certificateRevocationList/ { print $2 }' <<< "$ldif" | $OPENSSL base64 -d -A -out "$tmpfile" 2>/dev/null - [[ -s "$tmpfile" ]] || return 1 - return 0 - else - pr_litecyan " (for LDAP CRL check install \"curl\")" - fileout "$jsonID" "INFO" "LDAP CRL revocation check needs \"curl\"" - return 2 - fi -} - -# checks whether the public key in arg1 appears in the https://pwnedkeys.com/ database. -# arg1: file containing certificate -# arg2: public key algorithm -# arg3 key size -# Responses are as follows: -# 0 - not checked -# 1 - key not found in database -# 2 - key found in database -# 7 - network/proxy failure -check_pwnedkeys() { - local cert="$1" - local cert_key_algo="$2" - local -i cert_keysize="$3" - local pubkey curve response - - "$PHONE_OUT" || return 0 - - # https://pwnedkeys.com only keeps records on 1024 bit and larger RSA keys, - # as well as elliptic-curve keys on the P-256, P-384, and P-521 curves. - if [[ "$cert_key_algo" =~ RSA ]] || [[ "$cert_key_algo" =~ rsa ]]; then - [[ $cert_keysize -ge 1024 ]] || return 0 - elif [[ "$cert_key_algo" =~ ecdsa ]] || [[ "$cert_key_algo" == *ecPublicKey ]]; then - [[ $cert_keysize -eq 256 ]] || [[ $cert_keysize -eq 384 ]] || \ - [[ $cert_keysize -eq 521 ]] || return 0 - else - return 0 - fi - - pubkey="$($OPENSSL x509 -in "$cert" -pubkey -noout 2>/dev/null)" - # If it is an elliptic curve key, check that it is P-256, P-384, or P-521. - if [[ "$cert_key_algo" =~ ecdsa ]] || [[ "$cert_key_algo" == *ecPublicKey ]]; then - curve="$($OPENSSL ec -pubin -text <<< "$pubkey" 2>/dev/null)" - curve="${curve#*ASN1 OID: }" - [[ "$curve" == prime256v1* ]] || [[ "$curve" == secp384r1* ]] || \ - [[ "$curve" == secp521r1* ]] || return 0 - fi - fingerprint="$($OPENSSL pkey -pubin -outform DER <<< "$pubkey" 2>/dev/null | $OPENSSL dgst -sha256 -hex 2>/dev/null)" - fingerprint="${fingerprint#*= }" - response="$(http_get_header "https://v1.pwnedkeys.com/$fingerprint")" - # Handle curl's/wget's connectivity exit codes - case $? in - 4|5|7) return 7 ;; - 1|2|3|6) return 0 ;; - # unknown codes we just say "not checked" - esac - if [[ "$response" =~ "404 Not Found" ]]; then - return 1 - elif [[ "$response" =~ "200 OK" ]]; then - return 2 - else - return 0 - fi -} - -check_revocation_crl() { - local crl="$1" - local jsonID="$2" - local tmpfile="" - local scheme retcode - local -i success - - "$PHONE_OUT" || return 0 - [[ -n "$GOOD_CA_BUNDLE" ]] || return 0 - scheme="$(tolower "${crl%%://*}")" - # The code for obtaining CRLs only supports LDAP, HTTP, and HTTPS URLs. - [[ "$scheme" == http ]] || [[ "$scheme" == https ]] || [[ "$scheme" == ldap ]] || return 0 - tmpfile=$TEMPDIR/${NODE}-${NODEIP}.${crl##*\/} || exit $ERR_FCREATE - if [[ "$scheme" == ldap ]]; then - ldap_get "$crl" "$tmpfile" "$jsonID" - success=$? - else - http_get "$crl" "$tmpfile" - success=$? - fi - if [[ $success -eq 2 ]]; then - return 0 - elif [[ $success -ne 0 ]]; then - out ", " - pr_warning "retrieval of \"$crl\" failed" - fileout "$jsonID" "WARN" "CRL retrieval from $crl failed" - return 1 - fi - # -crl_download could be more elegant but is supported from 1.0.2 onwards only - $OPENSSL crl -inform DER -in "$tmpfile" -outform PEM -out "${tmpfile%%.crl}.pem" &>$ERRFILE - if [[ $? -ne 0 ]]; then - pr_warning "conversion of \"$tmpfile\" failed" - fileout "$jsonID" "WARN" "conversion of CRL to PEM format failed" - return 1 - fi - if grep -q "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TEMPDIR/intermediatecerts.pem; then - $OPENSSL verify -crl_check -CAfile <(cat $ADDITIONAL_CA_FILES "$GOOD_CA_BUNDLE" "${tmpfile%%.crl}.pem") -untrusted $TEMPDIR/intermediatecerts.pem $HOSTCERT &> "${tmpfile%%.crl}.err" - else - $OPENSSL verify -crl_check -CAfile <(cat $ADDITIONAL_CA_FILES "$GOOD_CA_BUNDLE" "${tmpfile%%.crl}.pem") $HOSTCERT &> "${tmpfile%%.crl}.err" - fi - if [[ $? -eq 0 ]]; then - out ", " - pr_svrty_good "not revoked" - fileout "$jsonID" "OK" "not revoked" - else - retcode=$(awk '/error [1-9][0-9]? at [0-9]+ depth lookup:/ { if (!found) {print $2; found=1} }' "${tmpfile%%.crl}.err") - if [[ "$retcode" == 23 ]]; then # see verify_retcode_helper() - out ", " - pr_svrty_critical "revoked" - fileout "$jsonID" "CRITICAL" "revoked" - else - retcode="$(verify_retcode_helper "$retcode")" - out " $retcode" - retcode="${retcode#(}" - retcode="${retcode%)}" - fileout "$jsonID" "WARN" "$retcode" - if [[ $DEBUG -ge 2 ]]; then - outln - cat "${tmpfile%%.crl}.err" - fi - fi - fi - return 0 -} - -check_revocation_ocsp() { - local uri="$1" - local stapled_response="$2" - local jsonID="$3" - local tmpfile="" - local -i success - local response="" - local host_header="" - - "$PHONE_OUT" || [[ -n "$stapled_response" ]] || return 0 - [[ -n "$GOOD_CA_BUNDLE" ]] || return 0 - if [[ -n "$PROXY" ]] && ! "$IGN_OCSP_PROXY"; then - # see #1106 and https://github.com/openssl/openssl/issues/6965 - out ", " - pr_warning "revocation not tested as \"openssl ocsp\" doesn't support a proxy" - fileout "$jsonID" "WARN" "Revocation not tested as openssl ocsp doesn't support a proxy" - return 0 - fi - grep -q "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TEMPDIR/intermediatecerts.pem || return 0 - tmpfile=$TEMPDIR/${NODE}-${NODEIP}.${uri##*\/} || exit $ERR_FCREATE - if [[ -n "$stapled_response" ]]; then - asciihex_to_binary "$stapled_response" > "$TEMPDIR/stapled_ocsp_response.dd" - $OPENSSL ocsp -no_nonce -respin "$TEMPDIR/stapled_ocsp_response.dd" \ - -issuer $TEMPDIR/hostcert_issuer.pem -verify_other $TEMPDIR/intermediatecerts.pem \ - -CAfile <(cat $ADDITIONAL_CA_FILES "$GOOD_CA_BUNDLE") -cert $HOSTCERT -text &> "$tmpfile" - else - host_header=${uri##http://} - host_header=${host_header%%/*} - if [[ "$OSSL_NAME" =~ LibreSSL ]]; then - host_header="-header Host ${host_header}" - elif [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 1.1.0* ]] || [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 1.1.1* ]] || \ - [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 3.0.0* ]]; then - host_header="-header Host=${host_header}" - else - host_header="-header Host ${host_header}" - fi - $OPENSSL ocsp -no_nonce ${host_header} -url "$uri" \ - -issuer $TEMPDIR/hostcert_issuer.pem -verify_other $TEMPDIR/intermediatecerts.pem \ - -CAfile <(cat $ADDITIONAL_CA_FILES "$GOOD_CA_BUNDLE") -cert $HOSTCERT -text &> "$tmpfile" - fi - if [[ $? -eq 0 ]] && grep -Fq "Response verify OK" "$tmpfile"; then - response="$(grep -F "$HOSTCERT: " "$tmpfile")" - response="${response#$HOSTCERT: }" - response="${response%\.}" - if [[ "$response" =~ "good" ]]; then - out ", " - pr_svrty_good "not revoked" - fileout "$jsonID" "OK" "not revoked" - elif [[ "$response" =~ "revoked" ]]; then - out ", " - pr_svrty_critical "revoked" - fileout "$jsonID" "CRITICAL" "revoked" - else - out ", " - pr_warning "error querying OCSP responder" - fileout "$jsonID" "WARN" "$response" - if [[ $DEBUG -ge 2 ]]; then - outln - cat "$tmpfile" - else - out " ($response)" - fi - fi - else - [[ -s "$tmpfile" ]] || response="empty ocsp response" - [[ -z "$response" ]] && response="$(awk '/Responder Error:/ { print $3 }' "$tmpfile")" - [[ -z "$response" ]] && grep -Fq "Response Verify Failure" "$tmpfile" && response="unable to verify response" - [[ -z "$response" ]] && response="$(awk -F':' '/Code/ { print $NF }' $tmpfile)" - out ", " - pr_warning "error querying OCSP responder" - fileout "$jsonID" "WARN" "$response" - if [[ $DEBUG -ge 2 ]]; then - outln - [[ -s "$tmpfile" ]] && cat "$tmpfile" || echo "empty ocsp response" - elif [[ -n "$response" ]]; then - out " ($response)" - fi - fi -} - -wait_kill(){ - local pid=$1 # pid we wait for or kill - local maxsleep=$2 # how long we wait before killing - - HAD_SLEPT=0 - while true; do - if ! ps $pid >/dev/null ; then - return 0 # process terminated before didn't reach $maxsleep - fi - [[ "$DEBUG" -ge 6 ]] && ps $pid - sleep 1 - maxsleep=$((maxsleep - 1)) - HAD_SLEPT=$((HAD_SLEPT + 1)) - test $maxsleep -le 0 && break - done # needs to be killed: - kill $pid >&2 2>/dev/null - wait $pid 2>/dev/null # make sure pid terminated, see wait(1p) - return 3 # means killed -} - -# Convert date formats -- we always use GMT=UTC here -# argv1: source date string -# argv2: dest date string -if "$HAS_GNUDATE"; then # Linux and NetBSD - parse_date() { - LC_ALL=C TZ=GMT date -d "$1" "$2" - } -elif "$HAS_FREEBSDDATE"; then # FreeBSD, OS X and newer (~6.6) OpenBSD versions - parse_date() { - LC_ALL=C TZ=GMT date -j -f "$3" "$2" "$1" - } -elif "$HAS_OPENBSDDATE"; then -# We basically echo it as a conversion as we want it is too difficult. Approach for that would be: -# printf '%s\n' "$1" | awk '{ printf "%04d%02d%02d\n", $4, $2, (index("JanFebMarAprMayJunJulAugSepOctNovDec",$1)+2)/3}' -# 4: year, 1: month, 2: day, $3: time (e.g. "Dec 8 10:16:13 2016") -# This way we could also kind of convert args to epoch but as newer OpenBSDs "date" behave like FreeBSD - parse_date() { - local tmp="" - if [[ $2 == +%s* ]]; then - echo "${1// GMT}" - else - tmp="$(printf '%s\n' "$1" | awk '{ printf "%04d-%02d-%02d %08s\n", $4, (index("JanFebMarAprMayJunJulAugSepOctNovDec",$1)+2)/3, $2, $3 }')" - echo "${tmp%:*}" # remove seconds, result now is in line with GNU date 2016-12-08 10:16 - fi - } -else - parse_date() { - LC_ALL=C TZ=GMT date -j "$2" "$1" - } -fi - -# arg1: An ASCII-HEX string -# Print $arg1 in binary format -asciihex_to_binary() { - local string="$1" - local -i len - local -i i ip2 ip4 ip6 ip8 ip10 ip12 ip14 - local -i remainder - - len=${#string} - [[ $len%2 -ne 0 ]] && return 1 - - for (( i=0; i <= len-16 ; i=i+16 )); do - ip2=$((i+2)); ip4=$((i+4)); ip6=$((i+6)); ip8=$((i+8)); ip10=$((i+10)); ip12=$((i+12)); ip14=$((i+14)) - printf -- "\x${string:i:2}\x${string:ip2:2}\x${string:ip4:2}\x${string:ip6:2}\x${string:ip8:2}\x${string:ip10:2}\x${string:ip12:2}\x${string:ip14:2}" - done - - ip2=$((i+2)); ip4=$((i+4)); ip6=$((i+6)); ip8=$((i+8)); ip10=$((i+10)); ip12=$((i+12)); ip14=$((i+14)) - remainder=$len-$i - case $remainder in - 2) printf -- "\x${string:i:2}" ;; - 4) printf -- "\x${string:i:2}\x${string:ip2:2}" ;; - 6) printf -- "\x${string:i:2}\x${string:ip2:2}\x${string:ip4:2}" ;; - 8) printf -- "\x${string:i:2}\x${string:ip2:2}\x${string:ip4:2}\x${string:ip6:2}" ;; - 10) printf -- "\x${string:i:2}\x${string:ip2:2}\x${string:ip4:2}\x${string:ip6:2}\x${string:ip8:2}" ;; - 12) printf -- "\x${string:i:2}\x${string:ip2:2}\x${string:ip4:2}\x${string:ip6:2}\x${string:ip8:2}\x${string:ip10:2}" ;; - 14) printf -- "\x${string:i:2}\x${string:ip2:2}\x${string:ip4:2}\x${string:ip6:2}\x${string:ip8:2}\x${string:ip10:2}\x${string:ip12:2}" ;; - esac - return 0 -} - -# arg1: text string -# Output a comma-separated ASCII-HEX string representation of the input string. -string_to_asciihex() { - local string="$1" - local -i i eos - local output="" - - eos=${#string}-1 - for (( i=0; i<eos; i++ )); do - output+="$(printf "%02x," "'${string:i:1}")" - done - [[ -n "$string" ]] && output+="$(printf "%02x" "'${string:eos:1}")" - tm_out "$output" - return 0 - -} - -# Adjust options to $OPENSSL s_client based on OpenSSL version and protocol version -s_client_options() { - local options=" $1" - local ciphers="notpresent" tls13_ciphers="notpresent" - - # Extract the TLSv1.3 ciphers and the non-TLSv1.3 ciphers - if [[ " $options " =~ " -cipher " ]]; then - ciphers="${options#* -cipher }" - ciphers="${ciphers%% *}" - options="${options//-cipher $ciphers/}" - ciphers="${ciphers##\'}" - ciphers="${ciphers%%\'}" - fi - if [[ " $options " =~ " -ciphersuites " ]]; then - tls13_ciphers="${options#* -ciphersuites }" - tls13_ciphers="${tls13_ciphers%% *}" - options="${options//-ciphersuites $tls13_ciphers/}" - tls13_ciphers="${tls13_ciphers##\'}" - tls13_ciphers="${tls13_ciphers%%\'}" - [[ "$tls13_ciphers" == ALL ]] && tls13_ciphers="$TLS13_OSSL_CIPHERS" - fi - - # Don't include the -servername option for an SSLv2 or SSLv3 ClientHello. - [[ -n "$SNI" ]] && [[ " $options " =~ \ -ssl[2|3]\ ]] && options="${options//$SNI/}" - - # The server_name extension should not be included in the ClientHello unless - # the -servername option is provided. However, OpenSSL 1.1.1 will include the - # server_name extension unless the -noservername option is provided. So, if - # the command line doesn't include -servername and the -noservername option is - # supported, then add -noservername to the options. - "$HAS_NOSERVERNAME" && [[ ! " $options " =~ " -servername " ]] && options+=" -noservername" - - # Newer versions of OpenSSL have dropped support for the -no_ssl2 option, so - # remove any -no_ssl2 option if the option isn't supported. (Since versions of - # OpenSSL that don't support -no_ssl2 also don't support SSLv2, the option - # isn't needed for these versions of OpenSSL.) - ! "$HAS_NO_SSL2" && options="${options//-no_ssl2/}" - - # At least one server will fail under some circumstances if compression methods are offered. - # So, only offer compression methods if necessary for the test. In OpenSSL 1.1.0 and - # 1.1.1 compression is only offered if the "-comp" option is provided. - # OpenSSL 1.0.0, 1.0.1, and 1.0.2 offer compression unless the "-no_comp" option is provided. - # OpenSSL 0.9.8 does not support either the "-comp" or the "-no_comp" option. - if [[ " $options " =~ " -comp " ]]; then - # Compression is needed for the test. So, remove "-comp" if it isn't supported, but - # otherwise make no changes. - ! "$HAS_COMP" && options="${options//-comp/}" - else - # Compression is not needed. So, specify "-no_comp" if that option is supported. - "$HAS_NO_COMP" && options+=" -no_comp" - fi - - # If $OPENSSL is compiled with TLSv1.3 support and s_client is called without - # specifying a protocol, but specifying a list of ciphers that doesn't include - # any TLSv1.3 ciphers, then the command will always fail. So, if $OPENSSL supports - # TLSv1.3 and a cipher list is provided, but no protocol is specified, then add - # -no_tls1_3 if no TLSv1.3 ciphers are provided. - if "$HAS_TLS13" && [[ "$ciphers" != notpresent ]] && \ - ( [[ "$tls13_ciphers" == notpresent ]] || [[ -z "$tls13_ciphers" ]] ) && \ - [[ ! " $options " =~ \ -ssl[2|3]\ ]] && \ - [[ ! " $options " =~ \ -tls1\ ]] && \ - [[ ! " $options " =~ \ -tls1_[1|2|3]\ ]]; then - options+=" -no_tls1_3" - fi - - if [[ "$ciphers" != notpresent ]] || [[ "$tls13_ciphers" != notpresent ]]; then - if ! "$HAS_CIPHERSUITES"; then - [[ "$ciphers" == notpresent ]] && ciphers="" - [[ "$tls13_ciphers" == notpresent ]] && tls13_ciphers="" - [[ -n "$ciphers" ]] && [[ -n "$tls13_ciphers" ]] && ciphers=":$ciphers" - ciphers="$tls13_ciphers$ciphers" - options+=" -cipher $ciphers" - else - if [[ "$ciphers" != notpresent ]] && [[ -n "$ciphers" ]]; then - options+=" -cipher $ciphers" - fi - if [[ "$tls13_ciphers" != notpresent ]] && [[ -n "$tls13_ciphers" ]]; then - options+=" -ciphersuites $tls13_ciphers" - fi - fi - fi - - # OpenSSL's name for secp256r1 is prime256v1. So whenever we encounter this - # (e.g. client simulations) we replace it with the name which OpenSSL understands - # This shouldn't be needed. We have this here as a last resort - if [[ "$1" =~ " -curves " ]]; then - ! "$HAS_CURVES" && options="${options// -curves / -groups }" - [[ "$1" =~ secp192r1 ]] && options="${options//secp192r1/prime192v1}" - [[ "$1" =~ secp256r1 ]] && options="${options//secp256r1/prime256v1}" - fi - tm_out "$options $keyopts" -} - -###### check code starts here ###### - -# determines whether the port has an HTTP service running or not (plain TLS, no STARTTLS) -# arg1 could be the protocol determined as "working". IIS6 needs that -service_detection() { - local -i was_killed - - if ! "$CLIENT_AUTH"; then - # SNI is not standardized for !HTTPS but fortunately for other protocols s_client doesn't seem to care - printf "$GET_REQ11" | $OPENSSL s_client $(s_client_options "$1 -quiet $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE & - wait_kill $! $HEADER_MAXSLEEP - was_killed=$? - head $TMPFILE | grep -aq '^HTTP\/' && SERVICE=HTTP - [[ -z "$SERVICE" ]] && head $TMPFILE | grep -waq "SMTP|ESMTP|Exim|IdeaSmtpServer|Kerio Connect|Postfix" && SERVICE=SMTP # I know some overlap here - [[ -z "$SERVICE" ]] && head $TMPFILE | grep -Ewaq "POP|Gpop|MailEnable POP3 Server|OK Dovecot|Cyrus POP3" && SERVICE=POP # I know some overlap here - [[ -z "$SERVICE" ]] && head $TMPFILE | grep -Ewaq "IMAP|IMAP4|Cyrus IMAP4IMAP4rev1|IMAP4REV1|Gimap" && SERVICE=IMAP # I know some overlap here - [[ -z "$SERVICE" ]] && head $TMPFILE | grep -aq FTP && SERVICE=FTP - [[ -z "$SERVICE" ]] && head $TMPFILE | grep -Eaqi "jabber|xmpp" && SERVICE=XMPP - [[ -z "$SERVICE" ]] && head $TMPFILE | grep -Eaqw "Jive News|InterNetNews|NNRP|INN|Kerio Connect|NNTP Service|Kerio MailServer|NNTP server" && SERVICE=NNTP - # MongoDB port 27017 will respond to a GET request with a mocked HTTP response - [[ "$SERVICE" == HTTP ]] && head $TMPFILE | grep -Eaqw "MongoDB" && SERVICE=MongoDB - debugme head -50 $TMPFILE | sed -e '/<HTML>/,$d' -e '/<html>/,$d' -e '/<XML/,$d' -e '/<xml/,$d' -e '/<\?XML/,$d' -e '/<\?xml/,$d' -e '/<\!DOCTYPE/,$d' -e '/<\!doctype/,$d' - fi - - out " Service detected: $CORRECT_SPACES" - jsonID="service" - case $SERVICE in - HTTP) - out " $SERVICE" - fileout "${jsonID}" "INFO" "$SERVICE" - ;; - IMAP|POP|SMTP|NNTP|MongoDB) - out " $SERVICE, thus skipping HTTP specific checks" - fileout "${jsonID}" "INFO" "$SERVICE, thus skipping HTTP specific checks" - ;; - *) if "$CLIENT_AUTH"; then - out " certificate-based authentication => skipping all HTTP checks" - echo "certificate-based authentication => skipping all HTTP checks" >$TMPFILE - fileout "${jsonID}" "INFO" "certificate-based authentication => skipping all HTTP checks" - else - out " Couldn't determine what's running on port $PORT" - if "$ASSUME_HTTP"; then - SERVICE=HTTP - out " -- ASSUME_HTTP set though" - fileout "${jsonID}" "DEBUG" "Couldn't determine service -- ASSUME_HTTP set" - else - out ", assuming no HTTP service => skipping all HTTP checks" - fileout "${jsonID}" "DEBUG" "Couldn't determine service, skipping all HTTP checks" - fi - fi - ;; - esac - - outln "\n" - tmpfile_handle ${FUNCNAME[0]}.txt - return 0 -} - -# 1: counter variable -# 2: threshold for this variable -# 3: string for first occurrence of problem -# 4: string for repeated occurrence of problem -# -connectivity_problem() { - if [[ $1 -lt $2 ]]; then - if "$TLS13_ONLY" && ! "$HAS_TLS13"; then - : - else - prln_warning " Oops: $3" - fi - return 0 - fi - if [[ $1 -ge $2 ]]; then - if [[ "$4" =~ openssl\ s_client\ connect ]] ; then - fatal "$4" $ERR_CONNECT "Consider increasing MAX_OSSL_FAIL (currently: $2)" - elif [[ "$4" =~ repeated\ TCP\ connect ]]; then - fatal "$4" $ERR_CONNECT "Consider increasing MAX_SOCKET_FAIL (currently: $2)" - fi - fatal "$4" $ERR_CONNECT - fi -} - - -#problems not handled: chunked -run_http_header() { - local header - local referer useragent - local url redirect - local jsonID="HTTP_status_code" - local spaces=" " - - HEADERFILE=$TEMPDIR/$NODEIP.http_header.txt - if [[ $NR_HEADER_FAIL -eq 0 ]]; then - # skip repeating this line if it's 2nd, 3rd,.. try - outln; pr_headlineln " Testing HTTP header response @ \"$URL_PATH\" " - outln - fi - if [[ $NR_HEADER_FAIL -ge $MAX_HEADER_FAIL ]]; then - # signal to caller we have a problem - return 1 - fi - - pr_bold " HTTP Status Code " - [[ -z "$1" ]] && url="/" || url="$1" - printf "$GET_REQ11" | $OPENSSL s_client $(s_client_options "$OPTIMAL_PROTO $BUGS -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI") >$HEADERFILE 2>$ERRFILE & - wait_kill $! $HEADER_MAXSLEEP - if [[ $? -eq 0 ]]; then - # Issue HTTP GET again as it properly finished within $HEADER_MAXSLEEP and didn't hang. - # Doing it again in the foreground to get an accurate header time - printf "$GET_REQ11" | $OPENSSL s_client $(s_client_options "$OPTIMAL_PROTO $BUGS -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI") >$HEADERFILE 2>$ERRFILE - NOW_TIME=$(date "+%s") - HTTP_TIME=$(awk -F': ' '/^date:/ { print $2 } /^Date:/ { print $2 }' $HEADERFILE) - HAD_SLEPT=0 - else - # 1st GET request hung and needed to be killed. Check whether it succeeded anyway: - if grep -Eiaq "XML|HTML|DOCTYPE|HTTP|Connection" $HEADERFILE; then - # correct by seconds we slept, HAD_SLEPT comes from wait_kill() - NOW_TIME=$(($(date "+%s") - HAD_SLEPT)) - HTTP_TIME=$(awk -F': ' '/^date:/ { print $2 } /^Date:/ { print $2 }' $HEADERFILE) - else - prln_warning " likely HTTP header requests failed (#lines: $(wc -l $HEADERFILE | awk '{ print $1 }'))" - [[ "$DEBUG" -lt 1 ]] && outln "Rerun with DEBUG>=1 and inspect $HEADERFILE\n" - fileout "HTTP_status_code" "WARN" "HTTP header request failed" - debugme cat $HEADERFILE - ((NR_HEADER_FAIL++)) - fi - fi - if [[ ! -s $HEADERFILE ]]; then - ((NR_HEADER_FAIL++)) - if [[ $NR_HEADER_FAIL -ge $MAX_HEADER_FAIL ]]; then - # Now, try to give a hint whether it would make sense to try with OpenSSL 1.1.0 or 1.1.1 instead - if [[ $CURVES_OFFERED == X448 ]] && ! "$HAS_X448" ; then - generic_nonfatal "HTTP header was repeatedly zero due to missing X448 curve." "${spaces}OpenSSL 1.1.1 might help. Skipping complete HTTP header section." - elif [[ $CURVES_OFFERED == X25519 ]] && ! "$HAS_X25519" ; then - generic_nonfatal "HTTP header was repeatedly zero due to missing X25519 curve." "${spaces}OpenSSL 1.1.0 might help. Skipping complete HTTP header section." - elif [[ $CURVES_OFFERED =~ X25519 ]] && [[ $CURVES_OFFERED =~ X448 ]] && ! "$HAS_X25519" && ! "$HAS_X448"; then - generic_nonfatal "HTTP header was repeatedly zero due to missing X25519/X448 curves." "${spaces}OpenSSL >=1.1.0 might help. Skipping complete HTTP header section." - else - # we could give more hints but these are the most likely cases - generic_nonfatal "HTTP header was repeatedly zero." "Skipping complete HTTP header section." - fi - KNOWN_OSSL_PROB=true - return 1 - else - pr_warning "HTTP header reply empty. " - fileout "$jsonID" "WARN" "HTTP header reply empty" - fi - fi - - # Populate vars for HTTP time - debugme echo "NOW_TIME: $NOW_TIME | HTTP_TIME: $HTTP_TIME" - - # Quit on first empty line to catch 98% of the cases. Next pattern is there because the SEDs tested - # so far seem not to be fine with header containing x0d x0a (CRLF) which is the usual case. - # So we also trigger also on any sign on a single line which is not alphanumeric (plus _) - sed -e '/^$/q' -e '/^[^a-zA-Z_0-9]$/q' $HEADERFILE >$HEADERFILE.tmp - # Now to be more sure we delete from '<' or '{' maybe with a leading blank until the end - sed -e '/^ *<.*$/d' -e '/^ *{.*$/d' $HEADERFILE.tmp >$HEADERFILE - debugme echo -e "---\n $(< $HEADERFILE) \n---" - - HTTP_STATUS_CODE=$(awk '/^HTTP\// { print $2 }' $HEADERFILE 2>>$ERRFILE) - msg_thereafter=$(awk -F"$HTTP_STATUS_CODE" '/^HTTP\// { print $2 }' $HEADERFILE 2>>$ERRFILE) # dirty trick to use the status code as a - msg_thereafter=$(strip_lf "$msg_thereafter") # field separator, otherwise we need a loop with awk - debugme echo "Status/MSG: $HTTP_STATUS_CODE $msg_thereafter" - - [[ -n "$HTTP_STATUS_CODE" ]] && out " $HTTP_STATUS_CODE$msg_thereafter" - case $HTTP_STATUS_CODE in - 301|302|307|308) - redirect=$(grep -a '^Location' $HEADERFILE | sed 's/Location: //' | tr -d '\r\n') - out ", redirecting to \""; pr_url "$redirect"; out "\"" - if [[ $redirect =~ http:// ]]; then - pr_svrty_high " -- Redirect to insecure URL (NOT ok)" - fileout "insecure_redirect" "HIGH" "Redirect to insecure URL: \"$redirect\"" - fi - fileout "$jsonID" "INFO" "$HTTP_STATUS_CODE$msg_thereafter (\"$URL_PATH\")" - ;; - 200|204|403|405) - fileout "$jsonID" "INFO" "$HTTP_STATUS_CODE$msg_thereafter (\"$URL_PATH\")" - ;; - 206) - out " -- WHAT?" - fileout "$jsonID" "INFO" "$HTTP_STATUS_CODE$msg_thereafter (\"$URL_PATH\") -- WHAT?" - # partial content shouldn't happen - ;; - 400) - pr_cyan " (Hint: better try another URL)" - fileout "$jsonID" "INFO" "$HTTP_STATUS_CODE$msg_thereafter (\"$URL_PATH\") -- better try another URL" - ;; - 401) - grep -aq "^WWW-Authenticate" $HEADERFILE && out " "; out "$(strip_lf "$(grep -a "^WWW-Authenticate" $HEADERFILE)")" - fileout "$jsonID" "INFO" "$HTTP_STATUS_CODE$msg_thereafter (\"$URL_PATH\") -- $(grep -a "^WWW-Authenticate" $HEADERFILE)" - ;; - 404) - out " (Hint: supply a path which doesn't give a \"$HTTP_STATUS_CODE$msg_thereafter\")" - fileout "$jsonID" "INFO" "$HTTP_STATUS_CODE$msg_thereafter (\"$URL_PATH\")" - ;; - "") - prln_warning "No HTTP status code." - fileout "$jsonID" "WARN" "No HTTP status code" - return 1 - ;; - *) - pr_warning ". Oh, didn't expect \"$HTTP_STATUS_CODE$msg_thereafter\"" - fileout "$jsonID" "WARN" "Unexpected $HTTP_STATUS_CODE$msg_thereafter @ \"$URL_PATH\"" - ;; - esac - outln - - # we don't call "tmpfile_handle ${FUNCNAME[0]}.txt" as we need the header file in other functions! - return 0 -} - -# Borrowed from Glenn Jackman, see https://unix.stackexchange.com/users/4667/glenn-jackman -# -match_ipv4_httpheader() { - local octet="(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])" - local ipv4address="$octet\\.$octet\\.$octet\\.$octet" - local whitelisted_header="pagespeed|page-speed|^Content-Security-Policy|^MicrosoftSharePointTeamServices|^X-OWA-Version|^Location|^Server: PRTG" - local your_ip_msg="(check if it's your IP address or e.g. a cluster IP)" - local result - local first=true - local spaces=" " - local count - local jsonID="ipv4_in_header" - local cwe="CWE-212" - local cve="" - - if [[ ! -s $HEADERFILE ]]; then - run_http_header "$1" || return 1 - fi - - # Whitelist some headers as they are mistakenly identified as ipv4 address. Issues #158, #323. - # Also facebook used to have a CSP rule for 127.0.0.1 - if grep -Evai "$whitelisted_header" $HEADERFILE | grep -Eiq "$ipv4address"; then - pr_bold " IPv4 address in header " - count=0 - while read line; do - result="$(grep -E "$ipv4address" <<< "$line")" - result=$(strip_lf "$result") - if [[ -n "$result" ]]; then - if ! $first; then - out "$spaces" - your_ip_msg="" - else - first=false - fi - pr_svrty_medium "$result" - outln "\n$spaces$your_ip_msg" - fileout "$jsonID" "MEDIUM" "$result $your_ip_msg" "$cve" "$cwe" - fi - count=$count+1 - done < $HEADERFILE - fi -} - - -run_http_date() { - local difftime - local spaces=" " - jsonID="HTTP_clock_skew" - - if [[ $SERVICE != HTTP ]] || "$CLIENT_AUTH"; then - return 0 - fi - if [[ ! -s $HEADERFILE ]]; then - run_http_header "$1" || return 1 - fi - pr_bold " HTTP clock skew " - if [[ -n "$HTTP_TIME" ]]; then - HTTP_TIME="$(strip_lf "$HTTP_TIME")" - if "$HAS_OPENBSDDATE"; then - # We won't normalize the date under an OpenBSD thus no subtraction is feasible - outln "remote: $HTTP_TIME" - out "${spaces}local: $(LC_ALL=C TZ=GMT date "+%a, %d %b %Y %T %Z")" - fileout "$jsonID" "INFO" "$HTTP_TIME - $(TZ=GMT date "+%a, %d %b %Y %T %Z")" - else - HTTP_TIME="$(parse_date "$HTTP_TIME" "+%s" "%a, %d %b %Y %T %Z" 2>>$ERRFILE)" - difftime=$((HTTP_TIME - NOW_TIME)) - [[ $difftime != "-"* ]] && [[ $difftime != "0" ]] && difftime="+$difftime" - # process was killed, so we need to add an error - [[ $HAD_SLEPT -ne 0 ]] && difftime="$difftime (± 1.5)" - out "$difftime sec from localtime"; - fileout "$jsonID" "INFO" "$difftime seconds from localtime" - fi - else - out "Got no HTTP time, maybe try different URL?"; - fileout "$jsonID" "INFO" "Got no HTTP time, maybe try different URL?" - fi - debugme tm_out ", HTTP_TIME in epoch: $HTTP_TIME" - outln - match_ipv4_httpheader "$1" - return 0 -} - - -# HEADERFILE needs to contain the HTTP header (made sure by invoker) -# arg1: key=word to match -# arg2: hint for fileout() if double header -# arg3: indentation, i.e string w spaces -# arg4: whether we need a CR before "misconfiguration" -# returns: -# 0 if header not found -# 1-n nr of headers found, then in HEADERVALUE the first value from key -# -match_httpheader_key() { - local key="$1" - local spaces="$3" - local first=$4 - local -i nr=0 - - nr=$(grep -Eaic "^ *$key:" $HEADERFILE) - if [[ $nr -eq 0 ]]; then - HEADERVALUE="" - return 0 - elif [[ $nr -eq 1 ]]; then - HEADERVALUE="$(grep -Eia "^ *$key:" $HEADERFILE)" - HEADERVALUE="${HEADERVALUE#*:}" # remove leading part=key to colon - HEADERVALUE="$(strip_lf "$HEADERVALUE")" - HEADERVALUE="$(strip_leading_space "$HEADERVALUE")" - "$first" || out "$spaces" - return 1 - else - "$first" || out "$spaces" - pr_svrty_medium "misconfiguration: " - pr_italic "$key" - pr_svrty_medium " ${nr}x" - outln " -- checking first one only" - out "$spaces" - HEADERVALUE="$(fgrep -Fai "$key:" $HEADERFILE | head -1)" - HEADERVALUE="${HEADERVALUE#*:}" - HEADERVALUE="$(strip_lf "$HEADERVALUE")" - HEADERVALUE="$(strip_leading_space "$HEADERVALUE")" - [[ $DEBUG -ge 2 ]] && tm_italic "$HEADERVALUE" && tm_out "\n$spaces" - fileout "${2}_multiple" "MEDIUM" "Multiple $2 headers. Using first header: $HEADERVALUE" - return $nr - fi -} - -includeSubDomains() { - if grep -aiqw includeSubDomains "$1"; then - pr_svrty_good ", includeSubDomains" - return 0 - else - pr_litecyan ", just this domain" - return 1 - fi -} - -preload() { - if grep -aiqw preload "$1"; then - pr_svrty_good ", preload" - return 0 - else - return 1 - fi -} - - -run_hsts() { - local hsts_age_sec - local hsts_age_days - local spaces=" " - local jsonID="HSTS" - - if [[ ! -s $HEADERFILE ]]; then - run_http_header "$1" || return 1 - fi - pr_bold " Strict Transport Security " - match_httpheader_key "Strict-Transport-Security" "HSTS" "$spaces" "true" - if [[ $? -ne 0 ]]; then - echo "$HEADERVALUE" >$TMPFILE - hsts_age_sec=$(sed -e 's/[^0-9]*//g' <<< $HEADERVALUE) - debugme echo "hsts_age_sec: $hsts_age_sec" - if [[ -n $hsts_age_sec ]]; then - hsts_age_days=$(( hsts_age_sec / 86400)) - else - hsts_age_days=-1 - fi - if [[ $hsts_age_days -eq -1 ]]; then - pr_svrty_medium "misconfiguration: HSTS max-age (recommended > 15552000 seconds = 180 days ) is required but missing" - fileout "${jsonID}_time" "MEDIUM" "misconfiguration, parameter max-age (recommended > 15552000 seconds = 180 days) missing" - elif [[ $hsts_age_sec -eq 0 ]]; then - pr_svrty_low "HSTS max-age is set to 0. HSTS is disabled" - fileout "${jsonID}_time" "LOW" "0. HSTS is disabled" - elif [[ $hsts_age_sec -ge $HSTS_MIN ]]; then - pr_svrty_good "$hsts_age_days days" ; out "=$hsts_age_sec s" - fileout "${jsonID}_time" "OK" "$hsts_age_days days (=$hsts_age_sec seconds) > $HSTS_MIN seconds" - else - pr_svrty_medium "$hsts_age_sec s = $hsts_age_days days is too short ( >= $HSTS_MIN seconds recommended)" - fileout "${jsonID}_time" "MEDIUM" "max-age too short. $hsts_age_days days (=$hsts_age_sec seconds) < $HSTS_MIN seconds" - fi - if includeSubDomains "$TMPFILE"; then - fileout "${jsonID}_subdomains" "OK" "includes subdomains" - else - fileout "${jsonID}_subdomains" "INFO" "only for this domain" - fi - if preload "$TMPFILE"; then - fileout "${jsonID}_preload" "OK" "domain IS marked for preloading" - else - fileout "${jsonID}_preload" "INFO" "domain is NOT marked for preloading" - #FIXME: To be checked against preloading lists, - # e.g. https://dxr.mozilla.org/mozilla-central/source/security/manager/boot/src/nsSTSPreloadList.inc - # https://chromium.googlesource.com/chromium/src/+/master/net/http/transport_security_state_static.json - fi - else - pr_svrty_low "not offered" - fileout "$jsonID" "LOW" "not offered" - fi - outln - - tmpfile_handle ${FUNCNAME[0]}.txt - return 0 -} - - -run_hpkp() { - local -i hpkp_age_sec - local -i hpkp_age_days - local -i hpkp_nr_keys - local hpkp_spki hpkp_spki_hostcert - local -a backup_spki - local spaces=" " - local spaces_indented=" " - local certificate_found=false - local -i i nrsaved - local first_hpkp_header - local spki - local ca_hashes="$TESTSSL_INSTALL_DIR/etc/ca_hashes.txt" - - if [[ ! -s $HEADERFILE ]]; then - run_http_header "$1" || return 1 - fi - pr_bold " Public Key Pinning " - grep -aiw '^Public-Key-Pins' $HEADERFILE >$TMPFILE # TMPFILE includes report-only - if [[ $? -eq 0 ]]; then - if [[ $(grep -aci '^Public-Key-Pins:' $TMPFILE) -gt 1 ]]; then - pr_svrty_medium "Misconfiguration, multiple Public-Key-Pins headers" - outln ", taking first line" - fileout "HPKP_error" "MEDIUM" "multiple Public-Key-Pins in header" - first_hpkp_header="$(grep -ai '^Public-Key-Pins:' $TMPFILE | head -1)" - # we only evaluate the keys here, unless they a not present - out "$spaces " - elif [[ $(grep -aci '^Public-Key-Pins-Report-Only:' $TMPFILE) -gt 1 ]]; then - outln "Multiple HPKP headers (Report-Only), taking first line" - fileout "HPKP_notice" "INFO" "multiple Public-Key-Pins-Report-Only in header" - first_hpkp_header="$(grep -ai '^Public-Key-Pins-Report-Only:' $TMPFILE | head -1)" - out "$spaces " - elif [[ $(grep -Eaci '^Public-Key-Pins:|^Public-Key-Pins-Report-Only:' $TMPFILE) -eq 2 ]]; then - outln "Public-Key-Pins + Public-Key-Pins-Report-Only detected. Continue with first one" - first_hpkp_header="$(grep -ai '^Public-Key-Pins:' $TMPFILE)" - out "$spaces " - elif [[ $(grep -aci '^Public-Key-Pins:' $TMPFILE) -eq 1 ]]; then - first_hpkp_header="$(grep -ai '^Public-Key-Pins:' $TMPFILE)" - else - outln "Public-Key-Pins-Only detected" - first_hpkp_header="$(grep -ai '^Public-Key-Pins-Report-Only:' $TMPFILE)" - out "$spaces " - fileout "HPKP_SPKIs" "INFO" "Only Public-Key-Pins-Report-Only" - fi - - # remove leading Public-Key-Pins* and convert it to multiline arg - sed -e 's/Public-Key-Pins://g' -e s'/Public-Key-Pins-Report-Only://' <<< "$first_hpkp_header" | \ - tr ';' '\n' | sed -e 's/\"//g' -e 's/^ //' >$TMPFILE - - hpkp_nr_keys=$(grep -ac pin-sha $TMPFILE) - if [[ $hpkp_nr_keys -eq 1 ]]; then - pr_svrty_high "Only one key pinned (NOT ok), means the site may become unavailable in the future, " - fileout "HPKP_SPKIs" "HIGH" "Only one key pinned" - else - pr_svrty_good "$hpkp_nr_keys" - out " keys, " - fileout "HPKP_SPKIs" "OK" "$hpkp_nr_keys keys pinned in header" - fi - - # print key=value pair with awk, then strip non-numbers, to be improved with proper parsing of key-value with awk - if "$HAS_SED_E"; then - hpkp_age_sec=$(awk -F= '/max-age/{max_age=$2; print max_age}' $TMPFILE | sed -E 's/[^[:digit:]]//g') - else - hpkp_age_sec=$(awk -F= '/max-age/{max_age=$2; print max_age}' $TMPFILE | sed -r 's/[^[:digit:]]//g') - fi - hpkp_age_days=$((hpkp_age_sec / 86400)) - if [[ $hpkp_age_sec -ge $HPKP_MIN ]]; then - pr_svrty_good "$hpkp_age_days days" ; out "=$hpkp_age_sec s" - fileout "HPKP_age" "OK" "HPKP age is set to $hpkp_age_days days ($hpkp_age_sec sec)" - else - out "$hpkp_age_sec s = " - pr_svrty_medium "$hpkp_age_days days (< $HPKP_MIN s = $((HPKP_MIN / 86400)) days is not good enough)" - fileout "HPKP_age" "MEDIUM" "age is set to $hpkp_age_days days ($hpkp_age_sec sec) < $HPKP_MIN s = $((HPKP_MIN / 86400)) days is not good enough." - fi - - if includeSubDomains "$TMPFILE"; then - fileout "HPKP_subdomains" "INFO" "is valid for subdomains as well" - else - fileout "HPKP_subdomains" "INFO" "is valid for this domain only" - fi - if preload "$TMPFILE"; then - fileout "HPKP_preload" "INFO" "IS marked for browser preloading" - else - fileout "HPKP_preload" "INFO" "NOT marked for browser preloading" - fi - - # Get the SPKIs first - spki=$(tr ';' '\n' < $TMPFILE | tr -d ' ' | tr -d '\"' | awk -F'=' '/pin.*=/ { print $2 }') - debugme tmln_out "\n$spki" - - # Look at the host certificate first - if [[ ! -s "$HOSTCERT" ]]; then - get_host_cert || return 1 - # no host certificate - fi - - hpkp_spki_hostcert="$($OPENSSL x509 -in $HOSTCERT -pubkey -noout 2>/dev/null | grep -v PUBLIC | \ - $OPENSSL base64 -d 2>/dev/null | $OPENSSL dgst -sha256 -binary 2>/dev/null | $OPENSSL base64 2>/dev/null)" - hpkp_ca="$($OPENSSL x509 -in $HOSTCERT -issuer -noout 2>/dev/null |sed 's/^.*CN=//' | sed 's/\/.*$//')" - - # Get keys/hashes from intermediate certificates - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS $PROXY -showcerts -connect $NODEIP:$PORT $SNI") </dev/null >$TMPFILE 2>$ERRFILE - # Place the server's certificate in $HOSTCERT and any intermediate - # certificates that were provided in $TEMPDIR/intermediatecerts.pem - # https://backreference.org/2010/05/09/ocsp-verification-with-openssl/ - awk -v n=-1 "/Certificate chain/ {start=1} - /-----BEGIN CERTIFICATE-----/{ if (start) {inc=1; n++} } - inc { print > (\"$TEMPDIR/level\" n \".crt\") } - /---END CERTIFICATE-----/{ inc=0 }" $TMPFILE - nrsaved=$(count_words "$(echo $TEMPDIR/level?.crt 2>/dev/null)") - rm $TEMPDIR/level0.crt 2>/dev/null - - printf ""> "$TEMPDIR/intermediate.hashes" - if [[ $nrsaved -ge 2 ]]; then - for cert_fname in $TEMPDIR/level?.crt; do - hpkp_spki_ca="$($OPENSSL x509 -in "$cert_fname" -pubkey -noout 2>/dev/null | grep -v PUBLIC | $OPENSSL base64 -d 2>/dev/null | - $OPENSSL dgst -sha256 -binary 2>/dev/null | $OPENSSL enc -base64 2>/dev/null)" - hpkp_name="$(get_cn_from_cert $cert_fname)" - hpkp_ca="$($OPENSSL x509 -in $cert_fname -issuer -noout 2>/dev/null |sed 's/^.*CN=//' | sed 's/\/.*$//')" - [[ -n $hpkp_name ]] || hpkp_name=$($OPENSSL x509 -in "$cert_fname" -subject -noout 2>/dev/null | sed 's/^subject= //') - echo "$hpkp_spki_ca $hpkp_name" >> "$TEMPDIR/intermediate.hashes" - done - fi - - # This is where the matching magic starts. First host, intermediate, then root certificate from the supplied stores - spki_match=false - has_backup_spki=false - i=0 - for hpkp_spki in $spki; do - certificate_found=false - # compare collected SPKIs against the host certificate - if [[ "$hpkp_spki_hostcert" == "$hpkp_spki" ]] || [[ "$hpkp_spki_hostcert" == "$hpkp_spki=" ]]; then - certificate_found=true # We have a match - spki_match=true - out "\n$spaces_indented Host cert: " - pr_svrty_good "$hpkp_spki" - fileout "HPKP_$hpkp_spki" "OK" "SPKI $hpkp_spki matches the host certificate" - fi - debugme tm_out "\n $hpkp_spki | $hpkp_spki_hostcert" - - # Check for intermediate match - if ! "$certificate_found"; then - hpkp_matches=$(grep "$hpkp_spki" $TEMPDIR/intermediate.hashes 2>/dev/null) - if [[ -n $hpkp_matches ]]; then # hpkp_matches + hpkp_spki + '=' - # We have a match - certificate_found=true - spki_match=true - out "\n$spaces_indented Sub CA: " - pr_svrty_good "$hpkp_spki" - ca_cn="$(sed "s/^[a-zA-Z0-9\+\/]*=* *//" <<< $"$hpkp_matches" )" - pr_italic " $ca_cn" - fileout "HPKP_$hpkp_spki" "OK" "SPKI $hpkp_spki matches Intermediate CA \"$ca_cn\" pinned in the HPKP header" - fi - fi - - # we compare now against a precompiled list of SPKIs against the ROOT CAs we have in $ca_hashes - if ! "$certificate_found"; then - hpkp_matches=$(grep -h "$hpkp_spki" $ca_hashes 2>/dev/null | sort -u) - if [[ -n $hpkp_matches ]]; then - certificate_found=true # root CA found - spki_match=true - if [[ $(count_lines "$hpkp_matches") -eq 1 ]]; then - # replace by awk - match_ca=$(sed "s/[a-zA-Z0-9\+\/]*=* *//" <<< "$hpkp_matches") - else - match_ca="" - - fi - ca_cn="$(sed "s/^[a-zA-Z0-9\+\/]*=* *//" <<< $"$hpkp_matches" )" - if [[ "$match_ca" == "$hpkp_ca" ]]; then # part of the chain - out "\n$spaces_indented Root CA: " - pr_svrty_good "$hpkp_spki" - pr_italic " $ca_cn" - fileout "HPKP_$hpkp_spki" "INFO" "SPKI $hpkp_spki matches Root CA \"$ca_cn\" pinned. (Root CA part of the chain)" - else # not part of chain - match_ca="" - has_backup_spki=true # Root CA outside the chain --> we save it for unmatched - fileout "HPKP_$hpkp_spki" "INFO" "SPKI $hpkp_spki matches Root CA \"$ca_cn\" pinned. (Root backup SPKI)" - backup_spki[i]="$(strip_lf "$hpkp_spki")" # save it for later - backup_spki_str[i]="$ca_cn" # also the name=CN of the root CA - i=$((i + 1)) - fi - fi - fi - - # still no success --> it's probably a backup SPKI - if ! "$certificate_found"; then - # Most likely a backup SPKI, unfortunately we can't tell for what it is: host, intermediates - has_backup_spki=true - backup_spki[i]="$(strip_lf "$hpkp_spki")" # save it for later - backup_spki_str[i]="" # no root ca - i=$((i + 1)) - fileout "HPKP_$hpkp_spki" "INFO" "SPKI $hpkp_spki doesn't match anything. This is ok for a backup for any certificate" - # CSV/JSON output here for the sake of simplicity, rest we do en bloc below - fi - done - - # now print every backup spki out we saved before - out "\n$spaces_indented Backups: " - - # for i=0 manually do the same as below as there's other indentation here - if [[ -n "${backup_spki_str[0]}" ]]; then - pr_svrty_good "${backup_spki[0]}" - #out " Root CA: " - prln_italic " ${backup_spki_str[0]}" - else - outln "${backup_spki[0]}" - fi - # now for i=1 - for ((i=1; i < ${#backup_spki[@]} ;i++ )); do - if [[ -n "${backup_spki_str[i]}" ]]; then - # it's a Root CA outside the chain - pr_svrty_good "$spaces_indented ${backup_spki[i]}" - #out " Root CA: " - prln_italic " ${backup_spki_str[i]}" - else - outln "$spaces_indented ${backup_spki[i]}" - fi - done - if [[ ! -f "$ca_hashes" ]] && "$spki_match"; then - out "$spaces " - prln_warning "Attribution of further hashes couldn't be done as $ca_hashes could not be found" - fileout "HPKP_SPKImatch" "WARN" "Attribution of further hashes possible as $ca_hashes could not be found" - fi - - # If all else fails... - if ! "$spki_match"; then - "$has_backup_spki" && out "$spaces" # we had a few lines with backup SPKIs already - prln_svrty_high " No matching key for SPKI found " - fileout "HPKP_SPKImatch" "HIGH" "None of the SPKI match your host certificate, intermediate CA or known root CAs. Bricked site?" - fi - - if ! "$has_backup_spki"; then - prln_svrty_high " No backup keys found. Loss/compromise of the currently pinned key(s) will lead to bricked site. " - fileout "HPKP_backup" "HIGH" "No backup keys found. Loss/compromise of the currently pinned key(s) will lead to bricked site." - fi - else - outln "--" - fileout "HPKP" "INFO" "No support for HTTP Public Key Pinning" - fi - - tmpfile_handle ${FUNCNAME[0]}.txt - return 0 -} - -emphasize_stuff_in_headers(){ - local html_brown="<span style=\\\"color:olive;\\\">" - local html_yellow="<span style=\\\"color:olive;font-weight:bold;\\\">" - local html_off="<\\/span>" - -# see https://www.grymoire.com/Unix/Sed.html#uh-3 -# outln "$1" | sed "s/[0-9]*/$brown&${off}/g" - tmln_out "$1" | sed -e "s/\([0-9]\)/${brown}\1${off}/g" \ - -e "s/Unix/${yellow}Unix${off}/g" \ - -e "s/Debian/${yellow}Debian${off}/g" \ - -e "s/Win32/${yellow}Win32${off}/g" \ - -e "s/Win64/${yellow}Win64${off}/g" \ - -e "s/Ubuntu/${yellow}Ubuntu${off}/g" \ - -e "s/ubuntu/${yellow}ubuntu${off}/g" \ - -e "s/buster/${yellow}buster${off}/g" \ - -e "s/stretch/${yellow}stretch${off}/g" \ - -e "s/jessie/${yellow}jessie${off}/g" \ - -e "s/squeeze/${yellow}squeeze${off}/g" \ - -e "s/wheezy/${yellow}wheezy${off}/g" \ - -e "s/lenny/${yellow}lenny${off}/g" \ - -e "s/SUSE/${yellow}SUSE${off}/g" \ - -e "s/Red Hat Enterprise Linux/${yellow}Red Hat Enterprise Linux${off}/g" \ - -e "s/Red Hat/${yellow}Red Hat${off}/g" \ - -e "s/CentOS/${yellow}CentOS${off}/g" \ - -e "s/Via/${yellow}Via${off}/g" \ - -e "s/X-Forwarded/${yellow}X-Forwarded${off}/g" \ - -e "s/Liferay-Portal/${yellow}Liferay-Portal${off}/g" \ - -e "s/X-Cache-Lookup/${yellow}X-Cache-Lookup${off}/g" \ - -e "s/X-Cache/${yellow}X-Cache${off}/g" \ - -e "s/X-Squid/${yellow}X-Squid${off}/g" \ - -e "s/X-Server/${yellow}X-Server${off}/g" \ - -e "s/X-Varnish/${yellow}X-Varnish${off}/g" \ - -e "s/X-OWA-Version/${yellow}X-OWA-Version${off}/g" \ - -e "s/MicrosoftSharePointTeamServices/${yellow}MicrosoftSharePointTeamServices${off}/g" \ - -e "s/X-Application-Context/${yellow}X-Application-Context${off}/g" \ - -e "s/X-Version/${yellow}X-Version${off}/g" \ - -e "s/X-Powered-By/${yellow}X-Powered-By${off}/g" \ - -e "s/X-UA-Compatible/${yellow}X-UA-Compatible${off}/g" \ - -e "s/Link/${yellow}Link${off}/g" \ - -e "s/X-Rack-Cache/${yellow}X-Rack-Cache${off}/g" \ - -e "s/X-Runtime/${yellow}X-Runtime${off}/g" \ - -e "s/X-Pingback/${yellow}X-Pingback${off}/g" \ - -e "s/X-Permitted-Cross-Domain-Policies/${yellow}X-Permitted-Cross-Domain-Policies${off}/g" \ - -e "s/X-AspNet-Version/${yellow}X-AspNet-Version${off}/g" \ - -e "s/x-note/${yellow}x-note${off}/g" \ - -e "s/x-global-transaction-id/${yellow}x-global-transaction-id${off}/g" \ - -e "s/X-Global-Transaction-ID/${yellow}X-Global-Transaction-ID${off}/g" \ - -e "s/Alt-Svc/${yellow}Alt-Svc${off}/g" \ - -e "s/system-wsgw-management-loopback/${yellow}system-wsgw-management-loopback${off}/g" - - if "$do_html"; then - if [[ $COLOR -ge 2 ]]; then - html_out "$(tm_out "$1" | sed -e 's/\&/\&/g' \ - -e 's/</\</g' -e 's/>/\>/g' -e 's/"/\"/g' -e "s/'/\'/g" \ - -e "s/\([0-9]\)/${html_brown}\1${html_off}/g" \ - -e "s/Unix/${html_yellow}Unix${html_off}/g" \ - -e "s/Debian/${html_yellow}Debian${html_off}/g" \ - -e "s/Win32/${html_yellow}Win32${html_off}/g" \ - -e "s/Win64/${html_yellow}Win64${html_off}/g" \ - -e "s/Ubuntu/${html_yellow}Ubuntu${html_off}/g" \ - -e "s/ubuntu/${html_yellow}ubuntu${html_off}/g" \ - -e "s/buster/${html_yellow}buster${html_off}/g" \ - -e "s/stretch/${html_yellow}stretch${html_off}/g" \ - -e "s/jessie/${html_yellow}jessie${html_off}/g" \ - -e "s/squeeze/${html_yellow}squeeze${html_off}/g" \ - -e "s/wheezy/${html_yellow}wheezy${html_off}/g" \ - -e "s/lenny/${html_yellow}lenny${html_off}/g" \ - -e "s/SUSE/${html_yellow}SUSE${html_off}/g" \ - -e "s/Red Hat Enterprise Linux/${html_yellow}Red Hat Enterprise Linux${html_off}/g" \ - -e "s/Red Hat/${html_yellow}Red Hat${html_off}/g" \ - -e "s/CentOS/${html_yellow}CentOS${html_off}/g" \ - -e "s/Via/${html_yellow}Via${html_off}/g" \ - -e "s/X-Forwarded/${html_yellow}X-Forwarded${html_off}/g" \ - -e "s/Liferay-Portal/${html_yellow}Liferay-Portal${html_off}/g" \ - -e "s/X-Cache-Lookup/${html_yellow}X-Cache-Lookup${html_off}/g" \ - -e "s/X-Cache/${html_yellow}X-Cache${html_off}/g" \ - -e "s/X-Squid/${html_yellow}X-Squid${html_off}/g" \ - -e "s/X-Server/${html_yellow}X-Server${html_off}/g" \ - -e "s/X-Varnish/${html_yellow}X-Varnish${html_off}/g" \ - -e "s/X-OWA-Version/${html_yellow}X-OWA-Version${html_off}/g" \ - -e "s/MicrosoftSharePointTeamServices/${html_yellow}MicrosoftSharePointTeamServices${html_off}/g" \ - -e "s/X-Application-Context/${html_yellow}X-Application-Context${html_off}/g" \ - -e "s/X-Version/${html_yellow}X-Version${html_off}/g" \ - -e "s/X-Powered-By/${html_yellow}X-Powered-By${html_off}/g" \ - -e "s/X-UA-Compatible/${html_yellow}X-UA-Compatible${html_off}/g" \ - -e "s/Link/${html_yellow}Link${html_off}/g" \ - -e "s/X-Runtime/${html_yellow}X-Runtime${html_off}/g" \ - -e "s/X-Rack-Cache/${html_yellow}X-Rack-Cache${html_off}/g" \ - -e "s/X-Pingback/${html_yellow}X-Pingback${html_off}/g" \ - -e "s/X-Permitted-Cross-Domain-Policies/${html_yellow}X-Permitted-Cross-Domain-Policies${html_off}/g" \ - -e "s/X-AspNet-Version/${html_yellow}X-AspNet-Version${html_off}/g")" \ - -e "s/x-note/${html_yellow}x-note${html_off}/g" \ - -e "s/X-Global-Transaction-ID/${html_yellow}X-Global-Transaction-ID${html_off}/g" \ - -e "s/x-global-transaction-id/${html_yellow}x-global-transaction-id${html_off}/g" \ - -e "s/Alt-Svc/${html_yellow}Alt-Svc${html_off}/g" \ - -e "s/system-wsgw-management-loopback/${html_yellow}system-wsgw-management-loopback${html_off}/g" -#FIXME: this is double code. The pattern to emphasize would fit better into -# one function. -# Also we need another function like run_other_header as otherwise "Link" "Alt-Svc" will never be found. -# And: I matches case sensitive only which might not detect all banners. (sed ignorecase is not possible w/ BSD sed) - else - html_out "$(html_reserved "$1")" - fi - html_out "\n" - fi -} - -run_server_banner() { - local serverbanner - local jsonID="banner_server" - - if [[ ! -s $HEADERFILE ]]; then - run_http_header "$1" || return 1 - fi - pr_bold " Server banner " - grep -ai '^Server' $HEADERFILE >$TMPFILE - if [[ $? -eq 0 ]]; then - serverbanner=$(sed -e 's/^Server: //' -e 's/^server: //' $TMPFILE) - if [[ "$serverbanner" == $'\n' ]] || [[ "$serverbanner" == $'\r' ]] || [[ "$serverbanner" == $'\n\r' ]] || [[ -z "$serverbanner" ]]; then - outln "exists but empty string" - fileout "$jsonID" "INFO" "Server banner is empty" - else - emphasize_stuff_in_headers "$serverbanner" - fileout "$jsonID" "INFO" "$serverbanner" - if [[ "$serverbanner" == *Microsoft-IIS/6.* ]] && [[ $OSSL_VER == 1.0.2* ]]; then - prln_warning " It's recommended to run another test w/ OpenSSL 1.0.1 !" - # see https://github.com/PeterMosmans/openssl/issues/19#issuecomment-100897892 - fileout "${jsonID}" "WARN" "IIS6_openssl_mismatch: Recommended to rerun this test w/ OpenSSL 1.0.1. See https://github.com/PeterMosmans/openssl/issues/19#issuecomment-100897892" - fi - fi - # mozilla.github.io/server-side-tls/ssl-config-generator/ - # https://support.microsoft.com/en-us/kb/245030 - else - outln "(no \"Server\" line in header, interesting!)" - fileout "$jsonID" "INFO" "No Server banner line in header, interesting!" - fi - - tmpfile_handle ${FUNCNAME[0]}.txt - return 0 -} - -run_appl_banner() { - local line - local first=true - local spaces=" " - local appl_banners="" - local jsonID="banner_application" - - if [[ ! -s $HEADERFILE ]]; then - run_http_header "$1" || return 1 - fi - pr_bold " Application banner " - grep -Eai '^X-Powered-By|^X-AspNet-Version|^X-Version|^Liferay-Portal|^X-OWA-Version^|^MicrosoftSharePointTeamServices' $HEADERFILE >$TMPFILE - if [[ $? -ne 0 ]]; then - outln "--" - fileout "$jsonID" "INFO" "No application banner found" - else - while IFS='' read -r line; do - line=$(strip_lf "$line") - if ! $first; then - out "$spaces" - appl_banners="${appl_banners}, ${line}" - else - appl_banners="${line}" - first=false - fi - emphasize_stuff_in_headers "$line" - done < "$TMPFILE" - fileout "$jsonID" "INFO" "$appl_banners" - fi - tmpfile_handle ${FUNCNAME[0]}.txt - return 0 -} - -run_rp_banner() { - local line - local first=true - local spaces=" " - local rp_banners="" - local jsonID="banner_reverseproxy" - local cwe="CWE-200" - local cve="" - - if [[ ! -s $HEADERFILE ]]; then - run_http_header "$1" || return 1 - fi - pr_bold " Reverse Proxy banner " - grep -Eai '^Via:|^X-Cache|^X-Squid|^X-Varnish:|^X-Server-Name:|^X-Server-Port:|^x-forwarded|^Forwarded' $HEADERFILE >$TMPFILE - if [[ $? -ne 0 ]]; then - outln "--" - fileout "$jsonID" "INFO" "--" "$cve" "$cwe" - else - while read line; do - line=$(strip_lf "$line") - if $first; then - first=false - else - out "$spaces" - fi - emphasize_stuff_in_headers "$line" - rp_banners="${rp_banners}${line}" - done < $TMPFILE - fileout "$jsonID" "INFO" "$rp_banners" "$cve" "$cwe" - fi - outln - - tmpfile_handle ${FUNCNAME[0]}.txt - return 0 -} - - -# arg1: multiline string w cookies -# -sub_f5_bigip_check() { - local allcookies="$1" - local ip port cookievalue cookiename - local routed_domain offset - local savedcookies="" - local spaces="$2" - local cwe="CWE-212" - local cve="" - - # taken from https://github.com/drwetter/F5-BIGIP-Decoder, more details see there - - debugme echo -e "all cookies: >> $allcookies <<\n" - while true; do IFS='=' read cookiename cookievalue - [[ -z "$cookievalue" ]] && break - cookievalue=${cookievalue/;/} - debugme echo $cookiename : $cookievalue - if grep -Eq '[0-9]{9,10}\.[0-9]{3,5}\.0000' <<< "$cookievalue"; then - ip="$(f5_ip_oldstyle "$cookievalue")" - port="$(f5_port_decode $cookievalue)" - out "${spaces}F5 cookie (default IPv4 pool member): "; pr_italic "$cookiename "; prln_svrty_medium "${ip}:${port}" - fileout "cookie_bigip_f5" "MEDIUM" "Information leakage: F5 cookie $cookiename $cookievalue is default IPv4 pool member ${ip}:${port}" "$cve" "$cwe" - elif grep -Eq '^rd[0-9]{1,3}o0{20}f{4}[a-f0-9]{8}o[0-9]{1,5}' <<< "$cookievalue"; then - routed_domain="$(f5_determine_routeddomain "$cookievalue")" - offset=$(( 2 + ${#routed_domain} + 1 + 24)) - port="${cookievalue##*o}" - ip="$(f5_hex2ip "${cookievalue:$offset:8}")" - out "${spaces}F5 cookie (IPv4 pool in routed domain "; pr_svrty_medium "$routed_domain"; out "): "; pr_italic "$cookiename "; prln_svrty_medium "${ip}:${port}" - fileout "cookie_bigip_f5" "MEDIUM" "Information leakage: F5 cookie $cookiename $cookievalue is IPv4 pool member in routed domain $routed_domain ${ip}:${port}" "$cve" "$cwe" - elif grep -Eq '^vi[a-f0-9]{32}\.[0-9]{1,5}' <<< "$cookievalue"; then - ip="$(f5_hex2ip6 ${cookievalue:2:32})" - port="${cookievalue##*.}" - port=$(f5_port_decode "$port") - out "${spaces}F5 cookie (default IPv6 pool member): "; pr_italic "$cookiename "; prln_svrty_medium "${ip}:${port}" - fileout "cookie_bigip_f5" "MEDIUM" "Information leakage: F5 cookie $cookiename $cookievalue is default IPv6 pool member ${ip}:${port}" "$cve" "$cwe" - elif grep -Eq '^rd[0-9]{1,3}o[a-f0-9]{32}o[0-9]{1,5}' <<< "$cookievalue"; then - routed_domain="$(f5_determine_routeddomain "$cookievalue")" - offset=$(( 2 + ${#routed_domain} + 1 )) - port="${cookievalue##*o}" - ip="$(f5_hex2ip6 ${cookievalue:$offset:32})" - out "${spaces}F5 cookie (IPv6 pool in routed domain "; pr_svrty_medium "$routed_domain"; out "): "; pr_italic "$cookiename "; prln_svrty_medium "${ip}:${port}" - fileout "cookie_bigip_f5" "MEDIUM" "Information leakage: F5 cookie $cookiename $cookievalue is IPv6 pool member in routed domain $routed_domain ${ip}:${port}" "$cve" "$cwe" - elif grep -Eq '^\!.*=$' <<< "$cookievalue"; then - if [[ "${#cookievalue}" -eq 81 ]] ; then - savedcookies="${savedcookies} ${cookiename}=${cookievalue:1:79}" - out "${spaces}Encrypted F5 cookie named "; pr_italic "${cookiename}"; outln " detected" - fileout "cookie_bigip_f5" "INFO" "encrypted F5 cookie named ${cookiename}" - fi - fi - done <<< "$allcookies" -} - - -run_cookie_flags() { # ARG1: Path - local -i nr_cookies - local -i nr_httponly nr_secure - local negative_word - local msg302="" msg302_="" - local spaces=" " - - if [[ ! -s $HEADERFILE ]]; then - run_http_header "$1" || return 1 - fi - - if [[ ! "$HTTP_STATUS_CODE" =~ 20 ]]; then - if [[ "$HTTP_STATUS_CODE" =~ [301|302] ]]; then - msg302=" -- maybe better try target URL of 30x" - msg302_=" (30x detected, better try target URL of 30x)" - else - msg302=" -- HTTP status $HTTP_STATUS_CODE signals you maybe missed the web application" - msg302_=" (maybe missed the application)" - fi - fi - - pr_bold " Cookie(s) " - grep -ai '^Set-Cookie' $HEADERFILE >$TMPFILE - if [[ $? -ne 0 ]]; then - outln "(none issued at \"$1\")$msg302" - fileout "cookie_count" "INFO" "0 at \"$1\"$msg302_" - else - nr_cookies=$(count_lines "$(cat $TMPFILE)") - out "$nr_cookies issued: " - fileout "cookie_count" "INFO" "$nr_cookies at \"$1\"$msg302_" - if [[ $nr_cookies -gt 1 ]]; then - negative_word="NONE" - else - negative_word="NOT" - fi - nr_secure=$(grep -iac secure $TMPFILE) - case $nr_secure in - 0) pr_svrty_medium "$negative_word" ;; - [123456789]) pr_svrty_good "$nr_secure/$nr_cookies";; - esac - out " secure, " - if [[ $nr_cookies -eq $nr_secure ]]; then - fileout "cookie_secure" "OK" "All ($nr_cookies) at \"$1\" marked as secure" - else - fileout "cookie_secure" "INFO" "$nr_secure/$nr_cookies at \"$1\" marked as secure" - fi - nr_httponly=$(grep -cai httponly $TMPFILE) - case $nr_httponly in - 0) pr_svrty_medium "$negative_word" ;; - [123456789]) pr_svrty_good "$nr_httponly/$nr_cookies";; - esac - out " HttpOnly" - if [[ $nr_cookies -eq $nr_httponly ]]; then - fileout "cookie_httponly" "OK" "All ($nr_cookies) at \"$1\" marked as HttpOnly$msg302_" - else - fileout "cookie_httponly" "INFO" "$nr_secure/$nr_cookies at \"$1\" marked as HttpOnly$msg302_" - fi - outln "$msg302" - allcookies="$(awk '/[Ss][Ee][Tt]-[Cc][Oo][Oo][Kk][Ii][Ee]:/ { print $2 }' "$TMPFILE")" - sub_f5_bigip_check "$allcookies" "$spaces" - fi - - tmpfile_handle ${FUNCNAME[0]}.txt - return 0 -} - - -run_security_headers() { - local good_header="X-Frame-Options X-Content-Type-Options Content-Security-Policy X-Content-Security-Policy X-WebKit-CSP Content-Security-Policy-Report-Only Expect-CT" - local other_header="Access-Control-Allow-Origin Upgrade X-Served-By Referrer-Policy X-UA-Compatible Cache-Control Pragma X-XSS-Protection" - local header header_output - local first=true - local spaces=" " - local have_header=false - - if [[ ! -s $HEADERFILE ]]; then - run_http_header "$1" || return 1 - fi - - pr_bold " Security headers " - for header in $good_header; do - [[ "$DEBUG" -ge 5 ]] && echo "testing \"$header\"" - match_httpheader_key "$header" "$header" "$spaces" "$first" - if [[ $? -ge 1 ]]; then - have_header=true - if "$first"; then - first=false - fi - # Include $header when determining where to insert line breaks, but print $header - # separately. - pr_svrty_good "$header"; out ":" - header_output="$(out_row_aligned_max_width "${header:2} $HEADERVALUE" "$spaces " $TERM_WIDTH)" - outln "${header_output#${header:2}}" - fileout "$header" "OK" "$HEADERVALUE" - fi - done - - for header in $other_header; do - [[ "$DEBUG" -ge 5 ]] && echo "testing \"$header\"" - match_httpheader_key "$header" "$header" "$spaces" "$first" - if [[ $? -ge 1 ]]; then - have_header=true - if "$first"; then - first=false - fi - out "$header" - outln ": $HEADERVALUE" # shouldn't be that long - fileout "$header" "INFO" "$HEADERVALUE" - fi - done - #TODO: I am not testing for the correctness or anything stupid yet, e.g. "X-Frame-Options: allowall" or Access-Control-Allow-Origin: * - - if ! "$have_header"; then - prln_svrty_medium "--" - fileout "security_headers" "MEDIUM" "--" - fi - - tmpfile_handle ${FUNCNAME[0]}.txt - return 0 -} - - -# #1: string with 2 opensssl codes, output is same in NSS/ssllabs terminology -normalize_ciphercode() { - if [[ "${1:2:2}" == "00" ]]; then - tm_out "$(tolower "x${1:7:2}")" - else - tm_out "$(tolower "x${1:2:2}${1:7:2}${1:12:2}")" - fi - return 0 -} - -prettyprint_local() { - local arg line - local hexc hexcode dash ciph sslvers kx auth enc mac export - local re='^[0-9A-Fa-f]+$' - - if [[ "$1" == 0x* ]] || [[ "$1" == 0X* ]]; then - fatal "pls supply x<number> instead" $ERR_CMDLINE - fi - - if [[ -z "$1" ]]; then - pr_headline " Displaying all $OPENSSL_NR_CIPHERS local ciphers "; - else - pr_headline " Displaying all local ciphers "; - # pattern provided; which one? - [[ $1 =~ $re ]] && \ - pr_headline "matching number pattern \"$1\" " || \ - pr_headline "matching word pattern "\"$1\"" (ignore case) " - fi - outln "\n" - neat_header - - if [[ -z "$1" ]]; then - actually_supported_osslciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 'ALL' "-V" | while read -r hexcode dash ciph sslvers kx auth enc mac export ; do # -V doesn't work with openssl < 1.0 - hexc="$(normalize_ciphercode $hexcode)" - outln "$(neat_list "$hexc" "$ciph" "$kx" "$enc")" - done - else - #for arg in $(echo $@ | sed 's/,/ /g'); do - for arg in ${*//,/ /}; do - actually_supported_osslciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 'ALL' "-V" | while read -r hexcode dash ciph sslvers kx auth enc mac export ; do # -V doesn't work with openssl < 1.0 - hexc="$(normalize_ciphercode $hexcode)" - # for numbers we don't do word matching: - [[ $arg =~ $re ]] && \ - line="$(neat_list "$hexc" "$ciph" "$kx" "$enc" | grep -ai "$arg")" || \ - line="$(neat_list "$hexc" "$ciph" "$kx" "$enc" | grep -wai "$arg")" - [[ -n "$line" ]] && outln "$line" - done - done - fi - outln - return 0 -} - - -# Generic function for a rated output, no used yet. -# arg1: rating from 2 to -4 if available or not -# arg2: no/yes: decides whether positive or negative logic will be applied and "not" will be printed -# arg3: jsonID -# -rated_output() { - local jsonID=$3 - local logic="" - - if [[ $2 == no ]] || [[ $2 == negative ]]; then - logic="not " - fi - case $1 in - 2) pr_svrty_best "${logic}offered (OK)" - fileout "${jsonID}" "OK" "${logic}offered" - ;; - 1) pr_svrty_good "${logic}offered (OK)" - fileout "${jsonID}" "OK" "${logic}offered" - ;; - 0) out "${logic}offered" - fileout "${jsonID}" "INFO" "${logic}offered" - ;; - -1) pr_svrty_low "${logic}offered" - fileout "${jsonID}" "LOW" "${logic}offered" - ;; - -2) pr_svrty_medium "${logic}offered" - fileout "${jsonID}" "MEDIUM" "${logic}offered" - ;; - -3) pr_svrty_high "${logic}offered (NOT ok)" - fileout "${jsonID}" "HIGH" "${logic}offered" - ;; - -4) pr_svrty_critical "${logic}offered (NOT ok)" - fileout "${jsonID}" "CRITICAL" "${logic}offered" - ;; - *) pr_warning "FIXME: error around $LINENO, (please report this)" - fileout "${jsonID}" "WARN" "return condition $2 when $1 unclear" - return 1 - ;; - esac - return 0 -} - - -openssl2rfc() { - local rfcname="" - local -i i - - for (( i=0; i < TLS_NR_CIPHERS; i++ )); do - [[ "$1" == ${TLS_CIPHER_OSSL_NAME[i]} ]] && rfcname="${TLS_CIPHER_RFC_NAME[i]}" && break - done - [[ "$rfcname" == "-" ]] && rfcname="" - [[ -n "$rfcname" ]] && tm_out "$rfcname" - return 0 -} - -rfc2openssl() { - local ossl_name - local -i i - - for (( i=0; i < TLS_NR_CIPHERS; i++ )); do - [[ "$1" == ${TLS_CIPHER_RFC_NAME[i]} ]] && ossl_name="${TLS_CIPHER_OSSL_NAME[i]}" && break - done - [[ "$ossl_name" == "-" ]] && ossl_name="" - [[ -n "$ossl_name" ]] && tm_out "$ossl_name" - return 0 -} - -openssl2hexcode() { - local hexc="" - local -i i - - if [[ $TLS_NR_CIPHERS -eq 0 ]]; then - if "$HAS_CIPHERSUITES"; then - hexc="$($OPENSSL ciphers -V -ciphersuites "$TLS13_OSSL_CIPHERS" 'ALL:COMPLEMENTOFALL:@STRENGTH' | awk '/ '"$1"' / { print $1 }')" - else - hexc="$($OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL:@STRENGTH' | awk '/ '"$1"' / { print $1 }')" - fi - else - for (( i=0; i < TLS_NR_CIPHERS; i++ )); do - [[ "$1" == ${TLS_CIPHER_OSSL_NAME[i]} ]] && hexc="${TLS_CIPHER_HEXCODE[i]}" && break - done - fi - [[ -z "$hexc" ]] && return 1 - tm_out "$hexc" - return 0 -} - -rfc2hexcode() { - local hexc="" - local -i i - - for (( i=0; i < TLS_NR_CIPHERS; i++ )); do - [[ "$1" == "${TLS_CIPHER_RFC_NAME[i]}" ]] && hexc="${TLS_CIPHER_HEXCODE[i]}" && break - done - [[ -z "$hexc" ]] && return 1 - tm_out "$hexc" - return 0 -} - -show_rfc_style(){ - local rfcname="" hexcode - local -i i - - hexcode="$(toupper "$1")" - case ${#hexcode} in - 3) hexcode="0x00,0x${hexcode:1:2}" ;; - 5) hexcode="0x${hexcode:1:2},0x${hexcode:3:2}" ;; - 7) hexcode="0x${hexcode:1:2},0x${hexcode:3:2},0x${hexcode:5:2}" ;; - *) return 1 ;; - esac - for (( i=0; i < TLS_NR_CIPHERS; i++ )); do - [[ "$hexcode" == ${TLS_CIPHER_HEXCODE[i]} ]] && rfcname="${TLS_CIPHER_RFC_NAME[i]}" && break - done - [[ "$rfcname" == "-" ]] && rfcname="" - [[ -n "$rfcname" ]] && tm_out "$rfcname" - return 0 -} - -neat_header(){ - if [[ "$DISPLAY_CIPHERNAMES" =~ rfc ]]; then - out "$(printf -- "Hexcode Cipher Suite Name (IANA/RFC) KeyExch. Encryption Bits")" - [[ "$DISPLAY_CIPHERNAMES" != rfc-only ]] && out "$(printf -- " Cipher Suite Name (OpenSSL)")" - outln - out "$(printf -- "%s------------------------------------------------------------------------------------------")" - [[ "$DISPLAY_CIPHERNAMES" != rfc-only ]] && out "$(printf -- "---------------------------------------")" - outln - else - out "$(printf -- "Hexcode Cipher Suite Name (OpenSSL) KeyExch. Encryption Bits")" - [[ "$DISPLAY_CIPHERNAMES" != openssl-only ]] && out "$(printf -- " Cipher Suite Name (IANA/RFC)")" - outln - out "$(printf -- "%s--------------------------------------------------------------------------")" - [[ "$DISPLAY_CIPHERNAMES" != openssl-only ]] && out "$(printf -- "---------------------------------------------------")" - outln - fi -} - - -# arg1: hexcode -# arg2: cipher in openssl notation -# arg3: keyexchange -# arg4: encryption (maybe included "export") -# arg5: "true" if the cipher's "quality" should be highlighted -# "false" if the line should be printed in light grey -# empty if line should be returned as a string -neat_list(){ - local hexcode="$1" - local ossl_cipher="$2" tls_cipher="" - local kx enc strength line what_dh bits - local -i i len - - kx="${3//Kx=/}" - enc="${4//Enc=/}" - # In two cases LibreSSL uses very long names for encryption algorithms - # and doesn't include the number of bits. - [[ "$enc" == ChaCha20-Poly1305 ]] && enc="CHACHA20(256)" - [[ "$enc" == GOST-28178-89-CNT ]] && enc="GOST(256)" - - strength="${enc//\)/}" # retrieve (). first remove trailing ")" - strength="${strength#*\(}" # exfiltrate (VAL - enc="${enc%%\(*}" - - enc="${enc//POLY1305/}" # remove POLY1305 - enc="${enc//\//}" # remove "/" - - [[ "$export" =~ export ]] && strength="$strength,exp" - - [[ "$DISPLAY_CIPHERNAMES" != openssl-only ]] && tls_cipher="$(show_rfc_style "$hexcode")" - - if [[ "$5" != true ]]; then - if [[ "$DISPLAY_CIPHERNAMES" =~ rfc ]]; then - line="$(printf -- " %-7s %-49s %-10s %-12s%-8s" "$hexcode" "$tls_cipher" "$kx" "$enc" "$strength")" - [[ "$DISPLAY_CIPHERNAMES" != rfc-only ]] && line+="$(printf -- " %-33s${SHOW_EACH_C:+ %-0s}" "$ossl_cipher")" - else - line="$(printf -- " %-7s %-33s %-10s %-12s%-8s" "$hexcode" "$ossl_cipher" "$kx" "$enc" "$strength")" - [[ "$DISPLAY_CIPHERNAMES" != openssl-only ]] && line+="$(printf -- " %-49s${SHOW_EACH_C:+ %-0s}" "$tls_cipher")" - fi - if [[ -z "$5" ]]; then - tm_out "$line" - else - pr_deemphasize "$line" - fi - return 0 - fi - if [[ "$kx" =~ " " ]]; then - what_dh="${kx%% *}" - bits="${kx##* }" - else - what_dh="$kx" - bits="" - fi - if [[ "$COLOR" -le 2 ]]; then - if [[ "$DISPLAY_CIPHERNAMES" =~ rfc ]]; then - out "$(printf -- " %-7s %-49s " "$hexcode" "$tls_cipher")" - else - out "$(printf -- " %-7s %-33s " "$hexcode" "$ossl_cipher")" - fi - else - out "$(printf -- " %-7s " "$hexcode")" - if [[ "$DISPLAY_CIPHERNAMES" =~ rfc ]]; then - print_fixed_width "$tls_cipher" 49 pr_cipher_quality - else - print_fixed_width "$ossl_cipher" 33 pr_cipher_quality - fi - fi - out "$what_dh" - if [[ -n "$bits" ]]; then - if [[ $what_dh == DH ]] || [[ $what_dh == EDH ]]; then - pr_dh_quality "$bits" " $bits" - elif [[ $what_dh == ECDH ]]; then - pr_ecdh_quality "$bits" " $bits" - fi - fi - len=${#kx} - for (( i=len; i<10; i++ )); do - out " " - done - out "$(printf -- " %-12s%-8s " "$enc" "$strength")" - if [[ "$COLOR" -le 2 ]]; then - if [[ "$DISPLAY_CIPHERNAMES" == rfc ]]; then - out "$(printf -- "%-33s${SHOW_EACH_C:+ %-0s}" "$ossl_cipher")" - elif [[ "$DISPLAY_CIPHERNAMES" == openssl ]]; then - out "$(printf -- "%-49s${SHOW_EACH_C:+ %-0s}" "$tls_cipher")" - fi - else - if [[ "$DISPLAY_CIPHERNAMES" == rfc ]]; then - print_fixed_width "$ossl_cipher" 32 pr_cipher_quality - elif [[ "$DISPLAY_CIPHERNAMES" == openssl ]]; then - print_fixed_width "$tls_cipher" 48 pr_cipher_quality - fi - out "$(printf -- "${SHOW_EACH_C:+ %-0s}")" - fi -} - -run_cipher_match(){ - local hexc n auth export ciphers_to_test tls13_ciphers_to_test supported_sslv2_ciphers s - local -a hexcode normalized_hexcode ciph sslvers kx enc export2 sigalg - local -a ciphers_found ciphers_found2 ciph2 rfc_ciph rfc_ciph2 ossl_supported - local -a -i index - local -i nr_ciphers=0 nr_ossl_ciphers=0 nr_nonossl_ciphers=0 - local -i num_bundles mod_check bundle_size bundle end_of_bundle - local dhlen has_dh_bits="$HAS_DH_BITS" - local cipher proto protos_to_try - local available - local -i sclient_success - local re='^[0-9A-Fa-f]+$' - local using_sockets=true - - "$SSL_NATIVE" && using_sockets=false - "$FAST" && using_sockets=false - [[ $TLS_NR_CIPHERS == 0 ]] && using_sockets=false - - pr_headline " Testing ciphers with " - if [[ $1 =~ $re ]]; then - pr_headline "matching number pattern \"$1\" " - tjolines="$tjolines matching number pattern \"$1\"\n\n" - else - pr_headline "word pattern "\"$1\"" (ignore case) " - tjolines="$tjolines word pattern \"$1\" (ignore case)\n\n" - fi - outln - if ! "$using_sockets"; then - [[ $TLS_NR_CIPHERS == 0 ]] && ! "$SSL_NATIVE" && ! "$FAST" && pr_warning " Cipher mapping not available, doing a fallback to openssl" - if ! "$HAS_DH_BITS"; then - [[ $TLS_NR_CIPHERS == 0 ]] && ! "$SSL_NATIVE" && ! "$FAST" && out "." - prln_warning " (Your $OPENSSL cannot show DH/ECDH bits)" - fi - fi - outln - neat_header - #for arg in $(echo $@ | sed 's/,/ /g'); do - for arg in ${*//, /}; do - if "$using_sockets" || [[ $OSSL_VER_MAJOR -lt 1 ]]; then - for (( i=0; i < TLS_NR_CIPHERS; i++ )); do - hexc="${TLS_CIPHER_HEXCODE[i]}" - if [[ ${#hexc} -eq 9 ]]; then - hexcode[nr_ciphers]="${hexc:2:2},${hexc:7:2}" - if [[ "${hexc:2:2}" == "00" ]]; then - normalized_hexcode[nr_ciphers]="x${hexc:7:2}" - else - normalized_hexcode[nr_ciphers]="x${hexc:2:2}${hexc:7:2}" - fi - else - hexc="$(tolower "$hexc")" - hexcode[nr_ciphers]="${hexc:2:2},${hexc:7:2},${hexc:12:2}" - normalized_hexcode[nr_ciphers]="x${hexc:2:2}${hexc:7:2}${hexc:12:2}" - fi - if [[ $arg =~ $re ]]; then - neat_list "${normalized_hexcode[nr_ciphers]}" "${TLS_CIPHER_OSSL_NAME[i]}" "${TLS_CIPHER_KX[i]}" "${TLS_CIPHER_ENC[i]}" | grep -qai "$arg" - else - neat_list "${normalized_hexcode[nr_ciphers]}" "${TLS_CIPHER_OSSL_NAME[i]}" "${TLS_CIPHER_KX[i]}" "${TLS_CIPHER_ENC[i]}" | grep -qwai "$arg" - fi - if [[ $? -eq 0 ]] && ( "$using_sockets" || "${TLS_CIPHER_OSSL_SUPPORTED[i]}" ); then # string matches, so we can ssl to it: - normalized_hexcode[nr_ciphers]="$(tolower "${normalized_hexcode[nr_ciphers]}")" - ciph[nr_ciphers]="${TLS_CIPHER_OSSL_NAME[i]}" - rfc_ciph[nr_ciphers]="${TLS_CIPHER_RFC_NAME[i]}" - kx[nr_ciphers]="${TLS_CIPHER_KX[i]}" - enc[nr_ciphers]="${TLS_CIPHER_ENC[i]}" - sslvers[nr_ciphers]="${TLS_CIPHER_SSLVERS[i]}" - export2[nr_ciphers]="${TLS_CIPHER_EXPORT[i]}" - ciphers_found[nr_ciphers]=false - sigalg[nr_ciphers]="" - ossl_supported[nr_ciphers]="${TLS_CIPHER_OSSL_SUPPORTED[i]}" - if "$using_sockets" && ! "$has_dh_bits" && \ - ( [[ ${kx[nr_ciphers]} == "Kx=ECDH" ]] || [[ ${kx[nr_ciphers]} == "Kx=DH" ]] || [[ ${kx[nr_ciphers]} == "Kx=EDH" ]] ); then - ossl_supported[nr_ciphers]=false - fi - nr_ciphers+=1 - fi - done - else - while read hexc n ciph[nr_ciphers] sslvers[nr_ciphers] kx[nr_ciphers] auth enc[nr_ciphers] mac export2[nr_ciphers]; do - hexc="$(normalize_ciphercode $hexc)" - # is argument a number? - if [[ $arg =~ $re ]]; then - neat_list "$hexc" "${ciph[nr_ciphers]}" "${kx[nr_ciphers]}" "${enc[nr_ciphers]}" | grep -qai "$arg" - else - neat_list "$hexc" "${ciph[nr_ciphers]}" "${kx[nr_ciphers]}" "${enc[nr_ciphers]}" | grep -qwai "$arg" - fi - if [[ $? -eq 0 ]]; then # string matches, so we can ssl to it: - ciphers_found[nr_ciphers]=false - normalized_hexcode[nr_ciphers]="$hexc" - sigalg[nr_ciphers]="" - ossl_supported[nr_ciphers]=true - nr_ciphers+=1 - fi - done < <(actually_supported_osslciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 'ALL' "-V") - fi - - # Test the SSLv2 ciphers, if any. - if "$using_sockets"; then - ciphers_to_test="" - for (( i=0; i < nr_ciphers; i++ )); do - if [[ "${sslvers[i]}" == SSLv2 ]]; then - ciphers_to_test+=", ${hexcode[i]}" - fi - done - if [[ -n "$ciphers_to_test" ]]; then - sslv2_sockets "${ciphers_to_test:2}" "true" - if [[ $? -eq 3 ]] && [[ "$V2_HELLO_CIPHERSPEC_LENGTH" -ne 0 ]]; then - supported_sslv2_ciphers="$(grep "Supported cipher: " "$TEMPDIR/$NODEIP.parse_sslv2_serverhello.txt")" - "$SHOW_SIGALGO" && s="$(read_sigalg_from_file "$HOSTCERT")" - for (( i=0 ; i<nr_ciphers; i++ )); do - if [[ "${sslvers[i]}" == SSLv2 ]] && [[ "$supported_sslv2_ciphers" =~ ${normalized_hexcode[i]} ]]; then - ciphers_found[i]=true - "$SHOW_SIGALGO" && sigalg[i]="$s" - fi - done - fi - fi - else - ciphers_to_test="" - for (( i=0; i < nr_ciphers; i++ )); do - if [[ "${sslvers[i]}" == SSLv2 ]]; then - ciphers_to_test+=":${ciph[i]}" - fi - done - if [[ -n "$ciphers_to_test" ]]; then - $OPENSSL s_client -cipher "${ciphers_to_test:1}" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY -ssl2 >$TMPFILE 2>$ERRFILE </dev/null - sclient_connect_successful $? "$TMPFILE" - if [[ $? -eq 0 ]]; then - supported_sslv2_ciphers="$(grep -A 4 "Ciphers common between both SSL endpoints:" $TMPFILE)" - "$SHOW_SIGALGO" && s="$(read_sigalg_from_file "$TMPFILE")" - for (( i=0 ; i<nr_ciphers; i++ )); do - if [[ "${sslvers[i]}" == SSLv2 ]] && [[ "$supported_sslv2_ciphers" =~ ${ciph[i]} ]]; then - ciphers_found[i]=true - "$SHOW_SIGALGO" && sigalg[i]="$s" - fi - done - fi - fi - fi - - for (( i=0; i < nr_ciphers; i++ )); do - if "${ossl_supported[i]}" && [[ "${sslvers[i]}" != "SSLv2" ]]; then - ciphers_found2[nr_ossl_ciphers]=false - ciph2[nr_ossl_ciphers]="${ciph[i]}" - index[nr_ossl_ciphers]=$i - nr_ossl_ciphers+=1 - fi - done - if [[ $nr_ossl_ciphers -eq 0 ]]; then - num_bundles=0 - else - # Some servers can't handle a handshake with >= 128 ciphers. So, - # test cipher suites in bundles of 128 or less. - num_bundles=$nr_ossl_ciphers/128 - mod_check=$nr_ossl_ciphers%128 - [[ $mod_check -ne 0 ]] && num_bundles=$num_bundles+1 - - bundle_size=$nr_ossl_ciphers/$num_bundles - mod_check=$nr_ossl_ciphers%$num_bundles - [[ $mod_check -ne 0 ]] && bundle_size+=1 - fi - - if "$HAS_TLS13"; then - protos_to_try="-no_ssl2 -tls1_2 -tls1_1 -tls1" - else - protos_to_try="-no_ssl2 -tls1_1 -tls1" - fi - "$HAS_SSL3" && protos_to_try+=" -ssl3" - - for proto in $protos_to_try; do - if [[ "$proto" == -tls1_1 ]]; then - num_bundles=1 - bundle_size=$nr_ossl_ciphers - fi - for (( bundle=0; bundle < num_bundles; bundle++ )); do - end_of_bundle=$bundle*$bundle_size+$bundle_size - [[ $end_of_bundle -gt $nr_ossl_ciphers ]] && end_of_bundle=$nr_ossl_ciphers - while true; do - ciphers_to_test="" - tls13_ciphers_to_test="" - for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do - if ! "${ciphers_found2[i]}"; then - if [[ "${ciph2[i]}" == TLS13* ]] || [[ "${ciph2[i]}" == TLS_* ]]; then - tls13_ciphers_to_test+=":${ciph2[i]}" - else - ciphers_to_test+=":${ciph2[i]}" - fi - fi - done - [[ -z "$ciphers_to_test" ]] && [[ -z "$tls13_ciphers_to_test" ]] && break - $OPENSSL s_client $(s_client_options "$proto -cipher "\'${ciphers_to_test:1}\'" -ciphersuites "\'${tls13_ciphers_to_test:1}\'" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE </dev/null - sclient_connect_successful $? "$TMPFILE" || break - cipher=$(get_cipher $TMPFILE) - [[ -z "$cipher" ]] && break - for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do - [[ "$cipher" == "${ciph2[i]}" ]] && ciphers_found2[i]=true && break - done - [[ $i -eq $end_of_bundle ]] && break - i=${index[i]} - ciphers_found[i]=true - if [[ "$cipher" == TLS13* ]] || [[ "$cipher" == TLS_* ]]; then - kx[i]="$(read_dhtype_from_file $TMPFILE)" - fi - if [[ ${kx[i]} == "Kx=ECDH" ]] || [[ ${kx[i]} == "Kx=DH" ]] || [[ ${kx[i]} == "Kx=EDH" ]]; then - dhlen=$(read_dhbits_from_file "$TMPFILE" quiet) - kx[i]="${kx[i]} $dhlen" - fi - "$SHOW_SIGALGO" && grep -q "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TMPFILE && \ - sigalg[i]="$(read_sigalg_from_file "$TMPFILE")" - done - done - done - - if "$using_sockets"; then - for (( i=0; i < nr_ciphers; i++ )); do - if ! "${ciphers_found[i]}" && [[ "${sslvers[i]}" != "SSLv2" ]]; then - ciphers_found2[nr_nonossl_ciphers]=false - hexcode2[nr_nonossl_ciphers]="${hexcode[i]}" - rfc_ciph2[nr_nonossl_ciphers]="${rfc_ciph[i]}" - index[nr_nonossl_ciphers]=$i - nr_nonossl_ciphers+=1 - fi - done - fi - - if [[ $nr_nonossl_ciphers -eq 0 ]]; then - num_bundles=0 - else - # Some servers can't handle a handshake with >= 128 ciphers. So, - # test cipher suites in bundles of 128 or less. - num_bundles=$nr_nonossl_ciphers/128 - mod_check=$nr_nonossl_ciphers%128 - [[ $mod_check -ne 0 ]] && num_bundles=$num_bundles+1 - - bundle_size=$nr_nonossl_ciphers/$num_bundles - mod_check=$nr_nonossl_ciphers%$num_bundles - [[ $mod_check -ne 0 ]] && bundle_size+=1 - fi - - for proto in 04 03 02 01 00; do - for (( bundle=0; bundle < num_bundles; bundle++ )); do - end_of_bundle=$bundle*$bundle_size+$bundle_size - [[ $end_of_bundle -gt $nr_nonossl_ciphers ]] && end_of_bundle=$nr_nonossl_ciphers - while true; do - ciphers_to_test="" - for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do - ! "${ciphers_found2[i]}" && ciphers_to_test+=", ${hexcode2[i]}" - done - [[ -z "$ciphers_to_test" ]] && break - [[ "$proto" == 04 ]] && [[ ! "$ciphers_to_test" =~ ,\ 13,[0-9a-f][0-9a-f] ]] && break - ciphers_to_test="$(strip_inconsistent_ciphers "$proto" "$ciphers_to_test")" - [[ -z "$ciphers_to_test" ]] && break - if "$SHOW_SIGALGO"; then - tls_sockets "$proto" "${ciphers_to_test:2}, 00,ff" "all" - else - tls_sockets "$proto" "${ciphers_to_test:2}, 00,ff" "ephemeralkey" - fi - sclient_success=$? - [[ $sclient_success -ne 0 ]] && [[ $sclient_success -ne 2 ]] && break - cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") - for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do - [[ "$cipher" == "${rfc_ciph2[i]}" ]] && ciphers_found2[i]=true && break - done - [[ $i -eq $end_of_bundle ]] && break - i=${index[i]} - ciphers_found[i]=true - [[ "${kx[i]}" == "Kx=any" ]] && kx[i]="$(read_dhtype_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")" - if [[ ${kx[i]} == "Kx=ECDH" ]] || [[ ${kx[i]} == "Kx=DH" ]] || [[ ${kx[i]} == "Kx=EDH" ]]; then - dhlen=$(read_dhbits_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" quiet) - kx[i]="${kx[i]} $dhlen" - fi - "$SHOW_SIGALGO" && [[ -r "$HOSTCERT" ]] && \ - sigalg[i]="$(read_sigalg_from_file "$HOSTCERT")" - done - done - done - - for (( i=0; i < nr_ciphers; i++ )); do - "${ciphers_found[i]}" || "$SHOW_EACH_C" || continue - export="${export2[i]}" - neat_list "${normalized_hexcode[i]}" "${ciph[i]}" "${kx[i]}" "${enc[i]}" "${ciphers_found[i]}" - available="" - if "$SHOW_EACH_C"; then - if "${ciphers_found[i]}"; then - available="available" - pr_cyan "available" - else - available="not a/v" - pr_deemphasize "not a/v" - fi - fi - outln "${sigalg[i]}" - fileout "cipher_${normalized_hexcode[i]}" "INFO" "$(neat_list "${normalized_hexcode[i]}" "${ciph[i]}" "${kx[i]}" "${enc[i]}") $available" - done - "$using_sockets" && HAS_DH_BITS="$has_dh_bits" - tmpfile_handle ${FUNCNAME[0]}.txt - done - outln - - tmpfile_handle ${FUNCNAME[0]}.txt - return 0 # this is a single test for a cipher -} - - - -# test for all ciphers locally configured (w/o distinguishing whether they are good or bad) -run_allciphers() { - local -i nr_ciphers_tested=0 nr_ciphers=0 nr_ossl_ciphers=0 nr_nonossl_ciphers=0 sclient_success=0 - local n auth mac export hexc sslv2_ciphers="" s - local -a normalized_hexcode hexcode ciph sslvers kx enc export2 sigalg ossl_supported - local -i i end_of_bundle bundle bundle_size num_bundles mod_check - local -a ciphers_found ciphers_found2 hexcode2 ciph2 rfc_ciph2 - local -i -a index - local proto protos_to_try - local dhlen available ciphers_to_test tls13_ciphers_to_test supported_sslv2_ciphers - local has_dh_bits="$HAS_DH_BITS" - local using_sockets=true - - "$SSL_NATIVE" && using_sockets=false - "$FAST" && using_sockets=false - [[ $TLS_NR_CIPHERS == 0 ]] && using_sockets=false - - # get a list of all the cipher suites to test - if "$using_sockets" || [[ $OSSL_VER_MAJOR -lt 1 ]]; then - for (( i=0; i < TLS_NR_CIPHERS; i++ )); do - hexc="$(tolower "${TLS_CIPHER_HEXCODE[i]}")" - ciph[i]="${TLS_CIPHER_OSSL_NAME[i]}" - sslvers[i]="${TLS_CIPHER_SSLVERS[i]}" - kx[i]="${TLS_CIPHER_KX[i]}" - enc[i]="${TLS_CIPHER_ENC[i]}" - export2[i]="${TLS_CIPHER_EXPORT[i]}" - ciphers_found[i]=false - sigalg[i]="" - ossl_supported[i]=${TLS_CIPHER_OSSL_SUPPORTED[i]} - if "$using_sockets" && ! "$HAS_DH_BITS" && ( [[ ${kx[i]} == Kx=ECDH ]] || [[ ${kx[i]} == Kx=DH ]] || [[ ${kx[i]} == Kx=EDH ]] ); then - ossl_supported[i]=false - fi - if [[ ${#hexc} -eq 9 ]]; then - hexcode[i]="${hexc:2:2},${hexc:7:2}" - if [[ "${hexc:2:2}" == 00 ]]; then - normalized_hexcode[i]="x${hexc:7:2}" - else - normalized_hexcode[i]="x${hexc:2:2}${hexc:7:2}" - fi - else - hexcode[i]="${hexc:2:2},${hexc:7:2},${hexc:12:2}" - normalized_hexcode[i]="x${hexc:2:2}${hexc:7:2}${hexc:12:2}" - sslv2_ciphers="$sslv2_ciphers, ${hexcode[i]}" - fi - if "$using_sockets" || "${TLS_CIPHER_OSSL_SUPPORTED[i]}"; then - nr_ciphers_tested+=1 - fi - done - nr_ciphers=$TLS_NR_CIPHERS - else - while read -r hexc n ciph[nr_ciphers] sslvers[nr_ciphers] kx[nr_ciphers] auth enc[nr_ciphers] mac export2[nr_ciphers]; do - ciphers_found[nr_ciphers]=false - if [[ ${#hexc} -eq 9 ]]; then - if [[ "${hexc:2:2}" == 00 ]]; then - normalized_hexcode[nr_ciphers]="$(tolower "x${hexc:7:2}")" - else - normalized_hexcode[nr_ciphers]="$(tolower "x${hexc:2:2}${hexc:7:2}")" - fi - else - normalized_hexcode[nr_ciphers]="$(tolower "x${hexc:2:2}${hexc:7:2}${hexc:12:2}")" - fi - sigalg[nr_ciphers]="" - ossl_supported[nr_ciphers]=true - nr_ciphers=$nr_ciphers+1 - done < <(actually_supported_osslciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 'ALL' "-V") - nr_ciphers_tested=$nr_ciphers - fi - - if "$using_sockets"; then - sslv2_sockets "${sslv2_ciphers:2}" "true" - if [[ $? -eq 3 ]] && [[ "$V2_HELLO_CIPHERSPEC_LENGTH" -ne 0 ]]; then - supported_sslv2_ciphers="$(grep "Supported cipher: " "$TEMPDIR/$NODEIP.parse_sslv2_serverhello.txt")" - "$SHOW_SIGALGO" && s="$(read_sigalg_from_file "$HOSTCERT")" - for (( i=0 ; i<nr_ciphers; i++ )); do - if [[ "${sslvers[i]}" == SSLv2 ]] && [[ "$supported_sslv2_ciphers" =~ ${normalized_hexcode[i]} ]]; then - ciphers_found[i]=true - "$SHOW_SIGALGO" && sigalg[i]="$s" - fi - done - fi - elif "$HAS_SSL2"; then - $OPENSSL s_client $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY -ssl2 >$TMPFILE 2>$ERRFILE </dev/null - sclient_connect_successful $? "$TMPFILE" - if [[ $? -eq 0 ]]; then - supported_sslv2_ciphers="$(grep -A 4 "Ciphers common between both SSL endpoints:" $TMPFILE)" - "$SHOW_SIGALGO" && s="$(read_sigalg_from_file "$TMPFILE")" - for (( i=0 ; i<nr_ciphers; i++ )); do - if [[ "${sslvers[i]}" == SSLv2 ]] && [[ "$supported_sslv2_ciphers" =~ ${ciph[i]} ]]; then - ciphers_found[i]=true - "$SHOW_SIGALGO" && sigalg[i]="$s" - fi - done - fi - fi - - outln - if "$using_sockets"; then - pr_headlineln " Testing $nr_ciphers_tested ciphers via OpenSSL plus sockets against the server, ordered by encryption strength " - else - pr_headlineln " Testing all $nr_ciphers_tested locally available ciphers against the server, ordered by encryption strength " - [[ $TLS_NR_CIPHERS == 0 ]] && ! "$SSL_NATIVE" && ! "$FAST" && pr_warning " Cipher mapping not available, doing a fallback to openssl" - outln - if ! "$HAS_DH_BITS"; then - [[ $TLS_NR_CIPHERS == 0 ]] && ! "$SSL_NATIVE" && ! "$FAST" && out "." - prln_warning " Your $OPENSSL cannot show DH/ECDH bits" - fi - fi - outln - neat_header - - for (( i=0; i < nr_ciphers; i++ )); do - if "${ossl_supported[i]}"; then - [[ "${sslvers[i]}" == SSLv2 ]] && continue - ciphers_found2[nr_ossl_ciphers]=false - ciph2[nr_ossl_ciphers]="${ciph[i]}" - index[nr_ossl_ciphers]=$i - nr_ossl_ciphers+=1 - fi - done - - if [[ $nr_ossl_ciphers -eq 0 ]]; then - num_bundles=0 - else - # Some servers can't handle a handshake with >= 128 ciphers. So, - # test cipher suites in bundles of 128 or less. - num_bundles=$nr_ossl_ciphers/128 - mod_check=$nr_ossl_ciphers%128 - [[ $mod_check -ne 0 ]] && num_bundles=$num_bundles+1 - - bundle_size=$nr_ossl_ciphers/$num_bundles - mod_check=$nr_ossl_ciphers%$num_bundles - [[ $mod_check -ne 0 ]] && bundle_size+=1 - fi - - if "$HAS_TLS13"; then - protos_to_try="-no_ssl2 -tls1_2 -tls1_1 -tls1" - else - protos_to_try="-no_ssl2 -tls1_1 -tls1" - fi - "$HAS_SSL3" && protos_to_try+=" -ssl3" - - for proto in $protos_to_try; do - if [[ "$proto" == -tls1_1 ]]; then - num_bundles=1 - bundle_size=$nr_ossl_ciphers - fi - - [[ "$proto" != "-no_ssl2" ]] && [[ $(has_server_protocol "${proto:1}") -eq 1 ]] && continue - for (( bundle=0; bundle < num_bundles; bundle++ )); do - end_of_bundle=$bundle*$bundle_size+$bundle_size - [[ $end_of_bundle -gt $nr_ossl_ciphers ]] && end_of_bundle=$nr_ossl_ciphers - while true; do - ciphers_to_test="" - tls13_ciphers_to_test="" - for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do - if ! "${ciphers_found2[i]}"; then - if [[ "${ciph2[i]}" == TLS13* ]] || [[ "${ciph2[i]}" == TLS_* ]]; then - tls13_ciphers_to_test+=":${ciph2[i]}" - else - ciphers_to_test+=":${ciph2[i]}" - fi - fi - done - [[ -z "$ciphers_to_test" ]] && [[ -z "$tls13_ciphers_to_test" ]] && break - $OPENSSL s_client $(s_client_options "$proto -cipher "\'${ciphers_to_test:1}\'" -ciphersuites "\'${tls13_ciphers_to_test:1}\'" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE </dev/null - sclient_connect_successful $? "$TMPFILE" || break - cipher=$(get_cipher $TMPFILE) - [[ -z "$cipher" ]] && break - for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do - [[ "$cipher" == "${ciph2[i]}" ]] && ciphers_found2[i]=true && break - done - [[ $i -eq $end_of_bundle ]] && break - i=${index[i]} - ciphers_found[i]=true - if [[ "$cipher" == TLS13* ]] || [[ "$cipher" == TLS_* ]]; then - kx[i]="$(read_dhtype_from_file $TMPFILE)" - fi - if [[ ${kx[i]} == Kx=ECDH ]] || [[ ${kx[i]} == Kx=DH ]] || [[ ${kx[i]} == Kx=EDH ]]; then - dhlen=$(read_dhbits_from_file "$TMPFILE" quiet) - kx[i]="${kx[i]} $dhlen" - fi - "$SHOW_SIGALGO" && grep -q "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TMPFILE && \ - sigalg[i]="$(read_sigalg_from_file "$TMPFILE")" - done - done - done - - if "$using_sockets"; then - for (( i=0; i < nr_ciphers; i++ )); do - if ! "${ciphers_found[i]}"; then - [[ "${sslvers[i]}" == SSLv2 ]] && continue - ciphers_found2[nr_nonossl_ciphers]=false - hexcode2[nr_nonossl_ciphers]="${hexcode[i]}" - rfc_ciph2[nr_nonossl_ciphers]="${TLS_CIPHER_RFC_NAME[i]}" - index[nr_nonossl_ciphers]=$i - nr_nonossl_ciphers+=1 - fi - done - fi - - if [[ $nr_nonossl_ciphers -eq 0 ]]; then - num_bundles=0 - else - # Some servers can't handle a handshake with >= 128 ciphers. So, - # test cipher suites in bundles of 128 or less. - num_bundles=$nr_nonossl_ciphers/128 - mod_check=$nr_nonossl_ciphers%128 - [[ $mod_check -ne 0 ]] && num_bundles=$num_bundles+1 - - bundle_size=$nr_nonossl_ciphers/$num_bundles - mod_check=$nr_nonossl_ciphers%$num_bundles - [[ $mod_check -ne 0 ]] && bundle_size+=1 - fi - - for proto in 04 03 02 01 00; do - for (( bundle=0; bundle < num_bundles; bundle++ )); do - end_of_bundle=$bundle*$bundle_size+$bundle_size - [[ $end_of_bundle -gt $nr_nonossl_ciphers ]] && end_of_bundle=$nr_nonossl_ciphers - while true; do - ciphers_to_test="" - for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do - ! "${ciphers_found2[i]}" && ciphers_to_test+=", ${hexcode2[i]}" - done - [[ -z "$ciphers_to_test" ]] && break - [[ "$proto" == 04 ]] && [[ ! "$ciphers_to_test" =~ ,\ 13,[0-9a-f][0-9a-f] ]] && break - ciphers_to_test="$(strip_inconsistent_ciphers "$proto" "$ciphers_to_test")" - [[ -z "$ciphers_to_test" ]] && break - if "$SHOW_SIGALGO"; then - tls_sockets "$proto" "${ciphers_to_test:2}, 00,ff" "all" - else - tls_sockets "$proto" "${ciphers_to_test:2}, 00,ff" "ephemeralkey" - fi - sclient_success=$? - [[ $sclient_success -ne 0 ]] && [[ $sclient_success -ne 2 ]] && break - cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") - for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do - [[ "$cipher" == "${rfc_ciph2[i]}" ]] && ciphers_found2[i]=true && break - done - [[ $i -eq $end_of_bundle ]] && break - i=${index[i]} - ciphers_found[i]=true - [[ "${kx[i]}" == "Kx=any" ]] && kx[i]="$(read_dhtype_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")" - if [[ ${kx[i]} == "Kx=ECDH" ]] || [[ ${kx[i]} == "Kx=DH" ]] || [[ ${kx[i]} == "Kx=EDH" ]]; then - dhlen=$(read_dhbits_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" quiet) - kx[i]="${kx[i]} $dhlen" - fi - "$SHOW_SIGALGO" && [[ -r "$HOSTCERT" ]] && sigalg[i]="$(read_sigalg_from_file "$HOSTCERT")" - done - done - done - - for (( i=0 ; i<nr_ciphers; i++ )); do - if "${ciphers_found[i]}" || ( "$SHOW_EACH_C" && ( "$using_sockets" || "${ossl_supported[i]}" ) ); then - export=${export2[i]} - neat_list "${normalized_hexcode[i]}" "${ciph[i]}" "${kx[i]}" "${enc[i]}" "${ciphers_found[i]}" - available="" - if "$SHOW_EACH_C"; then - if ${ciphers_found[i]}; then - available="available" - pr_cyan "$available" - else - available="not a/v" - pr_deemphasize "$available" - fi - fi - outln "${sigalg[i]}" - fileout "cipher_${normalized_hexcode[i]}" "INFO" "$(neat_list "${normalized_hexcode[i]}" "${ciph[i]}" "${kx[i]}" "${enc[i]}") $available" - fi - done - "$using_sockets" && HAS_DH_BITS="$has_dh_bits" - - outln - [[ $sclient_success -ge 6 ]] && return 1 - return 0 -} - -# test for all ciphers per protocol locally configured (w/o distinguishing whether they are good or bad) -# for the specified protocol, test for all ciphers locally configured (w/o distinguishing whether they -# are good or bad) and list them in order to encryption strength. -ciphers_by_strength() { - local proto="$1" proto_hex="$2" proto_text="$3" - local using_sockets="$4" - local ossl_ciphers_proto - local -i nr_ciphers nr_ossl_ciphers nr_nonossl_ciphers success - local n sslvers auth mac export hexc sslv2_ciphers="" cipher - local -a hexcode normalized_hexcode ciph rfc_ciph kx enc export2 - local -a hexcode2 ciph2 rfc_ciph2 - local -i i bundle end_of_bundle bundle_size num_bundles mod_check - local -a ciphers_found ciphers_found2 sigalg ossl_supported index - local dhlen supported_sslv2_ciphers ciphers_to_test tls13_ciphers_to_test addcmd temp - local available - local id - local has_dh_bits="$HAS_DH_BITS" - - pr_underline "$(printf -- "%b" "$proto_text")" - # for local problem if it happens - out " " - if ! "$using_sockets" && ! locally_supported "$proto"; then - return 0 - fi - outln - - [[ $(has_server_protocol "${proto:1}") -eq 1 ]] && return 0 - - # get a list of all the cipher suites to test - nr_ciphers=0 - if "$using_sockets" || [[ $OSSL_VER_MAJOR -lt 1 ]]; then - for (( i=0; i < TLS_NR_CIPHERS; i++ )); do - hexc="${TLS_CIPHER_HEXCODE[i]}" - ciph[nr_ciphers]="${TLS_CIPHER_OSSL_NAME[i]}" - rfc_ciph[nr_ciphers]="${TLS_CIPHER_RFC_NAME[i]}" - kx[nr_ciphers]="${TLS_CIPHER_KX[i]}" - enc[nr_ciphers]="${TLS_CIPHER_ENC[i]}" - export2[nr_ciphers]="${TLS_CIPHER_EXPORT[i]}" - ciphers_found[nr_ciphers]=false - sigalg[nr_ciphers]="" - ossl_supported[nr_ciphers]=${TLS_CIPHER_OSSL_SUPPORTED[i]} - if "$using_sockets" && ! "$has_dh_bits" && ( [[ ${kx[nr_ciphers]} == "Kx=ECDH" ]] || [[ ${kx[nr_ciphers]} == "Kx=DH" ]] || [[ ${kx[nr_ciphers]} == "Kx=EDH" ]] ); then - ossl_supported[nr_ciphers]=false - fi - if [[ ${#hexc} -eq 9 ]]; then - hexcode[nr_ciphers]="${hexc:2:2},${hexc:7:2}" - if [[ "${hexc:2:2}" == 00 ]]; then - normalized_hexcode[nr_ciphers]="x${hexc:7:2}" - else - normalized_hexcode[nr_ciphers]="x${hexc:2:2}${hexc:7:2}" - fi - else - hexc="$(tolower "$hexc")" - hexcode[nr_ciphers]="${hexc:2:2},${hexc:7:2},${hexc:12:2}" - normalized_hexcode[nr_ciphers]="x${hexc:2:2}${hexc:7:2}${hexc:12:2}" - fi - if ( "$using_sockets" || "${TLS_CIPHER_OSSL_SUPPORTED[i]}" ); then - if [[ ${#hexc} -eq 9 ]] && [[ "$proto_text" != SSLv2 ]]; then - if [[ "$proto_text" == TLS\ 1.3 ]]; then - [[ "${hexc:2:2}" == 13 ]] && nr_ciphers+=1 - elif [[ "$proto_text" == TLS\ 1.2 ]]; then - [[ "${hexc:2:2}" != 13 ]] && nr_ciphers+=1 - elif [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SHA256 ]] && [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SHA384 ]] && \ - [[ "${TLS_CIPHER_RFC_NAME[i]}" != *_CCM ]] && [[ "${TLS_CIPHER_RFC_NAME[i]}" != *_CCM_8 ]]; then - nr_ciphers+=1 - fi - elif [[ ${#hexc} -eq 14 ]] && [[ "$proto_text" == SSLv2 ]]; then - sslv2_ciphers+=", ${hexcode[nr_ciphers]}" - nr_ciphers+=1 - fi - fi - done - else # no sockets, openssl! - # The OpenSSL ciphers function, prior to version 1.1.0, could only understand -ssl2, -ssl3, and -tls1. - if [[ "$OSSL_NAME" =~ LibreSSL ]]; then - ossl_ciphers_proto="" - elif [[ "$proto" == -ssl2 ]] || [[ "$proto" == -ssl3 ]] || \ - [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 1.1.0* ]] || [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 1.1.1* ]] || \ - [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 3.0.0* ]]; then - ossl_ciphers_proto="$proto" - else - ossl_ciphers_proto="-tls1" - fi - while read hexc n ciph[nr_ciphers] sslvers kx[nr_ciphers] auth enc[nr_ciphers] mac export2[nr_ciphers]; do - if [[ "$proto_text" == TLS\ 1.3 ]]; then - [[ "${ciph[nr_ciphers]}" == TLS13* ]] || [[ "${ciph[nr_ciphers]}" == TLS_* ]] || continue - elif [[ "$proto_text" == "TLS 1.2" ]]; then - if [[ "${ciph[nr_ciphers]}" == TLS13* ]] || [[ "${ciph[nr_ciphers]}" == TLS_* ]]; then - continue - fi - elif [[ "${ciph[nr_ciphers]}" == *-SHA256 ]] || [[ "${ciph[nr_ciphers]}" == *-SHA384 ]] || \ - [[ "${ciph[nr_ciphers]}" == *-CCM ]] || [[ "${ciph[nr_ciphers]}" == *-CCM8 ]] || \ - [[ "${ciph[nr_ciphers]}" =~ CHACHA20-POLY1305 ]]; then - continue - fi - ciphers_found[nr_ciphers]=false - normalized_hexcode[nr_ciphers]="$(normalize_ciphercode "$hexc")" - sigalg[nr_ciphers]="" - ossl_supported[nr_ciphers]=true - nr_ciphers+=1 - done < <(actually_supported_osslciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 'ALL' "$ossl_ciphers_proto -V") - fi - - if [[ "$proto" == -ssl2 ]]; then - if "$using_sockets"; then - sslv2_sockets "${sslv2_ciphers:2}" "true" - if [[ $? -eq 3 ]] && [[ "$V2_HELLO_CIPHERSPEC_LENGTH" -ne 0 ]]; then - supported_sslv2_ciphers="$(grep "Supported cipher: " "$TEMPDIR/$NODEIP.parse_sslv2_serverhello.txt")" - "$SHOW_SIGALGO" && s="$(read_sigalg_from_file "$HOSTCERT")" - for (( i=0 ; i<nr_ciphers; i++ )); do - if [[ "$supported_sslv2_ciphers" =~ ${normalized_hexcode[i]} ]]; then - ciphers_found[i]=true - "$SHOW_SIGALGO" && sigalg[i]="$s" - fi - done - fi - else - $OPENSSL s_client $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY -ssl2 >$TMPFILE 2>$ERRFILE </dev/null - sclient_connect_successful $? "$TMPFILE" - if [[ $? -eq 0 ]]; then - supported_sslv2_ciphers="$(grep -A 4 "Ciphers common between both SSL endpoints:" $TMPFILE)" - "$SHOW_SIGALGO" && s="$(read_sigalg_from_file "$TMPFILE")" - for (( i=0 ; i<nr_ciphers; i++ )); do - if [[ "$supported_sslv2_ciphers" =~ ${ciph[i]} ]]; then - ciphers_found[i]=true - "$SHOW_SIGALGO" && sigalg[i]="$s" - fi - done - fi - fi - else # no SSLv2 - nr_ossl_ciphers=0 - if ( "$HAS_SSL3" || [[ $proto != -ssl3 ]] ) && ( "$HAS_TLS13" || [[ $proto != -tls1_3 ]] ); then - for (( i=0; i < nr_ciphers; i++ )); do - if "${ossl_supported[i]}"; then - ciphers_found2[nr_ossl_ciphers]=false - ciph2[nr_ossl_ciphers]="${ciph[i]}" - index[nr_ossl_ciphers]=$i - nr_ossl_ciphers+=1 - fi - done - fi - if [[ $nr_ossl_ciphers -eq 0 ]]; then - num_bundles=0 - else - # Some servers can't handle a handshake with >= 128 ciphers. So, - # test cipher suites in bundles of 128 or less. - num_bundles=$nr_ossl_ciphers/128 - mod_check=$nr_ossl_ciphers%128 - [[ $mod_check -ne 0 ]] && num_bundles=$num_bundles+1 - - bundle_size=$nr_ossl_ciphers/$num_bundles - mod_check=$nr_ossl_ciphers%$num_bundles - [[ $mod_check -ne 0 ]] && bundle_size+=1 - fi - - for (( bundle=0; bundle < num_bundles; bundle++ )); do - end_of_bundle=$bundle*$bundle_size+$bundle_size - [[ $end_of_bundle -gt $nr_ossl_ciphers ]] && end_of_bundle=$nr_ossl_ciphers - for (( success=0; success==0 ; 1 )); do - ciphers_to_test="" - tls13_ciphers_to_test="" - for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do - if ! "${ciphers_found2[i]}"; then - if [[ "$proto" == -tls1_3 ]]; then - tls13_ciphers_to_test+=":${ciph2[i]}" - else - ciphers_to_test+=":${ciph2[i]}" - fi - fi - done - success=1 - if [[ -n "$ciphers_to_test" ]] || [[ -n "$tls13_ciphers_to_test" ]]; then - $OPENSSL s_client $(s_client_options "-cipher "\'${ciphers_to_test:1}\'" -ciphersuites "\'${tls13_ciphers_to_test:1}\'" $proto $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE </dev/null - sclient_connect_successful $? "$TMPFILE" - if [[ $? -eq 0 ]]; then - cipher=$(get_cipher $TMPFILE) - if [[ -n "$cipher" ]]; then - success=0 - for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do - [[ "$cipher" == "${ciph2[i]}" ]] && ciphers_found2[i]=true && break - done - i=${index[i]} - ciphers_found[i]=true - [[ "$proto_text" == TLS\ 1.3 ]] && kx[i]="$(read_dhtype_from_file $TMPFILE)" - if [[ ${kx[i]} == Kx=ECDH ]] || [[ ${kx[i]} == Kx=DH ]] || [[ ${kx[i]} == Kx=EDH ]]; then - dhlen=$(read_dhbits_from_file "$TMPFILE" quiet) - kx[i]="${kx[i]} $dhlen" - fi - "$SHOW_SIGALGO" && grep -q "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TMPFILE && \ - sigalg[i]="$(read_sigalg_from_file "$TMPFILE")" - fi - fi - fi - done - done - - if "$using_sockets"; then - nr_nonossl_ciphers=0 - for (( i=0; i < nr_ciphers; i++ )); do - if ! "${ciphers_found[i]}"; then - ciphers_found2[nr_nonossl_ciphers]=false - hexcode2[nr_nonossl_ciphers]="${hexcode[i]}" - rfc_ciph2[nr_nonossl_ciphers]="${rfc_ciph[i]}" - index[nr_nonossl_ciphers]=$i - nr_nonossl_ciphers+=1 - fi - done - fi - - if [[ $nr_nonossl_ciphers -eq 0 ]]; then - num_bundles=0 - else - # Some servers can't handle a handshake with >= 128 ciphers. So, - # test cipher suites in bundles of 128 or less. - num_bundles=$nr_nonossl_ciphers/128 - mod_check=$nr_nonossl_ciphers%128 - [[ $mod_check -ne 0 ]] && num_bundles=$num_bundles+1 - - bundle_size=$nr_nonossl_ciphers/$num_bundles - mod_check=$nr_nonossl_ciphers%$num_bundles - [[ $mod_check -ne 0 ]] && bundle_size+=1 - fi - - for (( bundle=0; bundle < num_bundles; bundle++ )); do - end_of_bundle=$bundle*$bundle_size+$bundle_size - [[ $end_of_bundle -gt $nr_nonossl_ciphers ]] && end_of_bundle=$nr_nonossl_ciphers - for (( success=0; success==0 ; 1 )); do - ciphers_to_test="" - for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do - ! "${ciphers_found2[i]}" && ciphers_to_test+=", ${hexcode2[i]}" - done - success=1 - if [[ -n "$ciphers_to_test" ]]; then - if "$SHOW_SIGALGO"; then - tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "all" - else - tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "ephemeralkey" - fi - if [[ $? -eq 0 ]]; then - success=0 - cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") - for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do - [[ "$cipher" == "${rfc_ciph2[i]}" ]] && ciphers_found2[i]=true && break - done - i=${index[i]} - ciphers_found[i]=true - [[ "$proto_text" == TLS\ 1.3 ]] && kx[i]="$(read_dhtype_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")" - if [[ ${kx[i]} == Kx=ECDH ]] || [[ ${kx[i]} == Kx=DH ]] || [[ ${kx[i]} == Kx=EDH ]]; then - dhlen=$(read_dhbits_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" quiet) - kx[i]="${kx[i]} $dhlen" - fi - "$SHOW_SIGALGO" && [[ -r "$HOSTCERT" ]] && \ - sigalg[i]="$(read_sigalg_from_file "$HOSTCERT")" - fi - fi - done - done - fi - - for (( i=0 ; i<nr_ciphers; i++ )); do - if "${ciphers_found[i]}" || "$SHOW_EACH_C"; then - export=${export2[i]} - normalized_hexcode[i]="$(tolower "${normalized_hexcode[i]}")" - neat_list "${normalized_hexcode[i]}" "${ciph[i]}" "${kx[i]}" "${enc[i]}" "${ciphers_found[i]}" - available="" - if "$SHOW_EACH_C"; then - if "${ciphers_found[i]}"; then - available="available" - pr_cyan "$available" - else - available="not a/v" - pr_deemphasize "$available" - fi - fi - outln "${sigalg[i]}" - id="cipher$proto" - id+="_${normalized_hexcode[i]}" - fileout "$id" "INFO" "$proto_text $(neat_list "${normalized_hexcode[i]}" "${ciph[i]}" "${kx[i]}" "${enc[i]}") $available" - fi - done - - "$using_sockets" && HAS_DH_BITS="$has_dh_bits" - tmpfile_handle ${FUNCNAME[0]}${proto}.txt - return 0 -#FIXME: no error condition -} - -# test for all ciphers per protocol locally configured (w/o distinguishing whether they are good or bad) -run_cipher_per_proto() { - local proto proto_hex proto_text - local using_sockets=true - - "$SSL_NATIVE" && using_sockets=false - "$FAST" && using_sockets=false - [[ $TLS_NR_CIPHERS == 0 ]] && using_sockets=false - - outln - if "$using_sockets"; then - pr_headlineln " Testing ciphers per protocol via OpenSSL plus sockets against the server, ordered by encryption strength " - else - pr_headlineln " Testing all locally available ciphers per protocol against the server, ordered by encryption strength " - [[ $TLS_NR_CIPHERS == 0 ]] && ! "$SSL_NATIVE" && ! "$FAST" && pr_warning " Cipher mapping not available, doing a fallback to openssl" - outln - if ! "$HAS_DH_BITS"; then - [[ $TLS_NR_CIPHERS == 0 ]] && ! "$SSL_NATIVE" && ! "$FAST" && out "." - prln_warning " (Your $OPENSSL cannot show DH/ECDH bits)" - fi - fi - outln - neat_header - echo -e " -ssl2 22 SSLv2\n -ssl3 00 SSLv3\n -tls1 01 TLS 1\n -tls1_1 02 TLS 1.1\n -tls1_2 03 TLS 1.2\n -tls1_3 04 TLS 1.3" | while read proto proto_hex proto_text; do - ciphers_by_strength "$proto" "$proto_hex" "$proto_text" "$using_sockets" - done - return 0 -#FIXME: no error condition -} - -# arg1 is an ASCII-HEX encoded SSLv3 or TLS ClientHello. -# arg2: new key_share extension (only present to response to HelloRetryRequest) -# arg3: cookie extension (if needed for response to HelloRetryRequest) -# -# This function may be used to either modify a ClientHello for client simulation -# or to create a second ClientHello in response to a HelloRetryRequest. -# If arg2 is present, then this is a response to a HelloRetryRequest, so the -# function replaces the key_share extension with arg2 and adds the cookie -# extension, if present. -# If arg2 is not present, then this is an initial ClientHello for client simulation. -# In this case, if the provided ClientHello contains a server name extension, -# then either: -# 1) replace it with one corresponding to $SNI; or -# 2) remove it, if $SNI is empty -modify_clienthello() { - local tls_handshake_ascii="$1" - local new_key_share="$2" cookie="$3" - local -i len offset tls_handshake_ascii_len len_all len_clienthello - local -i len_extensions len_extension - local tls_content_type tls_version_reclayer handshake_msg_type tls_clientversion - local tls_random tls_sid tls_cipher_suites tls_compression_methods - local tls_extensions="" extension_type len_extensions_hex - local len_servername hexdump_format_str servername_hexstr - local len_servername_hex len_sni_listlen len_sni_ext - local tls_client_hello len_clienthello_hex tls_handshake_ascii_len_hex - local sni_extension_found=false - - tls_handshake_ascii_len=${#tls_handshake_ascii} - - tls_content_type="${tls_handshake_ascii:0:2}" - tls_version_reclayer="${tls_handshake_ascii:2:4}" - len_all=$(hex2dec "${tls_handshake_ascii:6:4}") - - handshake_msg_type="${tls_handshake_ascii:10:2}" - len_clienthello=$(hex2dec "${tls_handshake_ascii:12:6}") - tls_clientversion="${tls_handshake_ascii:18:4}" - tls_random="${tls_handshake_ascii:22:64}" - len=2*$(hex2dec "${tls_handshake_ascii:86:2}")+2 - tls_sid="${tls_handshake_ascii:86:$len}" - offset=86+$len - - len=2*$(hex2dec "${tls_handshake_ascii:$offset:4}")+4 - tls_cipher_suites="${tls_handshake_ascii:$offset:$len}" - offset=$offset+$len - - len=2*$(hex2dec "${tls_handshake_ascii:$offset:2}")+2 - tls_compression_methods="${tls_handshake_ascii:$offset:$len}" - offset=$offset+$len - - if [[ $offset -ge $tls_handshake_ascii_len ]]; then - # No extensions - tm_out "$tls_handshake_ascii" - return 0 - fi - - len_extensions=2*$(hex2dec "${tls_handshake_ascii:$offset:4}") - offset+=4 - for (( 1; offset < tls_handshake_ascii_len; 1 )); do - extension_type="${tls_handshake_ascii:$offset:4}" - offset+=4 - len_extension=2*$(hex2dec "${tls_handshake_ascii:$offset:4}") - - if [[ "$extension_type" == 0000 ]] && [[ -z "$new_key_share" ]]; then - # If this is an initial ClientHello, then either remove - # the SNI extension or replace it with the correct server name. - sni_extension_found=true - if [[ -n "$SNI" ]]; then - # Create a server name extension that corresponds to $SNI - len_servername=${#NODE} - hexdump_format_str="$len_servername/1 \"%02x\"" - servername_hexstr=$(printf $NODE | hexdump -v -e "${hexdump_format_str}") - # convert lengths we need to fill in from dec to hex: - len_servername_hex=$(printf "%02x\n" $len_servername) - len_sni_listlen=$(printf "%02x\n" $((len_servername+3))) - len_sni_ext=$(printf "%02x\n" $((len_servername+5))) - tls_extensions+="000000${len_sni_ext}00${len_sni_listlen}0000${len_servername_hex}${servername_hexstr}" - fi - offset+=$len_extension+4 - elif [[ "$extension_type" != 00$KEY_SHARE_EXTN_NR ]] || [[ -z "$new_key_share" ]]; then - # If this is in response to a HelloRetryRequest, then do - # not copy over the old key_share extension, but - # all other extensions should be copied into the new ClientHello. - offset=$offset-4 - len=$len_extension+8 - tls_extensions+="${tls_handshake_ascii:$offset:$len}" - offset+=$len - else - # This is the key_share extension, and the modified ClientHello - # is being created in response to a HelloRetryRequest. Replace - # the existing key_share extension with the new one. - tls_extensions+="$new_key_share" - offset+=$len_extension+4 - fi - done - tls_extensions+="$cookie" - - if ! "$sni_extension_found" && [[ -z "$new_key_share" ]]; then - tm_out "$tls_handshake_ascii" - return 0 - fi - - len_extensions=${#tls_extensions}/2 - len_extensions_hex=$(printf "%02x\n" $len_extensions) - len2twobytes "$len_extensions_hex" - tls_extensions="${LEN_STR:0:2}${LEN_STR:4:2}${tls_extensions}" - - tls_client_hello="${tls_clientversion}${tls_random}${tls_sid}${tls_cipher_suites}${tls_compression_methods}${tls_extensions}" - len_clienthello=${#tls_client_hello}/2 - len_clienthello_hex=$(printf "%02x\n" $len_clienthello) - len2twobytes "$len_clienthello_hex" - tls_handshake_ascii="${handshake_msg_type}00${LEN_STR:0:2}${LEN_STR:4:2}${tls_client_hello}" - - tls_handshake_ascii_len=${#tls_handshake_ascii}/2 - tls_handshake_ascii_len_hex=$(printf "%02x\n" $tls_handshake_ascii_len) - len2twobytes "$tls_handshake_ascii_len_hex" - tls_handshake_ascii="${tls_content_type}${tls_version_reclayer}${LEN_STR:0:2}${LEN_STR:4:2}${tls_handshake_ascii}" - tm_out "$tls_handshake_ascii" - return 0 -} - -client_simulation_sockets() { - local -i len i ret=0 - local -i save=0 - local lines clienthello data="" - local cipher_list_2send="" - local sock_reply_file2 sock_reply_file3 - local tls_hello_ascii next_packet hello_done=0 - local -i sid_len offset1 offset2 - - if [[ "${1:0:4}" == 1603 ]]; then - clienthello="$(modify_clienthello "$1")" - TLS_CLIENT_HELLO="${clienthello:10}" - else - clienthello="$1" - TLS_CLIENT_HELLO="" - fi - len=${#clienthello} - for (( i=0; i < len; i=i+2 )); do - data+=", ${clienthello:i:2}" - done - # same as above. If a CIPHER_SUITES string was provided, then check that it is in the ServerHello - # this appeared 1st in yassl + MySQL (https://github.com/drwetter/testssl.sh/pull/784) but adds - # robustness to the implementation - # see also https://github.com/drwetter/testssl.sh/pull/797 - if [[ "${1:0:4}" == 1603 ]]; then - # Extract list of cipher suites from SSLv3 or later ClientHello - sid_len=4*$(hex2dec "${data:174:2}") - offset1=178+$sid_len - offset2=182+$sid_len - len=4*$(hex2dec "${data:offset1:2}${data:offset2:2}")-2 - offset1=186+$sid_len - code2network "$(tolower "${data:offset1:len}")" # convert CIPHER_SUITES to a "standardized" format - else - # Extract list of cipher suites from SSLv2 ClientHello - len=2*$(hex2dec "${clienthello:12:2}") - for (( i=22; i < 22+len; i=i+6 )); do - offset1=$i+2 - offset2=$i+4 - [[ "${clienthello:i:2}" == 00 ]] && cipher_list_2send+=", ${clienthello:offset1:2},${clienthello:offset2:2}" - done - code2network "$(tolower "${cipher_list_2send:2}")" # convert CIPHER_SUITES to a "standardized" format - fi - cipher_list_2send="$NW_STR" - - fd_socket 5 || return 6 - debugme echo -e "\nsending client hello... " - socksend_clienthello "${data}" - sleep $USLEEP_SND - - sockread_serverhello 32768 - tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE") - tls_hello_ascii="${tls_hello_ascii%%[!0-9A-F]*}" - - # Check if the response is a HelloRetryRequest. - resend_if_hello_retry_request "$clienthello" "$tls_hello_ascii" - ret=$? - if [[ $ret -eq 2 ]]; then - tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE") - tls_hello_ascii="${tls_hello_ascii%%[!0-9A-F]*}" - elif [[ $ret -eq 1 ]] || [[ $ret -eq 6 ]]; then - close_socket - TMPFILE=$SOCK_REPLY_FILE - tmpfile_handle ${FUNCNAME[0]}.dd - return $ret - fi - - if [[ "${tls_hello_ascii:0:1}" != "8" ]]; then - check_tls_serverhellodone "$tls_hello_ascii" "ephemeralkey" - hello_done=$? - fi - - for(( 1 ; hello_done==1; 1 )); do - if [[ $DEBUG -ge 1 ]]; then - sock_reply_file2=${SOCK_REPLY_FILE}.2 - mv "$SOCK_REPLY_FILE" "$sock_reply_file2" - fi - - debugme echo -n "requesting more server hello data... " - socksend "" $USLEEP_SND - sockread_serverhello 32768 - - next_packet=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE") - next_packet="${next_packet%%[!0-9A-F]*}" - if [[ ${#next_packet} -eq 0 ]]; then - # This shouldn't be necessary. However, it protects against - # getting into an infinite loop if the server has nothing - # left to send and check_tls_serverhellodone doesn't - # correctly catch it. - [[ $DEBUG -ge 1 ]] && mv "$sock_reply_file2" "$SOCK_REPLY_FILE" - hello_done=0 - else - tls_hello_ascii+="$next_packet" - if [[ $DEBUG -ge 1 ]]; then - sock_reply_file3=${SOCK_REPLY_FILE}.3 - mv "$SOCK_REPLY_FILE" "$sock_reply_file3" #FIXME: we moved that already - mv "$sock_reply_file2" "$SOCK_REPLY_FILE" - cat "$sock_reply_file3" >> "$SOCK_REPLY_FILE" - rm "$sock_reply_file3" - fi - - check_tls_serverhellodone "$tls_hello_ascii" "ephemeralkey" - hello_done=$? - fi - done - - debugme echo "reading server hello..." - if [[ "$DEBUG" -ge 4 ]]; then - hexdump -C $SOCK_REPLY_FILE | head -6 - echo - fi - if [[ "${tls_hello_ascii:0:1}" == 8 ]]; then - parse_sslv2_serverhello "$SOCK_REPLY_FILE" "false" - if [[ $? -eq 3 ]] && [[ "$V2_HELLO_CIPHERSPEC_LENGTH" -ne 0 ]]; then - echo "Protocol : SSLv2" > "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" - DETECTED_TLS_VERSION="0200" - ret=0 - else - ret=1 - fi - else - parse_tls_serverhello "$tls_hello_ascii" "ephemeralkey" "$cipher_list_2send" - save=$? - - if [[ $save -eq 0 ]]; then - send_close_notify "$DETECTED_TLS_VERSION" - fi - - if [[ $DEBUG -ge 2 ]]; then - # see https://secure.wand.net.nz/trac/libprotoident/wiki/SSL - lines=$(count_lines "$(hexdump -C "$SOCK_REPLY_FILE" 2>$ERRFILE)") - tm_out " ($lines lines returned) " - fi - - # determine the return value for higher level, so that they can tell what the result is - if [[ $save -eq 1 ]] || [[ $lines -eq 1 ]]; then - ret=1 # NOT available - else - ret=0 - fi - debugme tmln_out - fi - - close_socket - TMPFILE=$SOCK_REPLY_FILE - tmpfile_handle ${FUNCNAME[0]}.dd - return $ret -} - -run_client_simulation() { - # Runs browser simulations. Browser capabilities gathered from: - # https://www.ssllabs.com/ssltest/clients.html on 10 jan 2016 - local names=() - local short=() - local protos=() - local ciphers=() - local ciphersuites=() - local tlsvers=() - local sni=() - local warning=() - local handshakebytes=() - local lowest_protocol=() - local highest_protocol=() - local service=() - local minDhBits=() - local maxDhBits=() - local minRsaBits=() - local maxRsaBits=() - local minEcdsaBits=() - local curves=() - local requiresSha2=() - local current=() - local i=0 - local name tls proto cipher temp what_dh bits curve supported_curves - local has_dh_bits using_sockets=true - local client_service - local options - local -i ret=0 - local jsonID="clientsimulation" - local client_service="" - - # source the external file - . "$TESTSSL_INSTALL_DIR/etc/client-simulation.txt" 2>/dev/null - if [[ $? -ne 0 ]]; then - prln_local_problem "couldn't find client simulation data in $TESTSSL_INSTALL_DIR/etc/client-simulation.txt" - return 1 - fi - - "$SSL_NATIVE" && using_sockets=false - - if [[ $SERVICE != "" ]]; then - client_service="$SERVICE" - elif [[ -n "$STARTTLS_PROTOCOL" ]]; then - # Can we take the service from STARTTLS? - client_service=$(toupper "${STARTTLS_PROTOCOL%s}") # strip trailing 's' in ftp(s), smtp(s), pop3(s), etc - elif "$ASSUME_HTTP"; then - client_service="HTTP" - else - outln "Could not determine the protocol, only simulating generic clients." - fi - - outln - pr_headline " Running client simulations " - [[ "$client_service" == HTTP ]] && pr_headline "($client_service) " - if "$using_sockets"; then - pr_headlineln "via sockets " - else - pr_headline "via openssl " - prln_warning " -- pls note \"--ssl-native\" will return some false results" - fileout "$jsonID" "WARN" "You shouldn't run this with \"--ssl-native\" as you will get false results" - ret=1 - fi - outln - debugme echo - - if "$WIDE"; then - if [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]]; then - out " Browser Protocol Cipher Suite Name (OpenSSL) " - ( "$using_sockets" || "$HAS_DH_BITS") && out "Forward Secrecy" - outln - out "--------------------------------------------------------------------------" - else - out " Browser Protocol Cipher Suite Name (IANA/RFC) " - ( "$using_sockets" || "$HAS_DH_BITS") && out "Forward Secrecy" - outln - out "------------------------------------------------------------------------------------------" - fi - ( "$using_sockets" || "$HAS_DH_BITS") && out "----------------------" - outln - fi - if ! "$using_sockets"; then - # We can't use the connectivity checker here as of now the openssl reply is always empty (reason??) - save_max_ossl_fail=$MAX_OSSL_FAIL - nr_ossl_fail=$NR_OSSL_FAIL - MAX_OSSL_FAIL=100 - fi - for name in "${short[@]}"; do - if "${current[i]}" || "$ALL_CLIENTS" ; then - # for ANY we test this service or if the service we determined from STARTTLS matches - if [[ "${service[i]}" == ANY ]] || [[ "${service[i]}" =~ $client_service ]]; then - out " $(printf -- "%-29s" "${names[i]}")" - if "$using_sockets" && [[ -n "${handshakebytes[i]}" ]]; then - client_simulation_sockets "${handshakebytes[i]}" - sclient_success=$? - if [[ $sclient_success -eq 0 ]]; then - if [[ "0x${DETECTED_TLS_VERSION}" -lt ${lowest_protocol[i]} ]] || \ - [[ "0x${DETECTED_TLS_VERSION}" -gt ${highest_protocol[i]} ]]; then - sclient_success=1 - fi - [[ $sclient_success -eq 0 ]] && cp "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" $TMPFILE >$ERRFILE - fi - else - if [[ -n "${curves[i]}" ]]; then - # "$OPENSSL s_client" will fail if the -curves option includes any unsupported curves. - supported_curves="" - for curve in $(colon_to_spaces "${curves[i]}"); do - # Attention! secp256r1 = prime256v1 and secp192r1 = prime192v1 - # We need to map two curves here as otherwise handshakes will go wrong if "-curves" are supplied - # https://github.com/openssl/openssl/blob/master/apps/ecparam.c#L221 + ./ssl/t1_lib.c - [[ "$curve" =~ secp256r1 ]] && curve="${curve//secp256r1/prime256v1}" - [[ "$curve" =~ secp192r1 ]] && curve="${curve//secp192r1/prime192v1}" - [[ "$OSSL_SUPPORTED_CURVES" =~ " $curve " ]] && supported_curves+=":$curve" - done - curves[i]="" - [[ -n "$supported_curves" ]] && curves[i]="-curves ${supported_curves:1}" - fi - options="$(s_client_options "-cipher ${ciphers[i]} -ciphersuites "\'${ciphersuites[i]}\'" ${curves[i]} ${protos[i]} $STARTTLS $BUGS $PROXY -connect $NODEIP:$PORT ${sni[i]}")" - debugme echo "$OPENSSL s_client $options </dev/null" - $OPENSSL s_client $options </dev/null >$TMPFILE 2>$ERRFILE - sclient_connect_successful $? $TMPFILE - sclient_success=$? - fi - if [[ $sclient_success -eq 0 ]]; then - # If an ephemeral DH key was used, check that the number of bits is within range. - temp=$(awk -F': ' '/^Server Temp Key/ { print $2 }' "$TMPFILE") # extract line - what_dh="${temp%%,*}" - bits="${temp##*, }" - # formatting - curve="${temp#*, }" - if [[ "$curve" == $bits ]]; then - curve="" - else - curve="${curve%%,*}" - fi - bits="${bits/bits/}" - bits="${bits// /}" - if [[ "$what_dh" == X25519 ]] || [[ "$what_dh" == X448 ]]; then - curve="$what_dh" - what_dh="ECDH" - fi - if [[ "$what_dh" == DH ]]; then - [[ ${minDhBits[i]} -ne -1 ]] && [[ $bits -lt ${minDhBits[i]} ]] && sclient_success=1 - [[ ${maxDhBits[i]} -ne -1 ]] && [[ $bits -gt ${maxDhBits[i]} ]] && sclient_success=1 - fi - fi - if [[ $sclient_success -ne 0 ]]; then - outln "No connection" - fileout "${jsonID}-${short[i]}" "INFO" "No connection" - else - proto=$(get_protocol $TMPFILE) - # hack: - [[ "$proto" == TLSv1 ]] && proto="TLSv1.0" - [[ "$proto" == SSLv3 ]] && proto="SSLv3 " - if [[ "$proto" == TLSv1.2 ]] && ( ! "$using_sockets" || [[ -z "${handshakebytes[i]}" ]] ); then - # OpenSSL reports TLS1.2 even if the connection is TLS1.1 or TLS1.0. Need to figure out which one it is... - for tls in ${tlsvers[i]}; do - # If the handshake data includes TLS 1.3 we need to remove it, otherwise the - # simulation will fail with # 'Oops: openssl s_client connect problem' - # before/after trying another protocol. We only print a warning it in debug mode - # as otherwise we would need e.g. handle the curves in a similar fashion -- not - # to speak about ciphers - if [[ $tls =~ 1_3 ]] && ! "$HAS_TLS13"; then - debugme pr_local_problem "TLS 1.3 not supported, " - continue - fi - options="$(s_client_options "$tls -cipher ${ciphers[i]} -ciphersuites "\'${ciphersuites[i]}\'" ${curves[i]} $STARTTLS $BUGS $PROXY -connect $NODEIP:$PORT ${sni[i]}")" - debugme echo "$OPENSSL s_client $options </dev/null" - $OPENSSL s_client $options </dev/null >$TMPFILE 2>$ERRFILE - sclient_connect_successful $? $TMPFILE - sclient_success=$? - if [[ $sclient_success -eq 0 ]]; then - case "$tls" in - "-tls1_2") break ;; - "-tls1_1") proto="TLSv1.1" - break ;; - "-tls1") proto="TLSv1.0" - break ;; - esac - fi - done - fi - cipher=$(get_cipher $TMPFILE) - if [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]] && ( [[ "$cipher" == TLS_* ]] || [[ "$cipher" == SSL_* ]] ); then - cipher="$(rfc2openssl "$cipher")" - [[ -z "$cipher" ]] && cipher=$(get_cipher $TMPFILE) - elif [[ "$DISPLAY_CIPHERNAMES" =~ rfc ]] && [[ "$cipher" != TLS_* ]] && [[ "$cipher" != SSL_* ]]; then - cipher="$(openssl2rfc "$cipher")" - [[ -z "$cipher" ]] && cipher=$(get_cipher $TMPFILE) - fi - out "$proto " - "$WIDE" && out " " - if [[ "$COLOR" -le 2 ]]; then - out "$cipher" - else - pr_cipher_quality "$cipher" - fi - if "$WIDE"; then - if [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]]; then - for (( j=${#cipher}; j < 34; j++ )); do - out " " - done - else - for (( j=${#cipher}; j < 50; j++ )); do - out " " - done - fi - fi - if [[ -n "$what_dh" ]]; then - [[ -n "$curve" ]] && curve="($curve)" - "$WIDE" || out ", " - if [[ "$what_dh" == ECDH ]]; then - pr_ecdh_quality "$bits" "$(printf -- "%-12s" "$bits bit $what_dh") $curve" - else - pr_dh_quality "$bits" "$(printf -- "%-12s" "$bits bit $what_dh") $curve" - fi - else - if "$HAS_DH_BITS" || ( "$using_sockets" && [[ -n "${handshakebytes[i]}" ]] ); then - "$WIDE" || out ", " - out "No FS" - fi - fi - outln - if [[ -n "${warning[i]}" ]]; then - out " " - outln "${warning[i]}" - fi - fileout "${jsonID}-${short[i]}" "INFO" "$proto $cipher ${warning[i]}" - debugme cat $TMPFILE - fi - fi # correct service? - fi #current? - ((i++)) - done - if ! "$using_sockets"; then - # restore from above - MAX_OSSL_FAIL=$save_max_ossl_fail - NR_OSSL_FAIL=$nr_ossl_fail - fi - - tmpfile_handle ${FUNCNAME[0]}.txt - return $ret -} - -# generic function whether $1 is supported by s_client ($2: string to display, currently nowhere being used) -# -locally_supported() { - [[ -n "$2" ]] && out "$2 " - if $OPENSSL s_client "$1" -connect invalid. 2>&1 | grep -aiq "unknown option"; then - prln_local_problem "$OPENSSL doesn't support \"s_client $1\"" - return 7 - fi - return 0 -} - - -# The protocol check in run_protocols needs to be redone. The using_sockets part there kind of sucks. -# 1) we need to have a variable where the results are being stored so that every other test doesn't have to do this again -# --> we have that but certain information like "downgraded" are not being passed. That's not ok for run_protocols()/ -# for all other functions we can use it -# 2) the code is old and one can do that way better -# We should do what's available and faster (openssl vs. sockets). Keep in mind that the socket reply for SSLv2 returns the number # of ciphers! -# -# arg1: -ssl2|-ssl3|-tls1|-tls1_1|-tls1_2|-tls1_3 -# -run_prototest_openssl() { - local -i ret=0 - local protos proto - - # check whether the protocol being tested is supported by $OPENSSL - $OPENSSL s_client "$1" -connect invalid. 2>&1 | grep -aiq "unknown option" && return 7 - case "$1" in - -ssl2) protos="-ssl2" ;; - -ssl3) protos="-ssl3" ;; - -tls1) protos="-no_tls1_2 -no_tls1_1 -no_ssl2"; "$HAS_TLS13" && protos+=" -no_tls1_3" ;; - -tls1_1) protos="-no_tls1_2 -no_ssl2"; "$HAS_TLS13" && protos+=" -no_tls1_3" ;; - -tls1_2) protos="-no_ssl2"; "$HAS_TLS13" && protos+=" -no_tls1_3" ;; - -tls1_3) protos="" ;; - esac - $OPENSSL s_client $(s_client_options "-state $protos $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>&1 </dev/null - sclient_connect_successful $? $TMPFILE - ret=$? - debugme grep -E "error|failure" $ERRFILE | grep -Eav "unable to get local|verify error" - if [[ $ret -ne 0 ]]; then - if grep -aq "no cipher list" $TMPFILE; then - ret=5 # <--- important indicator for SSL2 (maybe others, too) - else - # try again without $PROXY - $OPENSSL s_client $(s_client_options "-state $protos $STARTTLS $BUGS -connect $NODEIP:$PORT $SNI") >$TMPFILE 2>&1 </dev/null - sclient_connect_successful $? $TMPFILE - ret=$? - debugme grep -E "error|failure" $ERRFILE | grep -Eav "unable to get local|verify error" - grep -aq "no cipher list" $TMPFILE && ret=5 # <--- important indicator for SSL2 (maybe others, too) - fi - fi - if [[ $ret -eq 0 ]]; then - proto="$(get_protocol "$TMPFILE")" - proto=${proto/\./_} - proto=${proto/v/} - proto="-$(tolower $proto)" - [[ "$proto" != $1 ]] && ret=2 - case "$proto" in - -ssl3) DETECTED_TLS_VERSION="0300" ;; - -tls1) DETECTED_TLS_VERSION="0301" ;; - -tls1_1) DETECTED_TLS_VERSION="0302" ;; - -tls1_2) DETECTED_TLS_VERSION="0303" ;; - -tls1_3) DETECTED_TLS_VERSION="0304" ;; - esac - fi - tmpfile_handle ${FUNCNAME[0]}$1.txt - return $ret - - # 0: offered - # 1: not offered - # 2: downgraded - # 5: protocol ok, but no cipher - # 7: no local support -} - -# Idempotent function to add SSL/TLS protocols. It should accelerate testing. -# PROTOS_OFFERED can be e.g. "ssl2:no ssl3:no tls1_2:yes" which means that -# SSLv2 and SSLv3 was tested but not available, TLS 1.2 was tested and available -# TLS 1.0 and TLS 1.2 not tested yet -# -# arg1: protocol -# arg2: available (yes) or not (no) -add_tls_offered() { - # the ":" is mandatory here (and @ other places), otherwise e.g. tls1 will match tls1_2 - if [[ "$PROTOS_OFFERED" =~ $1: ]]; then - # we got that protocol already - : - else - PROTOS_OFFERED+="${1}:$2 " - fi -} - -# function which checks whether SSLv2 - TLS 1.2 is being offered, see add_tls_offered() -# arg1: protocol string or hex code for TLS protocol -# echos: 0 if proto known being offered, 1: known not being offered, 2: we don't know yet whether proto is being offered -# return value is always zero -has_server_protocol() { - local proto - local proto_val_pair - - case "$1" in - 04) proto="tls1_3" ;; - 03) proto="tls1_2" ;; - 02) proto="tls1_1" ;; - 01) proto="tls1" ;; - 00) proto="ssl3" ;; - *) proto="$1" ;; - esac - - if [[ "$PROTOS_OFFERED" =~ $proto: ]]; then - for proto_val_pair in $PROTOS_OFFERED; do - if [[ $proto_val_pair =~ $proto: ]]; then - if [[ ${proto_val_pair#*:} == yes ]]; then - echo 0 - return 0 - else - echo 1 - return 0 - fi - fi - done - else - # if empty echo 2, hinting to the caller to check at additional cost/connect - echo 2 - return 0 - fi -} - - -# the protocol check needs to be revamped. It sucks, see above -run_protocols() { - local using_sockets=true - local supported_no_ciph1="supported but couldn't detect a cipher (may need debugging)" - local supported_no_ciph2="supported but couldn't detect a cipher" - local latest_supported="" # version.major and version.minor of highest version supported by the server - local detected_version_string latest_supported_string - local key_share_extn_nr="$KEY_SHARE_EXTN_NR" - local lines nr_ciphers_detected - local tls13_ciphers_to_test="" - local i drafts_offered="" drafts_offered_str="" supported_versions debug_recomm="" - local tls12_detected_version - local -i ret=0 ret_val_ssl3 ret_val_tls1 ret_val_tls11 ret_val_tls12=0 ret_val_tls13=0 - local offers_tls13=false - local jsonID="SSLv2" - - outln; pr_headline " Testing protocols " - - if "$SSL_NATIVE"; then - using_sockets=false - prln_underline "via native openssl" - else - using_sockets=true - if [[ -n "$STARTTLS" ]]; then - prln_underline "via sockets " - else - prln_underline "via sockets except NPN+ALPN " - fi - fi - outln - [[ "$DEBUG" -le 1 ]] && debug_recomm=", rerun with DEBUG>=2 or --ssl-native" - - pr_bold " SSLv2 "; - if ! "$SSL_NATIVE"; then - sslv2_sockets - case $? in - 6) # couldn't open socket - prln_fixme "couldn't open socket" - fileout "$jsonID" "WARN" "couldn't be tested, socket problem" - ((ret++)) - ;; - 7) # strange reply, couldn't convert the cipher spec length to a hex number - pr_cyan "strange v2 reply " - outln "$debug_recomm" - [[ $DEBUG -ge 3 ]] && hexdump -C "$TEMPDIR/$NODEIP.sslv2_sockets.dd" | head -1 - fileout "$jsonID" "WARN" "received a strange SSLv2 reply (rerun with DEBUG>=2)" - ;; - 1) # no sslv2 server hello returned, like in openlitespeed which returns HTTP! - prln_svrty_best "not offered (OK)" - fileout "$jsonID" "OK" "not offered" - add_tls_offered ssl2 no - ;; - 0) # reset - prln_svrty_best "not offered (OK)" - fileout "$jsonID" "OK" "not offered" - add_tls_offered ssl2 no - ;; - 4) out "likely "; pr_svrty_best "not offered (OK), " - fileout "$jsonID" "OK" "likely not offered" - add_tls_offered ssl2 no - pr_warning "received 4xx/5xx after STARTTLS handshake"; outln "$debug_recomm" - fileout "$jsonID" "WARN" "received 4xx/5xx after STARTTLS handshake${debug_recomm}" - ;; - 3) lines=$(count_lines "$(hexdump -C "$TEMPDIR/$NODEIP.sslv2_sockets.dd" 2>/dev/null)") - [[ "$DEBUG" -ge 2 ]] && tm_out " ($lines lines) " - if [[ "$lines" -gt 1 ]]; then - nr_ciphers_detected=$((V2_HELLO_CIPHERSPEC_LENGTH / 3)) - add_tls_offered ssl2 yes - if [[ 0 -eq "$nr_ciphers_detected" ]]; then - prln_svrty_high "supported but couldn't detect a cipher and vulnerable to CVE-2015-3197 "; - fileout "$jsonID" "HIGH" "offered, no cipher" "CVE-2015-3197" "CWE-310" - else - pr_svrty_critical "offered (NOT ok), also VULNERABLE to DROWN attack"; - outln " -- $nr_ciphers_detected ciphers" - fileout "$jsonID" "CRITICAL" "vulnerable with $nr_ciphers_detected ciphers" - fi - fi - ;; - *) pr_fixme "unexpected value around line $((LINENO))"; outln "$debug_recomm" - ((ret++)) - ;; - esac - debugme tmln_out - else - run_prototest_openssl "-ssl2" - case $? in - 0) prln_svrty_critical "offered (NOT ok)" - fileout "$jsonID" "CRITICAL" "offered" - add_tls_offered ssl2 yes - ;; - 1) prln_svrty_best "not offered (OK)" - fileout "$jsonID" "OK" "not offered" - add_tls_offered ssl2 no - ;; - 5) prln_svrty_high "CVE-2015-3197: $supported_no_ciph2"; - fileout "$jsonID" "HIGH" "offered, no cipher" "CVE-2015-3197" "CWE-310" - add_tls_offered ssl2 yes - ;; - 7) prln_local_problem "$OPENSSL doesn't support \"s_client -ssl2\"" - fileout "$jsonID" "INFO" "not tested due to lack of local support" - ((ret++)) - ;; - esac - fi - - pr_bold " SSLv3 "; - jsonID="SSLv3" - if [[ $(has_server_protocol ssl3) -eq 0 ]]; then - ret_val_ssl3=0 - elif "$using_sockets"; then - tls_sockets "00" "$TLS_CIPHER" - ret_val_ssl3=$? - else - run_prototest_openssl "-ssl3" - ret_val_ssl3=$? - fi - case $ret_val_ssl3 in - 0) prln_svrty_high "offered (NOT ok)" - fileout "$jsonID" "HIGH" "offered" - if "$using_sockets" || "$HAS_SSL3"; then - latest_supported="0300" - latest_supported_string="SSLv3" - fi - add_tls_offered ssl3 yes - ;; - 1) prln_svrty_best "not offered (OK)" - fileout "$jsonID" "OK" "not offered" - add_tls_offered ssl3 no - ;; - 2) if [[ "$DETECTED_TLS_VERSION" == 03* ]]; then - detected_version_string="TLSv1.$((0x$DETECTED_TLS_VERSION-0x0301))" - prln_svrty_critical "server responded with higher version number ($detected_version_string) than requested by client (NOT ok)" - fileout "$jsonID" "CRITICAL" "server responded with higher version number ($detected_version_string) than requested by client" - else - if [[ ${#DETECTED_TLS_VERSION} -eq 4 ]]; then - prln_svrty_critical "server responded with version number ${DETECTED_TLS_VERSION:0:2}.${DETECTED_TLS_VERSION:2:2} (NOT ok)" - fileout "$jsonID" "CRITICAL" "server responded with version number ${DETECTED_TLS_VERSION:0:2}.${DETECTED_TLS_VERSION:2:2}" - else - prln_svrty_medium "strange, server ${DETECTED_TLS_VERSION}" - fileout "$jsonID" "MEDIUM" "strange, server ${DETECTED_TLS_VERSION}" - ((ret++)) - fi - fi - ;; - 3) pr_svrty_best "not offered (OK), " - fileout "$jsonID" "OK" "not offered" - add_tls_offered ssl3 no - pr_warning "SSL downgraded to STARTTLS plaintext"; outln - fileout "$jsonID" "WARN" "SSL downgraded to STARTTLS plaintext" - ;; - 4) out "likely "; pr_svrty_best "not offered (OK), " - fileout "$jsonID" "OK" "not offered" - add_tls_offered ssl3 no - pr_warning "received 4xx/5xx after STARTTLS handshake"; outln "$debug_recomm" - fileout "$jsonID" "WARN" "received 4xx/5xx after STARTTLS handshake${debug_recomm}" - ;; - 5) pr_svrty_high "$supported_no_ciph1" # protocol detected but no cipher --> comes from run_prototest_openssl - fileout "$jsonID" "HIGH" "$supported_no_ciph1" - add_tls_offered ssl3 yes - ;; - 7) if "$using_sockets" ; then - # can only happen in debug mode - pr_warning "strange reply, maybe a client side problem with SSLv3"; outln "$debug_recomm" - else - prln_local_problem "$OPENSSL doesn't support \"s_client -ssl3\"" - fileout "$jsonID" "WARN" "not tested due to lack of local support" - fi - ;; - *) pr_fixme "unexpected value around line $((LINENO))"; outln "$debug_recomm" - ((ret++)) - ;; - esac - - pr_bold " TLS 1 "; - jsonID="TLS1" - if [[ $(has_server_protocol tls1) -eq 0 ]]; then - ret_val_tls1=0 - elif "$using_sockets"; then - tls_sockets "01" "$TLS_CIPHER" - ret_val_tls1=$? - else - run_prototest_openssl "-tls1" - ret_val_tls1=$? - fi - case $ret_val_tls1 in - 0) pr_svrty_low "offered" ; outln " (deprecated)" - fileout "$jsonID" "LOW" "offered (deprecated)" - latest_supported="0301" - latest_supported_string="TLSv1.0" - add_tls_offered tls1 yes - ;; # nothing wrong with it -- per se - 1) out "not offered" - add_tls_offered tls1 no - if [[ -z $latest_supported ]]; then - outln - fileout "$jsonID" "INFO" "not offered" # neither good or bad - else - prln_svrty_critical " -- connection failed rather than downgrading to $latest_supported_string (NOT ok)" - fileout "$jsonID" "CRITICAL" "connection failed rather than downgrading to $latest_supported_string" - fi - ;; - 2) pr_svrty_medium "not offered" - add_tls_offered tls1 no - if [[ "$DETECTED_TLS_VERSION" == 0300 ]]; then - [[ $DEBUG -ge 1 ]] && tm_out " -- downgraded" - outln - fileout "$jsonID" "MEDIUM" "not offered, and downgraded to SSL" - elif [[ "$DETECTED_TLS_VERSION" == 03* ]]; then - detected_version_string="TLSv1.$((0x$DETECTED_TLS_VERSION-0x0301))" - prln_svrty_critical " -- server responded with higher version number ($detected_version_string) than requested by client" - fileout "$jsonID" "CRITICAL" "server responded with higher version number ($detected_version_string) than requested by client" - else - if [[ ${#DETECTED_TLS_VERSION} -eq 4 ]]; then - prln_svrty_critical "server responded with version number ${DETECTED_TLS_VERSION:0:2}.${DETECTED_TLS_VERSION:2:2} (NOT ok)" - fileout "$jsonID" "CRITICAL" "server responded with version number ${DETECTED_TLS_VERSION:0:2}.${DETECTED_TLS_VERSION:2:2}" - else - prln_svrty_medium " -- strange, server ${DETECTED_TLS_VERSION}" - fileout "$jsonID" "MEDIUM" "strange, server ${DETECTED_TLS_VERSION}" - fi - fi - ;; - 3) out "not offered, " - fileout "$jsonID" "OK" "not offered" - add_tls_offered tls1 no - pr_warning "TLS downgraded to STARTTLS plaintext"; outln - fileout "$jsonID" "WARN" "TLS downgraded to STARTTLS plaintext" - ;; - 4) out "likely not offered, " - fileout "$jsonID" "INFO" "likely not offered" - add_tls_offered tls1 no - pr_warning "received 4xx/5xx after STARTTLS handshake"; outln "$debug_recomm" - fileout "$jsonID" "WARN" "received 4xx/5xx after STARTTLS handshake${debug_recomm}" - ;; - 5) outln "$supported_no_ciph1" # protocol detected but no cipher --> comes from run_prototest_openssl - fileout "$jsonID" "INFO" "$supported_no_ciph1" - add_tls_offered tls1 yes - ;; - 7) if "$using_sockets" ; then - # can only happen in debug mode - pr_warning "strange reply, maybe a client side problem with TLS 1.0"; outln "$debug_recomm" - else - prln_local_problem "$OPENSSL doesn't support \"s_client -tls1\"" - fileout "$jsonID" "WARN" "not tested due to lack of local support" - fi - ((ret++)) - ;; - *) pr_fixme "unexpected value around line $((LINENO))"; outln "$debug_recomm" - ((ret++)) - ;; - esac - - pr_bold " TLS 1.1 "; - jsonID="TLS1_1" - if [[ $(has_server_protocol tls1_1) -eq 0 ]]; then - ret_val_tls11=0 - elif "$using_sockets"; then - tls_sockets "02" "$TLS_CIPHER" - ret_val_tls11=$? - else - run_prototest_openssl "-tls1_1" - ret_val_tls11=$? - fi - case $ret_val_tls11 in - 0) pr_svrty_low "offered" ; outln " (deprecated)" - fileout "$jsonID" "LOW" "offered (deprecated)" - latest_supported="0302" - latest_supported_string="TLSv1.1" - add_tls_offered tls1_1 yes - ;; # nothing wrong with it - 1) out "not offered" - add_tls_offered tls1_1 no - if [[ -z $latest_supported ]]; then - outln - fileout "$jsonID" "INFO" "not offered" # neither good or bad - else - prln_svrty_critical " -- connection failed rather than downgrading to $latest_supported_string" - fileout "$jsonID" "CRITICAL" "connection failed rather than downgrading to $latest_supported_string" - fi - ;; - 2) out "not offered" - add_tls_offered tls1_1 no - if [[ "$DETECTED_TLS_VERSION" == "$latest_supported" ]]; then - [[ $DEBUG -ge 1 ]] && tm_out " -- downgraded" - outln - fileout "$jsonID" "CRITICAL" "TLSv1.1 is not offered, and downgraded to a weaker protocol" - elif [[ "$DETECTED_TLS_VERSION" == 0300 ]] && [[ "$latest_supported" == 0301 ]]; then - prln_svrty_critical " -- server supports TLSv1.0, but downgraded to SSLv3 (NOT ok)" - fileout "$jsonID" "CRITICAL" "not offered, and downgraded to SSLv3 rather than TLSv1.0" - elif [[ "$DETECTED_TLS_VERSION" == 03* ]] && [[ 0x$DETECTED_TLS_VERSION -gt 0x0302 ]]; then - detected_version_string="TLSv1.$((0x$DETECTED_TLS_VERSION-0x0301))" - prln_svrty_critical " -- server responded with higher version number ($detected_version_string) than requested by client (NOT ok)" - fileout "$jsonID" "CRITICAL" "not offered, server responded with higher version number ($detected_version_string) than requested by client" - else - if [[ ${#DETECTED_TLS_VERSION} -eq 4 ]]; then - prln_svrty_critical "server responded with version number ${DETECTED_TLS_VERSION:0:2}.${DETECTED_TLS_VERSION:2:2} (NOT ok)" - fileout "$jsonID" "CRITICAL" "server responded with version number ${DETECTED_TLS_VERSION:0:2}.${DETECTED_TLS_VERSION:2:2}" - else - prln_svrty_medium " -- strange, server ${DETECTED_TLS_VERSION}" - fileout "$jsonID" "MEDIUM" "strange, server ${DETECTED_TLS_VERSION}" - fi - fi - ;; - 3) out "not offered, " - fileout "$jsonID" "OK" "not offered" - add_tls_offered tls1_1 no - pr_warning "TLS downgraded to STARTTLS plaintext"; outln - fileout "$jsonID" "WARN" "TLS downgraded to STARTTLS plaintext" - ;; - 4) out "likely not offered, " - fileout "$jsonID" "INFO" "not offered" - add_tls_offered tls1_1 no - pr_warning "received 4xx/5xx after STARTTLS handshake"; outln "$debug_recomm" - fileout "$jsonID" "WARN" "received 4xx/5xx after STARTTLS handshake${debug_recomm}" - ;; - 5) outln "$supported_no_ciph1" # protocol detected but no cipher --> comes from run_prototest_openssl - fileout "$jsonID" "INFO" "$supported_no_ciph1" - add_tls_offered tls1_1 yes - ;; - 7) if "$using_sockets" ; then - # can only happen in debug mode - pr_warning "strange reply, maybe a client side problem with TLS 1.1"; outln "$debug_recomm" - else - prln_local_problem "$OPENSSL doesn't support \"s_client -tls1_1\"" - fileout "$jsonID" "WARN" "not tested due to lack of local support" - fi - ((ret++)) - ;; - *) pr_fixme "unexpected value around line $((LINENO))"; outln "$debug_recomm" - ((ret++)) - ;; - esac - - # Now, we are doing a basic/pre test for TLS 1.2 and 1.3 in order not to penalize servers (medium) - # running TLS 1.3 only when TLS 1.2 is not offered. 0 and 5 are the return codes for - # TLS 1.3 support (kind of, including deprecated pre-versions of TLS 1.3) - if [[ $(has_server_protocol tls1_2) -eq 0 ]]; then - ret_val_tls12=0 - elif "$using_sockets"; then - tls_sockets "03" "$TLS12_CIPHER" - ret_val_tls12=$? - tls12_detected_version="$DETECTED_TLS_VERSION" - else - run_prototest_openssl "-tls1_2" - ret_val_tls12=$? - tls12_detected_version="$DETECTED_TLS_VERSION" - fi - - if [[ $(has_server_protocol tls1_3) -eq 0 ]]; then - ret_val_tls13=0 - elif "$using_sockets"; then - # Need to ensure that at most 128 ciphers are included in ClientHello. - # If the TLSv1.2 test in determine_optimal_sockets_params() was successful, - # then use the 5 TLSv1.3 ciphers plus the cipher selected in the TLSv1.2 test. - # If the TLSv1.2 test was not successful, then just use the 5 TLSv1.3 ciphers - # plus the list of ciphers used in all of the previous tests ($TLS_CIPHER). - if [[ -n "$TLS12_CIPHER_OFFERED" ]]; then - tls13_ciphers_to_test="$TLS13_CIPHER, $TLS12_CIPHER_OFFERED, 00,ff" - else - tls13_ciphers_to_test="$TLS13_CIPHER,$TLS_CIPHER" - fi - tls_sockets "04" "$tls13_ciphers_to_test" - ret_val_tls13=$? - else - run_prototest_openssl "-tls1_3" - ret_val_tls13=$? - fi - if [[ $ret_val_tls13 -eq 0 ]] || [[ $ret_val_tls13 -eq 5 ]]; then - offers_tls13=true # This variable comes in handy for further if statements below - fi - # Done with pretesting TLS 1.2 and 1.3. - - pr_bold " TLS 1.2 "; - jsonID="TLS1_2" - case $ret_val_tls12 in - 0) prln_svrty_best "offered (OK)" - fileout "$jsonID" "OK" "offered" - latest_supported="0303" - latest_supported_string="TLSv1.2" - add_tls_offered tls1_2 yes - ;; # GCM cipher in TLS 1.2: very good! - 1) add_tls_offered tls1_2 no - if "$offers_tls13"; then - out "not offered" - else - pr_svrty_medium "not offered" - fi - if [[ -z $latest_supported ]]; then - outln - if "$offers_tls13"; then - fileout "$jsonID" "INFO" "not offered" - else - fileout "$jsonID" "MEDIUM" "not offered" # TLS 1.3, no TLS 1.2 --> no GCM, penalty - fi - else - prln_svrty_critical " -- connection failed rather than downgrading to $latest_supported_string" - fileout "$jsonID" "CRITICAL" "connection failed rather than downgrading to $latest_supported_string" - fi - ;; - 2) add_tls_offered tls1_2 no - pr_svrty_medium "not offered and downgraded to a weaker protocol" - if [[ "$tls12_detected_version" == 0300 ]]; then - detected_version_string="SSLv3" - elif [[ "$tls12_detected_version" == 03* ]]; then - detected_version_string="TLSv1.$((0x$tls12_detected_version-0x0301))" - fi - if [[ "$tls12_detected_version" == "$latest_supported" ]]; then - outln - fileout "$jsonID" "MEDIUM" "not offered and downgraded to a weaker protocol" - elif [[ "$tls12_detected_version" == 03* ]] && [[ 0x$tls12_detected_version -lt 0x$latest_supported ]]; then - prln_svrty_critical " -- server supports $latest_supported_string, but downgraded to $detected_version_string" - fileout "$jsonID" "CRITICAL" "not offered, and downgraded to $detected_version_string rather than $latest_supported_string" - elif [[ "$tls12_detected_version" == 03* ]] && [[ 0x$tls12_detected_version -gt 0x0303 ]]; then - prln_svrty_critical " -- server responded with higher version number ($detected_version_string) than requested by client" - fileout "$jsonID" "CRITICAL" "not offered, server responded with higher version number ($detected_version_string) than requested by client" - else - if [[ ${#tls12_detected_version} -eq 4 ]]; then - prln_svrty_critical "server responded with version number ${tls12_detected_version:0:2}.${tls12_detected_version:2:2} (NOT ok)" - fileout "$jsonID" "CRITICAL" "server responded with version number ${tls12_detected_version:0:2}.${tls12_detected_version:2:2}" - else - prln_svrty_medium " -- strange, server ${tls12_detected_version}" - fileout "$jsonID" "MEDIUM" "strange, server ${tls12_detected_version}" - fi - fi - ;; - 3) out "not offered, " - fileout "$jsonID" "INFO" "not offered" - add_tls_offered tls1_2 no - pr_warning "TLS downgraded to STARTTLS plaintext"; outln - fileout "$jsonID" "WARN" "TLS downgraded to STARTTLS plaintext" - ;; - 4) out "likely "; pr_svrty_medium "not offered, " - fileout "$jsonID" "MEDIUM" "not offered" - add_tls_offered tls1_2 no - pr_warning "received 4xx/5xx after STARTTLS handshake"; outln "$debug_recomm" - fileout "$jsonID" "WARN" "received 4xx/5xx after STARTTLS handshake${debug_recomm}" - ;; - 5) outln "$supported_no_ciph1" # protocol detected, but no cipher --> comes from run_prototest_openssl - fileout "$jsonID" "INFO" "$supported_no_ciph1" - add_tls_offered tls1_2 yes - ;; - 7) if "$using_sockets" ; then - # can only happen in debug mode - pr_warning "strange reply, maybe a client side problem with TLS 1.2"; outln "$debug_recomm" - else - prln_local_problem "$OPENSSL doesn't support \"s_client -tls1_2\"" - fileout "$jsonID" "WARN" "not tested due to lack of local support" - fi - ((ret++)) - ;; - *) pr_fixme "unexpected value around line $((LINENO))"; outln "$debug_recomm" - ((ret++)) - ;; - esac - - pr_bold " TLS 1.3 "; - jsonID="TLS1_3" - case $ret_val_tls13 in - 0) if ! "$using_sockets"; then - prln_svrty_best "offered (OK)" - fileout "$jsonID" "OK" "offered" - else - # If TLS 1.3 is offered, then its support was detected - # by determine_optimal_sockets_params(). - if [[ $(has_server_protocol tls1_3_rfc8446) -eq 0 ]]; then - drafts_offered+=" 0304 " - else - for i in 1C 1B 1A 19 18 17 16 15 14 13 12; do - if [[ $(has_server_protocol tls1_3_draft$(hex2dec "$i")) -eq 0 ]]; then - drafts_offered+=" 7F$i " - break - fi - done - fi - KEY_SHARE_EXTN_NR="28" - while true; do - supported_versions="" - for i in 16 15 14 13 12; do - [[ "$drafts_offered" =~ \ 7F$i\ ]] || supported_versions+=",7f,$i" - done - [[ -z "$supported_versions" ]] && break - supported_versions="00, 2b, 00, $(printf "%02x" $((${#supported_versions}/3+1))), $(printf "%02x" $((${#supported_versions}/3))) $supported_versions" - tls_sockets "04" "$TLS13_CIPHER" "" "$supported_versions" - [[ $? -eq 0 ]] || break - if [[ "${TLS_SERVER_HELLO:8:3}" == 7F1 ]]; then - drafts_offered+=" ${TLS_SERVER_HELLO:8:4} " - elif [[ "$TLS_SERVER_HELLO" =~ 002B00027F1[2-6] ]]; then - drafts_offered+=" ${BASH_REMATCH:8:4} " - fi - done - KEY_SHARE_EXTN_NR="33" - while true; do - supported_versions="" - for i in 1C 1B 1A 19 18 17; do - [[ "$drafts_offered" =~ \ 7F$i\ ]] || supported_versions+=",7f,$i" - done - [[ "$drafts_offered" =~ \ 0304\ ]] || supported_versions+=",03,04" - [[ -z "$supported_versions" ]] && break - supported_versions="00, 2b, 00, $(printf "%02x" $((${#supported_versions}/3+1))), $(printf "%02x" $((${#supported_versions}/3))) $supported_versions" - tls_sockets "04" "$TLS13_CIPHER" "" "$supported_versions" - [[ $? -eq 0 ]] || break - if [[ "$TLS_SERVER_HELLO" =~ 002B00020304 ]]; then - drafts_offered+=" 0304 " - elif [[ "$TLS_SERVER_HELLO" =~ 002B00027F1[7-9A-C] ]]; then - drafts_offered+=" ${BASH_REMATCH:8:4} " - fi - done - KEY_SHARE_EXTN_NR="$key_share_extn_nr" - if [[ -n "$drafts_offered" ]]; then - for i in 1C 1B 1A 19 18 17 16 15 14 13 12; do - if [[ "$drafts_offered" =~ \ 7F$i\ ]]; then - [[ -n "$drafts_offered_str" ]] && drafts_offered_str+=", " - drafts_offered_str+="draft $(printf "%d" 0x$i)" - fi - done - if [[ "$drafts_offered" =~ \ 0304\ ]]; then - [[ -n "$drafts_offered_str" ]] && drafts_offered_str+=", " - drafts_offered_str+="final" - fi - if [[ "$drafts_offered" =~ \ 0304\ ]]; then - pr_svrty_best "offered (OK)"; outln ": $drafts_offered_str" - fileout "$jsonID" "OK" "offered with $drafts_offered_str" - else - out "offered (OK)"; outln ": $drafts_offered_str" - fileout "$jsonID" "INFO" "offered with $drafts_offered_str" - fi - else - pr_warning "Unexpected results"; outln "$debug_recomm" - fileout "$jsonID" "WARN" "unexpected results" - fi - fi - latest_supported="0304" - latest_supported_string="TLSv1.3" - add_tls_offered tls1_3 yes - ;; - 1) pr_svrty_low "not offered" - if [[ -z $latest_supported ]]; then - outln - fileout "$jsonID" "LOW" "not offered" - else - prln_svrty_critical " -- connection failed rather than downgrading to $latest_supported_string" - fileout "$jsonID" "CRITICAL" "connection failed rather than downgrading to $latest_supported_string" - fi - add_tls_offered tls1_3 no - ;; - 2) if [[ "$DETECTED_TLS_VERSION" == 0300 ]]; then - detected_version_string="SSLv3" - elif [[ "$DETECTED_TLS_VERSION" == 03* ]]; then - detected_version_string="TLSv1.$((0x$DETECTED_TLS_VERSION-0x0301))" - fi - if [[ "$DETECTED_TLS_VERSION" == "$latest_supported" ]]; then - outln "not offered and downgraded to a weaker protocol" - fileout "$jsonID" "INFO" "not offered + downgraded to weaker protocol" - elif [[ "$DETECTED_TLS_VERSION" == 03* ]] && [[ 0x$DETECTED_TLS_VERSION -lt 0x$latest_supported ]]; then - out "not offered" - prln_svrty_critical " -- server supports $latest_supported_string, but downgraded to $detected_version_string" - fileout "$jsonID" "CRITICAL" "not offered, and downgraded to $detected_version_string rather than $latest_supported_string" - elif [[ "$DETECTED_TLS_VERSION" == 03* ]] && [[ 0x$DETECTED_TLS_VERSION -gt 0x0304 ]]; then - out "not offered" - prln_svrty_critical " -- server responded with higher version number ($detected_version_string) than requested by client" - fileout "$jsonID" "CRITICAL" "not offered, server responded with higher version number ($detected_version_string) than requested by client" - else - out "not offered" - prln_svrty_critical " -- server responded with version number ${DETECTED_TLS_VERSION:0:2}.${DETECTED_TLS_VERSION:2:2}" - fileout "$jsonID" "CRITICAL" "server responded with version number ${DETECTED_TLS_VERSION:0:2}.${DETECTED_TLS_VERSION:2:2}" - fi - add_tls_offered tls1_3 no - ;; - 3) out "not offered " - fileout "$jsonID" "INFO" "not offered" - add_tls_offered tls1_3 no - pr_warning "TLS downgraded to STARTTLS plaintext"; outln - fileout "$jsonID" "WARN" "TLS downgraded to STARTTLS plaintext" - ;; - 4) out "likely not offered, " - fileout "$jsonID" "INFO" "not offered" - add_tls_offered tls1_3 no - pr_warning "received 4xx/5xx after STARTTLS handshake"; outln "$debug_recomm" - fileout "$jsonID" "WARN" "received 4xx/5xx after STARTTLS handshake${debug_recomm}" - ;; - 5) outln "$supported_no_ciph1" # protocol detected but no cipher --> comes from run_prototest_openssl - fileout "$jsonID" "INFO" "$supported_no_ciph1" - add_tls_offered tls1_3 yes - ;; - 7) if "$using_sockets" ; then - # can only happen in debug mode - prln_warning "strange reply, maybe a client side problem with TLS 1.3"; outln "$debug_recomm" - else - prln_local_problem "$OPENSSL doesn't support \"s_client -tls1_3\"" - fileout "$jsonID" "WARN" "not tested due to lack of local support" - fi - ((ret++)) - ;; - *) pr_fixme "unexpected value around line $((LINENO))"; outln "$debug_recomm" - ((ret++)) - ;; - esac - - debugme echo "PROTOS_OFFERED: $PROTOS_OFFERED" - if [[ ! "$PROTOS_OFFERED" =~ yes ]]; then - outln - ignore_no_or_lame "You should not proceed as no protocol was detected. If you still really really want to, say \"YES\"" "YES" - [[ $? -ne 0 ]] && exit $ERR_CLUELESS - fi - - return $ret -} - - -# list ciphers (and makes sure you have them locally configured) -# arg[1]: non-TLSv1.3 cipher list (or anything else) -# arg[2]: TLSv1.3 cipher list -# arg[3]: protocol (e.g., -ssl2) -# -listciphers() { - local -i ret - local debugname="" - local tls13_ciphers="$TLS13_OSSL_CIPHERS" - - [[ "$2" != ALL ]] && tls13_ciphers="$2" - if "$HAS_CIPHERSUITES"; then - $OPENSSL ciphers $OSSL_CIPHERS_S $3 -ciphersuites "$tls13_ciphers" "$1" &>$TMPFILE - elif [[ -n "$tls13_ciphers" ]]; then - $OPENSSL ciphers $OSSL_CIPHERS_S $3 "$tls13_ciphers:$1" &>$TMPFILE - else - $OPENSSL ciphers $OSSL_CIPHERS_S $3 "$1" &>$TMPFILE - fi - ret=$? - debugme cat $TMPFILE - debugname="$(sed -e s'/\!/not/g' -e 's/\:/_/g' <<< "$1")" - tmpfile_handle ${FUNCNAME[0]}.${debugname}.txt - return $ret -} - - -# argv[1]: non-TLSv1.3 cipher list to test in OpenSSL syntax -# argv[2]: TLSv1.3 cipher list to test in OpenSSL syntax -# argv[3]: string on console / HTML or "finding" -# argv[4]: rating whether ok to offer -# argv[5]: string to be appended for fileout -# argv[6]: non-SSLv2 cipher list to test (hexcodes), if using sockets -# argv[7]: SSLv2 cipher list to test (hexcodes), if using sockets -# argv[8]: true if using sockets, false if not -# argv[9]: CVE -# argv[10]: CWE -# -sub_cipherlists() { - local -i i len sclient_success=1 - local cipherlist sslv2_cipherlist detected_ssl2_ciphers - local singlespaces - local proto="" - local -i ret=0 - local jsonID="cipherlist" - local using_sockets="${8}" - local cve="${9}" - local cwe="${10}" - - pr_bold "$3 " - [[ "$OPTIMAL_PROTO" == -ssl2 ]] && proto="$OPTIMAL_PROTO" - jsonID="${jsonID}_$5" - - if "$using_sockets" || listciphers "$1" "$2" $proto; then - if ! "$using_sockets" || ( "$FAST" && listciphers "$1" "$2" -tls1 ); then - for proto in -no_ssl2 -tls1_2 -tls1_1 -tls1 -ssl3; do - if [[ "$proto" == -tls1_2 ]]; then - # If $OPENSSL doesn't support TLSv1.3 or if no TLSv1.3 - # ciphers are being tested, then a TLSv1.2 ClientHello - # was tested in the first iteration. - ! "$HAS_TLS13" && continue - [[ -z "$2" ]] && continue - fi - ! "$HAS_SSL3" && [[ "$proto" == -ssl3 ]] && continue - if [[ "$proto" != -no_ssl2 ]]; then - "$FAST" && continue - [[ $(has_server_protocol "${proto:1}") -eq 1 ]] && continue - fi - $OPENSSL s_client $(s_client_options "-cipher "$1" -ciphersuites "\'$2\'" $BUGS $STARTTLS -connect $NODEIP:$PORT $PROXY $SNI $proto") 2>$ERRFILE >$TMPFILE </dev/null - sclient_connect_successful $? $TMPFILE - sclient_success=$? - debugme cat $ERRFILE - [[ $sclient_success -eq 0 ]] && break - done - else - for proto in 04 03 02 01 00; do - # If $cipherlist doesn't contain any TLSv1.3 ciphers, then there is - # no reason to try a TLSv1.3 ClientHello. - [[ "$proto" == 04 ]] && [[ ! "$6" =~ 13,0 ]] && continue - [[ $(has_server_protocol "$proto") -eq 1 ]] && continue - cipherlist="$(strip_inconsistent_ciphers "$proto" ", $6")" - cipherlist="${cipherlist:2}" - if [[ -n "$cipherlist" ]] && [[ "$cipherlist" != 00,ff ]]; then - tls_sockets "$proto" "$cipherlist" - sclient_success=$? - [[ $sclient_success -eq 2 ]] && sclient_success=0 - [[ $sclient_success -eq 0 ]] && break - fi - done - fi - if [[ $sclient_success -ne 0 ]] && [[ 1 -ne $(has_server_protocol ssl2) ]]; then - if ( [[ -z "$7" ]] || "$FAST" ) && "$HAS_SSL2" && listciphers "$1" "" -ssl2; then - $OPENSSL s_client -cipher "$1" $BUGS $STARTTLS -connect $NODEIP:$PORT $PROXY -ssl2 2>$ERRFILE >$TMPFILE </dev/null - sclient_connect_successful $? $TMPFILE - sclient_success=$? - debugme cat $ERRFILE - elif [[ -n "$7" ]]; then - sslv2_sockets "$7" "true" - if [[ $? -eq 3 ]] && [[ "$V2_HELLO_CIPHERSPEC_LENGTH" -ne 0 ]]; then - sslv2_cipherlist="$(strip_spaces "${6//,/}")" - len=${#sslv2_cipherlist} - detected_ssl2_ciphers="$(grep "Supported cipher: " "$TEMPDIR/$NODEIP.parse_sslv2_serverhello.txt")" - for (( i=0; i<len; i=i+6 )); do - [[ "$detected_ssl2_ciphers" =~ "x${sslv2_cipherlist:i:6}" ]] && sclient_success=0 && break - done - fi - fi - fi - if [[ $sclient_success -ne 0 ]] && $BAD_SERVER_HELLO_CIPHER; then - # If server failed with a known error, raise it to the user. - if [[ $STARTTLS_PROTOCOL == mysql ]]; then - pr_warning "SERVER_ERROR: test inconclusive due to MySQL Community Edition (yaSSL) bug." - fileout "$jsonID" "WARN" "SERVER_ERROR, test inconclusive due to MySQL Community Edition (yaSSL) bug." "$cve" "$cwe" - else - pr_warning "SERVER_ERROR: test inconclusive." - fileout "$jsonID" "WARN" "SERVER_ERROR, test inconclusive." "$cve" "$cwe" - fi - ((ret++)) - else - # Otherwise the error means the server doesn't support that cipher list. - case $4 in - 7) if [[ $sclient_success -eq 0 ]]; then - # Strong is excellent to offer - pr_svrty_best "offered (OK)" - fileout "$jsonID" "OK" "offered" "$cve" "$cwe" - else - pr_svrty_medium "not offered" - fileout "$jsonID" "MEDIUM" "not offered" "$cve" "$cwe" - fi - ;; - 6) if [[ $sclient_success -eq 0 ]]; then - # High is good to offer - pr_svrty_good "offered (OK)" - fileout "$jsonID" "OK" "offered" "$cve" "$cwe" - else - # FIXME: we penalize the absence of high but don't know the result of strong encryption yet (next) - pr_svrty_medium "not offered" - fileout "$jsonID" "MEDIUM" "not offered" "$cve" "$cwe" - fi - ;; - 5) if [[ $sclient_success -eq 0 ]]; then - # Neither good nor bad to offer - out "offered (OK)" - fileout "$jsonID" "INFO" "offered" "$cve" "$cwe" - else - out "not offered" - fileout "$jsonID" "INFO" "not offered" "$cve" "$cwe" - fi - ;; - 4) if [[ $sclient_success -eq 0 ]]; then - # medium is not that bad - pr_svrty_low "offered" - fileout "$jsonID" "LOW" "offered" "$cve" "$cwe" - else - out "not offered" - fileout "$jsonID" "INFO" "not offered" "$cve" "$cwe" - fi - ;; - 3) if [[ $sclient_success -eq 0 ]]; then - pr_svrty_medium "offered" - fileout "$jsonID" "MEDIUM" "offered" "$cve" "$cwe" - else - out "not offered" - fileout "$jsonID" "INFO" "not offered" "$cve" "$cwe" - fi - ;; - 2) if [[ $sclient_success -eq 0 ]]; then - # bad but there is worse - pr_svrty_high "offered (NOT ok)" - fileout "$jsonID" "HIGH" "offered" "$cve" "$cwe" - else - # need a check for -eq 1 here - pr_svrty_good "not offered (OK)" - fileout "$jsonID" "OK" "not offered" "$cve" "$cwe" - fi - ;; - 1) if [[ $sclient_success -eq 0 ]]; then - # the ugly ones - pr_svrty_critical "offered (NOT ok)" - fileout "$jsonID" "CRITICAL" "offered" "$cve" "$cwe" - else - pr_svrty_best "not offered (OK)" - fileout "$jsonID" "OK" "not offered" "$cve" "$cwe" - fi - ;; - *) # we shouldn't reach this - pr_warning "?: $4 (please report this)" - fileout "$jsonID" "WARN" "return condition $4 unclear" "$cve" "$cwe" - ((ret++)) - ;; - esac - fi - tmpfile_handle ${FUNCNAME[0]}.${5}.txt - [[ $DEBUG -ge 1 ]] && tm_out " -- $1" - outln - else - singlespaces=$(sed -e 's/ \+/ /g' -e 's/^ //' -e 's/ $//g' -e 's/ //g' <<< "$3") - if [[ "$OPTIMAL_PROTO" == -ssl2 ]]; then - prln_local_problem "No $singlespaces for SSLv2 configured in $OPENSSL" - else - prln_local_problem "No $singlespaces configured in $OPENSSL" - fi - fileout "$jsonID" "WARN" "Cipher $3 ($1) not supported by local OpenSSL ($OPENSSL)" - fi - return $ret -} - -#TODO: work with fixed lists here --> atm ok, as sockets are preferred. If there would be a single function for testing: yes. -run_cipherlists() { - local hexc hexcode strength - local -i i - local -i ret=0 - local ossl_null_ciphers null_ciphers sslv2_null_ciphers - local ossl_anon_ciphers anon_ciphers sslv2_anon_ciphers - local ossl_exp_ciphers exp_ciphers sslv2_exp_ciphers - local ossl_low_ciphers low_ciphers sslv2_low_ciphers - local ossl_tdes_ciphers tdes_ciphers sslv2_tdes_cipher - local ossl_average_ciphers average_ciphers - local strong_ciphers - local cwe="CWE-327" - local cwe2="CWE-310" - local cve="" - local using_sockets=true - - outln - pr_headlineln " Testing cipher categories " - outln - "$SSL_NATIVE" && using_sockets=false - - # conversion 2 byte ciphers via: echo "$@" | sed -e 's/[[:xdigit:]]\{2\},/0x&/g' -e 's/, /\n/g' | while read ci; do grep -wi $ci etc/cipher-mapping.txt; done - - ossl_null_ciphers='NULL:eNULL' - null_ciphers="c0,10, c0,06, c0,15, c0,0b, c0,01, c0,3b, c0,3a, c0,39, 00,b9, 00,b8, 00,b5, 00,b4, 00,2e, 00,2d, 00,b1, 00,b0, 00,2c, 00,3b, 00,02, 00,01, 00,82, 00,83, ff,87, 00,ff" - sslv2_null_ciphers="FF,80,10, 00,00,00" - - ossl_anon_ciphers='aNULL:ADH' - anon_ciphers="c0,19, 00,a7, 00,6d, 00,3a, 00,c5, 00,89, c0,47, c0,5b, c0,85, c0,18, 00,a6, 00,6c, 00,34, 00,bf, 00,9b, 00,46, c0,46, c0,5a, c0,84, c0,16, 00,18, c0,17, 00,1b, 00,1a, 00,19, 00,17, c0,15, 00,ff" - sslv2_anon_ciphers="FF,80,10" - - ossl_exp_ciphers='EXPORT:!ADH:!NULL' - # grep -i EXP etc/cipher-mapping.txt - exp_ciphers="00,63, 00,62, 00,61, 00,65, 00,64, 00,60, 00,14, 00,11, 00,19, 00,08, 00,06, 00,27, 00,26, 00,2a, 00,29, 00,0b, 00,0e, 00,17, 00,03, 00,28, 00,2b, 00,ff" - sslv2_exp_ciphers="04,00,80, 02,00,80, 00,00,00" - - ossl_low_ciphers='LOW:DES:RC2:RC4:!ADH:!EXP:!NULL:!eNULL' - # egrep -w '64|56|RC2|RC4' etc/cipher-mapping.txt | egrep -v 'Au=None|export' - low_ciphers="00,04, 00,05, 00,09, 00,0C, 00,0F, 00,12, 00,15, 00,1E, 00,20, 00,22, 00,24, 00,66, 00,8A, 00,8E, 00,92, C0,02, C0,07, C0,0C, C0,11, C0,33, FE,FE, FF,E1, 00,FF" - sslv2_low_ciphers="01,00,80, 03,00,80, 06,00,40, 06,01,40, 08,00,80, FF,80,00" - - ossl_tdes_ciphers='3DES:IDEA:!aNULL:!ADH' - # egrep -w '3DES|IDEA' etc/cipher-mapping.txt | grep -v "Au=None" - tdes_ciphers="00,07, 00,0A, 00,0D, 00,10, 00,13, 00,16, 00,1F, 00,21, 00,23, 00,25, 00,8B, 00,8F, 00,93, C0,03, C0,08, C0,0D, C0,12, C0,1A, C0,1B, C0,1C, C0,34, FE,FF, FF,E0, 00,FF" - sslv2_tdes_ciphers="05,00,80, 07,00,c0, 07,01,c0" - - # Now all AES, CAMELLIA, ARIA and SEED CBC ciphers plus GOST - ossl_average_ciphers='HIGH:MEDIUM:AES:CAMELLIA:ARIA:!IDEA:!CHACHA20:!3DES:!RC2:!RC4:!AESCCM8:!AESCCM:!AESGCM:!ARIAGCM:!aNULL' - # egrep -w "256|128" etc/cipher-mapping.txt | egrep -v "Au=None|AEAD|RC2|RC4|IDEA" - average_ciphers="00,2F, 00,30, 00,31, 00,32, 00,33, 00,35, 00,36, 00,37, 00,38, 00,39, 00,3C, 00,3D, 00,3E, 00,3F, 00,40, 00,41, 00,42, 00,43, 00,44, 00,45, 00,67, 00,68, 00,69, 00,6A, 00,6B, 00,84, 00,85, 00,86, 00,87, 00,88, 00,8C, 00,8D, 00,90, 00,91, 00,94, 00,95, 00,96, 00,97, 00,98, 00,99, 00,9A, 00,AE, 00,AF, 00,B2, 00,B3, 00,B6, 00,B7, 00,BA, 00,BB, 00,BC, 00,BD, 00,BE, 00,C0, 00,C1, 00,C2, 00,C3, 00,C4, C0,04, C0,05, C0,09, C0,0A, C0,0E, C0,0F, C0,13, C0,14, C0,1D, C0,1E, C0,1F, C0,20, C0,21, C0,22, C0,23, C0,24, C0,25, C0,26, C0,27, C0,28, C0,29, C0,2A, C0,35, C0,36, C0,37, C0,38, C0,3C, C0,3D, C0,3E, C0,3F, C0,40, C0,41, C0,42, C0,43, C0,44, C0,45, C0,48, C0,49, C0,4A, C0,4B, C0,4C, C0,4D, C0,4E, C0,4F, C0,64, C0,65, C0,66, C0,67, C0,68, C0,69, C0,70, C0,71, C0,72, C0,73, C0,74, C0,75, C0,76, C0,77, C0,78, C0,79, C0,94, C0,95, C0,96, C0,97, C0,98, C0,99, C0,9A, C0,9B" - # Workaround: If we use sockets and in order not to hit 132+1 ciphers we omit the GOST ciphers if SERVER_SIZE_LIMIT_BUG is true. - # This won't be supported by Cisco ACE anyway. Catch is, if SERVER_SIZE_LIMIT_BUG was not tested for before (only this function is being called) - "$SERVER_SIZE_LIMIT_BUG" || average_ciphers="${average_ciphers}, 00,80, 00,81, FF,00, FF,01, FF,02, FF,03, FF,85" - average_ciphers="${average_ciphers}, 00,FF" - - # Here's the strongest discrepancy between sockets and OpenSSL - ossl_strong_ciphers='AESGCM:CHACHA20:AESGCM:CamelliaGCM:AESCCM:ARIAGCM' - # grep AEAD etc/cipher-mapping.txt | grep -v Au=None - strong_ciphers="00,9C, 00,9D, 00,9E, 00,9F, 00,A0, 00,A1, 00,A2, 00,A3, 00,A4, 00,A5, 00,A8, 00,A9, 00,AA, 00,AB, 00,AC, 00,AD, 13,01, 13,02, 13,03, 13,04, 13,05, 16,B7, 16,B8, 16,B9, 16,BA, C0,2B, C0,2C, C0,2D, C0,2E, C0,2F, C0,30, C0,31, C0,32, C0,50, C0,51, C0,52, C0,53, C0,54, C0,55, C0,56, C0,57, C0,58, C0,59, C0,5C, C0,5D, C0,5E, C0,5F, C0,60, C0,61, C0,62, C0,63, C0,6A, C0,6B, C0,6C, C0,6D, C0,6E, C0,6F, C0,7A, C0,7B, C0,7C, C0,7D, C0,7E, C0,7F, C0,80, C0,81, C0,82, C0,83, C0,86, C0,87, C0,88, C0,89, C0,8A, C0,8B, C0,8C, C0,8D, C0,8E, C0,8F, C0,90, C0,91, C0,92, C0,93, C0,9C, C0,9D, C0,9E, C0,9F, C0,A0, C0,A1, C0,A2, C0,A3, C0,A4, C0,A5, C0,A6, C0,A7, C0,A8, C0,A9, C0,AA, C0,AB, C0,AC, C0,AD, C0,AE, C0,AF, CC,13, CC,14, CC,15, CC,A8, CC,A9, CC,AA, CC,AB, CC,AC, CC,AD, CC,AE, 00,FF" - - # argv[1]: non-TLSv1.3 cipher list to test in OpenSSL syntax - # argv[2]: TLSv1.3 cipher list to test in OpenSSL syntax - # argv[3]: string on console / HTML or "finding" - # argv[4]: rating whether ok to offer - # argv[5]: string to be appended for fileout - # argv[6]: non-SSLv2 cipher list to test (hexcodes), if using sockets - # argv[7]: SSLv2 cipher list to test (hexcodes), if using sockets - # argv[8]: true if using sockets, false if not - # argv[9]: CVE - # argv[10]: CWE - - sub_cipherlists "$ossl_null_ciphers" "" " NULL ciphers (no encryption) " 1 "NULL" "$null_ciphers" "$sslv2_null_ciphers" "$using_sockets" "$cve" "$cwe" - ret=$? - sub_cipherlists "$ossl_anon_ciphers" "" " Anonymous NULL Ciphers (no authentication)" 1 "aNULL" "$anon_ciphers" "$sslv2_anon_ciphers" "$using_sockets" "$cve" "$cwe" - ret=$((ret + $?)) - sub_cipherlists "$ossl_exp_ciphers" "" " Export ciphers (w/o ADH+NULL) " 1 "EXPORT" "$exp_ciphers" "$sslv2_exp_ciphers" "$using_sockets" "$cve" "$cwe" - ret=$((ret + $?)) - sub_cipherlists "$ossl_low_ciphers" "" " LOW: 64 Bit + DES, RC[2,4] (w/o export) " 2 "LOW" "$low_ciphers" "$sslv2_low_ciphers" "$using_sockets" "$cve" "$cwe" - ret=$((ret + $?)) - sub_cipherlists "$ossl_tdes_ciphers" "" " Triple DES Ciphers / IDEA " 3 "3DES_IDEA" "$tdes_ciphers" "$sslv2_tdes_ciphers" "$using_sockets" "$cve" "$cwe2" - ret=$((ret + $?)) - sub_cipherlists "$ossl_average_ciphers" "" " Obsolete CBC ciphers (AES, ARIA etc.) " 4 "AVERAGE" "$average_ciphers" "" "$using_sockets" "$cve" "$cwe2" - ret=$((ret + $?)) - sub_cipherlists "$ossl_strong_ciphers" 'ALL' " Strong encryption (AEAD ciphers) " 7 "STRONG" "$strong_ciphers" "" "$using_sockets" "" "" - ret=$((ret + $?)) - - outln - return $ret -} - -# The return value is an indicator of the quality of the DH key length in $1: -# 1 = pr_svrty_critical, 2 = pr_svrty_high, 3 = pr_svrty_medium, 4 = pr_svrty_low -# 5 = neither good nor bad, 6 = pr_svrty_good, 7 = pr_svrty_best -pr_dh_quality() { - local bits="$1" - local string="$2" - - if [[ "$bits" -le 600 ]]; then - pr_svrty_critical "$string" - return 1 - elif [[ "$bits" -le 800 ]]; then - pr_svrty_high "$string" - return 2 - elif [[ "$bits" -le 1280 ]]; then - pr_svrty_medium "$string" - return 3 - elif [[ "$bits" -ge 2048 ]]; then - pr_svrty_good "$string" - return 6 - else - out "$string" - return 5 - fi -} - -# prints out dh group=prime and in round brackets DH bits and labels it accordingly -# arg1: name of dh group, arg2=bit length -pr_dh() { - local -i quality=0 - - pr_italic "$1" - out " (" - pr_dh_quality "$2" "$2 bits" - quality=$? - out ")" - return $quality -} - -pr_ecdh_quality() { - local bits="$1" - local string="$2" - - if [[ "$bits" -le 80 ]]; then # has that ever existed? - pr_svrty_critical "$string" - elif [[ "$bits" -le 108 ]]; then # has that ever existed? - pr_svrty_high "$string" - elif [[ "$bits" -le 163 ]]; then - pr_svrty_medium "$string" - elif [[ "$bits" -le 193 ]]; then # hmm, according to https://wiki.openssl.org/index.php/Elliptic_Curve_Cryptography it should ok - pr_svrty_low "$string" # but openssl removed it https://github.com/drwetter/testssl.sh/issues/299#issuecomment-220905416 - elif [[ "$bits" -le 224 ]]; then - out "$string" - elif [[ "$bits" -gt 224 ]]; then - pr_svrty_good "$string" - else - out "$string" - fi -} - -pr_ecdh_curve_quality() { - curve="$1" - local -i bits=0 - - case "$curve" in - "sect163k1") bits=163 ;; - "sect163r1") bits=162 ;; - "sect163r2") bits=163 ;; - "sect193r1") bits=193 ;; - "sect193r2") bits=193 ;; - "sect233k1") bits=232 ;; - "sect233r1") bits=233 ;; - "sect239k1") bits=238 ;; - "sect283k1") bits=281 ;; - "sect283r1") bits=282 ;; - "sect409k1") bits=407 ;; - "sect409r1") bits=409 ;; - "sect571k1") bits=570 ;; - "sect571r1") bits=570 ;; - "secp160k1") bits=161 ;; - "secp160r1") bits=161 ;; - "secp160r2") bits=161 ;; - "secp192k1") bits=192 ;; - "prime192v1") bits=192 ;; - "secp224k1") bits=225 ;; - "secp224r1") bits=224 ;; - "secp256k1") bits=256 ;; - "prime256v1") bits=256 ;; - "secp384r1") bits=384 ;; - "secp521r1") bits=521 ;; - "brainpoolP256r1") bits=256 ;; - "brainpoolP384r1") bits=384 ;; - "brainpoolP512r1") bits=512 ;; - "X25519") bits=253 ;; - "X448") bits=448 ;; - esac - pr_ecdh_quality "$bits" "$curve" -} - -# Print $2 based on the quality of the cipher in $1. If $2 is empty, just print $1. -# The return value is an indicator of the quality of the cipher in $1: -# 0 = $1 is empty -# 1 = pr_svrty_critical, 2 = pr_svrty_high, 3 = pr_svrty_medium, 4 = pr_svrty_low -# 5 = neither good nor bad, 6 = pr_svrty_good, 7 = pr_svrty_best -# -# Please note this section isn't particular spot on. It needs to be reconsidered/redone -# SHA1, SSLv3 ciphers are some points which need to be considered. -# Hint: find out by "grep <pattern> etc/cipher-mapping.txt" but it' might be be easier -# to look out Enc= and Au= or Mac= -# -pr_cipher_quality() { - local cipher="$1" - local text="$2" - - [[ -z "$1" ]] && return 0 - [[ -z "$text" ]] && text="$cipher" - - if [[ "$cipher" != TLS_* ]] && [[ "$cipher" != SSL_* ]]; then - # This must be the OpenSSL name for a cipher or for TLS 1.3 ($TLS13_OSSL_CIPHERS) - # We can ignore them however as the OpenSSL and RFC names currently match - if [[ $TLS_NR_CIPHERS -eq 0 ]]; then - # We have an OpenSSL name and can't convert it to the RFC name which is rarely - # the case, see "prepare_arrays()" and "./etc/cipher-mapping.txt" - case "$cipher" in - *NULL*|EXP*|ADH*) - pr_svrty_critical "$text" - return 1 - ;; - *RC4*|*RC2*|*MD5|*M1) - pr_svrty_high "$text" - return 2 - ;; - AES256-GCM-SHA384|AES128-GCM-SHA256|AES256-CCM|AES128-CCM|ARIA256-GCM-SHA384|ARIA128-GCM-SHA256) - # RSA kx and e.g. GCM isn't certainly the best - pr_svrty_good "$text" - return 6 - ;; - *GCM*|*CCM*|*CHACHA20*) - pr_svrty_best "$text" - return 7 - ;; #best ones - *CBC3*|*SEED*|*3DES*|*IDEA*) - pr_svrty_medium "$text" - return 3 - ;; - ECDHE*AES*|DHE*AES*SHA*|*CAMELLIA*SHA) - pr_svrty_low "$text" - return 4 - ;; - *) - out "$text" - return 5 - ;; - esac - fi - cipher="$(openssl2rfc "$cipher")" - fi - - # Now we look at the RFC cipher names. The sequence matters - as above. - case "$cipher" in - *NULL*|*EXP*|*_DES40_*|*anon*) - pr_svrty_critical "$text" - return 1 - ;; - *RC4*|*RC2*|*MD5|*MD5_1) - pr_svrty_high "$text" - return 2 - ;; - *_DES_*) - if [[ "$cipher" =~ EDE3 ]]; then - pr_svrty_medium "$text" # 3DES - return 3 - fi - pr_svrty_high "$text" - return 2 - ;; - *CBC3*|*SEED*|*3DES*|*IDEA*) - pr_svrty_medium "$text" - return 3 - ;; - TLS_RSA_*) - if [[ "$cipher" =~ CBC ]]; then - pr_svrty_low "$text" - return 4 - else - pr_svrty_good "$text" - # RSA kx and e.g. GCM isn't certainly the best - return 6 - fi - ;; - *GCM*|*CCM*|*CHACHA20*) - pr_svrty_best "$text" - return 7 - ;; - *ECDHE*AES*CBC*|*DHE*AES*SHA*|*RSA*AES*SHA*|*CAMELLIA*SHA*) - pr_svrty_low "$text" - return 4 - ;; - *) - out "$text" - return 5 - ;; - esac -} - -# arg1: file with input for grepping the type of ephemeral DH key (DH ECDH) -read_dhtype_from_file() { - local temp kx - - temp=$(awk -F': ' '/^Server Temp Key/ { print $2 }' "$1") # extract line - kx="Kx=${temp%%,*}" - [[ "$kx" == "Kx=X25519" ]] && kx="Kx=ECDH" - [[ "$kx" == "Kx=X448" ]] && kx="Kx=ECDH" - tm_out "$kx" - return 0 -} - -# arg1: certificate file -read_sigalg_from_file() { - $OPENSSL x509 -noout -text -in "$1" 2>/dev/null | awk -F':' '/Signature Algorithm/ { print $2; exit; }' -} - - -# arg1: file with input for grepping the bit length for ECDH/DHE -# arg2: whether to print warning "old fart" or not (empty: no) -read_dhbits_from_file() { - local bits what_dh temp curve="" - local add="" - local old_fart=" (your $OPENSSL cannot show DH bits)" - - temp=$(awk -F': ' '/^Server Temp Key/ { print $2 }' "$1") # extract line - what_dh="${temp%%,*}" - bits="${temp##*, }" - curve="${temp#*, }" - if [[ "$curve" == "$bits" ]]; then - curve="" - else - curve="${curve%%,*}" - fi - bits="${bits/bits/}" - bits="${bits// /}" - - if [[ "$what_dh" == X25519 ]] || [[ "$what_dh" == X448 ]]; then - curve="$what_dh" - what_dh="ECDH" - fi - if [[ -z "$2" ]]; then - if [[ -n "$curve" ]]; then - debugme echo ">$HAS_DH_BITS|$what_dh($curve)|$bits<" - else - debugme echo ">$HAS_DH_BITS|$what_dh|$bits<" - fi - fi - [[ -n "$what_dh" ]] && HAS_DH_BITS=true # FIX 190 - if [[ -z "$what_dh" ]] && ! "$HAS_DH_BITS"; then - if [[ "$2" == "string" ]]; then - tm_out "$old_fart" - elif [[ -z "$2" ]]; then - pr_warning "$old_fart" - fi - return 0 - fi - if [[ "$2" == quiet ]]; then - tm_out "$bits" - return 0 - fi - [[ -z "$2" ]] && [[ -n "$bits" ]] && out ", " - if [[ $what_dh == DH ]] || [[ $what_dh == EDH ]]; then - add="bit DH" - [[ -n "$curve" ]] && add+=" ($curve)" - if [[ "$2" == string ]]; then - tm_out ", $bits $add" - else - pr_dh_quality "$bits" "$bits $add" - fi - # https://wiki.openssl.org/index.php/Elliptic_Curve_Cryptography, https://www.keylength.com/en/compare/ - elif [[ $what_dh == ECDH ]]; then - add="bit ECDH" - [[ -n "$curve" ]] && add+=" ($curve)" - if [[ "$2" == string ]]; then - tm_out ", $bits $add" - else - pr_ecdh_quality "$bits" "$bits $add" - fi - fi - return 0 -} - - -# arg1: ID or empty. If empty resumption by ticket will be tested, otherwise by ID -# return: 0: it has resumption, 1:nope, 2: nope (OpenSSL 1.1.1), 6: CLIENT_AUTH --> problem for resumption, 7: can't tell -# -# This is basically a short(?) version from Bulletproof SSL and TLS (p386). The version according to that would be e.g. -# echo | $OPENSSL s_client -connect testssl.sh:443 -servername testssl.sh -no_ssl2 -reconnect 2>&1 | grep -E 'New|Reused' -# echo | $OPENSSL s_client -connect testssl.sh:443 -servername testssl.sh -no_ssl2 -no_ticket -reconnect 2>&1 | grep -E 'New|Reused|Session-ID' -# -# FIXME: actually Ivan's version seems faster. Worth to check and since when -reconnect is a/v -# -sub_session_resumption() { - local ret ret1 ret2 - local tmpfile=$(mktemp $TEMPDIR/session_resumption.$NODEIP.XXXXXX) - local sess_data=$(mktemp $TEMPDIR/sub_session_data_resumption.$NODEIP.XXXXXX) - local -a rw_line - local not_new_reused=false - local protocol="$1" - - if [[ "$2" == ID ]]; then - local byID=true - local addcmd="-no_ticket" - else - local byID=false - local addcmd="" - if ! "$TLS_TICKETS"; then - return 1 - fi - fi - "$CLIENT_AUTH" && return 6 - if "$HAS_NO_SSL2"; then - addcmd+=" -no_ssl2" - else - protocol=${protocol/\./_} - protocol=${protocol/v/} - protocol="-$(tolower $protocol)" - # In some cases a server will not support session tickets, but will support session resumption - # by ID. In such a case, it may be more likely to support session resumption with TLSv1.2 than - # with TLSv1.3. So, if testing a server that does not support session tickets and that supports - # both TLSv1.3 and TLSv1.2 for session resumption by ID, then use a TLSv1.2 ClientHello. (Note that - # the line below assumes that if $protocol is -tls1_3, then the server either supports TLSv1.2 or - # is TLSv1.3-only. - ! "$TLS_TICKETS" && "$byID" && [[ $(has_server_protocol "tls1_2") -eq 0 ]] && protocol="-tls1_2" - addcmd+=" $protocol" - fi - - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI $addcmd -sess_out $sess_data") </dev/null &>/dev/null - ret1=$? - if [[ $ret1 -ne 0 ]]; then - debugme echo -n "Couldn't connect #1 " - return 7 - fi - if "$byID" && [[ ! "$OSSL_NAME" =~ LibreSSL ]] && \ - ( [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 1.1.1* ]] || [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 3.0.0* ]] ) && \ - [[ ! -s "$sess_data" ]]; then - # it seems OpenSSL indicates no Session ID resumption by just not generating output - debugme echo -n "No session resumption byID (empty file)" - ret=2 - else - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI $addcmd -sess_in $sess_data") </dev/null >$tmpfile 2>$ERRFILE - ret2=$? - if [[ $DEBUG -ge 2 ]]; then - echo -n "$ret1, $ret2, " - [[ -s "$sess_data" ]] && echo "not empty" || echo "empty" - fi - if [[ $ret2 -ne 0 ]]; then - debugme echo -n "Couldn't connect #2 " - return 7 - fi - # "Reused" indicates session material was reused, "New": not - if grep -aq "^Reused" "$tmpfile"; then - new_sid=false - elif grep -aq "^New" "$tmpfile"; then - new_sid=true - else - debugme echo -n "Problem with 2nd ServerHello " - not_new_reused=true - fi - # Now get the line and compare the numbers "read" and "written" as a second criteria. - # If the "read" number is bigger: a new session ID was probably used - rw_line="$(awk '/^SSL handshake has read/ { print $5" "$(NF-1) }' "$tmpfile" )" - rw_line=($rw_line) - if [[ "${rw_line[0]}" -gt "${rw_line[1]}" ]]; then - new_sid2=true - else - new_sid2=false - fi - debugme echo "${rw_line[0]}, ${rw_line[1]}" - - if "$new_sid2" && "$new_sid"; then - debugme echo -n "No session resumption " - ret=1 - elif ! "$new_sid2" && ! "$new_sid"; then - debugme echo -n "Session resumption " - ret=0 - else - debugme echo -n "unclear status: $ret1, $ret2, $new_sid, $new_sid2 -- " - ret=5 - fi - if [[ $DEBUG -ge 2 ]]; then - "$byID" && echo "byID" || echo "by ticket" - fi - fi - "$byID" && \ - tmpfile_handle ${FUNCNAME[0]}.byID.log $tmpfile || \ - tmpfile_handle ${FUNCNAME[0]}.byticket.log $tmpfile - return $ret -} - -run_server_preference() { - local cipher1="" cipher2="" tls13_cipher1="" tls13_cipher2="" default_proto="" - local prev_cipher="" default_cipher="" - local limitedsense="" supported_sslv2_ciphers - local -a cipher proto - local proto_ossl proto_txt proto_hex cipherlist i - local -i ret=0 j sclient_success str_len - local list_fwd="DHE-RSA-SEED-SHA:SEED-SHA:DES-CBC3-SHA:RC4-MD5:DES-CBC-SHA:RC4-SHA:AES128-SHA:AES128-SHA256:AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-RSA-AES128-SHA:ECDH-RSA-AES256-SHA:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:DHE-DSS-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:AES256-SHA256:ECDHE-RSA-DES-CBC3-SHA:ECDHE-RSA-AES128-SHA256:AES256-GCM-SHA384:AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-SHA256:ADH-AES256-GCM-SHA384:AECDH-AES128-SHA:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-AES128-SHA" - local list_reverse="ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-RC4-SHA:AECDH-AES128-SHA:ADH-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-GCM-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-DES-CBC3-SHA:AES256-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDH-RSA-AES256-SHA:ECDH-RSA-AES128-SHA:ECDH-RSA-DES-CBC3-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-AES128-SHA:AES256-SHA:AES128-SHA256:AES128-SHA:RC4-SHA:DES-CBC-SHA:RC4-MD5:DES-CBC3-SHA:SEED-SHA:DHE-RSA-SEED-SHA" - tls_list_fwd="c0,2c, c0,30, 00,9f, cc,a9, cc,a8, cc,aa, c0,2b, c0,2f, 00,9e, c0,24, c0,28, 00,6b, c0,23, c0,27, 00,67, c0,0a, 00,04, 00,05, 00,09, 00,0a, 00,9a, 00,96, - c0,14, 00,39, c0,09, c0,13, 00,33, 00,9d, 00,9c, 13,01, 13,02, 13,03, 13,04, 13,05, 00,3d, 00,3c, 00,35, 00,2f, 00,ff" - tls_list_rev="00,2f, 00,35, 00,3c, 00,3d, 13,05, 13,04, 13,03, 13,02, 13,01, 00,9c, 00,9d, 00,33, c0,13, c0,09, 00,39, c0,14, 00,96, 00,9a, 00,0a, 00,09, 00,05, 00,04, - c0,0a, 00,67, c0,27, c0,23, 00,6b, c0,28, c0,24, 00,9e, c0,2f, c0,2b, cc,aa, cc,a8, cc,a9, 00,9f, c0,30, c0,2c, 00,ff" - local has_cipher_order=false has_tls13_cipher_order=false - local addcmd="" addcmd2="" - local using_sockets=true - local jsonID="cipher_order" - local cwe="CWE-310" - local cve="" - - "$SSL_NATIVE" && using_sockets=false - - outln - pr_headlineln " Testing server preferences " - - outln - pr_bold " Has server cipher order? " - - if [[ "$OPTIMAL_PROTO" == -ssl2 ]]; then - addcmd="$OPTIMAL_PROTO" - else - # the supplied openssl will send an SSLv2 ClientHello if $SNI is empty - # and the -no_ssl2 isn't provided. - addcmd="-no_ssl2 $SNI" - fi - - # Determine negotiated protocol upfront - sclient_success=1 - if "$using_sockets" && [[ $(has_server_protocol "tls1_3") -ne 1 ]]; then - # Send similar list of cipher suites as OpenSSL 1.1.1 does - tls_sockets "04" \ - "c0,2c, c0,30, 00,9f, cc,a9, cc,a8, cc,aa, c0,2b, c0,2f, 00,9a, 00,96, - 00,9e, c0,24, c0,28, 00,6b, c0,23, c0,27, 00,67, c0,0a, - c0,14, 00,39, c0,09, c0,13, 00,33, 00,9d, 00,9c, 13,02, - 13,03, 13,01, 13,04, 13,05, 00,3d, 00,3c, 00,35, 00,2f, 00,ff" \ - "ephemeralkey" - sclient_success=$? - if [[ $sclient_success -eq 0 ]]; then - add_tls_offered tls1_3 yes - elif [[ $sclient_success -eq 2 ]]; then - sclient_success=0 # 2: downgraded - case $DETECTED_TLS_VERSION in - 0303) add_tls_offered tls1_2 yes ;; - 0302) add_tls_offered tls1_1 yes ;; - 0301) add_tls_offered tls1 yes ;; - 0300) add_tls_offered ssl3 yes ;; - esac - fi - if [[ $sclient_success -eq 0 ]] ; then - cp "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" $TMPFILE - cp "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" "$TEMPDIR/$NODEIP.parse_tls13_serverhello.txt" - cipher0=$(get_cipher $TMPFILE) - fi - fi - if [[ $sclient_success -ne 0 ]]; then - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $addcmd") </dev/null 2>>$ERRFILE >$TMPFILE - if sclient_connect_successful $? $TMPFILE; then - cipher0=$(get_cipher $TMPFILE) - debugme tm_out "0 --> $cipher0\n" - cp $TMPFILE "$TEMPDIR/$NODEIP.parse_tls13_serverhello.txt" - else - # 2 second try with $OPTIMAL_PROTO especially for intolerant IIS6 servers: - $OPENSSL s_client $(s_client_options "$STARTTLS $OPTIMAL_PROTO $BUGS -connect $NODEIP:$PORT $PROXY $SNI") </dev/null 2>>$ERRFILE >$TMPFILE - if ! sclient_connect_successful $? $TMPFILE; then - pr_warning "Handshake error!" - ret=1 - fi - fi - fi - default_proto=$(get_protocol $TMPFILE) - [[ "$default_proto" == TLSv1.0 ]] && default_proto="TLSv1" - # debugme tm_out " --> $default_proto\n" - - # Some servers don't have a TLS 1.3 cipher order, see #1163 - if [[ "$default_proto" == TLSv1.3 ]]; then - tls_sockets "04" "13,05, 13,04, 13,03, 13,02, 13,01, 00,ff" - [[ $? -ne 0 ]] && ret=1 && prln_fixme "something weird happened around line $((LINENO - 1))" - cp "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" $TMPFILE - tls13_cipher1=$(get_cipher $TMPFILE) - debugme tm_out "TLS 1.3: --> $tls13_cipher1\n" - tls_sockets "04" "13,01, 13,02, 13,03, 13,04, 13,05, 00,ff" - [[ $? -ne 0 ]] && ret=1 && prln_fixme "something weird happened around line $((LINENO - 1))" - cp "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" $TMPFILE - tls13_cipher2=$(get_cipher $TMPFILE) - debugme tm_out "TLS 1.3: --> $tls13_cipher2\n" - - [[ $tls13_cipher1 == $tls13_cipher2 ]] && has_tls13_cipher_order=true - fi - # Check whether the server has a cipher order for SSLv3 - TLSv1.2 - if [[ $(has_server_protocol "tls1_2") -ne 0 ]] && [[ $(has_server_protocol "tls1_1") -ne 0 ]] && \ - [[ $(has_server_protocol "tls1") -ne 0 ]] && [[ $(has_server_protocol "ssl3") -ne 0 ]]; then - # Based on testing performed by determine_optimal_sockets_params(), it is believed that - # this server does not offer SSLv3 - TLSv1.2. - has_cipher_order="$has_tls13_cipher_order" - elif [[ "$OPTIMAL_PROTO" != -ssl2 ]]; then - if [[ -n "$STARTTLS_OPTIMAL_PROTO" ]]; then - [[ ! "$STARTTLS_OPTIMAL_PROTO" =~ ssl ]] && addcmd2="$SNI" - [[ "$STARTTLS_OPTIMAL_PROTO" != -tls1_3 ]] && addcmd2+=" $STARTTLS_OPTIMAL_PROTO" - else - addcmd2="-no_ssl2 $SNI" - fi - [[ $DEBUG -ge 4 ]] && echo -e "\n Forward: ${list_fwd}" - $OPENSSL s_client $(s_client_options "$STARTTLS -cipher $list_fwd $BUGS -connect $NODEIP:$PORT $PROXY $addcmd2") </dev/null 2>$ERRFILE >$TMPFILE - if ! sclient_connect_successful $? $TMPFILE; then - list_fwd="$(actually_supported_osslciphers $list_fwd '' '-tls1')" - pr_warning "no matching cipher in this list found (pls report this): " - outln "$list_fwd . " - fileout "$jsonID" "WARN" "Could not determine server cipher order, no matching cipher in list found (pls report this): $list_fwd" - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - # we assume the problem is with testing here but it could be also the server side - fi - cipher1=$(get_cipher $TMPFILE) # cipher1 from 1st serverhello - debugme tm_out "1 --> $cipher1\n" - - # second client hello with reverse list - [[ $DEBUG -ge 4 ]] && echo -e "\n Reverse: ${list_reverse}" - $OPENSSL s_client $(s_client_options "$STARTTLS -cipher $list_reverse $BUGS -connect $NODEIP:$PORT $PROXY $addcmd2") </dev/null 2>>$ERRFILE >$TMPFILE - # first handshake worked above so no error handling here - cipher2=$(get_cipher $TMPFILE) # cipher2 from 2nd serverhello - debugme tm_out "2 --> $cipher2\n" - - [[ $cipher1 == $cipher2 ]] && has_cipher_order=true - fi - debugme echo "has_cipher_order: $has_cipher_order" - debugme echo "has_tls13_cipher_order: $has_tls13_cipher_order" - - if "$TLS13_ONLY" && ! "$has_tls13_cipher_order"; then - out "no (TLS 1.3 only)" - limitedsense=" (limited sense as client will pick)" - fileout "$jsonID" "INFO" "not a cipher order for TLS 1.3 configured" - elif ! "$has_cipher_order" && ! "$has_tls13_cipher_order"; then - # server used the different ends (ciphers) from the client hello - pr_svrty_high "no (NOT ok)" - limitedsense=" (limited sense as client will pick)" - fileout "$jsonID" "HIGH" "NOT a cipher order configured" - elif "$has_cipher_order" && ! "$has_tls13_cipher_order" && [[ "$default_proto" == TLSv1.3 ]]; then - pr_svrty_good "yes (OK)"; out " -- only for < TLS 1.3" - fileout "$jsonID" "OK" "server -- TLS 1.3 client determined" - elif ! "$has_cipher_order" && "$has_tls13_cipher_order"; then - pr_svrty_high "no (NOT ok)"; out " -- only for TLS 1.3" - fileout "$jsonID" "HIGH" "server -- < TLS 1.3 client determined" - else - if "$has_tls13_cipher_order"; then - if "$TLS13_ONLY"; then - out "yes (TLS 1.3 only)" - fileout "$jsonID" "INFO" "server (TLS 1.3)" - else - pr_svrty_best "yes (OK)" - out " -- TLS 1.3 and below" - fileout "$jsonID" "OK" "server" - fi - else - # we don't have TLS 1.3 at all - pr_svrty_best "yes (OK)" - fileout "$jsonID" "OK" "server" - fi - fi - outln - - pr_bold " Negotiated protocol " - jsonID="protocol_negotiated" - - case "$default_proto" in - *TLSv1.3) - prln_svrty_best $default_proto - fileout "$jsonID" "OK" "Default protocol TLS1.3" - ;; - *TLSv1.2) - prln_svrty_best $default_proto - fileout "$jsonID" "OK" "Default protocol TLS1.2" - ;; - *TLSv1.1) - prln_svrty_low $default_proto - fileout "$jsonID" "LOW" "Default protocol TLS1.1" - ;; - *TLSv1) - prln_svrty_low $default_proto - fileout "$jsonID" "LOW" "Default protocol TLS1.0" - ;; - *SSLv2) - prln_svrty_critical $default_proto - fileout "$jsonID" "CRITICAL" "Default protocol SSLv2" - ;; - *SSLv3) - prln_svrty_critical $default_proto - fileout "$jsonID" "CRITICAL" "Default protocol SSLv3" - ;; - "") - pr_warning "default proto empty" - if [[ $OSSL_VER == 1.0.2* ]]; then - outln " (Hint: if IIS6 give OpenSSL 1.0.1 a try)" - fileout "$jsonID" "WARN" "Default protocol empty (Hint: if IIS6 give OpenSSL 1.0.1 a try)" - else - outln - fileout "$jsonID" "WARN" "Default protocol empty" - fi - ret=1 - ;; - *) - pr_warning "FIXME line $LINENO: $default_proto" - fileout "$jsonID" "WARN" "FIXME line $LINENO: $default_proto" - ret=1 - ;; - esac - - pr_bold " Negotiated cipher " - jsonID="cipher_negotiated" - - # restore file from above - [[ "$default_proto" == TLSv1.3 ]] && cp "$TEMPDIR/$NODEIP.parse_tls13_serverhello.txt" $TMPFILE - cipher1=$(get_cipher $TMPFILE) - - # Sanity check: Handshake with no ciphers and one with forward list didn't overlap - if [[ "$cipher0" != $cipher1 ]]; then - limitedsense=" (matching cipher in list missing)" - fi - - if [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]] && ( [[ "$cipher1" == TLS_* ]] || [[ "$cipher1" == SSL_* ]] ); then - default_cipher="$(rfc2openssl "$cipher1")" - elif [[ "$DISPLAY_CIPHERNAMES" =~ rfc ]] && [[ "$cipher1" != TLS_* ]] && [[ "$cipher1" != SSL_* ]]; then - default_cipher="$(openssl2rfc "$cipher1")" - fi - [[ -z "$default_cipher" ]] && default_cipher="$cipher1" - pr_cipher_quality "$default_cipher" - case $? in - 1) fileout "$jsonID" "CRITICAL" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") $limitedsense" - ;; - 2) fileout "$jsonID" "HIGH" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") $limitedsense" - ;; - 3) fileout "$jsonID" "MEDIUM" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") $limitedsense" - ;; - 6|7) fileout "$jsonID" "OK" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") $limitedsense" - ;; # best ones - 4) fileout "$jsonID" "LOW" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") (cbc) $limitedsense" - ;; # it's CBC. --> lucky13 - 0) pr_warning "default cipher empty" ; - if [[ $OSSL_VER == 1.0.2* ]]; then - out " (Hint: if IIS6 give OpenSSL 1.0.1 a try)" - fileout "$jsonID" "WARN" "Default cipher empty (if IIS6 give OpenSSL 1.0.1 a try) $limitedsense" - else - fileout "$jsonID" "WARN" "Default cipher empty $limitedsense" - fi - ret=1 - ;; - *) fileout "$jsonID" "INFO" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") $limitedsense" - ;; - esac - read_dhbits_from_file "$TMPFILE" - - if [[ "$cipher0" != $cipher1 ]]; then - pr_warning " -- inconclusive test, matching cipher in list missing" - outln ", better see below" - #FIXME: This is ugly but the best we can do before rewrite this section - else - outln "$limitedsense" - fi - - if "$has_cipher_order"; then - "$FAST" && using_sockets=false - [[ $TLS_NR_CIPHERS == 0 ]] && using_sockets=false - - pr_bold " Cipher order" - while read proto_ossl proto_hex proto_txt; do - [[ "$proto_ossl" == tls1_3 ]] && ! "$has_tls13_cipher_order" && continue - cipher_pref_check "$proto_ossl" "$proto_hex" "$proto_txt" "$using_sockets" - done <<< "$(tm_out " ssl3 00 SSLv3\n tls1 01 TLSv1\n tls1_1 02 TLSv1.1\n tls1_2 03 TLSv1.2\n tls1_3 04 TLSv1.3\n")" - outln - outln - else - pr_bold " Negotiated cipher per proto"; outln " $limitedsense" - i=1 - for proto_ossl in ssl2 ssl3 tls1 tls1_1 tls1_2 tls1_3; do - if [[ $proto_ossl == ssl2 ]] && ! "$HAS_SSL2"; then - if ! "$using_sockets" || [[ $TLS_NR_CIPHERS -eq 0 ]]; then - out " (SSLv2: "; pr_local_problem "$OPENSSL doesn't support \"s_client -ssl2\""; outln ")"; - continue - else - sslv2_sockets "" "true" - if [[ $? -eq 3 ]] && [[ "$V2_HELLO_CIPHERSPEC_LENGTH" -ne 0 ]]; then - # Just arbitrarily pick the first cipher in the cipher-mapping.txt list. - proto[i]="SSLv2" - supported_sslv2_ciphers="$(grep "Supported cipher: " "$TEMPDIR/$NODEIP.parse_sslv2_serverhello.txt")" - for (( j=0; j < TLS_NR_CIPHERS; j++ )); do - if [[ "${TLS_CIPHER_SSLVERS[j]}" == "SSLv2" ]]; then - cipher1="${TLS_CIPHER_HEXCODE[j]}" - cipher1="$(tolower "x${cipher1:2:2}${cipher1:7:2}${cipher1:12:2}")" - if [[ "$supported_sslv2_ciphers" =~ $cipher1 ]]; then - if ( [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]] && [[ "${TLS_CIPHER_OSSL_NAME[j]}" != "-" ]] ) || [[ "${TLS_CIPHER_RFC_NAME[j]}" == "-" ]]; then - cipher[i]="${TLS_CIPHER_OSSL_NAME[j]}" - else - cipher[i]="${TLS_CIPHER_RFC_NAME[j]}" - fi - break - fi - fi - done - [[ $DEBUG -ge 2 ]] && tmln_out "Default cipher for ${proto[i]}: ${cipher[i]}" - else - proto[i]="" - cipher[i]="" - fi - fi - elif ( [[ $proto_ossl == ssl3 ]] && ! "$HAS_SSL3" ) || ( [[ $proto_ossl == tls1_3 ]] && ! "$HAS_TLS13" ); then - if [[ $proto_ossl == ssl3 ]]; then - proto_txt="SSLv3" ; proto_hex="00" ; cipherlist="$TLS_CIPHER" - else - proto_txt="TLSv1.3" ; proto_hex="04" ; cipherlist="$TLS13_CIPHER" - fi - if ! "$using_sockets"; then - out " ($proto_txt: "; pr_local_problem "$OPENSSL doesn't support \"s_client -$proto_ossl\"" ; outln ")"; - continue - else - tls_sockets "$proto_hex" "$cipherlist" - if [[ $? -eq 0 ]]; then - proto[i]="$proto_txt" - cipher1=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") - cipher[i]="$cipher1" - if [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]] && [[ $TLS_NR_CIPHERS -ne 0 ]]; then - cipher[i]="$(rfc2openssl "$cipher1")" - [[ -z "${cipher[i]}" ]] && cipher[i]="$cipher1" - fi - [[ $DEBUG -ge 2 ]] && tmln_out "Default cipher for ${proto[i]}: ${cipher[i]}" - else - proto[i]="" - cipher[i]="" - fi - fi - else - $OPENSSL s_client $(s_client_options "$STARTTLS -"$proto_ossl" $BUGS -connect $NODEIP:$PORT $PROXY $SNI") </dev/null 2>>$ERRFILE >$TMPFILE - if sclient_connect_successful $? $TMPFILE; then - proto[i]=$(get_protocol $TMPFILE) - cipher[i]=$(get_cipher $TMPFILE) - [[ ${cipher[i]} == "0000" ]] && cipher[i]="" # Hack! - if [[ "$DISPLAY_CIPHERNAMES" =~ rfc ]] && [[ -n "${cipher[i]}" ]]; then - cipher[i]="$(openssl2rfc "${cipher[i]}")" - [[ -z "${cipher[i]}" ]] && cipher[i]=$(get_cipher $TMPFILE) - fi - [[ $DEBUG -ge 2 ]] && tmln_out "Default cipher for ${proto[i]}: ${cipher[i]}" - else - proto[i]="" - cipher[i]="" - fi - fi - [[ -n "${cipher[i]}" ]] && add_tls_offered "$proto_ossl" yes - i=$((i + 1)) - done - - for i in 1 2 3 4 5 6; do - if [[ -n "${cipher[i]}" ]]; then # cipher not empty - if [[ -z "$prev_cipher" ]] || [[ "$prev_cipher" != "${cipher[i]}" ]]; then - [[ -n "$prev_cipher" ]] && outln - str_len=${#cipher[i]} - out " " - if [[ "$COLOR" -le 2 ]]; then - out "${cipher[i]}" - else - pr_cipher_quality "${cipher[i]}" - fi - out ":" - if [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]]; then - for (( 1; str_len < 30; str_len++ )); do - out " " - done - else - for (( 1; str_len < 51; str_len++ )); do - out " " - done - fi - else - out ", " # same cipher --> only print out protocol behind it - fi - out "${proto[i]}" - prev_cipher="${cipher[i]}" - fi - fileout "cipher_order_${proto[i]}" "INFO" "${cipher[i]} at ${proto[i]} $limitedsense" - done - outln "\n No further cipher order check has been done as order is determined by the client" - outln - fi - return $ret -} - -check_tls12_pref() { - local batchremoved="-CAMELLIA:-IDEA:-KRB5:-PSK:-SRP:-aNULL:-eNULL" - local batchremoved_success=false - local tested_cipher="" cipher ciphers_to_test - local order="" - local -i nr_ciphers_found_r1=0 nr_ciphers_found_r2=0 - - while true; do - $OPENSSL s_client $(s_client_options "$STARTTLS -tls1_2 $BUGS -cipher "ALL$tested_cipher:$batchremoved" -connect $NODEIP:$PORT $PROXY $SNI") </dev/null 2>>$ERRFILE >$TMPFILE - if sclient_connect_successful $? $TMPFILE ; then - cipher=$(get_cipher $TMPFILE) - order+=" $cipher" - tested_cipher="$tested_cipher:-$cipher" - nr_ciphers_found_r1+=1 - "$FAST" && break - else - debugme tmln_out "A: $tested_cipher" - break - fi - done - batchremoved="${batchremoved//-/}" - while true; do - # no ciphers from "ALL$tested_cipher:$batchremoved" left - # now we check $batchremoved, and remove the minus signs first: - $OPENSSL s_client $(s_client_options "$STARTTLS -tls1_2 $BUGS -cipher "$batchremoved" -connect $NODEIP:$PORT $PROXY $SNI") </dev/null 2>>$ERRFILE >$TMPFILE - if sclient_connect_successful $? $TMPFILE ; then - batchremoved_success=true # signals that we have some of those ciphers and need to put everything together later on - cipher=$(get_cipher $TMPFILE) - order+=" $cipher" - batchremoved="$batchremoved:-$cipher" - nr_ciphers_found_r1+=1 - debugme tmln_out "B1: $batchremoved" - "$FAST" && break - else - debugme tmln_out "B2: $batchremoved" - break - # nothing left with batchremoved ciphers, we need to put everything together - fi - done - - if "$batchremoved_success"; then - # now we combine the two cipher sets from both while loops - combined_ciphers="$order" - order="" ; tested_cipher="" - while true; do - ciphers_to_test="" - for cipher in $combined_ciphers; do - [[ ! "$tested_cipher:" =~ :-$cipher: ]] && ciphers_to_test+=":$cipher" - done - [[ -z "$ciphers_to_test" ]] && break - $OPENSSL s_client $(s_client_options "$STARTTLS -tls1_2 $BUGS -cipher "${ciphers_to_test:1}" -connect $NODEIP:$PORT $PROXY $SNI") </dev/null 2>>$ERRFILE >$TMPFILE - if sclient_connect_successful $? $TMPFILE ; then - cipher=$(get_cipher $TMPFILE) - order+=" $cipher" - tested_cipher="$tested_cipher:-$cipher" - nr_ciphers_found_r2+=1 - "$FAST" && break - else - # This shouldn't happen. - break - fi - done - if "$FAST" && [[ $nr_ciphers_found_r2 -ne 1 ]]; then - prln_fixme "something weird happened around line $((LINENO - 14))" - return 1 - elif ! "$FAST" && [[ $nr_ciphers_found_r2 -ne $nr_ciphers_found_r1 ]]; then - prln_fixme "something weird happened around line $((LINENO - 16))" - return 1 - fi - fi - tm_out "$order" - - tmpfile_handle ${FUNCNAME[0]}.txt - return 0 -} - - -cipher_pref_check() { - local p="$1" proto_hex="$2" proto="$3" - local using_sockets="$4" - local tested_cipher cipher order rfc_cipher rfc_order - local overflow_probe_cipherlist="ALL:-ECDHE-RSA-AES256-GCM-SHA384:-AES128-SHA:-DES-CBC3-SHA" - local -i i nr_ciphers nr_nonossl_ciphers num_bundles mod_check bundle_size bundle end_of_bundle success - local hexc ciphers_to_test - local -a rfc_ciph hexcode ciphers_found ciphers_found2 - local -a -i index - local ciphers_found_with_sockets - - order=""; ciphers_found_with_sockets=false - if [[ $p == ssl3 ]] && ! "$HAS_SSL3" && ! "$using_sockets"; then - out "\n SSLv3: "; pr_local_problem "$OPENSSL doesn't support \"s_client -ssl3\""; - return 0 - fi - if [[ $p == tls1_3 ]] && ! "$HAS_TLS13" && ! "$using_sockets"; then - out "\n TLSv1.3 "; pr_local_problem "$OPENSSL doesn't support \"s_client -tls1_3\""; - return 0 - fi - - [[ $(has_server_protocol "$p") -eq 1 ]] && return 0 - - if ( [[ $p != tls1_3 ]] || "$HAS_TLS13" ) && ( [[ $p != ssl3 ]] || "$HAS_SSL3" ); then - if [[ $p == tls1_2 ]] && "$SERVER_SIZE_LIMIT_BUG"; then - order="$(check_tls12_pref)" - else - tested_cipher="" - while true; do - if [[ $p != tls1_3 ]]; then - ciphers_to_test="-cipher ALL:COMPLEMENTOFALL${tested_cipher}" - else - ciphers_to_test="" - for cipher in $(colon_to_spaces "$TLS13_OSSL_CIPHERS"); do - [[ ! "$tested_cipher" =~ ":-"$cipher ]] && ciphers_to_test+=":$cipher" - done - [[ -z "$ciphers_to_test" ]] && break - ciphers_to_test="-ciphersuites ${ciphers_to_test:1}" - fi - $OPENSSL s_client $(s_client_options "$STARTTLS -"$p" $BUGS $ciphers_to_test -connect $NODEIP:$PORT $PROXY $SNI") </dev/null 2>>$ERRFILE >$TMPFILE - sclient_connect_successful $? $TMPFILE || break - cipher=$(get_cipher $TMPFILE) - [[ -z "$cipher" ]] && break - order+="$cipher " - tested_cipher+=":-"$cipher - "$FAST" && break - done - fi - fi - - nr_nonossl_ciphers=0 - if "$using_sockets"; then - for (( i=0; i < TLS_NR_CIPHERS; i++ )); do - ciphers_found[i]=false - hexc="${TLS_CIPHER_HEXCODE[i]}" - if [[ ${#hexc} -eq 9 ]]; then - if [[ " $order " =~ " ${TLS_CIPHER_OSSL_NAME[i]} " ]]; then - ciphers_found[i]=true - else - ciphers_found2[nr_nonossl_ciphers]=false - hexcode[nr_nonossl_ciphers]="${hexc:2:2},${hexc:7:2}" - rfc_ciph[nr_nonossl_ciphers]="${TLS_CIPHER_RFC_NAME[i]}" - index[nr_nonossl_ciphers]=$i - # Only test ciphers that are relevant to the protocol. - if [[ "$p" == tls1_3 ]]; then - [[ "${hexc:2:2}" == "13" ]] && nr_nonossl_ciphers+=1 - elif [[ "$p" == tls1_2 ]]; then - [[ "${hexc:2:2}" != 13 ]] && nr_nonossl_ciphers+=1 - elif [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SHA256 ]] && \ - [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SHA384 ]] && \ - [[ "${TLS_CIPHER_RFC_NAME[i]}" != *"_CCM" ]] && \ - [[ "${TLS_CIPHER_RFC_NAME[i]}" != *"_CCM_8" ]]; then - nr_nonossl_ciphers+=1 - fi - fi - fi - done - fi - - if [[ $nr_nonossl_ciphers -eq 0 ]]; then - num_bundles=0 - elif [[ $p != tls1_2 ]] || ! "$SERVER_SIZE_LIMIT_BUG"; then - num_bundles=1 - bundle_size=$nr_nonossl_ciphers - else - num_bundles=$nr_nonossl_ciphers/128 - mod_check=$nr_nonossl_ciphers%128 - [[ $mod_check -ne 0 ]] && num_bundles=$num_bundles+1 - - bundle_size=$nr_nonossl_ciphers/$num_bundles - mod_check=$nr_nonossl_ciphers%$num_bundles - [[ $mod_check -ne 0 ]] && bundle_size+=1 - fi - - for (( bundle=0; bundle < num_bundles; bundle++ )); do - end_of_bundle=$bundle*$bundle_size+$bundle_size - [[ $end_of_bundle -gt $nr_nonossl_ciphers ]] && end_of_bundle=$nr_nonossl_ciphers - while true; do - ciphers_to_test="" - for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do - ! "${ciphers_found2[i]}" && ciphers_to_test+=", ${hexcode[i]}" - done - [[ -z "$ciphers_to_test" ]] && break - tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "ephemeralkey" - [[ $? -ne 0 ]] && break - cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") - for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do - [[ "$cipher" == "${rfc_ciph[i]}" ]] && ciphers_found2[i]=true && break - done - i=${index[i]} - ciphers_found[i]=true - ciphers_found_with_sockets=true - if [[ $p != tls1_2 ]] || ! "$SERVER_SIZE_LIMIT_BUG"; then - # Throw out the results found so far and start over using just sockets - bundle=$num_bundles - for (( i=0; i < TLS_NR_CIPHERS; i++ )); do - ciphers_found[i]=true - done - break - fi - done - done - - # If additional ciphers were found using sockets and there is no - # SERVER_SIZE_LIMIT_BUG, then just use sockets to find the cipher order. - # If there is a SERVER_SIZE_LIMIT_BUG, then use sockets to find the cipher - # order, but starting with the list of ciphers supported by the server. - if "$ciphers_found_with_sockets"; then - order="" - nr_ciphers=0 - for (( i=0; i < TLS_NR_CIPHERS; i++ )); do - hexc="${TLS_CIPHER_HEXCODE[i]}" - if "${ciphers_found[i]}" && [[ ${#hexc} -eq 9 ]]; then - ciphers_found2[nr_ciphers]=false - hexcode[nr_ciphers]="${hexc:2:2},${hexc:7:2}" - rfc_ciph[nr_ciphers]="${TLS_CIPHER_RFC_NAME[i]}" - if [[ "$p" == "tls1_3" ]]; then - [[ "${hexc:2:2}" == "13" ]] && nr_ciphers+=1 - elif [[ "$p" == "tls1_2" ]]; then - [[ "${hexc:2:2}" != "13" ]] && nr_ciphers+=1 - elif [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SHA256 ]] && \ - [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SHA384 ]] && \ - [[ "${TLS_CIPHER_RFC_NAME[i]}" != *"_CCM" ]] && \ - [[ "${TLS_CIPHER_RFC_NAME[i]}" != *"_CCM_8" ]]; then - nr_ciphers+=1 - fi - fi - done - while true; do - ciphers_to_test="" - for (( i=0; i < nr_ciphers; i++ )); do - ! "${ciphers_found2[i]}" && ciphers_to_test+=", ${hexcode[i]}" - done - [[ -z "$ciphers_to_test" ]] && break - tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "ephemeralkey" - [[ $? -ne 0 ]] && break - cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") - for (( i=0; i < nr_ciphers; i++ )); do - [[ "$cipher" == ${rfc_ciph[i]} ]] && ciphers_found2[i]=true && break - done - if [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]] && [[ $TLS_NR_CIPHERS -ne 0 ]]; then - cipher="$(rfc2openssl "$cipher")" - # If there is no OpenSSL name for the cipher, then use the RFC name - [[ -z "$cipher" ]] && cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") - fi - order+="$cipher " - done - elif [[ -n "$order" ]] && [[ "$DISPLAY_CIPHERNAMES" =~ rfc ]]; then - rfc_order="" - while read -d " " cipher; do - rfc_cipher="$(openssl2rfc "$cipher")" - if [[ -n "$rfc_cipher" ]]; then - rfc_order+="$rfc_cipher " - else - rfc_order+="$cipher " - fi - done <<< "$order" - order="$rfc_order" - fi - - if [[ -n "$order" ]]; then - add_tls_offered "$p" yes - outln - out "$(printf " %-10s " "$proto: ")" - if [[ "$COLOR" -le 2 ]]; then - out "$(out_row_aligned_max_width "$order" " " $TERM_WIDTH)" - else - out_row_aligned_max_width_by_entry "$order" " " $TERM_WIDTH pr_cipher_quality - fi - fileout "cipherorder_${proto//./_}" "INFO" "$order" - fi - - tmpfile_handle ${FUNCNAME[0]}-$p.txt - return 0 -} - - -# arg1 is OpenSSL s_client parameter or empty -# -get_host_cert() { - local tmpvar=$TEMPDIR/${FUNCNAME[0]}.txt # change later to $TMPFILE - - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI $1") 2>/dev/null </dev/null >$tmpvar - if sclient_connect_successful $? $tmpvar; then - awk '/-----BEGIN/,/-----END/ { print $0 }' $tmpvar >$HOSTCERT - return 0 - else - if [[ -z "$1" ]]; then - prln_warning "could not retrieve host certificate!" - fileout "host_certificate_Problem" "WARN" "Could not retrieve host certificate!" - fi - return 1 - fi - #tmpfile_handle ${FUNCNAME[0]}.txt - #return $((${PIPESTATUS[0]} + ${PIPESTATUS[1]})) -} - -verify_retcode_helper() { - local ret=0 - local -i retcode=$1 - - case $retcode in - # codes from ./doc/apps/verify.pod | verify(1ssl) - 44) tm_out "(different CRL scope)" ;; # X509_V_ERR_DIFFERENT_CRL_SCOPE - 26) tm_out "(unsupported certificate purpose)" ;; # X509_V_ERR_INVALID_PURPOSE - 24) tm_out "(certificate unreadable)" ;; # X509_V_ERR_INVALID_CA - 23) tm_out "(certificate revoked)" ;; # X509_V_ERR_CERT_REVOKED - 21) tm_out "(chain incomplete, only 1 cert provided)" ;; # X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE - 20) tm_out "(chain incomplete)" ;; # X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY - 19) tm_out "(self signed CA in chain)" ;; # X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN - 18) tm_out "(self signed)" ;; # X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT - 10) tm_out "(expired)" ;; # X509_V_ERR_CERT_HAS_EXPIRED - 9) tm_out "(not yet valid)" ;; # X509_V_ERR_CERT_NOT_YET_VALID - 2) tm_out "(issuer cert missing)" ;; # X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT - *) ret=1 ; tm_out " (unknown, pls report) $1" ;; - esac - return $ret -} - -# arg1: number of certificate if provided >1 -determine_trust() { - local jsonID="$1" - local json_postfix="$2" - local -i i=1 - local -i num_ca_bundles=0 - local bundle_fname="" - local -a certificate_file verify_retcode trust - local ok_was="" - local notok_was="" - local all_ok=true - local some_ok=false - local code - local ca_bundles="" - local spaces=" " - local -i certificates_provided=1+$(grep -c "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TEMPDIR/intermediatecerts.pem) - local addtl_warning - - # If $json_postfix is not empty, then there is more than one certificate - # and the output should should be indented by two more spaces. - [[ -n $json_postfix ]] && spaces=" " - - case $OSSL_VER_MAJOR.$OSSL_VER_MINOR in - 1.0.2|1.1.0|1.1.1|2.[1-9].*|3.*) # 2.x is LibreSSL. 2.1.1 was tested to work, below is not sure - : - ;; - *) addtl_warning="Your $OPENSSL <= 1.0.2 might be too unreliable to determine trust" - fileout "${jsonID}${json_postfix}" "WARN" "$addtl_warning" - addtl_warning="(${addtl_warning})" - ;; - esac - debugme tmln_out - - # if you run testssl.sh from a different path /you can set either TESTSSL_INSTALL_DIR or CA_BUNDLES_PATH to find the CA BUNDLES - if [[ -z "$CA_BUNDLES_PATH" ]]; then - ca_bundles="$TESTSSL_INSTALL_DIR/etc/*.pem" - else - ca_bundles="$CA_BUNDLES_PATH/*.pem" - fi - for bundle_fname in $ca_bundles; do - certificate_file[i]=$(basename ${bundle_fname//.pem}) - if [[ ! -r $bundle_fname ]]; then - prln_warning "\"$bundle_fname\" cannot be found / not readable" - return 1 - fi - debugme printf -- " %-12s" "${certificate_file[i]}" - # Set SSL_CERT_DIR to /dev/null so that $OPENSSL verify will only use certificates in $bundle_fname - # in a subshell because that should be valid here only - (export SSL_CERT_DIR="/dev/null"; export SSL_CERT_FILE="/dev/null" - if [[ $certificates_provided -ge 2 ]]; then - $OPENSSL verify -purpose sslserver -CAfile <(cat $ADDITIONAL_CA_FILES "$bundle_fname") -untrusted $TEMPDIR/intermediatecerts.pem $HOSTCERT >$TEMPDIR/${certificate_file[i]}.1 2>$TEMPDIR/${certificate_file[i]}.2 - else - $OPENSSL verify -purpose sslserver -CAfile <(cat $ADDITIONAL_CA_FILES "$bundle_fname") $HOSTCERT >$TEMPDIR/${certificate_file[i]}.1 2>$TEMPDIR/${certificate_file[i]}.2 - fi) - verify_retcode[i]=$(awk '/error [1-9][0-9]? at [0-9]+ depth lookup:/ { if (!found) {print $2; found=1} }' $TEMPDIR/${certificate_file[i]}.1 $TEMPDIR/${certificate_file[i]}.2) - [[ -z "${verify_retcode[i]}" ]] && verify_retcode[i]=0 - if [[ ${verify_retcode[i]} -eq 0 ]]; then - trust[i]=true - some_ok=true - [[ -z "$GOOD_CA_BUNDLE" ]] && GOOD_CA_BUNDLE="$bundle_fname" - debugme tm_svrty_good "Ok " - debugme tmln_out "${verify_retcode[i]}" - else - trust[i]=false - all_ok=false - debugme tm_svrty_high "not trusted " - debugme tmln_out "${verify_retcode[i]}" - fi - ((i++)) - done - num_ca_bundles=$((i - 1)) - debugme tm_out " " - if "$all_ok"; then - # all stores ok - pr_svrty_good "Ok "; pr_warning "$addtl_warning" - # we did to stdout the warning above already, so we could stay here with OK: - fileout "${jsonID}${json_postfix}" "OK" "passed. $addtl_warning" - else - # at least one failed - pr_svrty_critical "NOT ok" - if ! "$some_ok"; then - # all failed (we assume with the same issue), we're displaying the reason - out " " - code="$(verify_retcode_helper "${verify_retcode[1]}")" - if [[ "$code" =~ "pls report" ]]; then - pr_warning "$code" - else - out "$code" - fi - fileout "${jsonID}${json_postfix}" "CRITICAL" "failed $code. $addtl_warning" - else - # is one ok and the others not ==> display the culprit store - if "$some_ok"; then - pr_svrty_critical ":" - for ((i=1;i<=num_ca_bundles;i++)); do - if ${trust[i]}; then - ok_was="${certificate_file[i]} $ok_was" - else - #code="$(verify_retcode_helper ${verify_retcode[i]})" - #notok_was="${certificate_file[i]} $notok_was" - pr_svrty_high " ${certificate_file[i]} " - code="$(verify_retcode_helper "${verify_retcode[i]}")" - if [[ "$code" =~ "pls report" ]]; then - pr_warning "$code" - else - out "$code" - fi - notok_was="${certificate_file[i]} $code $notok_was" - fi - done - #pr_svrty_high "$notok_was " - #outln "$code" - outln - # lf + green ones - [[ "$DEBUG" -eq 0 ]] && tm_out "$spaces" - pr_svrty_good "OK: $ok_was" - fi - fileout "${jsonID}${json_postfix}" "CRITICAL" "Some certificate trust checks failed -> $notok_was $addtl_warning, OK -> $ok_was" - fi - [[ -n "$addtl_warning" ]] && out "\n$spaces" && pr_warning "$addtl_warning" - fi - outln - return 0 -} - -# not handled: Root CA supplied ("contains anchor" in SSLlabs terminology) - -tls_time() { - local difftime - local spaces=" " - local jsonID="TLS_timestamp" - - pr_bold " TLS clock skew" ; out "$spaces" - - if ( [[ "$STARTTLS_PROTOCOL" =~ ldap ]] || [[ "$STARTTLS_PROTOCOL" =~ irc ]] ); then - prln_local_problem "STARTTLS/$STARTTLS_PROTOCOL and --ssl-native collide here" - return 1 - fi - - TLS_DIFFTIME_SET=true # this is a switch whether we want to measure the remote TLS_TIME - tls_sockets "01" "$TLS_CIPHER" # try first TLS 1.0 (most frequently used protocol) - [[ -z "$TLS_TIME" ]] && tls_sockets "03" "$TLS12_CIPHER" # TLS 1.2 - [[ -z "$TLS_TIME" ]] && tls_sockets "02" "$TLS_CIPHER" # TLS 1.1 - [[ -z "$TLS_TIME" ]] && tls_sockets "00" "$TLS_CIPHER" # SSL 3 - - if [[ -n "$TLS_TIME" ]]; then # nothing returned a time! - difftime=$((TLS_TIME - TLS_NOW)) # TLS_NOW has been set in tls_sockets() - if [[ "${#difftime}" -gt 5 ]]; then - # openssl >= 1.0.1f fills this field with random values! --> good for possible fingerprint - out "Random values, no fingerprinting possible " - fileout "$jsonID" "INFO" "random" - else - [[ $difftime != "-"* ]] && [[ $difftime != "0" ]] && difftime="+$difftime" - out "$difftime"; out " sec from localtime"; - fileout "$jsonID" "INFO" "off by $difftime seconds from your localtime" - fi - debugme tm_out "$TLS_TIME" - outln - else - outln "SSLv3 through TLS 1.2 didn't return a timestamp" - fileout "$jsonID" "INFO" "None returned by SSLv3 through TLSv1.2" - fi - TLS_DIFFTIME_SET=false # reset the switch to save calls to date and friend in tls_sockets() - return 0 -} - -# core function determining whether handshake succeeded or not -# arg1: return value of "openssl s_client connect" -# arg2: temporary file with the server hello -# returns 0 if connect was successful, 1 if not -# -sclient_connect_successful() { - local server_hello="$(cat -v "$2")" - local re='Master-Key: ([^\ -]*)' - - [[ $1 -eq 0 ]] && return 0 - if [[ "$server_hello" =~ $re ]]; then - [[ -n "${BASH_REMATCH[1]}" ]] && return 0 - fi - [[ "$server_hello" =~ (New|Reused)", "(SSLv[23]|TLSv1(\.[0-3])?(\/SSLv3)?)", Cipher is "([A-Z0-9]+-[A-Za-z0-9\-]+|TLS_[A-Za-z0-9_]+) ]] && return 0 - # what's left now is: master key empty and Session-ID not empty - # ==> probably client-based auth with x509 certificate. We handle that at other places - # - # For robustness we also detected here network / server connectivity problems: - # Just need to check whether $TMPFILE=$2 is empty - if [[ ! -s "$2" ]]; then - ((NR_OSSL_FAIL++)) - connectivity_problem $NR_OSSL_FAIL $MAX_OSSL_FAIL "openssl s_client connect problem" "repeated openssl s_client connect problem, doesn't make sense to continue" - fi - return 1 -} - -extract_new_tls_extensions() { - local tls_extensions - - # this is not beautiful (grep+sed) - # but maybe we should just get the ids and do a private matching, according to - # https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml - tls_extensions=$(grep -a 'TLS server extension ' "$1" | \ - sed -e 's/TLS server extension //g' -e 's/\" (id=/\/#/g' \ - -e 's/,.*$/,/g' -e 's/),$/\"/g' \ - -e 's/elliptic curves\/#10/supported_groups\/#10/g') - tls_extensions=$(echo $tls_extensions) # into one line - - if [[ -n "$tls_extensions" ]]; then - # check to see if any new TLS extensions were returned and add any new ones to TLS_EXTENSIONS - while read -d "\"" -r line; do - if [[ $line != "" ]] && [[ ! "$TLS_EXTENSIONS" =~ "$line" ]]; then -#FIXME: This is a string of quoted strings, so this seems to determine the output format already. Better e.g. would be an array - TLS_EXTENSIONS+=" \"${line}\"" - fi - done <<<$tls_extensions - [[ "${TLS_EXTENSIONS:0:1}" == " " ]] && TLS_EXTENSIONS="${TLS_EXTENSIONS:1}" - fi -} - -# Note that since, at the moment, this function is only called by run_server_defaults() -# and run_heartbleed(), this function does not look for the status request or NPN -# extensions. For run_heartbleed(), only the heartbeat extension needs to be detected. -# For run_server_defaults(), the status request and NPN would already be detected by -# get_server_certificate(), if they are supported. In the case of the status extension, -# since including a status request extension in a ClientHello does not work for GOST -# only servers. In the case of NPN, since a server will not include both the NPN and -# ALPN extensions in the same ServerHello. -# -determine_tls_extensions() { - local addcmd - local -i success=1 - local line params="" tls_extensions="" - local alpn_proto alpn="" alpn_list_len_hex alpn_extn_len_hex - local -i alpn_list_len alpn_extn_len - local cbc_cipher_list="ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DH-RSA-AES256-SHA256:DH-DSS-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DH-RSA-AES256-SHA:DH-DSS-AES256-SHA:ECDHE-RSA-CAMELLIA256-SHA384:ECDHE-ECDSA-CAMELLIA256-SHA384:DHE-RSA-CAMELLIA256-SHA256:DHE-DSS-CAMELLIA256-SHA256:DH-RSA-CAMELLIA256-SHA256:DH-DSS-CAMELLIA256-SHA256:DHE-RSA-CAMELLIA256-SHA:DHE-DSS-CAMELLIA256-SHA:DH-RSA-CAMELLIA256-SHA:DH-DSS-CAMELLIA256-SHA:ECDH-RSA-AES256-SHA384:ECDH-ECDSA-AES256-SHA384:ECDH-RSA-AES256-SHA:ECDH-ECDSA-AES256-SHA:ECDH-RSA-CAMELLIA256-SHA384:ECDH-ECDSA-CAMELLIA256-SHA384:AES256-SHA256:AES256-SHA:CAMELLIA256-SHA256:CAMELLIA256-SHA:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DH-RSA-AES128-SHA256:DH-DSS-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:DH-RSA-AES128-SHA:DH-DSS-AES128-SHA:ECDHE-RSA-CAMELLIA128-SHA256:ECDHE-ECDSA-CAMELLIA128-SHA256:DHE-RSA-CAMELLIA128-SHA256:DHE-DSS-CAMELLIA128-SHA256:DH-RSA-CAMELLIA128-SHA256:DH-DSS-CAMELLIA128-SHA256:DHE-RSA-SEED-SHA:DHE-DSS-SEED-SHA:DH-RSA-SEED-SHA:DH-DSS-SEED-SHA:DHE-RSA-CAMELLIA128-SHA:DHE-DSS-CAMELLIA128-SHA:DH-RSA-CAMELLIA128-SHA:DH-DSS-CAMELLIA128-SHA:ECDH-RSA-AES128-SHA256:ECDH-ECDSA-AES128-SHA256:ECDH-RSA-AES128-SHA:ECDH-ECDSA-AES128-SHA:ECDH-RSA-CAMELLIA128-SHA256:ECDH-ECDSA-CAMELLIA128-SHA256:AES128-SHA256:AES128-SHA:CAMELLIA128-SHA256:SEED-SHA:CAMELLIA128-SHA:IDEA-CBC-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:DH-RSA-DES-CBC3-SHA:DH-DSS-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:DES-CBC3-SHA:EXP1024-DHE-DSS-DES-CBC-SHA:EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:DH-RSA-DES-CBC-SHA:DH-DSS-DES-CBC-SHA:EXP1024-DES-CBC-SHA:DES-CBC-SHA:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-DH-DSS-DES-CBC-SHA:EXP-DH-RSA-DES-CBC-SHA" - local cbc_cipher_list_hex="c0,28, c0,24, c0,14, c0,0a, 00,6b, 00,6a, 00,69, 00,68, 00,39, 00,38, 00,37, 00,36, c0,77, c0,73, 00,c4, 00,c3, 00,c2, 00,c1, 00,88, 00,87, 00,86, 00,85, c0,2a, c0,26, c0,0f, c0,05, c0,79, c0,75, 00,3d, 00,35, 00,c0, 00,84, c0,3d, c0,3f, c0,41, c0,43, c0,45, c0,49, c0,4b, c0,4d, c0,4f, c0,27, c0,23, c0,13, c0,09, 00,67, 00,40, 00,3f, 00,3e, 00,33, 00,32, 00,31, 00,30, c0,76, c0,72, 00,be, 00,bd, 00,bc, 00,bb, 00,9a, 00,99, 00,98, 00,97, 00,45, 00,44, 00,43, 00,42, c0,29, c0,25, c0,0e, c0,04, c0,78, c0,74, 00,3c, 00,2f, 00,ba, 00,96, 00,41, 00,07, c0,3c, c0,3e, c0,40, c0,42, c0,44, c0,48, c0,4a, c0,4c, c0,4e, c0,12, c0,08, 00,16, 00,13, 00,10, 00,0d, c0,0d, c0,03, 00,0a, fe,ff, ff,e0, 00,63, 00,15, 00,12, 00,0f, 00,0c, 00,62, 00,09, fe,fe, ff,e1, 00,14, 00,11, 00,08, 00,06, 00,0b, 00,0e" - local using_sockets=true - - [[ "$OPTIMAL_PROTO" == -ssl2 ]] && return 0 - "$SSL_NATIVE" && using_sockets=false - - if "$using_sockets"; then - tls_extensions="00,01,00,01,02, 00,02,00,00, 00,04,00,00, 00,12,00,00, 00,16,00,00, 00,17,00,00" - if [[ -z $STARTTLS ]]; then - for alpn_proto in $ALPN_PROTOs; do - alpn+=",$(printf "%02x" ${#alpn_proto}),$(string_to_asciihex "$alpn_proto")" - done - alpn_list_len=${#alpn}/3 - alpn_list_len_hex=$(printf "%04x" $alpn_list_len) - alpn_extn_len=$alpn_list_len+2 - alpn_extn_len_hex=$(printf "%04x" $alpn_extn_len) - tls_extensions+=", 00,10,${alpn_extn_len_hex:0:2},${alpn_extn_len_hex:2:2},${alpn_list_len_hex:0:2},${alpn_list_len_hex:2:2}$alpn" - fi - if [[ ! "$TLS_EXTENSIONS" =~ encrypt-then-mac ]]; then - tls_sockets "03" "$cbc_cipher_list_hex, 00,ff" "all" "$tls_extensions" - success=$? - fi - if [[ $success -ne 0 ]] && [[ $success -ne 2 ]]; then - tls_sockets "03" "$TLS12_CIPHER" "all" "$tls_extensions" - success=$? - fi - [[ $success -eq 2 ]] && success=0 - [[ $success -eq 0 ]] && extract_new_tls_extensions "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" - if [[ -r "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" ]]; then - cp "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" $TMPFILE - tmpfile_handle ${FUNCNAME[0]}.txt - fi - else - if "$HAS_ALPN" && [[ -z $STARTTLS ]]; then - params="-alpn \"${ALPN_PROTOs// /,}\"" # we need to replace " " by "," - elif "$HAS_NPN" && [[ -z $STARTTLS ]]; then - params="-nextprotoneg \"$NPN_PROTOs\"" - fi - if [[ -z "$OPTIMAL_PROTO" ]] && [[ -z "$SNI" ]] && "$HAS_NO_SSL2"; then - addcmd="-no_ssl2" - else - addcmd="$SNI" - fi - if [[ ! "$TLS_EXTENSIONS" =~ encrypt-then-mac ]]; then - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $addcmd $OPTIMAL_PROTO -tlsextdebug $params -cipher $cbc_cipher_list") </dev/null 2>$ERRFILE >$TMPFILE - sclient_connect_successful $? $TMPFILE - success=$? - fi - if [[ $success -ne 0 ]]; then - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $addcmd $OPTIMAL_PROTO -tlsextdebug $params") </dev/null 2>$ERRFILE >$TMPFILE - sclient_connect_successful $? $TMPFILE - success=$? - fi - [[ $success -eq 0 ]] && extract_new_tls_extensions $TMPFILE - tmpfile_handle ${FUNCNAME[0]}.txt - fi - return $success -} - -extract_certificates() { - local version="$1" - local savedir - local -i i success nrsaved=0 - local issuerDN CAsubjectDN previssuerDN - - # Place the server's certificate in $HOSTCERT and any intermediate - # certificates that were provided in $TEMPDIR/intermediatecerts.pem - savedir="$PWD"; cd $TEMPDIR - # https://backreference.org/2010/05/09/ocsp-verification-with-openssl/ - if [[ "$version" == ssl2 ]]; then - awk -v n=-1 '/Server certificate/ {start=1} - /-----BEGIN CERTIFICATE-----/{ if (start) {inc=1; n++} } - inc { print > ("level" n ".crt") } - /---END CERTIFICATE-----/{ inc=0 }' $TMPFILE - else - awk -v n=-1 '/Certificate chain/ {start=1} - /-----BEGIN CERTIFICATE-----/{ if (start) {inc=1; n++} } - inc { print > ("level" n ".crt") } - /---END CERTIFICATE-----/{ inc=0 }' $TMPFILE - fi - [[ -s level0.crt ]] && nrsaved=$(count_words "$(echo level?.crt 2>/dev/null)") - if [[ $nrsaved -eq 0 ]]; then - success=1 - else - success=0 - CERTIFICATE_LIST_ORDERING_PROBLEM=false - mv level0.crt $HOSTCERT - if [[ $nrsaved -eq 1 ]]; then - echo "" > $TEMPDIR/intermediatecerts.pem - else - cat level?.crt > $TEMPDIR/intermediatecerts.pem - issuerDN="$($OPENSSL x509 -in $HOSTCERT -noout -issuer 2>/dev/null)" - issuerDN="${issuerDN:8}" - previssuerDN="$issuerDN" - # The second certificate (level1.crt) SHOULD be issued to the CA - # that issued the server's certificate. But, according to RFC 8446 - # clients SHOULD be prepared to handle cases in which the server - # does not order the certificates correctly. - for (( i=1; i < nrsaved; i++ )); do - CAsubjectDN="$($OPENSSL x509 -in "level$i.crt" -noout -subject 2>/dev/null)" - if [[ "${CAsubjectDN:9}" == "$issuerDN" ]]; then - cp "level$i.crt" $TEMPDIR/hostcert_issuer.pem - issuerDN="" # set to empty to prevent further matches - fi - [[ "${CAsubjectDN:9}" != "$previssuerDN" ]] && CERTIFICATE_LIST_ORDERING_PROBLEM=true - "$CERTIFICATE_LIST_ORDERING_PROBLEM" && [[ -z "$issuerDN" ]] && break - previssuerDN="$($OPENSSL x509 -in "level$i.crt" -noout -issuer 2>/dev/null)" - previssuerDN="${previssuerDN:8}" - done - # This should never happen, but if more than one certificate was - # provided and none of them belong to the CA that issued the - # server's certificate, then the extra certificates should just - # be deleted. There is code elsewhere that assumes that if - # $TEMPDIR/intermediatecerts.pem is non-empty, then - # $TEMPDIR/hostcert_issuer.pem is also present. - [[ -n "$issuerDN" ]] && echo "" > $TEMPDIR/intermediatecerts.pem - rm level?.crt - fi - fi - cd "$savedir" - return $success -} - -extract_stapled_ocsp() { - local response="$(cat $TMPFILE)" - local ocsp tmp - local -i ocsp_len - - STAPLED_OCSP_RESPONSE="" - if [[ "$response" =~ CertificateStatus ]]; then - # This is OpenSSL 1.1.0 or 1.1.1 and the response - # is TLS 1.2 or earlier. - ocsp="${response##*CertificateStatus}" - ocsp="16${ocsp#*16}" - ocsp="${ocsp%%<<<*}" - ocsp="$(strip_spaces "$(newline_to_spaces "$ocsp")")" - ocsp="${ocsp:8}" - elif [[ "$response" =~ "TLS server extension \"status request\" (id=5), len=0" ]]; then - # This is not OpenSSL 1.1.0 or 1.1.1, and the response - # is TLS 1.2 or earlier. - ocsp="${response%%OCSP response:*}" - ocsp="${ocsp##*<<<}" - ocsp="16${ocsp#*16}" - ocsp="$(strip_spaces "$(newline_to_spaces "$ocsp")")" - ocsp="${ocsp:8}" - elif [[ "$response" =~ "TLS server extension \"status request\" (id=5), len=" ]]; then - # This is OpenSSL 1.1.1 and the response is TLS 1.3. - ocsp="${response##*TLS server extension \"status request\" (id=5), len=}" - ocsp="${ocsp%%<<<*}" - tmp="${ocsp%%[!0-9]*}" - ocsp="${ocsp#$tmp}" - ocsp_len=2*$tmp - ocsp="$(awk ' { print $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 $14 $15 $16 $17 } ' <<< "$ocsp" | sed 's/-//')" - ocsp="$(strip_spaces "$(newline_to_spaces "$ocsp")")" - ocsp="${ocsp:0:ocsp_len}" - else - return 0 - fi - # Determine whether this is a single OCSP response or a sequence of - # responses and then extract just the response for the server's - # certificate. - if [[ "${ocsp:0:2}" == "01" ]]; then - STAPLED_OCSP_RESPONSE="${ocsp:8}" - elif [[ "${ocsp:0:2}" == "02" ]]; then - ocsp_len=2*$(hex2dec "${tls_certificate_status_ascii:8:6}") - STAPLED_OCSP_RESPONSE="${ocsp:14:ocsp_len}" - fi - return 0 -} - -# arg1 is "<OpenSSL cipher>" -# arg2 is a list of protocols to try (tls1_2, tls1_1, tls1, ssl3) or empty (if all should be tried) -get_server_certificate() { - local protocols_to_try proto - local success ret - local npn_params="" line - local ciphers_to_test="" - # Cipher suites that use a certificate with an RSA (signature) public key - local -r a_rsa="cc,13, cc,15, c0,30, c0,28, c0,14, 00,9f, cc,a8, cc,aa, c0,a3, c0,9f, 00,6b, 00,39, c0,77, 00,c4, 00,88, c0,45, c0,4d, c0,53, c0,61, c0,7d, c0,8b, 16,b7, 16,b9, c0,2f, c0,27, c0,13, 00,9e, c0,a2, c0,9e, 00,67, 00,33, c0,76, 00,be, 00,9a, 00,45, c0,44, c0,4c, c0,52, c0,60, c0,7c, c0,8a, c0,11, c0,12, 00,16, 00,15, 00,14, c0,10" - # Cipher suites that use a certificate with an RSA (encryption) public key - local -r e_rsa="00,b7, c0,99, 00,ad, cc,ae, 00,9d, c0,a1, c0,9d, 00,3d, 00,35, 00,c0, 00,84, 00,95, c0,3d, c0,51, c0,69, c0,6f, c0,7b, c0,93, ff,01, 00,ac, c0,a0, c0,9c, 00,9c, 00,3c, 00,2f, 00,ba, 00,b6, 00,96, 00,41, c0,98, 00,07, 00,94, c0,3c, c0,50, c0,68, c0,6e, c0,7a, c0,92, 00,05, 00,04, 00,92, 00,0a, 00,93, fe,ff, ff,e0, 00,62, 00,09, 00,61, fe,fe, ff,e1, 00,64, 00,60, 00,08, 00,06, 00,03, 00,b9, 00,b8, 00,2e, 00,3b, 00,02, 00,01, ff,00" - # Cipher suites that use a certificate with a DSA public key - local -r a_dss="00,a3, 00,6a, 00,38, 00,c3, 00,87, c0,43, c0,57, c0,81, 00,a2, 00,40, 00,32, 00,bd, 00,99, 00,44, c0,42, c0,56, c0,80, 00,66, 00,13, 00,63, 00,12, 00,65, 00,11" - # Cipher suites that use a certificate with a DH public key - local -r a_dh="00,a5, 00,a1, 00,69, 00,68, 00,37, 00,36, 00,c2, 00,c1, 00,86, 00,85, c0,3f, c0,41, c0,55, c0,59, c0,7f, c0,83, 00,a4, 00,a0, 00,3f, 00,3e, 00,31, 00,30, 00,bc, 00,bb, 00,98, 00,97, 00,43, 00,42, c0,3e, c0,40, c0,54, c0,58, c0,7e, c0,82, 00,10, 00,0d, 00,0f, 00,0c, 00,0b, 00,0e" - # Cipher suites that use a certificate with an ECDH public key - local -r a_ecdh="c0,32, c0,2e, c0,2a, c0,26, c0,0f, c0,05, c0,79, c0,75, c0,4b, c0,4f, c0,5f, c0,63, c0,89, c0,8d, c0,31, c0,2d, c0,29, c0,25, c0,0e, c0,04, c0,78, c0,74, c0,4a, c0,4e, c0,5e, c0,62, c0,88, c0,8c, c0,0c, c0,02, c0,0d, c0,03, c0,0b, c0,01" - # Cipher suites that use a certificate with an ECDSA public key - local -r a_ecdsa="cc,14, c0,2c, c0,24, c0,0a, cc,a9, c0,af, c0,ad, c0,73, c0,49, c0,5d, c0,87, 16,b8, 16,ba, c0,2b, c0,23, c0,09, c0,ae, c0,ac, c0,72, c0,48, c0,5c, c0,86, c0,07, c0,08, c0,06" - # Cipher suites that use a certificate with a GOST public key - local -r a_gost="00,80, 00,81, 00,82, 00,83" - local using_sockets=true - - "$SSL_NATIVE" && using_sockets=false - - CERTIFICATE_LIST_ORDERING_PROBLEM=false - if [[ "$1" =~ "tls1_3" ]]; then - [[ $(has_server_protocol "tls1_3") -eq 1 ]] && return 1 - if "$HAS_TLS13"; then - if [[ "$1" =~ "tls1_3_RSA" ]]; then - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -showcerts -connect $NODEIP:$PORT $PROXY $SNI -tls1_3 -tlsextdebug -status -msg -sigalgs PSS+SHA256:PSS+SHA384") </dev/null 2>$ERRFILE >$TMPFILE - elif [[ "$1" =~ "tls1_3_ECDSA" ]]; then - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -showcerts -connect $NODEIP:$PORT $PROXY $SNI -tls1_3 -tlsextdebug -status -msg -sigalgs ECDSA+SHA256:ECDSA+SHA384") </dev/null 2>$ERRFILE >$TMPFILE - else - return 1 - fi - sclient_connect_successful $? $TMPFILE || return 1 - DETECTED_TLS_VERSION="0304" - extract_certificates "tls1_3" - extract_stapled_ocsp - success=$? - else - # For STARTTLS protocols not being implemented yet via sockets this is a bypass otherwise it won't be usable at all (e.g. LDAP) - if ( [[ "$STARTTLS" =~ ldap ]] || [[ "$STARTTLS" =~ irc ]] ); then - return 1 - elif [[ "$1" =~ "tls1_3_RSA" ]]; then - tls_sockets "04" "$TLS13_CIPHER" "all+" "00,12,00,00, 00,05,00,05,01,00,00,00,00, 00,0d,00,10,00,0e,08,04,08,05,08,06,04,01,05,01,06,01,02,01" - elif [[ "$1" =~ "tls1_3_ECDSA" ]]; then - tls_sockets "04" "$TLS13_CIPHER" "all+" "00,12,00,00, 00,05,00,05,01,00,00,00,00, 00,0d,00,0a,00,08,04,03,05,03,06,03,02,03" - else - return 1 - fi - success=$? - [[ $success -eq 0 ]] || return 1 - cp "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" $TMPFILE - fi - [[ $success -eq 0 ]] && add_tls_offered tls1_3 yes - extract_new_tls_extensions $TMPFILE - tmpfile_handle ${FUNCNAME[0]}.txt - return $success - fi - - "$HAS_NPN" && [[ -z "$STARTTLS" ]] && npn_params="-nextprotoneg \"$NPN_PROTOs\"" - - if [[ -n "$2" ]]; then - protocols_to_try="$2" - else - protocols_to_try="tls1_2 tls1_1 tls1 ssl3" - fi - - # throwing 1st every cipher/protocol at the server to know what works - success=7 - - if [[ "$OPTIMAL_PROTO" == -ssl2 ]]; then - success=1 - sslv2_sockets "" "true" - if [[ $? -eq 3 ]]; then - mv $TEMPDIR/$NODEIP.parse_sslv2_serverhello.txt $TMPFILE - success=0 - fi - tmpfile_handle ${FUNCNAME[0]}.txt - return $success - fi - - if "$using_sockets"; then - protocols_to_try="${protocols_to_try/tls1_2/03}" - protocols_to_try="${protocols_to_try/tls1_1/02}" - protocols_to_try="${protocols_to_try/tls1/01}" - protocols_to_try="${protocols_to_try/ssl3/00}" - - [[ "$1" =~ aRSA ]] && ciphers_to_test+=", $a_rsa" - [[ "$1" =~ eRSA ]] && ciphers_to_test+=", $e_rsa" - [[ "$1" =~ aDSS ]] && ciphers_to_test+=", $a_dss" - [[ "$1" =~ aDH ]] && ciphers_to_test+=", $a_dh" - [[ "$1" =~ aECDH ]] && ciphers_to_test+=", $a_ecdh" - [[ "$1" =~ aECDSA ]] && ciphers_to_test+=", $a_ecdsa" - [[ "$1" =~ aGOST ]] && ciphers_to_test+=", $a_gost" - - [[ -z "$ciphers_to_test" ]] && return 1 - ciphers_to_test="${ciphers_to_test:2}" - - for proto in $protocols_to_try; do - [[ 1 -eq $(has_server_protocol $proto) ]] && continue - tls_sockets "$proto" "$ciphers_to_test, 00,ff" "all" "00,12,00,00, 00,05,00,05,01,00,00,00,00" - ret=$? - [[ $ret -eq 0 ]] && success=0 && break - [[ $ret -eq 2 ]] && success=0 && break - done # this loop is needed for IIS6 and others which have a handshake size limitations - if [[ $success -eq 7 ]]; then - # "-status" above doesn't work for GOST only servers, so we do another test without it and see whether that works then: - tls_sockets "$proto" "$ciphers_to_test, 00,ff" "all" "00,12,00,00" - ret=$? - [[ $ret -eq 0 ]] && success=0 - [[ $ret -eq 2 ]] && success=0 - if [[ $success -eq 7 ]]; then - if [ -z "$1" ]; then - prln_warning "Strange, no SSL/TLS protocol seems to be supported (error around line $((LINENO - 6)))" - fi - tmpfile_handle ${FUNCNAME[0]}.txt - return 7 # this is ugly, I know - else - GOST_STATUS_PROBLEM=true - fi - fi - cp $TEMPDIR/$NODEIP.parse_tls_serverhello.txt $TMPFILE - - # When "$2" is empty, get_server_certificate() is being called with SNI="". - # In case the extensions returned by the server differ depending on whether - # SNI is provided or not, don't collect extensions when SNI="" (unless - # no DNS name was provided at the command line). - [[ -z "$2" ]] && extract_new_tls_extensions $TMPFILE - else - # no sockets, openssl - ciphers_to_test="$1" - if [[ "$1" =~ aRSA ]] && [[ "$1" =~ eRSA ]]; then - ciphers_to_test="${ciphers_to_test/eRSA/}" - elif [[ "$1" =~ aRSA ]]; then - ciphers_to_test="${ciphers_to_test/aRSA/}" - for ciph in $(colon_to_spaces $(actually_supported_osslciphers "aRSA")); do - [[ "$ciph" =~ -RSA- ]] && ciphers_to_test+=":$ciph" - done - elif [[ "$1" =~ eRSA ]]; then - ciphers_to_test="${ciphers_to_test/eRSA/}" - for ciph in $(colon_to_spaces $(actually_supported_osslciphers "aRSA")); do - [[ ! "$ciph" =~ -RSA- ]] && ciphers_to_test+=":$ciph" - done - fi - ciphers_to_test="${ciphers_to_test/::/:}" - [[ "${ciphers_to_test:0:1}" == : ]] && ciphers_to_test="${ciphers_to_test:1}" - [[ $(count_ciphers $(actually_supported_osslciphers "$ciphers_to_test")) -ge 1 ]] || return 1 - - for proto in $protocols_to_try; do - [[ 1 -eq $(has_server_protocol $proto) ]] && continue - [[ "$proto" == ssl3 ]] && ! "$HAS_SSL3" && continue - addcmd="" - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -cipher $ciphers_to_test -showcerts -connect $NODEIP:$PORT $PROXY $SNI -$proto -tlsextdebug $npn_params -status -msg") </dev/null 2>$ERRFILE >$TMPFILE - if sclient_connect_successful $? $TMPFILE; then - success=0 - break # now we have the certificate - fi - done # this loop is needed for IIS6 and others which have a handshake size limitations - if [[ $success -eq 7 ]]; then - # "-status" above doesn't work for GOST only servers, so we do another test without it and see whether that works then: - [[ "$proto" == ssl3 ]] && ! "$HAS_SSL3" && return 7 - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -cipher $ciphers_to_test -showcerts -connect $NODEIP:$PORT $PROXY $SNI -$proto -tlsextdebug") </dev/null 2>>$ERRFILE >$TMPFILE - if ! sclient_connect_successful $? $TMPFILE; then - if [ -z "$1" ]; then - prln_warning "Strange, no SSL/TLS protocol seems to be supported (error around line $((LINENO - 6)))" - fi - tmpfile_handle ${FUNCNAME[0]}.txt - return 7 # this is ugly, I know - else - GOST_STATUS_PROBLEM=true - fi - fi - case "$proto" in - "tls1_2") DETECTED_TLS_VERSION="0303" ;; - "tls1_1") DETECTED_TLS_VERSION="0302" ;; - "tls1") DETECTED_TLS_VERSION="0301" ;; - "ssl3") DETECTED_TLS_VERSION="0300" ;; - esac - # When "$2" is empty, get_server_certificate() is being called with SNI="". - # In case the extensions returned by the server differ depending on whether - # SNI is provided or not, don't collect extensions when SNI="" (unless - # no DNS name was provided at the command line). - [[ -z "$2" ]] && extract_new_tls_extensions $TMPFILE - - extract_certificates "$proto" - extract_stapled_ocsp - success=$? - fi - tmpfile_handle ${FUNCNAME[0]}.txt - return $success -} - -# arg1: path to certificate -# returns CN -get_cn_from_cert() { - local subject - - # attention! openssl 1.0.2 doesn't properly handle online output from certificates from trustwave.com/github.com - #FIXME: use -nameopt oid for robustness - - # for e.g. russian sites -esc_msb,utf8 works in an UTF8 terminal -- any way to check platform independent? - # see x509(1ssl): - subject="$($OPENSSL x509 -in $1 -noout -subject -nameopt multiline,-align,sname,-esc_msb,utf8,-space_eq 2>>$ERRFILE)" - echo "$(awk -F'=' '/CN=/ { print $2 }' <<< "$subject" | tr '\n' ' ')" - return $? -} - -# Return 0 if the name provided in arg1 is a wildcard name -is_wildcard() -{ - local certname="$1" - - # If the first label in the DNS name begins "xn--", then assume it is an - # A-label and not a wildcard name (RFC 6125, Section 6.4.3). - [[ "${certname:0:4}" == "xn--" ]] && return 1 - - # Remove part of name preceding '*' or '.'. If no "*" appears in the - # left-most label, then it is not a wildcard name (RFC 6125, Section 6.4.3). - basename="$(echo -n "$certname" | sed 's/^[_a-zA-Z0-9\-]*//')" - [[ "${basename:0:1}" != "*" ]] && return 1 # not a wildcard name - - # Check that there are no additional wildcard ('*') characters or any - # other characters that do not belong in a DNS name. - [[ -n $(echo -n "${basename:1}" | sed 's/^[_\.a-zA-Z0-9\-]*//') ]] && return 1 - return 0 -} - -# Return 0 if the name provided in arg2 is a wildcard name and it matches the name provided in arg1. -wildcard_match() -{ - local servername="$1" - local certname="$2" - local basename - local -i basename_offset len_certname len_part1 len_basename - local -i len_servername len_wildcard - - len_servername=${#servername} - len_certname=${#certname} - - # Use rules from RFC 6125 to perform the match. - - # Assume the "*" in the wildcard needs to be replaced by one or more - # characters, although RFC 6125 is not clear about that. - [[ $len_servername -lt $len_certname ]] && return 1 - - is_wildcard "$certname" - [[ $? -ne 0 ]] && return 1 - - # Comparisons of DNS names are case insensitive, so convert both names to uppercase. - certname="$(toupper "$certname")" - servername="$(toupper "$servername")" - - # Extract part of name that comes after the "*" - basename="$(echo -n "$certname" | sed 's/^[_A-Z0-9\-]*\*//')" - len_basename=${#basename} - len_part1=$len_certname-$len_basename-1 - len_wildcard=$len_servername-$len_certname+1 - basename_offset=$len_servername-$len_basename - - # Check that initial part of $servername matches initial part of $certname - # and that final part of $servername matches final part of $certname. - [[ "${servername:0:len_part1}" != "${certname:0:len_part1}" ]] && return 1 - [[ "${servername:basename_offset:len_basename}" != "$basename" ]] && return 1 - - # Check that part of $servername that matches "*" is all part of a single - # domain label. - [[ -n $(echo -n "${servername:len_part1:len_wildcard}" | sed 's/^[_A-Z0-9\-]*//') ]] && return 1 - - return 0 -} - -# Compare the server name provided in arg1 to the CN and SAN in arg2 and return: -# 0, if server name provided does not match any of the names in the CN or SAN -# 1, if the server name provided matches a name in the SAN -# 2, if the server name provided is a wildcard match against a name in the SAN -# 4, if the server name provided matches the CN -# 5, if the server name provided matches the CN AND a name in the SAN -# 6, if the server name provided matches the CN AND is a wildcard match against a name in the SAN -# 8, if the server name provided is a wildcard match against the CN -# 9, if the server name provided matches a name in the SAN AND is a wildcard match against the CN -# 10, if the server name provided is a wildcard match against the CN AND a name in the SAN - -compare_server_name_to_cert() { - local cert="$1" - local servername cn dns_sans ip_sans san dercert tag - local srv_id="" xmppaddr="" - local -i i len len1 - local -i subret=0 # no error condition, passing results - - HAS_DNS_SANS=false - if [[ -n "$XMPP_HOST" ]]; then - # RFC 6120, Section 13.7.2.1, states that for XMPP the identity that - # should appear in the server's certificate is identity that appears - # in the the 'to' address that the client communicates in the initial - # stream header. - servername="$(toupper "$XMPP_HOST")" - else - servername="$(toupper "$NODE")" - fi - - # Check whether any of the DNS names in the certificate match the servername - dns_sans="$(get_san_dns_from_cert "$cert")" - while read san; do - if [[ -n "$san" ]]; then - HAS_DNS_SANS=true - [[ $(toupper "$san") == "$servername" ]] && subret=1 && break - fi - done <<< "$dns_sans" - - if [[ $subret -eq 0 ]]; then - # Check whether any of the IP addresses in the certificate match the servername - ip_sans=$($OPENSSL x509 -in "$cert" -noout -text 2>>$ERRFILE | grep -A2 "Subject Alternative Name" | \ - tr ',' '\n' | grep "IP Address:" | sed -e 's/IP Address://g' -e 's/ //g') - while read san; do - [[ -n "$san" ]] && [[ "$san" == "$servername" ]] && subret=1 && break - done <<< "$ip_sans" - fi - - if [[ $subret -eq 0 ]] && [[ -n "$XMPP_HOST" ]]; then - # For XMPP hosts, in addition to checking for a matching DNS name, - # should also check for a matching SRV-ID or XmppAddr identifier. - dercert="$($OPENSSL x509 -in "$cert" -outform DER 2>>$ERRFILE | hexdump -v -e '16/1 "%02X"')" - # Look for the beginning of the subjectAltName extension. It - # will begin with the OID (2.5.29.17 = 0603551D11). After the OID - # there may be an indication that the extension is critical (0101FF). - # Finally will be the tag indicating that the value of the extension is - # encoded as an OCTET STRING (04). - if [[ "$dercert" =~ 0603551D110101FF04 ]]; then - dercert="${dercert##*0603551D110101FF04}" - else - dercert="${dercert##*0603551D1104}" - fi - # Skip over the encoding of the length of the OCTET STRING. - if [[ "${dercert:0:1}" == "8" ]]; then - i="${dercert:1:1}" - i=2*$i+2 - dercert="${dercert:i}" - else - dercert="${dercert:2}" - fi - # Next byte should be a 30 (SEQUENCE). - if [[ "${dercert:0:2}" == "30" ]]; then - # Get the length of the subjectAltName extension and then skip - # over the encoding of the length. - if [[ "${dercert:2:1}" == "8" ]]; then - case "${dercert:3:1}" in - 1) len=2*0x${dercert:4:2}; dercert="${dercert:6}" ;; - 2) len=2*0x${dercert:4:4}; dercert="${dercert:8}" ;; - 3) len=2*0x${dercert:4:6}; dercert="${dercert:10}" ;; - *) len=0 ;; - esac - else - len=2*0x${dercert:2:2} - dercert="${dercert:4}" - fi - if [[ $len -ne 0 ]] && [[ $len -lt ${#dercert} ]]; then - # loop through all the names and extract the SRV-ID and XmppAddr identifiers - for (( i=0; i < len; i=i+len_name )); do - tag="${dercert:i:2}" - i+=2 - if [[ "${dercert:i:1}" == "8" ]]; then - i+=1 - case "${dercert:i:1}" in - 1) i+=1; len_name=2*0x${dercert:i:2}; i+=2 ;; - 2) i+=1; len_name=2*0x${dercert:i:4}; i+=4 ;; - 3) i+=1; len_name=2*0x${dercert:i:6}; i+=4 ;; - *) len=0 ;; - esac - else - len_name=2*0x${dercert:i:2} - i+=2 - fi - if [[ "$tag" == "A0" ]]; then - # This is an otherName. - if [[ $len_name -gt 18 ]] && ( [[ "${dercert:i:20}" == "06082B06010505070805" ]] || \ - [[ "${dercert:i:20}" == "06082B06010505070807" ]] ); then - # According to the OID, this is either an SRV-ID or XmppAddr. - j=$i+20 - if [[ "${dercert:j:2}" == "A0" ]]; then - j+=2 - if [[ "${dercert:j:1}" == "8" ]]; then - j+=1 - j+=2*0x${dercert:j:1}+1 - else - j+=2 - fi - if ( [[ "${dercert:i:20}" == "06082B06010505070805" ]] && [[ "${dercert:j:2}" == "0C" ]] ) || \ - ( [[ "${dercert:i:20}" == "06082B06010505070807" ]] && [[ "${dercert:j:2}" == "16" ]] ); then - # XmppAddr should be encoded as UTF8STRING (0C) and - # SRV-ID should be encoded IA5STRING (16). - j+=2 - if [[ "${dercert:j:1}" == "8" ]]; then - j+=1 - case "${dercert:j:1}" in - 1) j+=1; len1=2*0x${dercert:j:2}; j+=2 ;; - 2) j+=1; len1=2*0x${dercert:j:4}; j+=4 ;; - 3) j+=1; len1=2*0x${dercert:j:6}; j+=6 ;; - 4) len1=0 ;; - esac - else - len1=2*0x${dercert:j:2} - j+=2 - fi - if [[ $len1 -ne 0 ]]; then - san="$(asciihex_to_binary "${dercert:j:len1}")" - if [[ "${dercert:i:20}" == "06082B06010505070805" ]]; then - xmppaddr+="$san " - else - srv_id+="$san " - fi - fi - fi - fi - fi - fi - done - fi - fi - [[ -n "$srv_id" ]] && HAS_DNS_SANS=true - [[ -n "$xmppaddr" ]] && HAS_DNS_SANS=true - while read -d " " san; do - [[ -n "$san" ]] && [[ $(toupper "$san") == "_XMPP-SERVER.$servername" ]] && subret=1 && break - done <<< "$srv_id" - if [[ $subret -eq 0 ]]; then - while read -d " " san; do - [[ -n "$san" ]] && [[ $(toupper "$san") == "$servername" ]] && subret=1 && break - done <<< "$xmppaddr" - fi - fi - - # Check whether any of the DNS names in the certificate are wildcard names - # that match the servername - if [[ $subret -eq 0 ]]; then - while read san; do - [[ -n "$san" ]] || continue - wildcard_match "$servername" "$san" - [[ $? -eq 0 ]] && subret=2 && break - done <<< "$dns_sans" - fi - - cn="$(get_cn_from_cert "$cert")" - - # If the CN contains any characters that are not valid for a DNS name, - # then assume it does not contain a DNS name. - [[ -n $(sed 's/^[_\.a-zA-Z0-9*\-]*//' <<< "$cn") ]] && return $subret - - # Check whether the CN in the certificate matches the servername - [[ $(toupper "$cn") == "$servername" ]] && subret+=4 && return $subret - - # Check whether the CN in the certificate is a wildcard name that matches - # the servername - wildcard_match "$servername" "$cn" - [[ $? -eq 0 ]] && subret+=8 - return $subret -} - -# This function determines whether the certificate (arg3) contains "visibility -# information" (see Section 4.3.3 of -# https://www.etsi.org/deliver/etsi_ts/103500_103599/10352303/01.01.01_60/ts_10352303v010101p.pdf . -etsi_etls_visibility_info() { - local jsonID="$1" - local spaces="$2" - local cert="$3" - local cert_txt="$4" - local dercert tag - local -a fingerprint=() access_description=() - local -i i j len len1 len_name nr_visnames=0 - - # If "visibility information" is present, it will appear in the subjectAltName - # extension (0603551D11) as an otherName with OID 0.4.0.3523.3.1 (060604009B430301). - # OpenSSL displays all names of type otherName as "othername:<unsupported>". - # As certificates will rarely include a name encoded as an otherName, check the - # text version of the certificate for "othername:<unsupported>" before calling - # external functions to obtain the DER encoded certificate. - if [[ "$cert_txt" =~ X509v3\ Subject\ Alternative\ Name:.*othername:\<unsupported\> ]]; then - dercert="$($OPENSSL x509 -in "$cert" -outform DER 2>>$ERRFILE | hexdump -v -e '16/1 "%02X"')" - if [[ "$dercert" =~ 0603551D110101FF04[0-9A-F]*060604009B430301 ]] || \ - [[ "$dercert" =~ 0603551D1104[0-9A-F]*060604009B430301 ]]; then - # Look for the beginning of the subjectAltName extension. It - # will begin with the OID (2.5.29.17 = 0603551D11). After the OID - # there may be an indication that the extension is critical (0101FF). - # Finally will be the tag indicating that the value of the extension is - # encoded as an OCTET STRING (04). - if [[ "$dercert" =~ 0603551D110101FF04 ]]; then - dercert="${dercert##*0603551D110101FF04}" - else - dercert="${dercert##*0603551D1104}" - fi - # Skip over the encoding of the length of the OCTET STRING. - if [[ "${dercert:0:1}" == 8 ]]; then - i="${dercert:1:1}" - i=2*$i+2 - dercert="${dercert:i}" - else - dercert="${dercert:2}" - fi - # Next byte should be a 30 (SEQUENCE). - if [[ "${dercert:0:2}" == 30 ]]; then - # Get the length of the subjectAltName extension and then skip - # over the encoding of the length. - if [[ "${dercert:2:1}" == 8 ]]; then - case "${dercert:3:1}" in - 1) len=2*0x${dercert:4:2}; dercert="${dercert:6}" ;; - 2) len=2*0x${dercert:4:4}; dercert="${dercert:8}" ;; - 3) len=2*0x${dercert:4:6}; dercert="${dercert:10}" ;; - *) len=0 ;; - esac - else - len=2*0x${dercert:2:2} - dercert="${dercert:4}" - fi - if [[ $len -ne 0 ]] && [[ $len -lt ${#dercert} ]]; then - # loop through all the names and extract the visibility information - for (( i=0; i < len; i=i+len_name )); do - tag="${dercert:i:2}" - i+=2 - if [[ "${dercert:i:1}" == 8 ]]; then - i+=1 - case "${dercert:i:1}" in - 1) i+=1; len_name=2*0x${dercert:i:2}; i+=2 ;; - 2) i+=1; len_name=2*0x${dercert:i:4}; i+=4 ;; - 3) i+=1; len_name=2*0x${dercert:i:6}; i+=4 ;; - *) len=0 ;; - esac - else - len_name=2*0x${dercert:i:2} - i+=2 - fi - [[ "$tag" == A0 ]] || continue - # This is an otherName. - [[ $len_name -gt 16 ]] || continue - [[ "${dercert:i:16}" == 060604009B430301 ]] || continue - # According to the OID, this is visibility information. - j=$i+16 - # Skip over the tag (A0) and length for the otherName value. - [[ "${dercert:j:2}" == A0 ]] || continue - j+=2 - if [[ "${dercert:j:1}" == 8 ]]; then - j+=1 - j+=2*0x${dercert:j:1}+1 - else - j+=2 - fi - # The value for this otherName is encoded as a SEQUENCE (30): - # VisibilityInformation ::= SEQUENCE { - # fingerprint OCTET STRING (SIZE(10)), - # accessDescription UTF8String } - [[ "${dercert:j:2}" == 30 ]] || continue - j+=2 - if [[ "${dercert:j:1}" == 8 ]]; then - j+=1 - case "${dercert:j:1}" in - 1) j+=1; len1=2*0x${dercert:j:2}; j+=2 ;; - 2) j+=1; len1=2*0x${dercert:j:4}; j+=4 ;; - 3) j+=1; len1=2*0x${dercert:j:6}; j+=6 ;; - 4) len1=0 ;; - esac - else - len1=2*0x${dercert:j:2} - j+=2 - fi - [[ $len1 -ne 0 ]] || continue - # Next is the 10-byte fingerprint, encoded as an OCTET STRING (04) - [[ "${dercert:j:4}" == 040A ]] || continue - j+=4 - fingerprint[nr_visnames]="$(asciihex_to_binary "${dercert:j:20}")" - j+=20 - # Finally comes the access description, encoded as a UTF8String (0C). - [[ "${dercert:j:2}" == 0C ]] || continue - j+=2 - if [[ "${dercert:j:1}" == "8" ]]; then - j+=1 - case "${dercert:j:1}" in - 1) j+=1; len1=2*0x${dercert:j:2}; j+=2 ;; - 2) j+=1; len1=2*0x${dercert:j:4}; j+=4 ;; - 3) j+=1; len1=2*0x${dercert:j:6}; j+=6 ;; - 4) len1=0 ;; - esac - else - len1=2*0x${dercert:j:2} - j+=2 - fi - access_description[nr_visnames]=""$(asciihex_to_binary "${dercert:j:len1}")"" - nr_visnames+=1 - done - fi - fi - fi - fi - if [[ $nr_visnames -eq 0 ]]; then - outln "not present" - fileout "$jsonID" "INFO" "not present" - else - for (( i=0; i < nr_visnames; i++ )); do - [[ $i -ne 0 ]] && out "$spaces" - outln "$(out_row_aligned_max_width "${fingerprint[i]} / ${access_description[i]}" "$spaces" $TERM_WIDTH)" - fileout "$jsonID" "INFO" "${fingerprint[i]} / ${access_description[i]}" - done - fi - return 0 -} - -# NOTE: arg3 must contain the text output of $HOSTCERT. -must_staple() { - local jsonID="cert_mustStapleExtension" - local json_postfix="$1" - local provides_stapling="$2" - local hostcert_txt="$3" - local cert extn - local -i extn_len - local supported=false - - # Note this function is only looking for status_request (5) and not - # status_request_v2 (17), since OpenSSL seems to only include status_request (5) - # in its ClientHello when the "-status" option is used. - - # OpenSSL 1.1.0 supports pretty-printing the "TLS Feature extension." For any - # previous versions of OpenSSL, OpenSSL can only show if the extension OID is present. - if grep -A 1 "TLS Feature:" <<< "$hostcert_txt" | grep -q "status_request"; then - # FIXME: This will indicate that must staple is supported if the - # certificate indicates status_request or status_request_v2. This is - # probably okay, since it seems likely that any TLS Feature extension - # that includes status_request_v2 will also include status_request. - supported=true - elif [[ "$hostcert_txt" =~ '1.3.6.1.5.5.7.1.24:' ]]; then - cert="$($OPENSSL x509 -in "$HOSTCERT" -outform DER 2>>$ERRFILE | hexdump -v -e '16/1 "%02X"')" - extn="${cert##*06082B06010505070118}" - # Check for critical bit, and skip over it if present. - [[ "${extn:0:6}" == "0101FF" ]] && extn="${extn:6}" - # Next is tag and length of extnValue OCTET STRING. Assume it is less than 128 bytes. - extn="${extn:4}" - # The TLS Feature is a SEQUENCE of INTEGER. Get the length of the SEQUENCE - extn_len=2*$(hex2dec "${extn:2:2}") - # If the extension include the status_request (5), then it supports must staple. - if [[ "${extn:4:extn_len}" =~ 020105 ]]; then - supported=true - fi - fi - - if "$supported"; then - if "$provides_stapling"; then - prln_svrty_good "supported" - fileout "${jsonID}${json_postfix}" "OK" "supported" - else - prln_svrty_high "requires OCSP stapling (NOT ok)" - fileout "${jsonID}${json_postfix}" "HIGH" "extension detected but no OCSP stapling provided" - fi - else - outln "--" - fileout "${jsonID}${json_postfix}" "INFO" "--" - fi - return 0 -} - -# TODO: This function checks for Certificate Transparency support based on RFC 6962. -# It will need to be updated to add checks for Certificate Transparency support based on 6962bis. -# return values are results, no error conditions -certificate_transparency() { - local cert_txt="$1" - local ocsp_response="$2" - local -i number_of_certificates=$3 - local cipher="$4" - local sni_used="$5" - local tls_version="$6" - local sni="" - local ciphers="" - local hexc n ciph sslver kx auth enc mac export - local extra_extns="" - local -i success - # Cipher suites that use a certificate with an RSA (signature) public key - local -r a_rsa="cc,13, cc,15, c0,30, c0,28, c0,14, 00,9f, cc,a8, cc,aa, c0,a3, c0,9f, 00,6b, 00,39, c0,77, 00,c4, 00,88, c0,45, c0,4d, c0,53, c0,61, c0,7d, c0,8b, 16,b7, 16,b9, c0,2f, c0,27, c0,13, 00,9e, c0,a2, c0,9e, 00,67, 00,33, c0,76, 00,be, 00,9a, 00,45, c0,44, c0,4c, c0,52, c0,60, c0,7c, c0,8a, c0,11, c0,12, 00,16, 00,15, 00,14, c0,10" - # Cipher suites that use a certificate with an RSA (encryption) public key - local -r e_rsa="00,b7, c0,99, 00,ad, cc,ae, 00,9d, c0,a1, c0,9d, 00,3d, 00,35, 00,c0, 00,84, 00,95, c0,3d, c0,51, c0,69, c0,6f, c0,7b, c0,93, ff,01, 00,ac, c0,a0, c0,9c, 00,9c, 00,3c, 00,2f, 00,ba, 00,b6, 00,96, 00,41, c0,98, 00,07, 00,94, c0,3c, c0,50, c0,68, c0,6e, c0,7a, c0,92, 00,05, 00,04, 00,92, 00,0a, 00,93, fe,ff, ff,e0, 00,62, 00,09, 00,61, fe,fe, ff,e1, 00,64, 00,60, 00,08, 00,06, 00,03, 00,b9, 00,b8, 00,2e, 00,3b, 00,02, 00,01, ff,00" - # Cipher suites that use a certificate with a DSA public key - local -r a_dss="00,a3, 00,6a, 00,38, 00,c3, 00,87, c0,43, c0,57, c0,81, 00,a2, 00,40, 00,32, 00,bd, 00,99, 00,44, c0,42, c0,56, c0,80, 00,66, 00,13, 00,63, 00,12, 00,65, 00,11" - # Cipher suites that use a certificate with a DH public key - local -r a_dh="00,a5, 00,a1, 00,69, 00,68, 00,37, 00,36, 00,c2, 00,c1, 00,86, 00,85, c0,3f, c0,41, c0,55, c0,59, c0,7f, c0,83, 00,a4, 00,a0, 00,3f, 00,3e, 00,31, 00,30, 00,bc, 00,bb, 00,98, 00,97, 00,43, 00,42, c0,3e, c0,40, c0,54, c0,58, c0,7e, c0,82, 00,10, 00,0d, 00,0f, 00,0c, 00,0b, 00,0e" - # Cipher suites that use a certificate with an ECDH public key - local -r a_ecdh="c0,32, c0,2e, c0,2a, c0,26, c0,0f, c0,05, c0,79, c0,75, c0,4b, c0,4f, c0,5f, c0,63, c0,89, c0,8d, c0,31, c0,2d, c0,29, c0,25, c0,0e, c0,04, c0,78, c0,74, c0,4a, c0,4e, c0,5e, c0,62, c0,88, c0,8c, c0,0c, c0,02, c0,0d, c0,03, c0,0b, c0,01" - # Cipher suites that use a certificate with an ECDSA public key - local -r a_ecdsa="cc,14, c0,2c, c0,24, c0,0a, cc,a9, c0,af, c0,ad, c0,73, c0,49, c0,5d, c0,87, 16,b8, 16,ba, c0,2b, c0,23, c0,09, c0,ae, c0,ac, c0,72, c0,48, c0,5c, c0,86, c0,07, c0,08, c0,06" - # Cipher suites that use a certificate with a GOST public key - local -r a_gost="00,80, 00,81, 00,82, 00,83" - - # First check whether signed certificate timestamps (SCT) are included in the - # server's certificate. If they aren't, check whether the server provided - # a stapled OCSP response with SCTs. If no SCTs were found in the certificate - # or OCSP response, check for an SCT TLS extension. - if [[ "$cert_txt" =~ CT\ Precertificate\ SCTs ]] || [[ "$cert_txt" =~ '1.3.6.1.4.1.11129.2.4.2' ]]; then - tm_out "certificate extension" - return 0 - fi - if [[ "$ocsp_response" =~ CT\ Certificate\ SCTs ]] || [[ "$ocsp_response" =~ '1.3.6.1.4.1.11129.2.4.5' ]]; then - tm_out "OCSP extension" - return 0 - fi - - # If the server only has one certificate, then it is sufficient to check whether - # determine_tls_extensions() discovered an SCT TLS extension. If the server has more than - # one certificate, then it is possible that an SCT TLS extension is returned for some - # certificates, but not for all of them. - if [[ $number_of_certificates -eq 1 ]] && [[ "$TLS_EXTENSIONS" =~ signed\ certificate\ timestamps ]]; then - tm_out "TLS extension" - return 0 - fi - - if [[ $number_of_certificates -gt 1 ]] && ! "$SSL_NATIVE"; then - if [[ "$tls_version" == 0304 ]]; then - ciphers=", 13,01, 13,02, 13,03, 13,04, 13,05" - if [[ "$cipher" == tls1_3_RSA ]]; then - extra_extns=", 00,0d,00,10,00,0e,08,04,08,05,08,06,04,01,05,01,06,01,02,01" - elif [[ "$cipher" == tls1_3_ECDSA ]]; then - extra_extns=", 00,0d,00,0a,00,08,04,03,05,03,06,03,02,03" - else - return 1 - fi - else - [[ "$cipher" =~ aRSA ]] && ciphers+=", $a_rsa" - [[ "$cipher" =~ eRSA ]] && ciphers+=", $e_rsa" - [[ "$cipher" =~ aDSS ]] && ciphers+=", $a_dss" - [[ "$cipher" =~ aDH ]] && ciphers+=", $a_dh" - [[ "$cipher" =~ aECDH ]] && ciphers+=", $a_ecdh" - [[ "$cipher" =~ aECDSA ]] && ciphers+=", $a_ecdsa" - [[ "$cipher" =~ aGOST ]] && ciphers+=", $a_gost" - - [[ -z "$ciphers" ]] && return 1 - ciphers+=", 00,ff" - fi - [[ -z "$sni_used" ]] && sni="$SNI" && SNI="" - tls_sockets "${tls_version:2:2}" "${ciphers:2}" "all" "00,12,00,00$extra_extns" - success=$? - [[ -z "$sni_used" ]] && SNI="$sni" - if ( [[ $success -eq 0 ]] || [[ $success -eq 2 ]] ) && \ - grep -a 'TLS server extension ' "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" | \ - grep -aq "signed certificate timestamps"; then - tm_out "TLS extension" - return 0 - fi - fi - - if [[ $SERVICE != HTTP ]] && ! "$CLIENT_AUTH"; then - # At the moment Certificate Transparency only applies to HTTPS. - tm_out "N/A" - else - tm_out "--" - fi - return 0 -} - -certificate_info() { - local proto - local -i certificate_number=$1 - local -i number_of_certificates=$2 - local cert_txt="$3" - local cipher=$4 - local cert_keysize=$5 - local cert_type="$6" - local ocsp_response_binary="$7" - local ocsp_response=$8 - local ocsp_response_status=$9 - local sni_used="${10}" - local ct="${11}" - local certificate_list_ordering_problem="${12}" - local cert_sig_algo cert_sig_hash_algo cert_key_algo cert_keyusage cert_ext_keyusage short_keyAlgo - local outok=true - local expire days2expire secs2warn ocsp_uri crl - local startdate enddate issuer_CN issuer_C issuer_O issuer sans san all_san="" cn - local issuer_DC issuerfinding cn_nosni="" - local cert_fingerprint_sha1 cert_fingerprint_sha2 cert_serial - local policy_oid - local spaces="" - local -i trust_sni=0 trust_nosni=0 diffseconds=0 - local has_dns_sans has_dns_sans_nosni - local trust_sni_finding - local -i certificates_provided - local cnfinding trustfinding trustfinding_nosni - local cnok="OK" - local expfinding expok="OK" - local -i ret=0 - local json_postfix="" # string to place at the end of JSON IDs when there is more than one certificate - local jsonID="" # string to place at beginning of JSON IDs - local indent="" - local days2warn2=$DAYS2WARN2 - local days2warn1=$DAYS2WARN1 - local provides_stapling=false - local caa_node="" all_caa="" caa_property_name="" caa_property_value="" - local response="" - local yearstart yearend clockstart clockend y m d - local gt_825=false gt_825warn=false - - if [[ $number_of_certificates -gt 1 ]]; then - [[ $certificate_number -eq 1 ]] && outln - indent=" " - out "$indent" - pr_headline "Server Certificate #$certificate_number" - [[ -z "$sni_used" ]] && pr_underline " (in response to request w/o SNI)" - outln - json_postfix=" <cert#${certificate_number}>" - spaces=" " - else - spaces=" " - fi - - GOOD_CA_BUNDLE="" - cert_sig_algo="$(awk -F':' '/Signature Algorithm/ { print $2; if (++Match >= 1) exit; }' <<< "$cert_txt")" - cert_sig_algo="${cert_sig_algo// /}" - cert_key_algo="$(awk -F':' '/Public Key Algorithm:/ { print $2; if (++Match >= 1) exit; }' <<< "$cert_txt")" - cert_key_algo="${cert_key_algo// /}" - - out "$indent" ; pr_bold " Signature Algorithm " - jsonID="cert_signatureAlgorithm" - case $cert_sig_algo in - sha1WithRSAEncryption) - pr_svrty_medium "SHA1 with RSA" - if [[ "$SERVICE" == HTTP ]] || "$ASSUME_HTTP"; then - out " -- besides: users will receive a "; pr_svrty_high "strong browser WARNING" - fi - outln - fileout "${jsonID}${json_postfix}" "MEDIUM" "SHA1 with RSA" - ;; - sha224WithRSAEncryption) - outln "SHA224 with RSA" - fileout "${jsonID}${json_postfix}" "INFO" "SHA224 with RSA" - ;; - sha256WithRSAEncryption) - prln_svrty_good "SHA256 with RSA" - fileout "${jsonID}${json_postfix}" "OK" "SHA256 with RSA" - ;; - sha384WithRSAEncryption) - prln_svrty_good "SHA384 with RSA" - fileout "${jsonID}${json_postfix}" "OK" "SHA384 with RSA" - ;; - sha512WithRSAEncryption) - prln_svrty_good "SHA512 with RSA" - fileout "${jsonID}${json_postfix}" "OK" "SHA512 with RSA" - ;; - ecdsa-with-SHA1) - prln_svrty_medium "ECDSA with SHA1" - fileout "${jsonID}${json_postfix}" "MEDIUM" "ECDSA with SHA1" - ;; - ecdsa-with-SHA224) - outln "ECDSA with SHA224" - fileout "${jsonID}${json_postfix}" "INFO" "ECDSA with SHA224" - ;; - ecdsa-with-SHA256) - prln_svrty_good "ECDSA with SHA256" - fileout "${jsonID}${json_postfix}" "OK" "ECDSA with SHA256" - ;; - ecdsa-with-SHA384) - prln_svrty_good "ECDSA with SHA384" - fileout "${jsonID}${json_postfix}" "OK" "ECDSA with SHA384" - ;; - ecdsa-with-SHA512) - prln_svrty_good "ECDSA with SHA512" - fileout "${jsonID}${json_postfix}" "OK" "ECDSA with SHA512" - ;; - dsaWithSHA1) - prln_svrty_medium "DSA with SHA1" - fileout "${jsonID}${json_postfix}" "MEDIUM" "DSA with SHA1" - ;; - dsa_with_SHA224) - outln "DSA with SHA224" - fileout "${jsonID}${json_postfix}" "INFO" "DSA with SHA224" - ;; - dsa_with_SHA256) - prln_svrty_good "DSA with SHA256" - fileout "${jsonID}${json_postfix}" "OK" "DSA with SHA256" - ;; - rsassaPss) - cert_sig_hash_algo="$(grep -A 1 "Signature Algorithm" <<< "$cert_txt" | head -2 | tail -1 | sed 's/^.*Hash Algorithm: //')" - case $cert_sig_hash_algo in - sha1) - prln_svrty_medium "RSASSA-PSS with SHA1" - fileout "${jsonID}${json_postfix}" "MEDIUM" "RSASSA-PSS with SHA1" - ;; - sha224) - outln "RSASSA-PSS with SHA224" - fileout "${jsonID}${json_postfix}" "INFO" "RSASSA-PSS with SHA224" - ;; - sha256) - prln_svrty_good "RSASSA-PSS with SHA256" - fileout "${jsonID}${json_postfix}" "OK" "RSASSA-PSS with SHA256" - ;; - sha384) - prln_svrty_good "RSASSA-PSS with SHA384" - fileout "${jsonID}${json_postfix}" "OK" "RSASSA-PSS with SHA384" - ;; - sha512) - prln_svrty_good "RSASSA-PSS with SHA512" - fileout "${jsonID}${json_postfix}" "OK" "RSASSA-PSS with SHA512" - ;; - *) - out "RSASSA-PSS with $cert_sig_hash_algo" - prln_warning " (Unknown hash algorithm)" - fileout "${jsonID}${json_postfix}" "DEBUG" "RSASSA-PSS with $cert_sig_hash_algo" - esac - ;; - md2*) - prln_svrty_critical "MD2" - fileout "${jsonID}${json_postfix}" "CRITICAL" "MD2" - ;; - md4*) - prln_svrty_critical "MD4" - fileout "${jsonID}${json_postfix}" "CRITICAL" "MD4" - ;; - md5*) - prln_svrty_critical "MD5" - fileout "${jsonID}${json_postfix}" "CRITICAL" "MD5" - ;; - *) - out "$cert_sig_algo (" - pr_warning "FIXME: can't tell whether this is good or not" - outln ")" - fileout "${jsonID}${json_postfix}" "DEBUG" "$cert_sig_algo" - ((ret++)) - ;; - esac - # old, but still interesting: https://blog.hboeck.de/archives/754-Playing-with-the-EFF-SSL-Observatory.html - - out "$indent"; pr_bold " Server key size " - jsonID="cert_keySize" - if [[ -z "$cert_keysize" ]]; then - outln "(couldn't determine)" - fileout "${jsonID}${json_postfix}" "cannot be determined" - ((ret++)) - else - case $cert_key_algo in - *RSA*|*rsa*) short_keyAlgo="RSA";; - *ecdsa*|*ecPublicKey) short_keyAlgo="EC";; - *DSA*|*dsa*) short_keyAlgo="DSA";; - *GOST*|*gost*) short_keyAlgo="GOST";; - *dh*|*DH*) short_keyAlgo="DH" ;; - *) pr_fixme "don't know $cert_key_algo " - let ret++ ;; - esac - out "$short_keyAlgo " - # https://tools.ietf.org/html/rfc4492, https://www.keylength.com/en/compare/ - # https://infoscience.epfl.ch/record/164526/files/NPDF-22.pdf - # see https://csrc.nist.gov/publications/detail/sp/800-57-part-1/rev-4/final - # Table 2 @ chapter 5.6.1 (~ p66) - if [[ $cert_key_algo =~ ecdsa ]] || [[ $cert_key_algo =~ ecPublicKey ]]; then - if [[ "$cert_keysize" -le 110 ]]; then # a guess - pr_svrty_critical "$cert_keysize" - fileout "${jsonID}${json_postfix}" "CRITICAL" "$short_keyAlgo $cert_keysize bits" - elif [[ "$cert_keysize" -le 123 ]]; then # a guess - pr_svrty_high "$cert_keysize" - fileout "${jsonID}${json_postfix}" "HIGH" "$short_keyAlgo $cert_keysize bits" - elif [[ "$cert_keysize" -le 163 ]]; then - pr_svrty_medium "$cert_keysize" - fileout "${jsonID}${json_postfix}" "MEDIUM" "$short_keyAlgo $cert_keysize bits" - elif [[ "$cert_keysize" -le 224 ]]; then - out "$cert_keysize" - fileout "${jsonID}${json_postfix}" "INFO" "$short_keyAlgo $cert_keysize bits" - elif [[ "$cert_keysize" -le 533 ]]; then - pr_svrty_good "$cert_keysize" - fileout "${jsonID}${json_postfix}" "OK" "$short_keyAlgo $cert_keysize bits" - else - out "keysize: $cert_keysize (not expected, FIXME)" - fileout "${jsonID}${json_postfix}" "DEBUG" " $cert_keysize bits (not expected)" - ((ret++)) - fi - outln " bits" - elif [[ $cert_key_algo =~ RSA ]] || [[ $cert_key_algo =~ rsa ]] || [[ $cert_key_algo =~ dsa ]] || \ - [[ $cert_key_algo =~ dhKeyAgreement ]] || [[ $cert_key_algo == X9.42\ DH ]]; then - if [[ "$cert_keysize" -le 512 ]]; then - pr_svrty_critical "$cert_keysize" - outln " bits" - fileout "${jsonID}${json_postfix}" "CRITICAL" "$short_keyAlgo $cert_keysize bits" - elif [[ "$cert_keysize" -le 768 ]]; then - pr_svrty_high "$cert_keysize" - outln " bits" - fileout "${jsonID}${json_postfix}" "HIGH" "$short_keyAlgo $cert_keysize bits" - elif [[ "$cert_keysize" -le 1024 ]]; then - pr_svrty_medium "$cert_keysize" - outln " bits" - fileout "${jsonID}${json_postfix}" "MEDIUM" "$short_keyAlgo $cert_keysize bits" - elif [[ "$cert_keysize" -le 2048 ]]; then - outln "$cert_keysize bits" - fileout "${jsonID}${json_postfix}" "INFO" "$short_keyAlgo $cert_keysize bits" - elif [[ "$cert_keysize" -le 4096 ]]; then - pr_svrty_good "$cert_keysize" - fileout "${jsonID}${json_postfix}" "OK" "$short_keyAlgo $cert_keysize bits" - outln " bits" - else - pr_warning "weird key size: $cert_keysize bits"; outln " (could cause compatibility problems)" - fileout "${jsonID}${json_postfix}" "WARN" "$cert_keysize bits (Odd)" - ((ret++)) - fi - else - out "$cert_key_algo + $cert_keysize bits (" - pr_warning "FIXME: can't tell whether this is good or not" - outln ")" - fileout "${jsonID}${json_postfix}" "WARN" "Server keys $cert_keysize bits, unknown public key algorithm $cert_key_algo" - ((ret++)) - fi - fi - - out "$indent"; pr_bold " Server key usage "; - outok=true - jsonID="cert_keyUsage" - cert_keyusage="$(strip_leading_space "$(awk '/X509v3 Key Usage:/ { getline; print $0 }' <<< "$cert_txt")")" - if [[ -n "$cert_keyusage" ]]; then - outln "$cert_keyusage" - if ( [[ " $cert_type " =~ " RSASig " ]] || [[ " $cert_type " =~ " DSA " ]] || [[ " $cert_type " =~ " ECDSA " ]] ) && \ - [[ ! "$cert_keyusage" =~ "Digital Signature" ]]; then - prln_svrty_high "$indent Certificate incorrectly used for digital signatures" - fileout "${jsonID}${json_postfix}" "HIGH" "Certificate incorrectly used for digital signatures: \"$cert_keyusage\"" - outok=false - fi - if [[ " $cert_type " =~ " RSAKMK " ]] && [[ ! "$cert_keyusage" =~ "Key Encipherment" ]]; then - prln_svrty_high "$indent Certificate incorrectly used for key encipherment" - fileout "${jsonID}${json_postfix}" "HIGH" "Certificate incorrectly used for key encipherment: \"$cert_keyusage\"" - outok=false - fi - if ( [[ " $cert_type " =~ " DH " ]] || [[ " $cert_type " =~ " ECDH " ]] ) && \ - [[ ! "$cert_keyusage" =~ "Key Agreement" ]]; then - prln_svrty_high "$indent Certificate incorrectly used for key agreement" - fileout "${jsonID}${json_postfix}" "HIGH" "Certificate incorrectly used for key agreement: \"$cert_keyusage\"" - outok=false - fi - else - outln "--" - fileout "${jsonID}${json_postfix}" "INFO" "No server key usage information" - outok=false - fi - if "$outok"; then - fileout "${jsonID}${json_postfix}" "INFO" "$cert_keyusage" - fi - - out "$indent"; pr_bold " Server extended key usage "; - jsonID="cert_extKeyUsage" - outok=true - cert_ext_keyusage="$(strip_leading_space "$(awk '/X509v3 Extended Key Usage:/ { getline; print $0 }' <<< "$cert_txt")")" - if [[ -n "$cert_ext_keyusage" ]]; then - outln "$cert_ext_keyusage" - if [[ ! "$cert_ext_keyusage" =~ "TLS Web Server Authentication" ]] && [[ ! "$cert_ext_keyusage" =~ "Any Extended Key Usage" ]]; then - prln_svrty_high "$indent Certificate incorrectly used for TLS Web Server Authentication" - fileout "${jsonID}${json_postfix}" "HIGH" "Certificate incorrectly used for TLS Web Server Authentication: \"$cert_ext_keyusage\"" - outok=false - fi - else - outln "--" - fileout "${jsonID}${json_postfix}" "INFO" "No server extended key usage information" - outok=false - fi - if "$outok"; then - fileout "${jsonID}${json_postfix}" "INFO" "$cert_ext_keyusage" - fi - - out "$indent"; pr_bold " Serial / Fingerprints " - cert_serial="$($OPENSSL x509 -noout -in $HOSTCERT -serial 2>>$ERRFILE | sed 's/serial=//')" - fileout "cert_serialNumber${json_postfix}" "INFO" "$cert_serial" - - cert_fingerprint_sha1="$($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha1 2>>$ERRFILE | sed 's/Fingerprint=//' | sed 's/://g')" - fileout "cert_fingerprintSHA1${json_postfix}" "INFO" "${cert_fingerprint_sha1//SHA1 /}" - outln "$cert_serial / $cert_fingerprint_sha1" - - cert_fingerprint_sha2="$($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha256 2>>$ERRFILE | sed 's/Fingerprint=//' | sed 's/://g' )" - fileout "cert_fingerprintSHA256${json_postfix}" "INFO" "${cert_fingerprint_sha2//SHA256 /}" - outln "$spaces$cert_fingerprint_sha2" - - # " " needs to be converted back to lf in JSON/CSV output - fileout "cert${json_postfix}" "INFO" "$(< $HOSTCERT)" - - [[ -z $CERT_FINGERPRINT_SHA2 ]] && \ - CERT_FINGERPRINT_SHA2="$cert_fingerprint_sha2" || - CERT_FINGERPRINT_SHA2="$cert_fingerprint_sha2 $CERT_FINGERPRINT_SHA2" - [[ -z $RSA_CERT_FINGERPRINT_SHA2 ]] && \ - ( [[ $cert_key_algo = *RSA* ]] || [[ $cert_key_algo = *rsa* ]] ) && - RSA_CERT_FINGERPRINT_SHA2="$cert_fingerprint_sha2" - - out "$indent"; pr_bold " Common Name (CN) " - cnfinding="Common Name (CN) : " - cn="$(get_cn_from_cert $HOSTCERT)" - if [[ -n "$cn" ]]; then - pr_italic "$cn" - cnfinding="$cn" - else - cn="no CN field in subject" - out "($cn)" - cnfinding="$cn" - cnok="INFO" - fi - fileout "cert_commonName${json_postfix}" "$cnok" "$cnfinding" - cnfinding="" - - if [[ -n "$sni_used" ]]; then - if grep -q "\-\-\-\-\-BEGIN" "$HOSTCERT.nosni"; then - cn_nosni="$(get_cn_from_cert "$HOSTCERT.nosni")" - [[ -z "$cn_nosni" ]] && cn_nosni="no CN field in subject" - fi - debugme tm_out "\"$NODE\" | \"$cn\" | \"$cn_nosni\"" - else - debugme tm_out "\"$NODE\" | \"$cn\"" - fi - - if [[ -z "$sni_used" ]] || [[ "$(toupper "$cn_nosni")" == "$(toupper "$cn")" ]]; then - outln - cnfinding="$cn" - elif [[ -z "$cn_nosni" ]]; then - out " (request w/o SNI didn't succeed"; - cnfinding+="request w/o SNI didn't succeed" - if [[ $cert_sig_algo =~ ecdsa ]]; then - out ", usual for EC certificates" - cnfinding+=", usual for EC certificates" - fi - outln ")" - cnfinding+="" - elif [[ "$cn_nosni" == *"no CN field"* ]]; then - outln ", (request w/o SNI: $cn_nosni)" - cnfinding="$cn_nosni" - else - out " (CN in response to request w/o SNI: "; pr_italic "$cn_nosni"; outln ")" - cnfinding="$cn_nosni" - fi - fileout "cert_commonName_wo_SNI${json_postfix}" "INFO" "$cnfinding" - - sans=$(grep -A2 "Subject Alternative Name" <<< "$cert_txt" | \ - grep -E "DNS:|IP Address:|email:|URI:|DirName:|Registered ID:" | tr ',' '\n' | \ - sed -e 's/ *DNS://g' -e 's/ *IP Address://g' -e 's/ *email://g' -e 's/ *URI://g' -e 's/ *DirName://g' \ - -e 's/ *Registered ID://g' \ - -e 's/ *othername:<unsupported>//g' -e 's/ *X400Name:<unsupported>//g' -e 's/ *EdiPartyName:<unsupported>//g') - # ^^^ CACert - - out "$indent"; pr_bold " subjectAltName (SAN) " - jsonID="cert_subjectAltName" - if [[ -n "$sans" ]]; then - while read san; do - [[ -n "$san" ]] && all_san+="$san " - done <<< "$sans" - prln_italic "$(out_row_aligned_max_width "$all_san" "$indent " $TERM_WIDTH)" - fileout "${jsonID}${json_postfix}" "INFO" "$all_san" - else - if [[ $SERVICE == "HTTP" ]] || "$ASSUME_HTTP"; then - pr_svrty_high "missing (NOT ok)"; outln " -- Browsers are complaining" - fileout "${jsonID}${json_postfix}" "HIGH" "No SAN, browsers are complaining" - else - pr_svrty_medium "missing"; outln " -- no SAN is deprecated" - fileout "${jsonID}${json_postfix}" "MEDIUM" "Providing no SAN is deprecated" - fi - fi - - out "$indent"; pr_bold " Issuer " - jsonID="cert_caIssuers" - #FIXME: oid would be better maybe (see above) - issuer="$($OPENSSL x509 -in $HOSTCERT -noout -issuer -nameopt multiline,-align,sname,-esc_msb,utf8,-space_eq 2>>$ERRFILE)" - issuer_CN="$(awk -F'=' '/CN=/ { print $2 }' <<< "$issuer")" - issuer_O="$(awk -F'=' '/O=/ { print $2 }' <<< "$issuer")" - issuer_C="$(awk -F'=' '/ C=/ { print $2 }' <<< "$issuer")" - issuer_DC="$(awk -F'=' '/DC=/ { print $2 }' <<< "$issuer")" - - if [[ "$issuer_O" == "issuer=" ]] || [[ "$issuer_O" == "issuer= " ]] || [[ "$issuer_CN" == "$cn" ]]; then - prln_svrty_critical "self-signed (NOT ok)" - fileout "${jsonID}${json_postfix}" "CRITICAL" "selfsigned" - else - issuerfinding="$issuer_CN" - pr_italic "$issuer_CN" - if [[ -z "$issuer_O" ]] && [[ -n "$issuer_DC" ]]; then - for san in $issuer_DC; do - if [[ -z "$issuer_O" ]]; then - issuer_O="${san}" - else - issuer_O="${san}.${issuer_O}" - fi - done - fi - if [[ -n "$issuer_O" ]]; then - issuerfinding+=" (" - out " (" - issuerfinding+="$issuer_O" - pr_italic "$issuer_O" - if [[ -n "$issuer_C" ]]; then - issuerfinding+=" from " - out " from " - issuerfinding+="$issuer_C" - pr_italic "$issuer_C" - fi - issuerfinding+=")" - out ")" - fi - outln - fileout "${jsonID}${json_postfix}" "INFO" "$issuerfinding" - fi - - out "$indent"; pr_bold " Trust (hostname) " - compare_server_name_to_cert "$HOSTCERT" - trust_sni=$? - - # Find out if the subjectAltName extension is present and contains - # a DNS name, since Section 6.3 of RFC 6125 says: - # Security Warning: A client MUST NOT seek a match for a reference - # identifier of CN-ID if the presented identifiers include a DNS-ID, - # SRV-ID, URI-ID, or any application-specific identifier types - # supported by the client. - has_dns_sans=$HAS_DNS_SANS - - case $trust_sni in - 0) trustfinding="certificate does not match supplied URI" ;; - 1) trustfinding="Ok via SAN" ;; - 2) trustfinding="Ok via SAN wildcard" ;; - 4) if "$has_dns_sans"; then - trustfinding="via CN, but not SAN" - else - trustfinding="via CN only" - fi - ;; - 5) trustfinding="Ok via SAN and CN" ;; - 6) trustfinding="Ok via SAN wildcard and CN" - ;; - 8) if "$has_dns_sans"; then - trustfinding="via CN wildcard, but not SAN" - else - trustfinding="via CN (wildcard) only" - fi - ;; - 9) trustfinding="Ok via CN wildcard and SAN" - ;; - 10) trustfinding="Ok via SAN wildcard and CN wildcard" - ;; - esac - - if [[ $trust_sni -eq 0 ]]; then - pr_svrty_high "$trustfinding" - trust_sni_finding="HIGH" - elif ( [[ $trust_sni -eq 4 ]] || [[ $trust_sni -eq 8 ]] ); then - if [[ $SERVICE == "HTTP" ]] || "$ASSUME_HTTP"; then - # https://bugs.chromium.org/p/chromium/issues/detail?id=308330 - # https://bugzilla.mozilla.org/show_bug.cgi?id=1245280 - # https://www.chromestatus.com/feature/4981025180483584 - pr_svrty_high "$trustfinding"; out " -- Browsers are complaining" - trust_sni_finding="HIGH" - else - pr_svrty_medium "$trustfinding" - trust_sni_finding="MEDIUM" - # we punish CN matching for non-HTTP as it is deprecated https://tools.ietf.org/html/rfc2818#section-3.1 - ! "$has_dns_sans" && out " -- CN only match is deprecated" - fi - else - pr_svrty_good "$trustfinding" - trust_sni_finding="OK" - fi - - if [[ -n "$cn_nosni" ]]; then - compare_server_name_to_cert "$HOSTCERT.nosni" - trust_nosni=$? - has_dns_sans_nosni=$HAS_DNS_SANS - fi - - # See issue #733. - if [[ -z "$sni_used" ]]; then - trustfinding_nosni="" - elif ( [[ $trust_sni -eq $trust_nosni ]] && [[ "$has_dns_sans" == "$has_dns_sans_nosni" ]] ) || \ - ( [[ $trust_sni -eq 0 ]] && [[ $trust_nosni -eq 0 ]] ); then - trustfinding_nosni=" (same w/o SNI)" - elif [[ $trust_nosni -eq 0 ]]; then - if [[ $trust_sni -eq 4 ]] || [[ $trust_sni -eq 8 ]]; then - trustfinding_nosni=" (w/o SNI: certificate does not match supplied URI)" - else - trustfinding_nosni=" (SNI mandatory)" - fi - elif [[ $trust_nosni -eq 4 ]] || [[ $trust_nosni -eq 8 ]] || [[ $trust_sni -eq 4 ]] || [[ $trust_sni -eq 8 ]]; then - case $trust_nosni in - 1) trustfinding_nosni="(w/o SNI: Ok via SAN)" ;; - 2) trustfinding_nosni="(w/o SNI: Ok via SAN wildcard)" ;; - 4) if "$has_dns_sans_nosni"; then - trustfinding_nosni="(w/o SNI: via CN, but not SAN)" - else - trustfinding_nosni="(w/o SNI: via CN only)" - fi - ;; - 5) trustfinding_nosni="(w/o SNI: Ok via SAN and CN)" ;; - 6) trustfinding_nosni="(w/o SNI: Ok via SAN wildcard and CN)" ;; - 8) if "$has_dns_sans_nosni"; then - trustfinding_nosni="(w/o SNI: via CN wildcard, but not SAN)" - else - trustfinding_nosni="(w/o SNI: via CN (wildcard) only)" - fi - ;; - 9) trustfinding_nosni="(w/o SNI: Ok via CN wildcard and SAN)" ;; - 10) trustfinding_nosni="(w/o SNI: Ok via SAN wildcard and CN wildcard)" ;; - esac - elif [[ $trust_sni -ne 0 ]]; then - trustfinding_nosni=" (works w/o SNI)" - else - trustfinding_nosni=" (however, works w/o SNI)" - fi - if [[ -n "$sni_used" ]] || [[ $trust_nosni -eq 0 ]] || ( [[ $trust_nosni -ne 4 ]] && [[ $trust_nosni -ne 8 ]] ); then - outln "$trustfinding_nosni" - elif [[ $SERVICE == "HTTP" ]] || "$ASSUME_HTTP"; then - prln_svrty_high "$trustfinding_nosni" - else - prln_svrty_medium "$trustfinding_nosni" - fi - - fileout "cert_trust${json_postfix}" "$trust_sni_finding" "${trustfinding}${trustfinding_nosni}" - - out "$indent"; pr_bold " Chain of trust"; out " " - jsonID="cert_chain_of_trust" - if [[ "$issuer_O" =~ StartCom ]] || [[ "$issuer_O" =~ WoSign ]] || [[ "$issuer_CN" =~ StartCom ]] || [[ "$issuer_CN" =~ WoSign ]]; then - # Shortcut for this special case here. - pr_italic "WoSign/StartCom"; out " are " ; prln_svrty_critical "not trusted anymore (NOT ok)" - fileout "${jsonID}${json_postfix}" "CRITICAL" "Issuer not trusted anymore (WoSign/StartCom)" - else - # Also handles fileout, keep error if happened - determine_trust "$jsonID" "$json_postfix" || ((ret++)) - fi - - # https://events.ccc.de/congress/2010/Fahrplan/attachments/1777_is-the-SSLiverse-a-safe-place.pdf, see page 40pp - out "$indent"; pr_bold " EV cert"; out " (experimental) " - jsonID="cert_certificatePolicies_EV" - # only the first one, seldom we have two - policy_oid=$(awk '/ .Policy: / { print $2 }' <<< "$cert_txt" | awk 'NR < 2') - if grep -Eq 'Extended Validation|Extended Validated|EV SSL|EV CA' <<< "$issuer" || \ - [[ 2.16.840.1.114028.10.1.2 == "$policy_oid" ]] || \ - [[ 2.16.840.1.114412.1.3.0.2 == "$policy_oid" ]] || \ - [[ 2.16.840.1.114412.2.1 == "$policy_oid" ]] || \ - [[ 2.16.578.1.26.1.3.3 == "$policy_oid" ]] || \ - [[ 1.3.6.1.4.1.17326.10.14.2.1.2 == "$policy_oid" ]] || \ - [[ 1.3.6.1.4.1.17326.10.8.12.1.2 == "$policy_oid" ]] || \ - [[ 1.3.6.1.4.1.13177.10.1.3.10 == "$policy_oid" ]] ; then - out "yes " - fileout "${jsonID}${json_postfix}" "OK" "yes" - else - out "no " - fileout "${jsonID}${json_postfix}" "INFO" "no" - fi - debugme echo "($(newline_to_spaces "$policy_oid"))" - outln -#TODO: check browser OIDs: -# https://mxr.mozilla.org/mozilla-central/source/security/certverifier/ExtendedValidation.cpp -# https://chromium.googlesource.com/chromium/chromium/+/master/net/base/ev_root_ca_metadata.cc -# https://certs.opera.com/03/ev-oids.xml -# see #967 - - out "$indent"; pr_bold " ETS/\"eTLS\"" - out ", visibility info " - jsonID="cert_eTLS" - etsi_etls_visibility_info "${jsonID}${json_postfix}" "$spaces" "$HOSTCERT" "$cert_txt" - # *Currently* this is even listed as a vulnerability (CWE-310, CVE-2019-919), see - # https://nvd.nist.gov/vuln/detail/CVE-2019-9191, https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9191 - # For now we leave this here. We may want to change that later or add infos to other sections (PFS & vulnerability) - - out "$indent"; pr_bold " Certificate Validity (UTC) " - # FreeBSD + OSX can't swallow the leading blank: - startdate="${cert_txt#*Validity*Not Before: }" - startdate="${startdate%%GMT*}GMT" - enddate="${cert_txt#*Validity*Not Before: *Not After : }" - enddate="${enddate%%GMT*}GMT" - debugme echo "$enddate - $startdate" - # Now we have a normalized enddate and startdate like "Feb 27 10:03:20 2017 GMT" -- also for OpenBSD - if "$HAS_OPENBSDDATE"; then - # Best we want to do under old versions of OpenBSD, first just remove the GMT and keep start/endate for later output - startdate="$(parse_date "$startdate" "+%s")" - enddate="$(parse_date "$enddate" "+%s")" - # Now we extract a date block and a time block which we need for later output - startdate="$(parse_date "$startdate" +"%F %H:%M" "%b %d %T %Y %Z")" - enddate="$(parse_date "$enddate" +"%F %H:%M" "%b %d %T %Y %Z")" - read yearstart clockstart <<< "$startdate" - read yearend clockend <<< "$enddate" - debugme echo "$yearstart, $clockstart" - debugme echo "$yearend, $clockend" - y=$(( ${yearend:0:4} - ${yearstart:0:4} )) - m=$(( ${yearend:5:1} - ${yearstart:5:1} + ${yearend:6:1} - ${yearstart:6:1} )) - d=$(( ${yearend:8:2} - ${yearstart:8:2} )) - # We take the year, month, days here as old OpenBSD's date is too difficult for real conversion - # see comment in parse_date(). In diffseconds then we have the estimated absolute validity period - diffseconds=$(( d + ((m*30)) + ((y*365)) )) - diffseconds=$((diffseconds * 3600 * 24)) - # Now we estimate the days left plus length of month/year: - yearnow="$(date -juz GMT "+%Y-%m-%d %H:%M")" - y=$(( ${yearend:0:4} - ${yearnow:0:4} )) - m=$(( ${yearend:5:1} - ${yearnow:5:1} + ${yearend:6:1} - ${yearnow:6:1} )) - d=$(( ${yearend:8:2} - ${yearnow:8:2} )) - days2expire=$(( d + ((m*30)) + ((y*365)) )) - else - startdate="$(parse_date "$startdate" +"%F %H:%M" "%b %d %T %Y %Z")" - enddate="$(parse_date "$enddate" +"%F %H:%M" "%b %d %T %Y %Z")" - days2expire=$(( $(parse_date "$enddate" "+%s" $'%F %H:%M') - $(LC_ALL=C date "+%s") )) # first in seconds - days2expire=$((days2expire / 3600 / 24 )) - diffseconds=$(( $(parse_date "$enddate" "+%s" $'%F %H:%M') - $(parse_date "$startdate" "+%s" $'%F %H:%M') )) - fi - # We adjust the thresholds by %50 for LE certificates, relaxing warnings for those certificates. - # . instead of \' because it does not break syntax highlighting in vim - if [[ "$issuer_O" =~ ^Let.s\ Encrypt ]] ; then - days2warn2=$((days2warn2 / 2)) - days2warn1=$((days2warn1 / 2)) - fi - - debugme echo -n "diffseconds: $diffseconds" - expire=$($OPENSSL x509 -in $HOSTCERT -checkend 1 2>>$ERRFILE) - if ! grep -qw not <<< "$expire" ; then - pr_svrty_critical "expired" - expfinding="expired" - expok="CRITICAL" - else - secs2warn=$((24 * 60 * 60 * days2warn2)) # low threshold first - expire=$($OPENSSL x509 -in $HOSTCERT -checkend $secs2warn 2>>$ERRFILE) - if grep -qw not <<< "$expire"; then - secs2warn=$((24 * 60 * 60 * days2warn1)) # high threshold - expire=$($OPENSSL x509 -in $HOSTCERT -checkend $secs2warn 2>>$ERRFILE) - if grep -qw not <<< "$expire"; then - pr_svrty_good "$days2expire >= $days2warn1 days" - expfinding+="$days2expire >= $days2warn1 days" - else - pr_svrty_medium "expires < $days2warn1 days ($days2expire)" - expfinding+="expires < $days2warn1 days ($days2expire)" - expok="MEDIUM" - fi - else - pr_svrty_high "expires < $days2warn2 days ($days2expire)" - expfinding+="expires < $days2warn2 days ($days2expire)" - expok="HIGH" - fi - fi - outln " ($startdate --> $enddate)" - fileout "cert_expirationStatus${json_postfix}" "$expok" "$expfinding" - fileout "cert_notBefore${json_postfix}" "INFO" "$startdate" # we assume that the certificate has no start time in the future - fileout "cert_notAfter${json_postfix}" "$expok" "$enddate" # They are in UTC - - # Internal certificates or those from appliances often have too high validity periods. - # We check for ~10 years and >~ 5 years - if [[ $diffseconds -ge $((3600 * 24 * 365 * 10)) ]]; then - out "$spaces" - prln_svrty_high ">= 10 years is way too long" - fileout "cert_validityPeriod${json_postfix}" "HIGH" "$((diffseconds / (3600 * 24) )) days" - elif [[ $diffseconds -ge $((3600 * 24 * 365 * 5)) ]]; then - out "$spaces" - prln_svrty_medium ">= 5 years is too long" - fileout "cert_validityPeriod${json_postfix}" "MEDIUM" "$((diffseconds / (3600 * 24) )) days" - elif [[ $diffseconds -ge $((3600 * 24 * 825 + 1)) ]]; then - # Also "official" certificates issued from March 1st, 2018 (1517353200) aren't supposed - # to be valid longer than 825 days which is 1517353200 in epoch seconds - gt_825=true - if "$HAS_OPENBSDDATE"; then - if [[ 20180301 -le ${yearstart//-/} ]]; then - gt_825warn=true - fi - elif [[ $(parse_date "$startdate" "+%s" $'%F %H:%M') -ge 1517353200 ]]; then - gt_825warn=true - fi - # Now, the verdict, depending on the issuing date - out "$spaces" - if "$gt_825warn" && "$gt_825"; then - prln_svrty_medium "> 825 days issued after 2018/03/01 is too long" - fileout "cert_validityPeriod${json_postfix}" "MEDIUM" "$((diffseconds / (3600 * 24) )) > 825 days" - elif "$gt_825"; then - outln ">= 825 days certificate life time but issued before 2018/03/01" - fileout "cert_validityPeriod${json_postfix}" "INFO" "$((diffseconds / (3600 * 24) )) =< 825 days" - fi - else - # All is fine with valididy period - # We ignore for now certificates < 2018/03/01. On the screen we only show debug info - [[ "$DEBUG" -ge 1 ]] && outln "${spaces}DEBUG: all is fine with total certificate life time" - fileout "cert_validityPeriod${json_postfix}" "INFO" "No finding" - fi - - certificates_provided=1+$(grep -c "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TEMPDIR/intermediatecerts.pem) - out "$indent"; pr_bold " # of certificates provided"; out " $certificates_provided" - fileout "certs_countServer${json_postfix}" "INFO" "${certificates_provided}" - if "$certificate_list_ordering_problem"; then - prln_svrty_low " (certificate list ordering problem)" - fileout "certs_list_ordering_problem${json_postfix}" "LOW" "yes" - else - fileout "certs_list_ordering_problem${json_postfix}" "INFO" "no" - outln - fi - - if "$PHONE_OUT"; then - out "$indent"; pr_bold " In pwnedkeys.com DB " - check_pwnedkeys "$HOSTCERT" "$cert_key_algo" "$cert_keysize" - case "$?" in - 0) outln "not checked"; fileout "pwnedkeys${json_postfix}" "INFO" "not checked" ;; - 1) outln "not in database"; fileout "pwnedkeys${json_postfix}" "INFO" "not in database" ;; - 2) pr_svrty_critical "NOT ok --"; outln " key appears in database"; fileout "pwnedkeys${json_postfix}" "CRITICAL" "private key is known" ;; - 7) prln_warning "error querying https://v1.pwnedkeys.com"; fileout "pwnedkeys${json_postfix}" "WARN" "connection error" ;; - esac - fi - - out "$indent"; pr_bold " Certificate Revocation List " - jsonID="cert_crlDistributionPoints" - # ~ get next 50 lines after pattern , strip until Signature Algorithm and retrieve URIs - crl="$(awk '/X509v3 CRL Distribution/{i=50} i&&i--' <<< "$cert_txt" | awk '/^$/,/^ [a-zA-Z0-9]+|^ Signature Algorithm:/' | awk -F'URI:' '/URI/ { print $2 }')" - if [[ -z "$crl" ]] ; then - fileout "${jsonID}${json_postfix}" "INFO" "--" - outln "--" - else - if [[ $(count_lines "$crl") -eq 1 ]]; then - out "$crl" - if [[ "$expfinding" != "expired" ]]; then - check_revocation_crl "$crl" "cert_crlRevoked${json_postfix}" - ret=$((ret +$?)) - fi - outln - else # more than one CRL - first_crl=true - while read -r line; do - if "$first_crl"; then - first_crl=false - else - out "$spaces" - fi - out "$line" - if [[ "$expfinding" != expired ]]; then - check_revocation_crl "$line" "cert_crlRevoked${json_postfix}" - ret=$((ret +$?)) - fi - outln - done <<< "$crl" - fi - fileout "${jsonID}${json_postfix}" "INFO" "$crl" - fi - - out "$indent"; pr_bold " OCSP URI " - jsonID="cert_ocspURL" - ocsp_uri=$($OPENSSL x509 -in $HOSTCERT -noout -ocsp_uri 2>>$ERRFILE) - if [[ -z "$ocsp_uri" ]]; then - outln "--" - fileout "${jsonID}${json_postfix}" "INFO" "--" - else - if [[ $(count_lines "$ocsp_uri") -eq 1 ]]; then - out "$ocsp_uri" - if [[ "$expfinding" != "expired" ]]; then - check_revocation_ocsp "$ocsp_uri" "" "cert_ocspRevoked${json_postfix}" - fi - ret=$((ret +$?)) - outln - else - first_ocsp=true - while read -r line; do - if "$first_ocsp"; then - first_ocsp=false - else - out "$spaces" - fi - out "$line" - if [[ "$expfinding" != "expired" ]]; then - check_revocation_ocsp "$line" "" "cert_ocspRevoked${json_postfix}" - ret=$((ret +$?)) - fi - outln - done <<< "$ocsp_uri" - fi - fileout "${jsonID}${json_postfix}" "INFO" "$ocsp_uri" - fi - if [[ -z "$ocsp_uri" ]] && [[ -z "$crl" ]]; then - out "$spaces" - pr_svrty_high "NOT ok --" - outln " neither CRL nor OCSP URI provided" - fileout "cert_revocation${json_postfix}" "HIGH" "Neither CRL nor OCSP URI provided" - fi - - out "$indent"; pr_bold " OCSP stapling " - jsonID="OCSP_stapling" - if grep -a "OCSP response" <<< "$ocsp_response" | grep -q "no response sent" ; then - if [[ -n "$ocsp_uri" ]]; then - pr_svrty_low "not offered" - fileout "${jsonID}${json_postfix}" "LOW" "not offered" - else - out "not offered" - fileout "${jsonID}${json_postfix}" "INFO" "not offered" - fi - else - if grep -a "OCSP Response Status" <<< "$ocsp_response_status" | grep -q successful; then - pr_svrty_good "offered" - fileout "${jsonID}${json_postfix}" "OK" "offered" - provides_stapling=true - check_revocation_ocsp "" "$ocsp_response_binary" "cert_ocspRevoked${json_postfix}" - elif [[ "$ocsp_response" =~ Responder\ Error: ]]; then - response="$(awk '/Responder Error:/ { print $3 }' <<< "$ocsp_response")" - pr_warning "stapled OCSP response contained an error response from OCSP responder: $response" - fileout "${jsonID}${json_postfix}" "WARN" "stapled OCSP response contained an error response from OCSP responder: $response" - else - if $GOST_STATUS_PROBLEM; then - pr_warning "(GOST servers make problems here, sorry)" - fileout "${jsonID}${json_postfix}" "WARN" "(The GOST server made a problem here, sorry)" - ((ret++)) - else - out "(response status unknown)" - fileout "${jsonID}${json_postfix}" "OK" " not sure what's going on here, '$ocsp_response'" - debugme grep -a -A20 -B2 "OCSP response" <<<"$ocsp_response" - ((ret++)) - fi - fi - fi - outln - - out "$indent"; pr_bold " OCSP must staple extension "; - must_staple "$json_postfix" "$provides_stapling" "$cert_txt" - - out "$indent"; pr_bold " DNS CAA RR"; out " (experimental) " - jsonID="DNS_CAArecord" - caa_node="$NODE" - caa="" - while ( [[ -z "$caa" ]] && [[ ! -z "$caa_node" ]] ); do - caa="$(get_caa_rr_record $caa_node)" - [[ $caa_node =~ '.'$ ]] || caa_node+="." - caa_node=${caa_node#*.} - done - if [[ -n "$caa" ]]; then - pr_svrty_good "available"; out " - please check for match with \"Issuer\" above" - if [[ $(count_lines "$caa") -eq 1 ]]; then - out ": " - else - outln; out "$spaces" - fi - while read caa; do - if [[ -n "$caa" ]]; then - all_caa+="$caa, " - fi - done <<< "$caa" - all_caa=${all_caa%, } # strip trailing comma - pr_italic "$(out_row_aligned_max_width "$all_caa" "$indent " $TERM_WIDTH)" - fileout "${jsonID}${json_postfix}" "OK" "$all_caa" - elif [[ -n "$NODNS" ]]; then - out "(instructed to minimize DNS queries)" - fileout "${jsonID}${json_postfix}" "INFO" "check skipped as instructed" - else - pr_svrty_low "not offered" - fileout "${jsonID}${json_postfix}" "LOW" "--" - fi - outln - - out "$indent"; pr_bold " Certificate Transparency "; - jsonID="certificate_transparency" - if [[ "$ct" =~ extension ]]; then - pr_svrty_good "yes"; outln " ($ct)" - fileout "${jsonID}${json_postfix}" "OK" "yes ($ct)" - else - outln "$ct" - fileout "${jsonID}${json_postfix}" "INFO" "$ct" - fi - outln - return $ret -} - -run_server_defaults() { - local ciph newhostcert sni - local match_found - local sessticket_lifetime_hint="" sessticket_proto="" lifetime unit - local -i i n - local -i certs_found=0 - local -i ret=0 - local -a previous_hostcert previous_hostcert_txt previous_hostcert_type - local -a previous_hostcert_issuer previous_intermediates previous_ordering_problem keysize cipher - local -a ocsp_response_binary ocsp_response ocsp_response_status sni_used tls_version ct - local -a ciphers_to_test certificate_type - local -a -i success - local cn_nosni cn_sni sans_nosni sans_sni san tls_extensions - local using_sockets=true - - "$SSL_NATIVE" && using_sockets=false - - # Try each public key type once: - # ciphers_to_test[1]: cipher suites using certificates with RSA signature public keys - # ciphers_to_test[2]: cipher suites using certificates with RSA key encipherment public keys - # ciphers_to_test[3]: cipher suites using certificates with DSA signature public keys - # ciphers_to_test[4]: cipher suites using certificates with DH key agreement public keys - # ciphers_to_test[5]: cipher suites using certificates with ECDH key agreement public keys - # ciphers_to_test[6]: cipher suites using certificates with ECDSA signature public keys - # ciphers_to_test[7]: cipher suites using certificates with GOST R 34.10 (either 2001 or 94) public keys - ciphers_to_test[1]="aRSA:eRSA" - ciphers_to_test[2]="" - ciphers_to_test[3]="aDSS:aDH:aECDH:aECDSA:aGOST" - ciphers_to_test[4]="" - ciphers_to_test[5]="" - ciphers_to_test[6]="" - ciphers_to_test[7]="" - ciphers_to_test[8]="tls1_3_RSA" - ciphers_to_test[9]="tls1_3_ECDSA" - certificate_type[1]="" ; certificate_type[2]="" - certificate_type[3]=""; certificate_type[4]="" - certificate_type[5]="" ; certificate_type[6]="" - certificate_type[7]="" ; certificate_type[8]="RSASig" - certificate_type[9]="ECDSA" - - for (( n=1; n <= 16 ; n++ )); do - # Some servers use a different certificate if the ClientHello - # specifies TLSv1.1 and doesn't include a server name extension. - # So, for each public key type for which a certificate was found, - # try again, but only with TLSv1.1 and without SNI. - if [[ $n -ne 1 ]] && [[ "$OPTIMAL_PROTO" == -ssl2 ]]; then - ciphers_to_test[n]="" - elif [[ $n -ge 10 ]]; then - ciphers_to_test[n]="" - [[ ${success[n-9]} -eq 0 ]] && [[ $(has_server_protocol "tls1_1") -ne 1 ]] && \ - ciphers_to_test[n]="${ciphers_to_test[n-9]}" && certificate_type[n]="${certificate_type[n-9]}" - fi - - if [[ -n "${ciphers_to_test[n]}" ]]; then - if [[ $n -ge 10 ]]; then - sni="$SNI" - SNI="" - get_server_certificate "${ciphers_to_test[n]}" "tls1_1" - success[n]=$? - SNI="$sni" - else - get_server_certificate "${ciphers_to_test[n]}" - success[n]=$? - fi - if [[ ${success[n]} -eq 0 ]] && [[ -s "$HOSTCERT" ]]; then - [[ $n -ge 10 ]] && [[ ! -e $HOSTCERT.nosni ]] && cp $HOSTCERT $HOSTCERT.nosni - cp "$TEMPDIR/$NODEIP.get_server_certificate.txt" $TMPFILE - >$ERRFILE - if [[ -z "$sessticket_lifetime_hint" ]]; then - sessticket_lifetime_hint=$(awk '/session ticket life/ { if (!found) print; found=1 }' $TMPFILE) - sessticket_proto="$(get_protocol "$TMPFILE")" - fi - - if [[ $n -le 7 ]]; then - ciph="$(get_cipher $TMPFILE)" - if [[ "$ciph" != TLS_* ]] && [[ "$ciph" != SSL_* ]]; then - ciph="$(openssl2rfc "$ciph")" - fi - if [[ "$ciph" == TLS_DHE_RSA_* ]] || [[ "$ciph" == TLS_ECDHE_RSA_* ]] || [[ "$ciph" == TLS_CECPQ1_RSA_* ]]; then - certificate_type[n]="RSASig" - if [[ -z "${ciphers_to_test[n+1]}" ]]; then - ciphers_to_test[n+1]="${ciphers_to_test[n]/aRSA/}" - ciphers_to_test[n+1]="${ciphers_to_test[n+1]/::/:}" - [[ "${ciphers_to_test[n+1]:0:1}" == : ]] && ciphers_to_test[n+1]="${ciphers_to_test[n+1]:1}" - fi - ciphers_to_test[n]="aRSA" - elif [[ "$ciph" == TLS_RSA_* ]] || [[ "$ciph" == SSL_* ]] || [[ "$ciph" == TLS_GOST*_RSA_* ]]; then - certificate_type[n]="RSAKMK" - if [[ -z "${ciphers_to_test[n+1]}" ]]; then - ciphers_to_test[n+1]="${ciphers_to_test[n]/eRSA/}" - ciphers_to_test[n+1]="${ciphers_to_test[n+1]/::/:}" - [[ "${ciphers_to_test[n+1]:0:1}" == : ]] && ciphers_to_test[n+1]="${ciphers_to_test[n+1]:1}" - fi - ciphers_to_test[n]="eRSA" - elif [[ "$ciph" == TLS_DHE_DSS_* ]]; then - certificate_type[n]="DSA" - if [[ -z "${ciphers_to_test[n+1]}" ]]; then - ciphers_to_test[n+1]="${ciphers_to_test[n]/aDSS/}" - ciphers_to_test[n+1]="${ciphers_to_test[n+1]/::/:}" - [[ "${ciphers_to_test[n+1]:0:1}" == : ]] && ciphers_to_test[n+1]="${ciphers_to_test[n+1]:1}" - fi - ciphers_to_test[n]="aDSS" - elif [[ "$ciph" == TLS_DH_* ]]; then - certificate_type[n]="DH" - if [[ -z "${ciphers_to_test[n+1]}" ]]; then - ciphers_to_test[n+1]="${ciphers_to_test[n]/aDH/}" - ciphers_to_test[n+1]="${ciphers_to_test[n+1]/::/:}" - [[ "${ciphers_to_test[n+1]:0:1}" == : ]] && ciphers_to_test[n+1]="${ciphers_to_test[n+1]:1}" - fi - ciphers_to_test[n]="aDH" - elif [[ "$ciph" == TLS_ECDH_* ]]; then - certificate_type[n]="ECDH" - if [[ -z "${ciphers_to_test[n+1]}" ]]; then - ciphers_to_test[n+1]="${ciphers_to_test[n]/aECDH/}" - ciphers_to_test[n+1]="${ciphers_to_test[n+1]/::/:}" - [[ "${ciphers_to_test[n+1]:0:1}" == : ]] && ciphers_to_test[n+1]="${ciphers_to_test[n+1]:1}" - fi - ciphers_to_test[n]="aECDH" - elif [[ "$ciph" == TLS_ECDHE_ECDSA_* ]] || [[ "$ciph" == TLS_CECPQ1_ECDSA_* ]]; then - certificate_type[n]="ECDSA" - if [[ -z "${ciphers_to_test[n+1]}" ]]; then - ciphers_to_test[n+1]="${ciphers_to_test[n]/aECDSA/}" - ciphers_to_test[n+1]="${ciphers_to_test[n+1]/::/:}" - [[ "${ciphers_to_test[n+1]:0:1}" == : ]] && ciphers_to_test[n+1]="${ciphers_to_test[n+1]:1}" - fi - ciphers_to_test[n]="aECDSA" - elif [[ "$ciph" == TLS_GOST* ]]; then - certificate_type[n]="GOST" - if [[ -z "${ciphers_to_test[n+1]}" ]]; then - ciphers_to_test[n+1]="${ciphers_to_test[n]/aGOST/}" - ciphers_to_test[n+1]="${ciphers_to_test[n+1]/::/:}" - [[ "${ciphers_to_test[n+1]:0:1}" == : ]] && ciphers_to_test[n+1]="${ciphers_to_test[n+1]:1}" - fi - ciphers_to_test[n]="aGOST" - fi - fi - # check whether the host's certificate has been seen before - match_found=false - i=1 - newhostcert=$(cat $HOSTCERT) - while [[ $i -le $certs_found ]]; do - if [[ "$newhostcert" == "${previous_hostcert[i]}" ]]; then - match_found=true - break; - fi - i=$((i + 1)) - done - if ! "$match_found" && [[ $n -ge 10 ]] && [[ $certs_found -ne 0 ]]; then - # A new certificate was found using TLSv1.1 without SNI. - # Check to see if the new certificate should be displayed. - # It should be displayed if it is either a match for the - # $NODE being tested or if it has the same subject - # (CN and SAN) as other certificates for this host. - compare_server_name_to_cert "$HOSTCERT" - [[ $? -ne 0 ]] && success[n]=0 || success[n]=1 - - if [[ ${success[n]} -ne 0 ]]; then - cn_nosni="$(toupper "$(get_cn_from_cert $HOSTCERT)")" - sans_nosni="$(toupper "$(get_san_dns_from_cert "$HOSTCERT")")" - - echo "${previous_hostcert[1]}" > $HOSTCERT - cn_sni="$(toupper "$(get_cn_from_cert $HOSTCERT)")" - - # FIXME: Not sure what the matching rule should be. At - # the moment, the no SNI certificate is considered a - # match if the CNs are the same and the SANs (if - # present) contain at least one DNS name in common. - if [[ "$cn_nosni" == "$cn_sni" ]]; then - sans_sni="$(toupper "$(get_san_dns_from_cert "$HOSTCERT")")" - if [[ "$sans_nosni" == "$sans_sni" ]]; then - success[n]=0 - else - while read -r san; do - [[ -n "$san" ]] && [[ " $sans_sni " =~ " $san " ]] && success[n]=0 && break - done <<< "$sans_nosni" - fi - fi - fi - # If the certificate found for TLSv1.1 w/o SNI appears to - # be for a different host, then set match_found to true so - # that the new certificate will not be included in the output. - [[ ${success[n]} -ne 0 ]] && match_found=true - fi - if ! "$match_found"; then - certs_found=$(( certs_found + 1)) - cipher[certs_found]=${ciphers_to_test[n]} - keysize[certs_found]=$(awk '/Server public key/ { print $(NF-1) }' $TMPFILE) - # If an OCSP response was sent, then get the full - # response so that certificate_info() can determine - # whether it includes a certificate transparency extension. - ocsp_response_binary[certs_found]="$STAPLED_OCSP_RESPONSE" - if grep -a "OCSP response:" $TMPFILE | grep -q "no response sent"; then - ocsp_response[certs_found]="$(grep -a "OCSP response" $TMPFILE)" - else - ocsp_response[certs_found]="$(awk -v n=2 '/OCSP response:/ {start=1; inc=2} /======================================/ { if (start) {inc--} } inc' $TMPFILE)" - fi - ocsp_response_status[certs_found]=$(grep -a "OCSP Response Status" $TMPFILE) - previous_hostcert[certs_found]=$newhostcert - previous_hostcert_txt[certs_found]="$($OPENSSL x509 -noout -text 2>>$ERRFILE <<< "$newhostcert")" - previous_intermediates[certs_found]=$(cat $TEMPDIR/intermediatecerts.pem) - previous_hostcert_issuer[certs_found]="" - [[ -n "${previous_intermediates[certs_found]}" ]] && [[ -r $TEMPDIR/hostcert_issuer.pem ]] && \ - previous_hostcert_issuer[certs_found]=$(cat $TEMPDIR/hostcert_issuer.pem) - previous_ordering_problem[certs_found]=$CERTIFICATE_LIST_ORDERING_PROBLEM - [[ $n -ge 10 ]] && sni_used[certs_found]="" || sni_used[certs_found]="$SNI" - tls_version[certs_found]="$DETECTED_TLS_VERSION" - previous_hostcert_type[certs_found]=" ${certificate_type[n]}" - if [[ $DEBUG -ge 1 ]]; then - echo "${previous_hostcert[certs_found]}" > $TEMPDIR/host_certificate_$certs_found.pem - echo "${previous_hostcert_txt[certs_found]}" > $TEMPDIR/host_certificate_$certs_found.txt - fi - else - previous_hostcert_type[i]+=" ${certificate_type[n]}" - fi - fi - fi - done - - determine_tls_extensions - - if [[ $? -eq 0 ]] && [[ "$OPTIMAL_PROTO" != -ssl2 ]]; then - cp "$TEMPDIR/$NODEIP.determine_tls_extensions.txt" $TMPFILE - >$ERRFILE - if [[ -z "$sessticket_lifetime_hint" ]]; then - sessticket_lifetime_hint=$(awk '/session ticket lifetime/ { if (!found) print; found=1 }' $TMPFILE) - sessticket_proto="$(get_protocol "$TMPFILE")" - fi - fi - if "$using_sockets" && ! "$TLS13_ONLY" && [[ -z "$sessticket_lifetime_hint" ]] && [[ "$OPTIMAL_PROTO" != -ssl2 ]]; then - if "$HAS_TLS13" && ( [[ -z "$OPTIMAL_PROTO" ]] || [[ "$OPTIMAL_PROTO" == -tls1_3 ]] ) ; then - # If a session ticket were sent in response to a TLSv1.3 ClientHello, then a session ticket - # would have been found by get_server_certificate(). So, try again with a TLSv1.2 ClientHello. - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -no_tls1_3 -connect $NODEIP:$PORT $PROXY $SNI") </dev/null 2>$ERRFILE >$TMPFILE - else - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS "$OPTIMAL_PROTO" -connect $NODEIP:$PORT $PROXY $SNI") </dev/null 2>$ERRFILE >$TMPFILE - fi - if sclient_connect_successful $? $TMPFILE; then - sessticket_lifetime_hint=$(awk '/session ticket lifetime/ { if (!found) print; found=1 }' $TMPFILE) - sessticket_proto="$(get_protocol "$TMPFILE")" - fi - fi - [[ -z "$sessticket_lifetime_hint" ]] && TLS_TICKETS=false || TLS_TICKETS=true - - debugme echo "# certificates found $certs_found" - # Now that all of the server's certificates have been found, determine for - # each certificate whether certificate transparency information is provided. - for (( i=1; i <= certs_found; i++ )); do - ct[i]="$(certificate_transparency "${previous_hostcert_txt[i]}" "${ocsp_response[i]}" "$certs_found" "${cipher[i]}" "${sni_used[i]}" "${tls_version[i]}")" - # If certificate_transparency() called tls_sockets() and found a "signed certificate timestamps" extension, - # then add it to $TLS_EXTENSIONS, since it may not have been found by determine_tls_extensions(). - [[ $certs_found -gt 1 ]] && [[ "${ct[i]}" == TLS\ extension ]] && extract_new_tls_extensions "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" - done - - outln - pr_headlineln " Testing server defaults (Server Hello) " - outln - - pr_bold " TLS extensions (standard) " - if [[ -z "$TLS_EXTENSIONS" ]]; then - outln "(none)" - fileout "TLS_extensions" "INFO" "(none)" - else -#FIXME: we rather want to have the chance to print each ext in italics or another format. -# Atm is a string of quoted strings -- that needs to be fixed at the root then - # out_row_aligned_max_width() places line breaks at space characters. - # So, in order to prevent the text for an extension from being broken - # across lines, temporarily replace space characters within the text - # of an extension with "}", and then convert the "}" back to space in - # the output of out_row_aligned_max_width(). - tls_extensions="${TLS_EXTENSIONS// /{}" - tls_extensions="${tls_extensions//\"{\"/\" \"}" - tls_extensions="$(out_row_aligned_max_width "$tls_extensions" " " $TERM_WIDTH)" - tls_extensions="${tls_extensions//{/ }" - outln "$tls_extensions" - fileout "TLS_extensions" "INFO" "$TLS_EXTENSIONS" - fi - - pr_bold " Session Ticket RFC 5077 hint " - jsonID="TLS_session_ticket" - if [[ -z "$sessticket_lifetime_hint" ]]; then - outln "no -- no lifetime advertised" - fileout "${jsonID}" "INFO" "no -- no lifetime advertised" - # it MAY be given a hint of the lifetime of the ticket, see https://tools.ietf.org/html/rfc5077#section-5.6 . - # Sometimes it just does not -- but it then may also support TLS session tickets reuse - else - lifetime=$(grep -a lifetime <<< "$sessticket_lifetime_hint" | sed 's/[A-Za-z:() ]//g') - unit=$(grep -a lifetime <<< "$sessticket_lifetime_hint" | sed -e 's/^.*'"$lifetime"'//' -e 's/[ ()]//g') - out "$lifetime $unit" - if [[ $((3600 * 24)) -lt $lifetime ]]; then - prln_svrty_low " but: PFS requires session ticket keys to be rotated < daily !" - fileout "$jsonID" "LOW" "valid for $lifetime $unit (>daily)" - else - outln ", session tickets keys seems to be rotated < daily" - fileout "$jsonID" "INFO" "valid for $lifetime $unit only (<daily)" - fi - fi - - pr_bold " SSL Session ID support " - jsonID="SSL_sessionID_support" - if "$NO_SSL_SESSIONID"; then - outln "no" - fileout "$jsonID" "INFO" "no" - else - outln "yes" - fileout "$jsonID" "INFO" "yes" - fi - - pr_bold " Session Resumption " - jsonID="sessionresumption_ticket" - sub_session_resumption "$sessticket_proto" - case $? in - 0) SESS_RESUMPTION[2]="ticket=yes" - out "Tickets: yes, " - fileout "$jsonID" "INFO" "supported" - ;; - 1) SESS_RESUMPTION[2]="ticket=no" - out "Tickets no, " - fileout "$jsonID" "INFO" "not supported" - ;; - 5) SESS_RESUMPTION[2]="ticket=noclue" - pr_warning "Ticket resumption test failed, pls report / " - fileout "$jsonID" "WARN" "check failed, pls report" - ((ret++)) - ;; - 6) SESS_RESUMPTION[2]="ticket=clientauth" - pr_warning "Client Auth: Ticket resumption test not supported / " - fileout "$jsonID" "WARN" "check couldn't be performed because of client authentication" - ;; - 7) SESS_RESUMPTION[2]="ticket=unsuccessful" - pr_warning "Connect problem: Ticket resumption test not possible / " - fileout "$jsonID" "WARN" "check failed because of connect problem" - ((ret++)) - ;; - esac - - jsonID="sessionresumption_ID" - if "$NO_SSL_SESSIONID"; then - SESS_RESUMPTION[1]="ID=no" - outln "ID: no" - fileout "$jsonID" "INFO" "No Session ID, no resumption" - else - sub_session_resumption "$sessticket_proto" ID - case $? in - 0) SESS_RESUMPTION[1]="ID=yes" - outln "ID: yes" - fileout "$jsonID" "INFO" "supported" - ;; - 1|2) SESS_RESUMPTION[1]="ID=no" - outln "ID: no" - fileout "$jsonID" "INFO" "not supported" - ;; - 5) SESS_RESUMPTION[1]="ID=noclue" - prln_warning "ID resumption test failed, pls report" - fileout "$jsonID" "WARN" "check failed, pls report" - ((ret++)) - ;; - 6) SESS_RESUMPTION[1]="ID=clientauth" - # [[ ${SESS_RESUMPTION[2]} =~ clientauth ]] || pr_warning "Client Auth: " - prln_warning "Client Auth: ID resumption test not supported" - fileout "$jsonID" "WARN" "check couldn't be performed because of client authentication" - ;; - 7) SESS_RESUMPTION[1]="ID=unsuccessful" - prln_warning "ID resumption test failed" - fileout "$jsonID" "WARN" "check failed because of connect problem" - ((ret++)) - ;; - esac - fi - - tls_time - - if [[ -n "$SNI" ]] && [[ $certs_found -ne 0 ]] && [[ ! -e $HOSTCERT.nosni ]]; then - # no cipher suites specified here. We just want the default vhost subject - if ! "$HAS_TLS13" && [[ $(has_server_protocol "tls1_3") -eq 0 ]]; then - sni="$SNI" ; SNI="" - mv $HOSTCERT $HOSTCERT.save - # Send same list of cipher suites as OpenSSL 1.1.1 sends (but with - # all 5 TLSv1.3 ciphers offered. - tls_sockets "04" \ - "c0,2c, c0,30, 00,9f, cc,a9, cc,a8, cc,aa, c0,2b, c0,2f, - 00,9e, c0,24, c0,28, 00,6b, c0,23, c0,27, 00,67, c0,0a, - c0,14, 00,39, c0,09, c0,13, 00,33, 00,9d, 00,9c, 13,02, - 13,03, 13,01, 13,04, 13,05, 00,3d, 00,3c, 00,35, 00,2f, - 00,ff" \ - "all+" - success[0]=$? - if [[ ${success[0]} -eq 0 ]] || [[ ${success[0]} -eq 2 ]]; then - if [[ -s $HOSTCERT ]]; then - mv $HOSTCERT $HOSTCERT.nosni - else - # The connection was successful, but the certificate could - # not be obtained (probably because the connection was TLS 1.3 - # and $OPENSSL does not support the key exchange group that was - # selected). So, try again using OpenSSL (which will not use a TLS 1.3 - # ClientHello). - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $OPTIMAL_PROTO") 2>>$ERRFILE </dev/null | \ - awk '/-----BEGIN/,/-----END/ { print $0 }' >$HOSTCERT.nosni - fi - else - >$HOSTCERT.nosni - fi - mv $HOSTCERT.save $HOSTCERT - SNI="$sni" - else - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $OPTIMAL_PROTO") 2>>$ERRFILE </dev/null | \ - awk '/-----BEGIN/,/-----END/ { print $0 }' >$HOSTCERT.nosni - fi - elif [[ $certs_found -eq 0 ]] && [[ -s "$HOSTCERT" ]]; then - outln - generic_nonfatal "Client problem, shouldn't happen: Host certificate found but we can't continue with \"server defaults\"." - elif [[ $certs_found -eq 0 ]]; then - outln - if $TLS13_ONLY; then - generic_nonfatal "Client problem: We need openssl supporting TLS 1.3. We can't continue with \"server defaults\" as we cannot retrieve the certificate. " - else - generic_nonfatal "Client problem, No server cerificate could be retrieved. Thus we can't continue with \"server defaults\"." - fi - fi - [[ $DEBUG -ge 1 ]] && [[ -e $HOSTCERT.nosni ]] && $OPENSSL x509 -in $HOSTCERT.nosni -text -noout 2>>$ERRFILE > $HOSTCERT.nosni.txt - - fileout "cert_numbers" "INFO" "$certs_found" - for (( i=1; i <= certs_found; i++ )); do - echo "${previous_hostcert[i]}" > $HOSTCERT - echo "${previous_intermediates[i]}" > $TEMPDIR/intermediatecerts.pem - echo "${previous_hostcert_issuer[i]}" > $TEMPDIR/hostcert_issuer.pem - certificate_info "$i" "$certs_found" "${previous_hostcert_txt[i]}" \ - "${cipher[i]}" "${keysize[i]}" "${previous_hostcert_type[i]}" \ - "${ocsp_response_binary[i]}" "${ocsp_response[i]}" \ - "${ocsp_response_status[i]}" "${sni_used[i]}" "${ct[i]}" \ - "${previous_ordering_problem[i]}" - [[ $? -ne 0 ]] && ((ret++)) - done - return $ret -} - -get_session_ticket_lifetime_from_serverhello() { - awk '/session ticket.*lifetime/ { print $(NF-1) "$1" }' -} - -get_san_dns_from_cert() { - echo "$($OPENSSL x509 -in "$1" -noout -text 2>>$ERRFILE | \ - grep -A2 "Subject Alternative Name" | tr ',' '\n' | grep "DNS:" | \ - sed -e 's/DNS://g' -e 's/ //g')" -} - - -run_pfs() { - local -i sclient_success - local pfs_offered=false ecdhe_offered=false ffdhe_offered=false - local pfs_tls13_offered=false - local protos_to_try proto hexc dash pfs_cipher sslvers auth mac export curve dhlen - local -a hexcode normalized_hexcode ciph rfc_ciph kx enc ciphers_found sigalg ossl_supported - # generated from 'kEECDH:kEDH:!aNULL:!eNULL:!DES:!3DES:!RC4' with openssl 1.0.2i and openssl 1.1.0 - local pfs_cipher_list="DHE-DSS-AES128-GCM-SHA256:DHE-DSS-AES128-SHA256:DHE-DSS-AES128-SHA:DHE-DSS-AES256-GCM-SHA384:DHE-DSS-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-DSS-CAMELLIA128-SHA256:DHE-DSS-CAMELLIA128-SHA:DHE-DSS-CAMELLIA256-SHA256:DHE-DSS-CAMELLIA256-SHA:DHE-DSS-SEED-SHA:DHE-RSA-AES128-CCM8:DHE-RSA-AES128-CCM:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-CCM8:DHE-RSA-AES256-CCM:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-CAMELLIA128-SHA256:DHE-RSA-CAMELLIA128-SHA:DHE-RSA-CAMELLIA256-SHA256:DHE-RSA-CAMELLIA256-SHA:DHE-RSA-CHACHA20-POLY1305-OLD:DHE-RSA-CHACHA20-POLY1305:DHE-RSA-SEED-SHA:ECDHE-ECDSA-AES128-CCM8:ECDHE-ECDSA-AES128-CCM:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-CCM8:ECDHE-ECDSA-AES256-CCM:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-CAMELLIA128-SHA256:ECDHE-ECDSA-CAMELLIA256-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305-OLD:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-RSA-CAMELLIA128-SHA256:ECDHE-RSA-CAMELLIA256-SHA384:ECDHE-RSA-CHACHA20-POLY1305-OLD:ECDHE-RSA-CHACHA20-POLY1305" - local pfs_hex_cipher_list="" ciphers_to_test tls13_ciphers_to_test - local ecdhe_cipher_list="" tls13_cipher_list="" ecdhe_cipher_list_hex="" ffdhe_cipher_list_hex="" - local curves_hex=("00,01" "00,02" "00,03" "00,04" "00,05" "00,06" "00,07" "00,08" "00,09" "00,0a" "00,0b" "00,0c" "00,0d" "00,0e" "00,0f" "00,10" "00,11" "00,12" "00,13" "00,14" "00,15" "00,16" "00,17" "00,18" "00,19" "00,1a" "00,1b" "00,1c" "00,1d" "00,1e") - local -a curves_ossl=("sect163k1" "sect163r1" "sect163r2" "sect193r1" "sect193r2" "sect233k1" "sect233r1" "sect239k1" "sect283k1" "sect283r1" "sect409k1" "sect409r1" "sect571k1" "sect571r1" "secp160k1" "secp160r1" "secp160r2" "secp192k1" "prime192v1" "secp224k1" "secp224r1" "secp256k1" "prime256v1" "secp384r1" "secp521r1" "brainpoolP256r1" "brainpoolP384r1" "brainpoolP512r1" "X25519" "X448") - local -a curves_ossl_output=("K-163" "sect163r1" "B-163" "sect193r1" "sect193r2" "K-233" "B-233" "sect239k1" "K-283" "B-283" "K-409" "B-409" "K-571" "B-571" "secp160k1" "secp160r1" "secp160r2" "secp192k1" "P-192" "secp224k1" "P-224" "secp256k1" "P-256" "P-384" "P-521" "brainpoolP256r1" "brainpoolP384r1" "brainpoolP512r1" "X25519" "X448") - local -ai curves_bits=(163 162 163 193 193 232 233 238 281 282 407 409 570 570 161 161 161 192 192 225 224 256 256 384 521 256 384 512 253 448) - # Many curves have been deprecated, and RFC 8446, Appendix B.3.1.4, states - # that these curves MUST NOT be offered in a TLS 1.3 ClientHello. - local -a curves_deprecated=("true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "false" "false" "false" "true" "true" "true" "false" "false") - local -a ffdhe_groups_hex=("01,00" "01,01" "01,02" "01,03" "01,04") - local -a ffdhe_groups_output=("ffdhe2048" "ffdhe3072" "ffdhe4096" "ffdhe6144" "ffdhe8192") - local -a supported_curve - local -i nr_supported_ciphers=0 nr_curves=0 nr_ossl_curves=0 i j low high - local pfs_ciphers curves_offered="" curves_to_test temp - local len1 len2 curve_found - local key_bitstring quality_str - local -i len_dh_p quality - local has_dh_bits="$HAS_DH_BITS" - local using_sockets=true - local jsonID="PFS" - - "$SSL_NATIVE" && using_sockets=false - "$FAST" && using_sockets=false - [[ $TLS_NR_CIPHERS == 0 ]] && using_sockets=false - - outln - pr_headline " Testing robust (perfect) forward secrecy"; prln_underline ", (P)FS -- omitting Null Authentication/Encryption, 3DES, RC4 " - if ! "$using_sockets"; then - [[ $TLS_NR_CIPHERS == 0 ]] && ! "$SSL_NATIVE" && ! "$FAST" && pr_warning " Cipher mapping not available, doing a fallback to openssl" - if ! "$HAS_DH_BITS" && "$WIDE"; then - [[ $TLS_NR_CIPHERS == 0 ]] && ! "$SSL_NATIVE" && ! "$FAST" && out "." - pr_warning " (Your $OPENSSL cannot show DH/ECDH bits)" - fi - outln - fi - - if "$using_sockets" || [[ $OSSL_VER_MAJOR -lt 1 ]]; then - for (( i=0; i < TLS_NR_CIPHERS; i++ )); do - pfs_cipher="${TLS_CIPHER_RFC_NAME[i]}" - hexc="${TLS_CIPHER_HEXCODE[i]}" - if ( [[ "$pfs_cipher" == "TLS_DHE_"* ]] || [[ "$pfs_cipher" == "TLS_ECDHE_"* ]] || [[ "${hexc:2:2}" == "13" ]] ) && \ - [[ ! "$pfs_cipher" =~ NULL ]] && [[ ! "$pfs_cipher" =~ DES ]] && [[ ! "$pfs_cipher" =~ RC4 ]] && \ - [[ ! "$pfs_cipher" =~ PSK ]] && ( "$using_sockets" || "${TLS_CIPHER_OSSL_SUPPORTED[i]}" ); then - pfs_hex_cipher_list+=", ${hexc:2:2},${hexc:7:2}" - ciph[nr_supported_ciphers]="${TLS_CIPHER_OSSL_NAME[i]}" - rfc_ciph[nr_supported_ciphers]="${TLS_CIPHER_RFC_NAME[i]}" - kx[nr_supported_ciphers]="${TLS_CIPHER_KX[i]}" - enc[nr_supported_ciphers]="${TLS_CIPHER_ENC[i]}" - ciphers_found[nr_supported_ciphers]=false - sigalg[nr_supported_ciphers]="" - ossl_supported[nr_supported_ciphers]="${TLS_CIPHER_OSSL_SUPPORTED[i]}" - hexcode[nr_supported_ciphers]="${hexc:2:2},${hexc:7:2}" - if [[ "${hexc:2:2}" == 00 ]]; then - normalized_hexcode[nr_supported_ciphers]="x${hexc:7:2}" - else - normalized_hexcode[nr_supported_ciphers]="x${hexc:2:2}${hexc:7:2}" - fi - "$using_sockets" && ! "$has_dh_bits" && "$WIDE" && ossl_supported[nr_supported_ciphers]=false - nr_supported_ciphers+=1 - fi - done - else - while read -r hexc dash ciph[nr_supported_ciphers] sslvers kx[nr_supported_ciphers] auth enc[nr_supported_ciphers] mac export; do - ciphers_found[nr_supported_ciphers]=false - if [[ "${hexc:2:2}" == 00 ]]; then - normalized_hexcode[nr_supported_ciphers]="x${hexc:7:2}" - else - normalized_hexcode[nr_supported_ciphers]="x${hexc:2:2}${hexc:7:2}" - fi - sigalg[nr_supported_ciphers]="" - ossl_supported[nr_supported_ciphers]=true - nr_supported_ciphers+=1 - done < <(actually_supported_osslciphers "$pfs_cipher_list" "ALL" "-V") - fi - export="" - - if [[ $(has_server_protocol "tls1_3") -eq 0 ]]; then - # All TLSv1.3 cipher suites offer robust PFS. - sclient_success=0 - elif "$using_sockets"; then - tls_sockets "04" "${pfs_hex_cipher_list:2}, 00,ff" - sclient_success=$? - [[ $sclient_success -eq 2 ]] && sclient_success=0 - else - debugme echo $nr_supported_ciphers - debugme echo $(actually_supported_osslciphers $pfs_cipher_list "ALL") - if [[ "$nr_supported_ciphers" -le "$CLIENT_MIN_PFS" ]]; then - outln - prln_local_problem "You only have $nr_supported_ciphers PFS ciphers on the client side " - fileout "$jsonID" "WARN" "tests skipped as you only have $nr_supported_ciphers PFS ciphers on the client site. ($CLIENT_MIN_PFS are required)" - return 1 - fi - $OPENSSL s_client $(s_client_options "-cipher $pfs_cipher_list -ciphersuites "ALL" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE </dev/null - sclient_connect_successful $? $TMPFILE - sclient_success=$? - [[ $sclient_success -eq 0 ]] && [[ $(grep -ac "BEGIN CERTIFICATE" $TMPFILE) -eq 0 ]] && sclient_success=1 - fi - - if [[ $sclient_success -ne 0 ]]; then - outln - prln_svrty_medium " No ciphers supporting Forward Secrecy offered" - fileout "$jsonID" "MEDIUM" "No ciphers supporting (P)FS offered" - else - outln - pfs_offered=true - pfs_ciphers="" - pr_svrty_good " PFS is offered (OK)" - fileout "$jsonID" "OK" "offered" - if "$WIDE"; then - outln ", ciphers follow (client/browser support is important here) \n" - neat_header - else - out " " - fi - if "$HAS_TLS13"; then - protos_to_try="-no_ssl2 -no_tls1_3" - else - protos_to_try="-no_ssl2" - fi - - for proto in $protos_to_try; do - while true; do - ciphers_to_test="" - tls13_ciphers_to_test="" - for (( i=0; i < nr_supported_ciphers; i++ )); do - if ! "${ciphers_found[i]}" && "${ossl_supported[i]}"; then - if [[ "${ciph[i]}" == TLS13* ]] || [[ "${ciph[i]}" == TLS_* ]]; then - tls13_ciphers_to_test+=":${ciph[i]}" - else - ciphers_to_test+=":${ciph[i]}" - fi - fi - done - [[ -z "$ciphers_to_test" ]] && [[ -z "$tls13_ciphers_to_test" ]] && break - $OPENSSL s_client $(s_client_options "$proto -cipher "\'${ciphers_to_test:1}\'" -ciphersuites "\'${tls13_ciphers_to_test:1}\'" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") &>$TMPFILE </dev/null - sclient_connect_successful $? $TMPFILE || break - pfs_cipher=$(get_cipher $TMPFILE) - [[ -z "$pfs_cipher" ]] && break - for (( i=0; i < nr_supported_ciphers; i++ )); do - [[ "$pfs_cipher" == "${ciph[i]}" ]] && break - done - [[ $i -eq $nr_supported_ciphers ]] && break - ciphers_found[i]=true - if [[ "$pfs_cipher" == TLS13* ]] || [[ "$pfs_cipher" == TLS_* ]]; then - pfs_tls13_offered=true - "$WIDE" && kx[i]="$(read_dhtype_from_file $TMPFILE)" - fi - if "$WIDE"; then - dhlen=$(read_dhbits_from_file "$TMPFILE" quiet) - kx[i]="${kx[i]} $dhlen" - fi - "$WIDE" && "$SHOW_SIGALGO" && grep -q "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TMPFILE && \ - sigalg[i]="$(read_sigalg_from_file "$TMPFILE")" - done - done - if "$using_sockets"; then - for proto in 04 03; do - while true; do - ciphers_to_test="" - for (( i=0; i < nr_supported_ciphers; i++ )); do - ! "${ciphers_found[i]}" && ciphers_to_test+=", ${hexcode[i]}" - done - [[ -z "$ciphers_to_test" ]] && break - [[ "$proto" == "04" ]] && [[ ! "$ciphers_to_test" =~ ,\ 13,[0-9a-f][0-9a-f] ]] && break - ciphers_to_test="$(strip_inconsistent_ciphers "$proto" "$ciphers_to_test")" - [[ -z "$ciphers_to_test" ]] && break - if "$WIDE" && "$SHOW_SIGALGO"; then - tls_sockets "$proto" "${ciphers_to_test:2}, 00,ff" "all" - else - tls_sockets "$proto" "${ciphers_to_test:2}, 00,ff" "ephemeralkey" - fi - sclient_success=$? - [[ $sclient_success -ne 0 ]] && [[ $sclient_success -ne 2 ]] && break - pfs_cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") - for (( i=0; i < nr_supported_ciphers; i++ )); do - [[ "$pfs_cipher" == "${rfc_ciph[i]}" ]] && break - done - [[ $i -eq $nr_supported_ciphers ]] && break - ciphers_found[i]=true - if [[ "${kx[i]}" == Kx=any ]]; then - pfs_tls13_offered=true - "$WIDE" && kx[i]="$(read_dhtype_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")" - fi - if "$WIDE"; then - dhlen=$(read_dhbits_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" quiet) - kx[i]="${kx[i]} $dhlen" - fi - "$WIDE" && "$SHOW_SIGALGO" && [[ -r "$HOSTCERT" ]] && \ - sigalg[i]="$(read_sigalg_from_file "$HOSTCERT")" - done - done - fi - for (( i=0; i < nr_supported_ciphers; i++ )); do - ! "${ciphers_found[i]}" && ! "$SHOW_EACH_C" && continue - if "${ciphers_found[i]}"; then - if ( [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]] && [[ "${ciph[i]}" != "-" ]] ) || [[ "${rfc_ciph[i]}" == "-" ]]; then - pfs_cipher="${ciph[i]}" - else - pfs_cipher="${rfc_ciph[i]}" - fi - pfs_ciphers+="$pfs_cipher " - - if [[ "${ciph[i]}" == ECDHE-* ]] || [[ "${ciph[i]}" == TLS13* ]] || [[ "${ciph[i]}" == TLS_* ]] || ( "$using_sockets" && [[ "${rfc_ciph[i]}" == TLS_ECDHE_* ]] ); then - ecdhe_offered=true - ecdhe_cipher_list_hex+=", ${hexcode[i]}" - if [[ "${ciph[i]}" != "-" ]]; then - if [[ "${ciph[i]}" == TLS13* ]] || [[ "${ciph[i]}" == TLS_* ]]; then - tls13_cipher_list+=":$pfs_cipher" - else - ecdhe_cipher_list+=":$pfs_cipher" - fi - fi - fi - if [[ "${ciph[i]}" == "DHE-"* ]] || ( "$using_sockets" && [[ "${rfc_ciph[i]}" == "TLS_DHE_"* ]] ); then - ffdhe_offered=true - ffdhe_cipher_list_hex+=", ${hexcode[i]}" - elif [[ "${ciph[i]}" == TLS13* ]] || [[ "${ciph[i]}" == TLS_* ]]; then - ffdhe_cipher_list_hex+=", ${hexcode[i]}" - fi - fi - if "$WIDE"; then - neat_list "$(tolower "${normalized_hexcode[i]}")" "${ciph[i]}" "${kx[i]}" "${enc[i]}" "${ciphers_found[i]}" - if "$SHOW_EACH_C"; then - if "${ciphers_found[i]}"; then - pr_cipher_quality "${rfc_ciph[i]}" "available" - else - pr_deemphasize "not a/v" - fi - fi - outln "${sigalg[i]}" - fi - done - if ! "$WIDE"; then - if [[ "$COLOR" -le 2 ]]; then - out "$(out_row_aligned_max_width "$pfs_ciphers" " " $TERM_WIDTH)" - else - out_row_aligned_max_width_by_entry "$pfs_ciphers" " " $TERM_WIDTH pr_cipher_quality - fi - fi - debugme echo $pfs_offered - "$WIDE" || outln - fileout "${jsonID}_ciphers" "INFO" "$pfs_ciphers" - fi - - # find out what elliptic curves are supported. - if "$ecdhe_offered"; then - for curve in "${curves_ossl[@]}"; do - ossl_supported[nr_curves]=false - supported_curve[nr_curves]=false - [[ "$OSSL_SUPPORTED_CURVES" =~ " $curve " ]] && ossl_supported[nr_curves]=true && nr_ossl_curves+=1 - nr_curves+=1 - done - - # OpenSSL limits the number of curves that can be specified in the - # "-curves" option to 28. So, break the list in two if there are more - # than 28 curves supported by OpenSSL. - for j in 1 2; do - if [[ $j -eq 1 ]]; then - if [[ $nr_ossl_curves -le 28 ]]; then - low=0; high=$nr_curves - else - low=0; high=$nr_curves/2 - fi - else - if [[ $nr_ossl_curves -le 28 ]]; then - continue # all curves tested in first round - else - low=$nr_curves/2; high=$nr_curves - fi - fi - if "$HAS_TLS13"; then - if "$pfs_tls13_offered"; then - protos_to_try="-no_ssl2 -no_tls1_3" - else - protos_to_try="-no_tls1_3" - fi - else - protos_to_try="-no_ssl2" - fi - - for proto in $protos_to_try; do - while true; do - curves_to_test="" - for (( i=low; i < high; i++ )); do - if ! "$HAS_TLS13" || ! "${curves_deprecated[i]}" || [[ "$proto" == "-no_tls1_3" ]]; then - "${ossl_supported[i]}" && ! "${supported_curve[i]}" && curves_to_test+=":${curves_ossl[i]}" - fi - done - [[ -z "$curves_to_test" ]] && break - $OPENSSL s_client $(s_client_options "$proto -cipher "\'${ecdhe_cipher_list:1}\'" -ciphersuites "\'${tls13_cipher_list:1}\'" -curves "${curves_to_test:1}" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") &>$TMPFILE </dev/null - sclient_connect_successful $? $TMPFILE || break - temp=$(awk -F': ' '/^Server Temp Key/ { print $2 }' "$TMPFILE") - curve_found="${temp%%,*}" - if [[ "$curve_found" == ECDH ]]; then - curve_found="${temp#*, }" - curve_found="${curve_found%%,*}" - fi - for (( i=low; i < high; i++ )); do - ! "${supported_curve[i]}" && [[ "${curves_ossl_output[i]}" == "$curve_found" ]] && break - done - [[ $i -eq $high ]] && break - supported_curve[i]=true - done - done - done - fi - if "$ecdhe_offered" && "$using_sockets"; then - protos_to_try="03" - "$pfs_tls13_offered" && protos_to_try="04 03" - for proto in $protos_to_try; do - if [[ "$proto" == 03 ]]; then - ecdhe_cipher_list_hex="$(strip_inconsistent_ciphers "03" "$ecdhe_cipher_list_hex")" - [[ -z "$ecdhe_cipher_list_hex" ]] && continue - fi - while true; do - curves_to_test="" - for (( i=0; i < nr_curves; i++ )); do - if ! "${curves_deprecated[i]}" || [[ "$proto" == 03 ]]; then - ! "${supported_curve[i]}" && curves_to_test+=", ${curves_hex[i]}" - fi - done - [[ -z "$curves_to_test" ]] && break - len1=$(printf "%02x" "$((2*${#curves_to_test}/7))") - len2=$(printf "%02x" "$((2*${#curves_to_test}/7+2))") - tls_sockets "$proto" "${ecdhe_cipher_list_hex:2}, 00,ff" "ephemeralkey" "00, 0a, 00, $len2, 00, $len1, ${curves_to_test:2}" - sclient_success=$? - [[ $sclient_success -ne 0 ]] && [[ $sclient_success -ne 2 ]] && break - temp=$(awk -F': ' '/^Server Temp Key/ { print $2 }' "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") - curve_found="${temp%%,*}" - if [[ "$curve_found" == "ECDH" ]]; then - curve_found="${temp#*, }" - curve_found="${curve_found%%,*}" - fi - for (( i=0; i < nr_curves; i++ )); do - ! "${supported_curve[i]}" && [[ "${curves_ossl_output[i]}" == "$curve_found" ]] && break - done - [[ $i -eq $nr_curves ]] && break - supported_curve[i]=true - done - done - fi - if "$ecdhe_offered"; then - low=1000 - for (( i=0; i < nr_curves; i++ )); do - if "${supported_curve[i]}"; then - curves_offered+="${curves_ossl[i]} " - [[ ${curves_bits[i]} -lt $low ]] && low=${curves_bits[i]} - fi - done - if [[ -n "$curves_offered" ]]; then - "$WIDE" && outln - pr_bold " Elliptic curves offered: " - out_row_aligned_max_width_by_entry "$curves_offered" " " $TERM_WIDTH pr_ecdh_curve_quality - outln - # severity ratings based on quality specified by - # pr_ecdh_quality() for shortest curve offered. - if [[ "$low" -le 163 ]]; then - fileout "${jsonID}_ECDHE_curves" "MEDIUM" "$curves_offered" - elif [[ "$low" -le 193 ]]; then - fileout "${jsonID}_ECDHE_curves" "LOW" "$curves_offered" - elif [[ "$low" -le 224 ]]; then - fileout "${jsonID}_ECDHE_curves" "INFO" "$curves_offered" - else - fileout "${jsonID}_ECDHE_curves" "OK" "$curves_offered" - fi - fi - fi - CURVES_OFFERED="$curves_offered" - CURVES_OFFERED=$(strip_trailing_space "$CURVES_OFFERED") - - # find out what groups are supported. - if "$using_sockets" && ( "$pfs_tls13_offered" || "$ffdhe_offered" ); then - nr_curves=0 - for curve in "${ffdhe_groups_output[@]}"; do - supported_curve[nr_curves]=false - [[ "$DH_GROUP_OFFERED" =~ $curve ]] && supported_curve[nr_curves]=true - nr_curves+=1 - done - protos_to_try="" - "$pfs_tls13_offered" && protos_to_try="04" - if "$ffdhe_offered"; then - if "$pfs_tls13_offered"; then - protos_to_try="04 03" - else - protos_to_try="03" - fi - fi - curve_found="" - for proto in $protos_to_try; do - while true; do - curves_to_test="" - for (( i=0; i < nr_curves; i++ )); do - ! "${supported_curve[i]}" && curves_to_test+=", ${ffdhe_groups_hex[i]}" - done - [[ -z "$curves_to_test" ]] && break - len1=$(printf "%02x" "$((2*${#curves_to_test}/7))") - len2=$(printf "%02x" "$((2*${#curves_to_test}/7+2))") - tls_sockets "$proto" "${ffdhe_cipher_list_hex:2}, 00,ff" "ephemeralkey" "00, 0a, 00, $len2, 00, $len1, ${curves_to_test:2}" - sclient_success=$? - [[ $sclient_success -ne 0 ]] && [[ $sclient_success -ne 2 ]] && break - temp=$(awk -F': ' '/^Server Temp Key/ { print $2 }' "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") - curve_found="${temp#*, }" - curve_found="${curve_found%%,*}" - if [[ "$proto" == "03" ]] && [[ -z "$DH_GROUP_OFFERED" ]] && [[ "$curve_found" =~ ffdhe ]]; then - DH_GROUP_OFFERED="RFC7919/$curve_found" - DH_GROUP_LEN_P="${curve_found#ffdhe}" - fi - [[ ! "$curve_found" =~ ffdhe ]] && break - for (( i=0; i < nr_curves; i++ )); do - ! "${supported_curve[i]}" && [[ "${ffdhe_groups_output[i]}" == "$curve_found" ]] && break - done - [[ $i -eq $nr_curves ]] && break - supported_curve[i]=true - done - done - curves_offered="" - for (( i=0; i < nr_curves; i++ )); do - "${supported_curve[i]}" && curves_offered+="${ffdhe_groups_output[i]} " - done - curves_offered="$(strip_trailing_space "$curves_offered")" - if "$ffdhe_offered" && [[ -z "$curves_offered" ]] && [[ -z "$curve_found" ]]; then - # Some servers will fail if the supported_groups extension is present. - tls_sockets "03" "${ffdhe_cipher_list_hex:2}, 00,ff" "ephemeralkey" - sclient_success=$? - if [[ $sclient_success -eq 0 ]] || [[ $sclient_success -eq 2 ]]; then - temp=$(awk -F': ' '/^Server Temp Key/ { print $2 }' "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") - curve_found="${temp#*, }" - curve_found="${curve_found%%,*}" - fi - fi - if [[ -z "$curves_offered" ]] && [[ -n "$curve_found" ]]; then - # The server is not using one of the groups from RFC 7919. - if [[ -z "$DH_GROUP_OFFERED" ]]; then - # this global will get the name of the group either here or in run_logjam() - key_bitstring="$(awk '/-----BEGIN PUBLIC KEY/,/-----END PUBLIC KEY/ { print $0 }' $TEMPDIR/$NODEIP.parse_tls_serverhello.txt)" - get_common_prime "$jsonID" "$key_bitstring" "" - case $? in - 0) curves_offered="$DH_GROUP_OFFERED" - len_dh_p=$DH_GROUP_LEN_P ;; - 2) pr_bold " DH or FF group offered : " - prln_local_problem "Your $OPENSSL does not support the pkey utility." - fileout "$jsonID" "WARN" "$OPENSSL does not support the pkey utility." - esac - else - curves_offered="$DH_GROUP_OFFERED" - len_dh_p=$DH_GROUP_LEN_P - fi - fi - if [[ -n "$curves_offered" ]]; then - if [[ ! "$curves_offered" =~ ffdhe ]] || [[ ! "$curves_offered" =~ \ ]]; then - pr_bold " DH group offered: " - else - pr_bold " Finite field group: " - fi - if [[ "$curves_offered" =~ ffdhe ]]; then - # ok not to display them in italics: - pr_svrty_good "$curves_offered" - quality=6 - else - pr_dh "$curves_offered" "$len_dh_p" - quality=$? - fi - case "$quality" in - 1) quality_str="CRITICAL" ;; - 2) quality_str="HIGH" ;; - 3) quality_str="MEDIUM" ;; - 4) quality_str="LOW" ;; - 5) quality_str="INFO" ;; - 6|7) quality_str="OK" ;; - esac - if [[ "$curves_offered" =~ Unknown ]]; then - fileout "DH_groups" "$quality_str" "$curves_offered ($len_dh_p bits)" - else - fileout "DH_groups" "$quality_str" "$curves_offered" - fi - fi - fi - outln - - tmpfile_handle ${FUNCNAME[0]}.txt - "$using_sockets" && HAS_DH_BITS="$has_dh_bits" - return 0 -} - - -# good source for configuration and bugs: https://wiki.mozilla.org/Security/Server_Side_TLS -# good start to read: https://en.wikipedia.org/wiki/Transport_Layer_Security#Attacks_against_TLS.2FSSL - - -npn_pre(){ - if [[ -n "$PROXY" ]]; then - pr_warning "not tested as proxies do not support proxying it" - fileout "NPN" "WARN" "not tested as proxies do not support proxying it" - return 1 - fi - if ! "$HAS_NPN"; then - pr_local_problem "$OPENSSL doesn't support NPN/SPDY"; - fileout "NPN" "WARN" "not tested $OPENSSL doesn't support NPN/SPDY" - return 7 - fi - return 0 -} - -alpn_pre(){ - if [[ -n "$PROXY" ]]; then - pr_warning "not tested as proxies do not support proxying it" - fileout "ALPN" "WARN" "not tested as proxies do not support proxying it" - return 1 - fi - if ! "$HAS_ALPN" && "$SSL_NATIVE"; then - prln_local_problem "$OPENSSL doesn't support ALPN/HTTP2"; - fileout "ALPN" "WARN" "not tested as $OPENSSL does not support it" - return 7 - fi - return 0 -} - -# modern browsers do not support it anymore but we should still test it at least for fingerprinting the server side -# Thus we don't label any support for NPN as good. -# FAST mode skips this test -run_npn() { - local tmpstr - local -i ret=0 - local jsonID="NPN" - - [[ -n "$STARTTLS" ]] && return 0 - "$FAST" && return 0 - pr_bold " NPN/SPDY " - if ! npn_pre; then - outln - return 0 - fi - $OPENSSL s_client $(s_client_options "-connect $NODEIP:$PORT $BUGS $SNI -nextprotoneg "$NPN_PROTOs"") </dev/null 2>$ERRFILE >$TMPFILE - [[ $? -ne 0 ]] && ret=1 - tmpstr="$(grep -a '^Protocols' $TMPFILE | sed 's/Protocols.*: //')" - if [[ -z "$tmpstr" ]] || [[ "$tmpstr" == " " ]]; then - outln "not offered" - fileout "$jsonID" "INFO" "not offered" - else - # now comes a strange thing: "Protocols advertised by server:" is empty but connection succeeded - if [[ "$tmpstr" =~ [h2|spdy|http] ]]; then - out "$tmpstr" - outln " (advertised)" - fileout "$jsonID" "INFO" "offered with $tmpstr (advertised)" - else - prln_cyan "please check manually, server response was ambiguous ..." - fileout "$jsonID" "INFO" "please check manually, server response was ambiguous ..." - ((ret++)) - fi - fi - # btw: nmap can do that too https://nmap.org/nsedoc/scripts/tls-nextprotoneg.html - # nmap --script=tls-nextprotoneg #NODE -p $PORT is your friend if your openssl doesn't want to test this - tmpfile_handle ${FUNCNAME[0]}.txt - return $ret -} - - -run_alpn() { - local tmpstr alpn_extn len - local -i ret=0 - local has_alpn_proto=false - local alpn_finding="" - local jsonID="ALPN" - - [[ -n "$STARTTLS" ]] && return 0 - pr_bold " ALPN/HTTP2 " - if ! alpn_pre; then - outln - return 0 - fi - for proto in $ALPN_PROTOs; do - # for some reason OpenSSL doesn't list the advertised protocols, so instead try common protocols - if "$HAS_ALPN"; then - $OPENSSL s_client $(s_client_options "-connect $NODEIP:$PORT $BUGS $SNI -alpn $proto") </dev/null 2>$ERRFILE >$TMPFILE - else - alpn_extn="$(printf "%02x" ${#proto}),$(string_to_asciihex "$proto")" - len="$(printf "%04x" $((${#proto}+1)))" - alpn_extn="${len:0:2},${len:2:2},$alpn_extn" - len="$(printf "%04x" $((${#proto}+3)))" - alpn_extn="00,10,${len:0:2},${len:2:2},$alpn_extn" - tls_sockets "03" "$TLS12_CIPHER" "all+" "$alpn_extn" - if [[ -r "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" ]]; then - cp "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" $TMPFILE - else - echo "" > $TMPFILE - fi - fi - tmpstr="$(awk -F':' '/^ALPN protocol*:/ { print $2 }' $TMPFILE)" - if [[ "$tmpstr" == *"$proto" ]]; then - if ! $has_alpn_proto; then - has_alpn_proto=true - else - out ", " - fi - # only h2 is what browser need to use HTTP/2.0 and brings a security,privacy and performance benefit - if [[ "$proto" == "h2" ]]; then - pr_svrty_good "$proto" - fileout "${jsonID}_HTTP2" "OK" "$proto" - else - out "$proto" - alpn_finding+="$proto" - fi - fi - done - if $has_alpn_proto; then - outln " (offered)" - # if h2 is not the only protocol: - [[ -n "$alpn_finding" ]] && fileout "$jsonID" "INFO" "$alpn_finding" - else - outln "not offered" - fileout "$jsonID" "INFO" "not offered" - fi - tmpfile_handle ${FUNCNAME[0]}.txt - return $ret -} - -# arg1: send string -# arg2: success string: an egrep pattern -# arg3: number of loops we should read from the buffer (optional, otherwise STARTTLS_SLEEP) -starttls_io() { - local nr_waits=$STARTTLS_SLEEP - local buffer="" - local -i i - - [[ -n "$3" ]] && waitsleep=$3 - [[ -z "$2" ]] && echo "FIXME $((LINENO))" - - # If there's a sending part it's IO. Postgres sends via socket and replies via - # strings "S". So there's no I part of IO ;-) - if [[ -n "$1" ]]; then - debugme echo -en "C: $1" - echo -en "$1" >&5 - fi - - # This seems a bit dangerous but works. No blockings yet. "if=nonblock" doesn't work on BSDs - buffer="$(dd bs=512 count=1 <&5 2>/dev/null)" - - for ((i=1; i < $nr_waits; i++ )); do - [[ "$DEBUG" -ge 2 ]] && echo -en "\nS: " && echo $buffer - if [[ "$buffer" =~ $2 ]]; then - debugme echo " ---> reply matched \"$2\"" - # the fd sometimes still seem to contain chars which confuses the following TLS handshake, trying to empty: - # dd of=/dev/null bs=512 count=1 <&5 2>/dev/null - return 0 - else - # no match yet, more reading from fd helps. - buffer+=$(dd bs=512 count=1 <&5 2>/dev/null) - fi - done - return 1 -} - - -# Line-based send with newline characters appended (arg2 empty) -# Stream-based send: arg2: <any> -starttls_just_send(){ - if [[ -z "$2" ]] ; then - debugme echo -e "C: $1 plus lf" - echo -ne "$1\r\n" >&5 - else - debugme echo -e "C: $1" - echo -ne "$1" >&5 - fi - return $? -} - -# arg1: (optional): wait time -starttls_just_read(){ - local waitsleep=$STARTTLS_SLEEP - [[ -n "$1" ]] && waitsleep=$1 - if [[ "$DEBUG" -ge 2 ]]; then - echo "=== just read banner ===" - cat <&5 & - else - dd of=/dev/null count=8 <&5 2>/dev/null & - fi - wait_kill $! $waitsleep - return 0 -} - -starttls_full_read(){ - local starttls_read_data=() - local one_line="" - local ret=0 - local cont_pattern="$1" - local end_pattern="$2" - local ret_found=0 - - debugme echo "=== reading banner ... ===" - if [[ $# -ge 3 ]]; then - debugme echo "=== we'll have to search for \"$3\" pattern ===" - ret_found=3 - fi - - local oldIFS="$IFS" - IFS='' - while read -r -t $STARTTLS_SLEEP one_line; ret=$?; (exit $ret); do - debugme echo "S: ${one_line}" - if [[ $# -ge 3 ]]; then - if [[ ${one_line} =~ $3 ]]; then - ret_found=0 - debugme echo "^^^^^^^ that's what we were looking for ===" - fi - fi - starttls_read_data+=("${one_line}") - if [[ $DEBUG -ge 4 ]]; then - echo "one_line: ${one_line}" - echo "end_pattern: ${end_pattern}" - echo "cont_pattern: ${cont_pattern}" - fi - if [[ ${one_line} =~ ${end_pattern} ]]; then - debugme echo "=== full read finished ===" - IFS="${oldIFS}" - return ${ret_found} - fi - if [[ ! ${one_line} =~ ${cont_pattern} ]]; then - debugme echo "=== full read syntax error, expected regex pattern ${cont_pattern} (cont) or ${end_pattern} (end) ===" - IFS="${oldIFS}" - return 2 - fi - done <&5 - if [[ $DEBUG -ge 2 ]]; then - if [[ $ret -ge 128 ]]; then - echo "=== timeout reading ===" - else - echo "=== full read error (no timeout) ===" - fi - fi - IFS="${oldIFS}" - return $ret -} - -starttls_ftp_dialog() { - debugme echo "=== starting ftp STARTTLS dialog ===" - local reAUTHTLS='^ AUTH TLS' - starttls_full_read '^220-' '^220 ' && debugme echo "received server greeting" && - starttls_just_send 'FEAT' && debugme echo "sent FEAT" && - starttls_full_read '^(211-| )' '^211 ' "${reAUTHTLS}" && debugme echo "received server features and checked STARTTLS availability" && - starttls_just_send 'AUTH TLS' && debugme echo "initiated STARTTLS" && - starttls_full_read '^234-' '^234 ' && debugme echo "received ack for STARTTLS" - local ret=$? - debugme echo "=== finished ftp STARTTLS dialog with ${ret} ===" - return $ret -} - -# argv1: empty: SMTP, "lmtp" : LMTP -# -starttls_smtp_dialog() { - local greet_str="EHLO" - local proto="smtp" - - if [[ "$1" == lmtp ]]; then - proto="lmtp" - greet_str="LHLO" - fi - debugme echo "=== starting $proto STARTTLS dialog ===" - - local re250STARTTLS='^250[ -]STARTTLS' - starttls_full_read '^220-' '^220 ' && debugme echo "received server greeting" && - starttls_just_send "$greet_str testssl.sh" && debugme echo "sent $greet_str" && - starttls_full_read '^250-' '^250 ' "${re250STARTTLS}" && debugme echo "received server capabilities and checked STARTTLS availability" && - starttls_just_send 'STARTTLS' && debugme echo "initiated STARTTLS" && - starttls_full_read '^220-' '^220 ' && debugme echo "received ack for STARTTLS" - local ret=$? - debugme echo "=== finished $proto STARTTLS dialog with ${ret} ===" - return $ret -} - -starttls_pop3_dialog() { - debugme echo "=== starting pop3 STARTTLS dialog ===" - starttls_full_read '^\+OK' '^\+OK' && debugme echo "received server greeting" && - starttls_just_send 'STLS' && debugme echo "initiated STARTTLS" && - starttls_full_read '^\+OK' '^\+OK' && debugme echo "received ack for STARTTLS" - local ret=$? - debugme echo "=== finished pop3 STARTTLS dialog with ${ret} ===" - return $ret -} - -starttls_imap_dialog() { - debugme echo "=== starting imap STARTTLS dialog ===" - local reSTARTTLS='^\* CAPABILITY(( .*)? IMAP4rev1( .*)? STARTTLS(.*)?|( .*)? STARTTLS( .*)? IMAP4rev1(.*)?)$' - starttls_full_read '^\* ' '^\* OK ' && debugme echo "received server greeting" && - starttls_just_send 'a001 CAPABILITY' && debugme echo "sent CAPABILITY" && - starttls_full_read '^\* ' '^a001 OK ' "${reSTARTTLS}" && debugme echo "received server capabilities and checked STARTTLS availability" && - starttls_just_send 'a002 STARTTLS' && debugme echo "initiated STARTTLS" && - starttls_full_read '^\* ' '^a002 OK ' && debugme echo "received ack for STARTTLS" - local ret=$? - debugme echo "=== finished imap STARTTLS dialog with ${ret} ===" - return $ret -} - -starttls_xmpp_dialog() { - debugme echo "=== starting xmpp STARTTLS dialog ===" - [[ -z $XMPP_HOST ]] && XMPP_HOST="$NODE" - - starttls_io "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client' to='"$XMPP_HOST"' version='1.0'>" 'starttls(.*)features' 1 && - starttls_io "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>" '<proceed' 1 - local ret=$? - debugme echo "=== finished xmpp STARTTLS dialog with ${ret} ===" - return $ret -} - -starttls_nntp_dialog() { - debugme echo "=== starting nntp STARTTLS dialog ===" - starttls_full_read '$^' '^20[01] ' && debugme echo "received server greeting" && - starttls_just_send 'STARTTLS' && debugme echo "initiated STARTTLS" && - starttls_full_read '$^' '^382 ' && debugme echo "received ack for STARTTLS" - local ret=$? - debugme echo "=== finished nntp STARTTLS dialog with ${ret} ===" - return $ret -} - -starttls_postgres_dialog() { - debugme echo "=== starting postgres STARTTLS dialog ===" - local init_tls=", x00, x00 ,x00 ,x08 ,x04 ,xD2 ,x16 ,x2F" - socksend "${init_tls}" 0 && debugme echo "initiated STARTTLS" && - starttls_io "" S 1 && debugme echo "received ack (="S") for STARTTLS" - local ret=$? - debugme echo "=== finished postgres STARTTLS dialog with ${ret} ===" - return $ret -} - -starttls_mysql_dialog() { - debugme echo "=== starting mysql STARTTLS dialog ===" - local login_request=" - , x20, x00, x00, x01, # payload_length, sequence_id - x85, xae, xff, x00, # capability flags, CLIENT_SSL always set - x00, x00, x00, x01, # max-packet size - x21, # character set - x00, x00, x00, x00, x00, x00, x00, x00, # string[23] reserved (all [0]) - x00, x00, x00, x00, x00, x00, x00, x00, - x00, x00, x00, x00, x00, x00, x00" - socksend "${login_request}" 0 - starttls_just_read 1 && debugme echo "read succeeded" - # 1 is the timeout value which only MySQL needs. Note, there seems no response whether STARTTLS - # succeeded. We could try harder, see https://github.com/openssl/openssl/blob/master/apps/s_client.c - # but atm this seems sufficient as later we will fail if there's no STARTTLS. - # BUT: there seem to be cases when the handshake fails (8S01Bad handshake --> 30 38 53 30 31 42 61 64 20 68 61 6e 64 73 68 61 6b 65). - # also there's a banner in the reply "<version><somebytes>mysql_native_password" - # TODO: We could detect if the server supports STARTTLS via the "Server Capabilities" - # bit field, but we'd need to parse the binary stream, with greater precision than regex. - local ret=$? - debugme echo "=== finished mysql STARTTLS dialog with ${ret} ===" - return $ret -} - -# arg1: fd for socket -- which we don't use as it is a hassle and it is not clear whether it works under every bash version -# returns 6 if opening the socket caused a problem, 1 if STARTTLS handshake failed, 0: all ok -# -fd_socket() { - local jabber="" - local proyxline="" - local nodeip="$(tr -d '[]' <<< $NODEIP)" # sockets do not need the square brackets we have of IPv6 addresses - # we just need do it here, that's all! - if [[ -t 5 ]]; then - pr_warning "$PROG_NAME: unable to open a socket because of a tty conflict" - return 6 - fi - if [[ -n "$PROXY" ]]; then - # PROXYNODE works better than PROXYIP on modern versions of squid - if ! exec 5<> /dev/tcp/${PROXYNODE}/${PROXYPORT}; then - outln - pr_warning "$PROG_NAME: unable to open a socket to proxy $PROXYNODE:$PROXYPORT" - return 6 - fi - if "$DNS_VIA_PROXY"; then - printf -- "%b" "CONNECT $NODE:$PORT HTTP/1.0\n\n" >&5 - else - printf -- "%b" "CONNECT $nodeip:$PORT HTTP/1.0\n\n" >&5 - fi - while true; do - read -t $PROXY_WAIT -r proyxline <&5 - if [[ $? -ge 128 ]]; then - pr_warning "Proxy timed out. Unable to CONNECT via proxy. " - close_socket - return 6 - elif [[ "${proyxline%/*}" == HTTP ]]; then - proyxline=${proyxline#* } - if [[ "${proyxline%% *}" != 200 ]]; then - pr_warning "Unable to CONNECT via proxy. " - [[ "$PORT" != 443 ]] && prln_warning "Check whether your proxy supports port $PORT and the underlying protocol." - close_socket - return 6 - fi - fi - if [[ "$proyxline" == $'\r' ]] || [[ -z "$proyxline" ]] ; then - break - fi - done - # For the following execs: 2>/dev/null would remove a potential error message, but disables debugging. - # First we check whether a socket connect timeout was specified - elif [[ -n "$CONNECT_TIMEOUT" ]]; then - if ! $TIMEOUT_CMD $CONNECT_TIMEOUT bash -c "exec 5<>/dev/tcp/$nodeip/$PORT"; then - ((NR_SOCKET_FAIL++)) - connectivity_problem $NR_SOCKET_FAIL $MAX_SOCKET_FAIL "TCP connect problem" "repeated TCP connect problems (connect timeout), giving up" - outln - pr_warning "Unable to open a socket to $NODEIP:$PORT. " - return 6 - fi - # Now comes the the usual case - elif ! exec 5<>/dev/tcp/$nodeip/$PORT; then - ((NR_SOCKET_FAIL++)) - connectivity_problem $NR_SOCKET_FAIL $MAX_SOCKET_FAIL "TCP connect problem" "repeated TCP connect problems, giving up" - outln - pr_warning "Unable to open a socket to $NODEIP:$PORT. " - return 6 - fi - - if [[ -n "$STARTTLS" ]]; then - case "$STARTTLS_PROTOCOL" in # port - ftp|ftps) # https://tools.ietf.org/html/rfc4217, https://tools.ietf.org/html/rfc959 - starttls_ftp_dialog - ;; - smtp|smtps) # SMTP, see https://tools.ietf.org/html/rfc{2033,3207,5321} - starttls_smtp_dialog - ;; - lmtp|lmtps) # LMTP, see https://tools.ietf.org/html/rfc{2033,3207,5321} - starttls_smtp_dialog lmtp - ;; - pop3|pop3s) # POP, see https://tools.ietf.org/html/rfc2595 - starttls_pop3_dialog - ;; - nntp|nntps) # NNTP, see https://tools.ietf.org/html/rfc4642 - starttls_nntp_dialog - ;; - imap|imaps) # IMAP, https://tools.ietf.org/html/rfc2595, https://tools.ietf.org/html/rfc3501 - starttls_imap_dialog - ;; - irc|ircs) # IRC, https://ircv3.net/specs/extensions/tls-3.1.html, https://ircv3.net/specs/core/capability-negotiation.html - fatal "FIXME: IRC+STARTTLS not yet supported" $ERR_NOSUPPORT - ;; - ldap|ldaps) # LDAP, https://tools.ietf.org/html/rfc2830, https://tools.ietf.org/html/rfc4511 - fatal "FIXME: LDAP+STARTTLS over sockets not supported yet (try \"--ssl-native\")" $ERR_NOSUPPORT - ;; - acap|acaps) # ACAP = Application Configuration Access Protocol, see https://tools.ietf.org/html/rfc2595 - fatal "ACAP Easteregg: not implemented -- probably never will" $ERR_NOSUPPORT - ;; - xmpp|xmpps) # XMPP, see https://tools.ietf.org/html/rfc6120 - starttls_xmpp_dialog - # IM observatory: https://xmpp.net , XMPP server directory: https://xmpp.net/directory.php - ;; - postgres) # Postgres SQL, see https://www.postgresql.org/docs/devel/static/protocol-message-formats.html - starttls_postgres_dialog - ;; - mysql) # MySQL, see https://dev.mysql.com/doc/internals/en/x-protocol-lifecycle-lifecycle.html#x-protocol-lifecycle-tls-extension - starttls_mysql_dialog - ;; - *) # we need to throw an error here -- otherwise testssl.sh treats the STARTTLS protocol as plain SSL/TLS which leads to FP - fatal "FIXME: STARTTLS protocol $STARTTLS_PROTOCOL is not yet supported" $ERR_NOSUPPORT - esac - fi - [[ $? -eq 0 ]] && return 0 - prln_warning " STARTTLS handshake failed" - return 1 -} - -close_socket(){ - exec 5<&- - exec 5>&- - return 0 -} - -send_close_notify() { - local detected_tlsversion="$1" - - debugme echo "sending close_notify..." - if [[ $detected_tlsversion == 0300 ]]; then - socksend ",x15, x03, x00, x00, x02, x02, x00" 0 - else - socksend ",x15, x03, x01, x00, x02, x02, x00" 0 - fi -} - -# Format string properly for socket -# ARG1: any commented sequence of two bytes hex, separated by commas. It can contain comments, new lines, tabs and white spaces -# NW_STR holds the global with the string prepared for printf, like '\x16\x03\x03\' -code2network() { - NW_STR=$(sed -e 's/,/\\\x/g' <<< "$1" | sed -e 's/# .*$//g' -e 's/ //g' -e '/^$/d' | tr -d '\n' | tr -d '\t') -} - -# sockets inspired by https://blog.chris007.de/using-bash-for-network-socket-operation/ -# ARG1: hexbytes separated by commas, with a leading comma -# ARG2: seconds to sleep -socksend_clienthello() { - local data="" - - code2network "$1" - data="$NW_STR" - [[ "$DEBUG" -ge 4 ]] && echo && echo "\"$data\"" - if [[ -z "$PRINTF" ]] ;then - # We could also use "dd ibs=1M obs=1M" here but is seems to be at max 3% slower - printf -- "$data" | cat >&5 2>/dev/null & - else - $PRINTF -- "$data" 2>/dev/null >&5 2>/dev/null & - fi - sleep $USLEEP_SND -} - - -# ARG1: hexbytes -- preceded by x -- separated by commas, with a leading comma -# ARG2: seconds to sleep -socksend() { - local data line - - # read line per line and strip comments (bash internal func can't handle multiline statements - data="$(while read line; do - printf "${line%%\#*}" - done <<< "$1" )" - data="${data// /}" # strip ' ' - data="${data//,/\\}" # s&r , by \ - [[ $DEBUG -ge 4 ]] && echo && echo "\"$data\"" - if [[ -z "$PRINTF" ]] ;then - printf -- "$data" | cat >&5 2>/dev/null & - else - $PRINTF -- "$data" 2>/dev/null >&5 2>/dev/null & - fi - sleep $2 -} - - -# for SSLv2 to TLS 1.2: -# ARG1: blocksize for reading -sockread_serverhello() { - [[ -z "$2" ]] && maxsleep=$MAX_WAITSOCK || maxsleep=$2 - SOCK_REPLY_FILE=$(mktemp $TEMPDIR/ddreply.XXXXXX) || return 7 - dd bs=$1 of=$SOCK_REPLY_FILE count=1 <&5 2>/dev/null & - wait_kill $! $maxsleep - return $? -} - -#trying a faster version -# ARG1: blocksize for reading -sockread_fast() { - dd bs=$1 count=1 <&5 2>/dev/null | hexdump -v -e '16/1 "%02X"' -} - -len2twobytes() { - local len_arg1=${#1} - [[ $len_arg1 -le 2 ]] && LEN_STR=$(printf "00, %02s \n" "$1") - [[ $len_arg1 -eq 3 ]] && LEN_STR=$(printf "0%s, %02s \n" "${1:0:1}" "${1:1:2}") - [[ $len_arg1 -eq 4 ]] && LEN_STR=$(printf "%02s, %02s \n" "${1:0:2}" "${1:2:2}") -} - - -get_pub_key_size() { - local pubkey pubkeybits - local -i i len1 len - - "$HAS_PKEY" || return 1 - - # OpenSSL displays the number of bits for RSA and ECC - pubkeybits=$($OPENSSL x509 -noout -pubkey -in $HOSTCERT 2>>$ERRFILE | $OPENSSL pkey -pubin -text_pub 2>>$ERRFILE | awk -F'(' '/Public-Key/ { print $2 }') - if [[ -n $pubkeybits ]]; then - # remainder e.g. "256 bit)" - pubkeybits="${pubkeybits//\)/}" - echo "Server public key is $pubkeybits" >> $TMPFILE - else - # This extracts the public key for DSA, DH, and GOST - pubkey=$($OPENSSL x509 -noout -pubkey -in $HOSTCERT 2>>$ERRFILE | $OPENSSL pkey -pubin -outform DER 2>>$ERRFILE | hexdump -v -e '16/1 "%02X"') - [[ -z "$pubkey" ]] && return 1 - # Skip over tag and length of subjectPublicKeyInfo - i=2 - len1="0x${pubkey:i:2}" - if [[ $len1 -lt 0x80 ]]; then - i=$i+2 - else - len1=$len1-0x80 - i=$i+2*$len1+2 - fi - - # Skip over algorithm field - i=$i+2 - len1="0x${pubkey:i:2}" - i=$i+2 - if [[ $len1 -lt 0x80 ]]; then - i=$i+2*$len1 - else - case $len1 in - 129) len="0x${pubkey:i:2}" ;; - 130) len="0x${pubkey:i:2}" - i=$i+2 - len=256*$len+"0x${pubkey:i:2}" - ;; - 131) len="0x${pubkey:i:2}" - i=$i+2 - len=256*$len+"0x${pubkey:i:2}" - i=$i+2 - len=256*$len+"0x${pubkey:i:2}" - ;; - 132) len="0x${pubkey:i:2}" - i=$i+2 - len=256*$len+"0x${pubkey:i:2}" - i=$i+2 - len=256*$len+"0x${pubkey:i:2}" - i=$i+2 - len=256*$len+"0x${pubkey:i:2}" - ;; - esac - i=$i+2+2*$len - fi - - # Next is the public key BIT STRING. Skip over tag, length, and number of unused bits. - i=$i+2 - len1="0x${pubkey:i:2}" - if [[ $len1 -lt 0x80 ]]; then - i=$i+4 - else - len1=$len1-0x80 - i=$i+2*$len1+4 - fi - - # Now get the length of the public key - i=$i+2 - len1="0x${pubkey:i:2}" - i=$i+2 - if [[ $len1 -lt 0x80 ]]; then - len=$len1 - else - case $len1 in - 129) len="0x${pubkey:i:2}" ;; - 130) len="0x${pubkey:i:2}" - i=$i+2 - len=256*$len+"0x${pubkey:i:2}" - ;; - 131) len="0x${pubkey:i:2}" - i=$i+2 - len=256*$len+"0x${pubkey:i:2}" - i=$i+2 - len=256*$len+"0x${pubkey:i:2}" - ;; - 132) len="0x${pubkey:i:2}" - i=$i+2 - len=256*"0x${pubkey:i:2}" - i=$i+2 - len=256*"0x${pubkey:i:2}" - i=$i+2 - len=256*"0x${pubkey:i:2}" - ;; - esac - fi - len=8*$len # convert from bytes to bits - pubkeybits="$(printf "%d" $len)" - echo "Server public key is $pubkeybits bit" >> $TMPFILE - fi - return 0 -} - -# Extract the DH ephemeral key from the ServerKeyExchange message -get_dh_ephemeralkey() { - local tls_serverkeyexchange_ascii="$1" - local -i tls_serverkeyexchange_ascii_len offset - local dh_p dh_g dh_y dh_param len1 key_bitstring - local -i i dh_p_len dh_g_len dh_y_len dh_param_len - - "$HAS_PKEY" || return 1 - - tls_serverkeyexchange_ascii_len=${#tls_serverkeyexchange_ascii} - dh_p_len=2*$(hex2dec "${tls_serverkeyexchange_ascii:0:4}") - offset=4+$dh_p_len - if [[ $tls_serverkeyexchange_ascii_len -lt $offset ]]; then - debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." - return 1 - fi - - # Subtract any leading 0 bytes - for (( i=4; i < offset; i=i+2 )); do - [[ "${tls_serverkeyexchange_ascii:i:2}" != "00" ]] && break - dh_p_len=$dh_p_len-2 - done - if [[ $i -ge $offset ]]; then - debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." - return 1 - fi - dh_p="${tls_serverkeyexchange_ascii:i:dh_p_len}" - - dh_g_len=2*$(hex2dec "${tls_serverkeyexchange_ascii:offset:4}") - i=4+$offset - offset+=4+$dh_g_len - if [[ $tls_serverkeyexchange_ascii_len -lt $offset ]]; then - debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." - return 1 - fi - # Subtract any leading 0 bytes - for (( 1; i < offset; i=i+2 )); do - [[ "${tls_serverkeyexchange_ascii:i:2}" != "00" ]] && break - dh_g_len=$dh_g_len-2 - done - if [[ $i -ge $offset ]]; then - debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." - return 1 - fi - dh_g="${tls_serverkeyexchange_ascii:i:dh_g_len}" - - dh_y_len=2*$(hex2dec "${tls_serverkeyexchange_ascii:offset:4}") - i=4+$offset - offset+=4+$dh_y_len - if [[ $tls_serverkeyexchange_ascii_len -lt $offset ]]; then - debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." - return 1 - fi - # Subtract any leading 0 bytes - for (( 1; i < offset; i=i+2 )); do - [[ "${tls_serverkeyexchange_ascii:i:2}" != "00" ]] && break - dh_y_len=$dh_y_len-2 - done - if [[ $i -ge $offset ]]; then - debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." - return 1 - fi - dh_y="${tls_serverkeyexchange_ascii:i:dh_y_len}" - - # The following code assumes that all lengths can be encoded using at most 2 bytes, - # which just means that the encoded length of the public key must be less than - # 65,536 bytes. If the length is anywhere close to that, it is almost certainly an - # encoding error. - if [[ $dh_p_len+$dh_g_len+$dh_y_len -ge 131000 ]]; then - debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." - return 1 - fi - # make ASN.1 INTEGER of p, g, and Y - [[ "0x${dh_p:0:1}" -ge 8 ]] && dh_p_len+=2 && dh_p="00$dh_p" - if [[ $dh_p_len -lt 256 ]]; then - len1="$(printf "%02x" $((dh_p_len/2)))" - elif [[ $dh_p_len -lt 512 ]]; then - len1="81$(printf "%02x" $((dh_p_len/2)))" - else - len1="82$(printf "%04x" $((dh_p_len/2)))" - fi - dh_p="02${len1}$dh_p" - - [[ "0x${dh_g:0:1}" -ge 8 ]] && dh_g_len+=2 && dh_g="00$dh_g" - if [[ $dh_g_len -lt 256 ]]; then - len1="$(printf "%02x" $((dh_g_len/2)))" - elif [[ $dh_g_len -lt 512 ]]; then - len1="81$(printf "%02x" $((dh_g_len/2)))" - else - len1="82$(printf "%04x" $((dh_g_len/2)))" - fi - dh_g="02${len1}$dh_g" - - [[ "0x${dh_y:0:1}" -ge 8 ]] && dh_y_len+=2 && dh_y="00$dh_y" - if [[ $dh_y_len -lt 256 ]]; then - len1="$(printf "%02x" $((dh_y_len/2)))" - elif [[ $dh_y_len -lt 512 ]]; then - len1="81$(printf "%02x" $((dh_y_len/2)))" - else - len1="82$(printf "%04x" $((dh_y_len/2)))" - fi - dh_y="02${len1}$dh_y" - - # Make a SEQUENCE of p and g - dh_param_len=${#dh_p}+${#dh_g} - if [[ $dh_param_len -lt 256 ]]; then - len1="$(printf "%02x" $((dh_param_len/2)))" - elif [[ $dh_param_len -lt 512 ]]; then - len1="81$(printf "%02x" $((dh_param_len/2)))" - else - len1="82$(printf "%04x" $((dh_param_len/2)))" - fi - dh_param="30${len1}${dh_p}${dh_g}" - - # Make a SEQUENCE of the parameters SEQUENCE and the OID - dh_param_len=22+${#dh_param} - if [[ $dh_param_len -lt 256 ]]; then - len1="$(printf "%02x" $((dh_param_len/2)))" - elif [[ $dh_param_len -lt 512 ]]; then - len1="81$(printf "%02x" $((dh_param_len/2)))" - else - len1="82$(printf "%04x" $((dh_param_len/2)))" - fi - dh_param="30${len1}06092A864886F70D010301${dh_param}" - - # Encapsulate public key, y, in a BIT STRING - dh_y_len=${#dh_y}+2 - if [[ $dh_y_len -lt 256 ]]; then - len1="$(printf "%02x" $((dh_y_len/2)))" - elif [[ $dh_y_len -lt 512 ]]; then - len1="81$(printf "%02x" $((dh_y_len/2)))" - else - len1="82$(printf "%04x" $((dh_y_len/2)))" - fi - dh_y="03${len1}00$dh_y" - - # Create the public key SEQUENCE - i=${#dh_param}+${#dh_y} - if [[ $i -lt 256 ]]; then - len1="$(printf "%02x" $((i/2)))" - elif [[ $i -lt 512 ]]; then - len1="81$(printf "%02x" $((i/2)))" - else - len1="82$(printf "%04x" $((i/2)))" - fi - key_bitstring="30${len1}${dh_param}${dh_y}" - key_bitstring="$(asciihex_to_binary "$key_bitstring" | $OPENSSL pkey -pubin -inform DER 2> $ERRFILE)" - [[ -z "$key_bitstring" ]] && return 1 - tm_out "$key_bitstring" - return 0 -} - -# arg1: name of file with socket reply -# arg2: true if entire server hello should be parsed -# return values: 0=no SSLv2 (reset) -# 1=no SSLv2 (plaintext reply like it happens with OLS webservers) -# 3=SSLv2 supported (in $TEMPDIR/$NODEIP.sslv2_sockets.dd is reply for further processing -# --> there could be checked whether ciphers e.g have been returned at all (or anything else) -# 4=looks like an STARTTLS 5xx message -# 6=socket couldn't be opened -# 7=strange reply we can't deal with -parse_sslv2_serverhello() { - local ret v2_hello_ascii v2_hello_initbyte v2_hello_length - local v2_hello_handshake v2_cert_type v2_hello_cert_length - local v2_hello_cipherspec_length - local -i certificate_len nr_ciphers_detected offset i - local ret=3 - local parse_complete="false" - # SSLv2 server hello: in hex representation, see below - # byte 1+2: length of server hello 0123 - # 3: 04=Handshake message, server hello 45 - # 4: session id hit or not (boolean: 00=false, this 67 - # is the normal case) - # 5: certificate type, 01 = x509 89 - # 6+7 version (00 02 = SSLv2) 10-13 - # 8+9 certificate length 14-17 - # 10+11 cipher spec length 17-20 - # 12+13 connection id length - # [certificate length] ==> certificate - # [cipher spec length] ==> ciphers GOOD: HERE ARE ALL CIPHERS ALREADY! - - # Note: recent SSL/TLS stacks reply with a TLS alert on a SSLv2 client hello. - # The TLS error message is different and could be used for fingerprinting. - - if [[ "$2" == "true" ]]; then - parse_complete=true - fi - "$parse_complete" && echo "======================================" > $TMPFILE - - v2_hello_ascii=$(hexdump -v -e '16/1 "%02X"' $1) - v2_hello_ascii="${v2_hello_ascii%%[!0-9A-F]*}" - [[ "$DEBUG" -ge 5 ]] && echo "$v2_hello_ascii" - if [[ -z "$v2_hello_ascii" ]]; then - ret=0 # 1 line without any blanks: no server hello received - debugme echo "server hello empty" - else - # now scrape two bytes out of the reply per byte - v2_hello_initbyte="${v2_hello_ascii:0:1}" # normally this belongs to the next, should be 8! - v2_hello_length="${v2_hello_ascii:1:3}" # + 0x8000 see above - v2_hello_handshake="${v2_hello_ascii:4:2}" - v2_cert_type="${v2_hello_ascii:8:2}" - v2_hello_cert_length="${v2_hello_ascii:14:4}" - v2_hello_cipherspec_length="${v2_hello_ascii:18:4}" - - V2_HELLO_CIPHERSPEC_LENGTH=$(printf "%d\n" "0x$v2_hello_cipherspec_length" 2>/dev/null) - [[ $? -ne 0 ]] && ret=7 - - if [[ "${v2_hello_ascii:0:2}" == "35" ]] && "$do_starttls"; then - # this could be a 500/5xx for some weird reason where the STARTTLS handshake failed - debugme echo "$(hex2ascii "$v2_hello_ascii")" - ret=4 - elif [[ "${v2_hello_ascii:0:4}" == "1503" ]]; then - # Cloudflare does this, OpenSSL 1.1.1 and picoTLS. With different alert messages - # Just in case somebody's interested in the exact error, we deliver it ;-) - debugme echo -n ">TLS< alert message discovered: ${v2_hello_ascii} " - case "${v2_hello_ascii:10:2}" in - 01) debugme echo "(01/warning: 0x"${v2_hello_ascii:12:2}"/$(tls_alert "${v2_hello_ascii:12:2}"))" ;; - 02) debugme echo "(02/fatal: 0x"${v2_hello_ascii:12:2}"/$(tls_alert "${v2_hello_ascii:12:2}"))" ;; - *) debugme echo "("${v2_hello_ascii:10:2}" : "${v2_hello_ascii:12:2}"))" ;; - esac - ret=0 - elif [[ $v2_hello_initbyte != "8" ]] || [[ $v2_hello_handshake != "04" ]]; then - ret=1 - if [[ $DEBUG -ge 2 ]]; then - echo "no correct server hello" - echo "SSLv2 server init byte: 0x0$v2_hello_initbyte" - echo "SSLv2 hello handshake : 0x$v2_hello_handshake" - fi - fi - - if [[ $DEBUG -ge 3 ]]; then - echo "SSLv2 server hello length: 0x0$v2_hello_length" - echo "SSLv2 certificate type: 0x$v2_cert_type" - echo "SSLv2 certificate length: 0x$v2_hello_cert_length" - echo "SSLv2 cipher spec length: 0x$v2_hello_cipherspec_length" - fi - - if "$parse_complete" && [[ 2*$(hex2dec "$v2_hello_length") -ne ${#v2_hello_ascii}-4 ]]; then - ret=7 - fi - fi - - "$parse_complete" || return $ret - - # not sure why we need this - rm -f $HOSTCERT - > $TEMPDIR/intermediatecerts.pem - if [[ $ret -eq 3 ]]; then - certificate_len=2*$(hex2dec "$v2_hello_cert_length") - - if [[ "$v2_cert_type" == "01" ]] && [[ "$v2_hello_cert_length" != "00" ]]; then - asciihex_to_binary "${v2_hello_ascii:26:certificate_len}" | \ - $OPENSSL x509 -inform DER -outform PEM -out $HOSTCERT 2>$ERRFILE - if [[ $? -ne 0 ]]; then - debugme echo "Malformed certificate in ServerHello." - return 1 - fi - get_pub_key_size - echo "======================================" >> $TMPFILE - fi - - # Output list of supported ciphers - offset=$((certificate_len+26)) - nr_ciphers_detected=$((V2_HELLO_CIPHERSPEC_LENGTH / 3)) - for (( i=0 ; i<nr_ciphers_detected; i++ )); do - echo "Supported cipher: x$(tolower "${v2_hello_ascii:offset:6}")" >> $TMPFILE - offset=$((offset+6)) - done - echo "======================================" >> $TMPFILE - - tmpfile_handle ${FUNCNAME[0]}.txt - fi - return $ret -} - -# arg1: hash function -# arg2: key -# arg3: text -hmac() { - local hash_fn="$1" - local key="$2" text="$3" output - local -i ret - - if [[ ! "$OSSL_NAME" =~ LibreSSL ]] && [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 3.0.0* ]]; then - output="$(asciihex_to_binary "$text" | $OPENSSL mac -macopt digest:"${hash_fn/-/}" -macopt hexkey:"$key" HMAC 2>/dev/null)" - ret=$? - tm_out "$(strip_lf "$output")" - else - output="$(asciihex_to_binary "$text" | $OPENSSL dgst "$hash_fn" -mac HMAC -macopt hexkey:"$key" 2>/dev/null)" - ret=$? - tm_out "$(awk '/=/ { print $2 }' <<< "$output")" - fi - return $ret -} - -# arg1: hash function -# arg2: pseudorandom key (PRK) -# arg2: info -# arg3: length of output keying material in octets -# See RFC 5869, Section 2.3 -hkdf-expand() { - local hash_fn="$1" - local prk="$2" info="$3" output="" - local -i out_len="$4" - local -i i n mod_check hash_len ret - local counter - local ti tim1 # T(i) and T(i-1) - - case "$hash_fn" in - "-sha256") hash_len=32 ;; - "-sha384") hash_len=48 ;; - *) return 7 - esac - - n=$out_len/$hash_len - mod_check=$out_len%$hash_len - [[ $mod_check -ne 0 ]] && n+=1 - - tim1="" - for (( i=1; i <= n; i++ )); do - counter="$(printf "%02X\n" $i)" - ti="$(hmac "$hash_fn" "$prk" "$tim1$info$counter")" - [[ $? -ne 0 ]] && return 7 - output+="$ti" - tim1="$ti" - done - out_len=2*$out_len - tm_out "${output:0:out_len}" - return 0 -} - -# arg1: hash function -# arg2: secret -# arg3: label -# arg4: context -# arg5: length -# See RFC 8446, Section 7.1 -hkdf-expand-label() { - local hash_fn="$1" - local secret="$2" label="$3" - local context="$4" - local -i length="$5" - local hkdflabel hkdflabel_label hkdflabel_context - local hkdflabel_length - local -i len - - hkdflabel_length="$(printf "%04X\n" $length)" - if [[ "${TLS_SERVER_HELLO:8:2}" == "7F" ]] && [[ 0x${TLS_SERVER_HELLO:10:2} -lt 0x14 ]]; then - # "544c5320312e332c20" = "TLS 1.3, " - hkdflabel_label="544c5320312e332c20$label" - else - # "746c73313320" = "tls13 " - hkdflabel_label="746c73313320$label" - fi - len=${#hkdflabel_label}/2 - hkdflabel_label="$(printf "%02X\n" $len)$hkdflabel_label" - len=${#context}/2 - hkdflabel_context="$(printf "%02X\n" $len)$context" - hkdflabel="$hkdflabel_length$hkdflabel_label$hkdflabel_context" - - hkdf-expand "$hash_fn" "$secret" "$hkdflabel" "$length" - return $? -} - -# arg1: hash function -# arg2: secret -# arg3: label -# arg4: ASCII-HEX of messages -# See RFC 8446, Section 7.1 -derive-secret() { - local hash_fn="$1" - local secret="$2" label="$3" messages="$4" - local hash_messages - local -i hash_len retcode - - case "$hash_fn" in - "-sha256") hash_len=32 ;; - "-sha384") hash_len=48 ;; - *) return 7 - esac - - hash_messages="$(asciihex_to_binary "$messages" | $OPENSSL dgst "$hash_fn" 2>/dev/null | awk '/=/ { print $2 }')" - hkdf-expand-label "$hash_fn" "$secret" "$label" "$hash_messages" "$hash_len" - return $? -} - -# arg1: hash function -# arg2: private key file -# arg3: file containing server's ephemeral public key -# arg4: ASCII-HEX of messages (ClientHello...ServerHello) -# See key derivation schedule diagram in Section 7.1 of RFC 8446 -derive-handshake-traffic-secret() { - local hash_fn="$1" - local priv_file="$2" pub_file="$3" - local messages="$4" - local -i i ret - local early_secret derived_secret shared_secret handshake_secret - - "$HAS_PKUTIL" || return 1 - - # early_secret="$(hmac "$hash_fn" "000...000" "000...000")" - case "$hash_fn" in - "-sha256") early_secret="33ad0a1c607ec03b09e6cd9893680ce210adf300aa1f2660e1b22e10f170f92a" - if [[ "${TLS_SERVER_HELLO:8:2}" == "7F" ]] && [[ 0x${TLS_SERVER_HELLO:10:2} -lt 0x14 ]]; then - # "6465726976656420736563726574" = "derived secret" - # derived_secret="$(derive-secret "$hash_fn" "$early_secret" "6465726976656420736563726574" "")" - derived_secret="c1c0c36bf8fb1d1afa949fbd360e71af69a6244a4c2eaef5bbbb6442a7277d2c" - else - # "64657269766564" = "derived" - # derived_secret="$(derive-secret "$hash_fn" "$early_secret" "64657269766564" "")" - derived_secret="6f2615a108c702c5678f54fc9dbab69716c076189c48250cebeac3576c3611ba" - fi - ;; - "-sha384") early_secret="7ee8206f5570023e6dc7519eb1073bc4e791ad37b5c382aa10ba18e2357e716971f9362f2c2fe2a76bfd78dfec4ea9b5" - if [[ "${TLS_SERVER_HELLO:8:2}" == "7F" ]] && [[ 0x${TLS_SERVER_HELLO:10:2} -lt 0x14 ]]; then - # "6465726976656420736563726574" = "derived secret" - # derived_secret="$(derive-secret "$hash_fn" "$early_secret" "6465726976656420736563726574" "")" - derived_secret="54c80fa05ee9e0532ce3db8ddeca37a0365683bcd3b27bdc88d2b9fdc115ca4ebc8edc1f0b72a6a0861e803fc34761ef" - else - # "64657269766564" = "derived" - # derived_secret="$(derive-secret "$hash_fn" "$early_secret" "64657269766564" "")" - derived_secret="1591dac5cbbf0330a4a84de9c753330e92d01f0a88214b4464972fd668049e93e52f2b16fad922fdc0584478428f282b" - fi - ;; - *) return 7 - esac - - shared_secret="$($OPENSSL pkeyutl -derive -inkey "$priv_file" -peerkey "$pub_file" 2>/dev/null | hexdump -v -e '16/1 "%02X"')" - - # For draft 18 use $early_secret rather than $derived_secret. - if [[ "${TLS_SERVER_HELLO:8:4}" == "7F12" ]]; then - handshake_secret="$(hmac "$hash_fn" "$early_secret" "${shared_secret%%[!0-9A-F]*}")" - else - handshake_secret="$(hmac "$hash_fn" "$derived_secret" "${shared_secret%%[!0-9A-F]*}")" - fi - [[ $? -ne 0 ]] && return 7 - - if [[ "${TLS_SERVER_HELLO:8:2}" == "7F" ]] && [[ 0x${TLS_SERVER_HELLO:10:2} -lt 0x14 ]]; then - # "7365727665722068616e647368616b65207472616666696320736563726574" = "server handshake traffic secret" - derived_secret="$(derive-secret "$hash_fn" "$handshake_secret" "7365727665722068616e647368616b65207472616666696320736563726574" "$messages")" - else - # "732068732074726166666963" = "s hs traffic" - derived_secret="$(derive-secret "$hash_fn" "$handshake_secret" "732068732074726166666963" "$messages")" - fi - [[ $? -ne 0 ]] && return 7 - tm_out "$derived_secret" - return 0 -} - -# arg1: hash function -# arg2: secret (created by derive-handshake-traffic-secret) -# arg3: purpose ("key" or "iv") -# arg4: length of the key -# See RFC 8446, Section 7.3 -derive-traffic-key() { - local hash_fn="$1" - local secret="$2" purpose="$3" - local -i key_length="$4" - local key - - key="$(hkdf-expand-label "$hash_fn" "$secret" "$purpose" "" "$key_length")" - [[ $? -ne 0 ]] && return 7 - tm_out "$key" - return 0 -} - -#arg1: TLS cipher -#arg2: file containing cipher name, public key, and private key -#arg3: First ClientHello, if response was a HelloRetryRequest -#arg4: HelloRetryRequest, if one was sent -#arg5: Final (or only) ClientHello -#arg6: ServerHello -derive-handshake-traffic-keys() { - local cipher="$1" - local tmpfile="$2" - local clienthello1="$3" hrr="$4" clienthello2="$5" serverhello="$6" - local hash_clienthello1 - local -i key_len - local -i retcode - local hash_fn - local pub_file priv_file tmpfile - local derived_secret server_write_key server_write_iv - - if [[ "$cipher" == *SHA256 ]]; then - hash_fn="-sha256" - elif [[ "$cipher" == *SHA384 ]]; then - hash_fn="-sha384" - else - return 1 - fi - if [[ "$cipher" == *AES_128* ]]; then - key_len=16 - elif ( [[ "$cipher" == *AES_256* ]] || [[ "$cipher" == *CHACHA20_POLY1305* ]] ); then - key_len=32 - else - return 1 - fi - pub_file="$(mktemp "$TEMPDIR/pubkey.XXXXXX")" || return 7 - awk '/-----BEGIN PUBLIC KEY/,/-----END PUBLIC KEY/ { print $0 }' \ - "$tmpfile" > "$pub_file" - [[ ! -s "$pub_file" ]] && return 1 - - priv_file="$(mktemp "$TEMPDIR/privkey.XXXXXX")" || return 7 - if grep -q "\-\-\-\-\-BEGIN EC PARAMETERS" "$tmpfile"; then - awk '/-----BEGIN EC PARAMETERS/,/-----END EC PRIVATE KEY/ { print $0 }' \ - "$tmpfile" > "$priv_file" - else - awk '/-----BEGIN PRIVATE KEY/,/-----END PRIVATE KEY/ { print $0 }' \ - "$tmpfile" > "$priv_file" - fi - [[ ! -s "$priv_file" ]] && return 1 - - if [[ -n "$hrr" ]] && [[ "${serverhello:8:4}" == "7F12" ]]; then - derived_secret="$(derive-handshake-traffic-secret "$hash_fn" "$priv_file" "$pub_file" "$clienthello1$hrr$clienthello2$serverhello")" - elif [[ -n "$hrr" ]]; then - hash_clienthello1="$(asciihex_to_binary "$clienthello1" | $OPENSSL dgst "$hash_fn" 2>/dev/null | awk '/=/ { print $2 }')" - derived_secret="$(derive-handshake-traffic-secret "$hash_fn" "$priv_file" "$pub_file" "FE0000$(printf "%02x" $((${#hash_clienthello1}/2)))$hash_clienthello1$hrr$clienthello2$serverhello")" - else - derived_secret="$(derive-handshake-traffic-secret "$hash_fn" "$priv_file" "$pub_file" "$clienthello2$serverhello")" - fi - retcode=$? - rm $pub_file $priv_file - [[ $retcode -ne 0 ]] && return 1 - # "6b6579" = "key" - server_write_key="$(derive-traffic-key "$hash_fn" "$derived_secret" "6b6579" "$key_len")" - [[ $? -ne 0 ]] && return 1 - # "6976" = "iv" - server_write_iv="$(derive-traffic-key "$hash_fn" "$derived_secret" "6976" "12")" - [[ $? -ne 0 ]] && return 1 - tm_out "$server_write_key $server_write_iv" - return 0 -} - -generate-ccm-gcm-keystream() { - local icb="$1" icb_msb icb_lsb1 - local -i i icb_lsb n="$2" - - icb_msb="${icb:0:24}" - icb_lsb=0x${icb:24:8} - - for (( i=0; i < n; i=i+1 )); do - icb_lsb1="$(printf "%08X" $icb_lsb)" - printf "\x${icb_msb:0:2}\x${icb_msb:2:2}\x${icb_msb:4:2}\x${icb_msb:6:2}\x${icb_msb:8:2}\x${icb_msb:10:2}\x${icb_msb:12:2}\x${icb_msb:14:2}\x${icb_msb:16:2}\x${icb_msb:18:2}\x${icb_msb:20:2}\x${icb_msb:22:2}\x${icb_lsb1:0:2}\x${icb_lsb1:2:2}\x${icb_lsb1:4:2}\x${icb_lsb1:6:2}" - icb_lsb+=1 - done - return 0 -} - -# arg1: an OpenSSL ecb cipher (e.g., -aes-128-ecb) -# arg2: key -# arg3: initial counter value (must be 128 bits) -# arg4: ciphertext -# See Sections 6.5 and 7.2 of SP 800-38D and Section 6.2 and Appendix A of SP 800-38C -ccm-gcm-decrypt() { - local cipher="$1" - local key="$2" - local icb="$3" - local ciphertext="$4" - local -i i i1 i2 i3 i4 - local -i ciphertext_len n mod_check - local y plaintext="" - - [[ ${#icb} -ne 32 ]] && return 7 - - ciphertext_len=${#ciphertext} - n=$ciphertext_len/32 - mod_check=$ciphertext_len%32 - [[ $mod_check -ne 0 ]] && n+=1 - y="$(generate-ccm-gcm-keystream "$icb" "$n" | $OPENSSL enc "$cipher" -K "$key" -nopad 2>/dev/null | hexdump -v -e '16/1 "%02X"')" - - # XOR the ciphertext with the keystream ($y). For efficiency, work in blocks of 16 bytes at a time (but with each XOR operation working on - # 32 bits. - [[ $mod_check -ne 0 ]] && n=$n-1 - for (( i=0; i < n; i++ )); do - i1=32*$i; i2=$i1+8; i3=$i1+16; i4=$i1+24 - plaintext+="$(printf "%08X%08X%08X%08X" "$((0x${ciphertext:i1:8} ^ 0x${y:i1:8}))" "$((0x${ciphertext:i2:8} ^ 0x${y:i2:8}))" "$((0x${ciphertext:i3:8} ^ 0x${y:i3:8}))" "$((0x${ciphertext:i4:8} ^ 0x${y:i4:8}))")" - done - # If the length of the ciphertext is not an even multiple of 16 bytes, then handle the final incomplete block. - if [[ $mod_check -ne 0 ]]; then - i1=32*$n - for (( i=0; i < mod_check; i=i+2 )); do - plaintext+="$(printf "%02X" "$((0x${ciphertext:i1:2} ^ 0x${y:i1:2}))")" - i1+=2 - done - fi - tm_out "$plaintext" - return 0 -} - -# See RFC 7539, Section 2.1 -chacha20_Qround() { - local -i a="0x$1" - local -i b="0x$2" - local -i c="0x$3" - local -i d="0x$4" - local -i x y - - a=$(((a+b) & 0xffffffff)) - d=$((d^a)) - # rotate d left 16 bits - x=$((d & 0xffff0000)) - x=$((x >> 16)) - y=$((d & 0x0000ffff)) - y=$((y << 16)) - d=$((x | y)) - - c=$(((c+d) & 0xffffffff)) - b=$((b^c)) - # rotate b left 12 bits - x=$((b & 0xfff00000)) - x=$((x >> 20)) - y=$((b & 0x000fffff)) - y=$((y << 12)) - b=$((x | y)) - - a=$(((a+b) & 0xffffffff)) - d=$((d^a)) - # rotate d left 8 bits - x=$((d & 0xff000000)) - x=$((x >> 24)) - y=$((d & 0x00ffffff)) - y=$((y << 8)) - d=$((x | y)) - - c=$(((c+d) & 0xffffffff)) - b=$((b^c)) - # rotate b left 7 bits - x=$((b & 0xfe000000)) - x=$((x >> 25)) - y=$((b & 0x01ffffff)) - y=$((y << 7)) - b=$((x | y)) - - tm_out "$(printf "%x" $a) $(printf "%x" $b) $(printf "%x" $c) $(printf "%x" $d)" - return 0 -} - -# See RFC 7539, Section 2.3.1 -chacha20_inner_block() { - local s0="$1" s1="$2" s2="$3" s3="$4" - local s4="$5" s5="$6" s6="$7" s7="$8" - local s8="$9" s9="${10}" s10="${11}" s11="${12}" - local s12="${13}" s13="${14}" s14="${15}" s15="${16}" - local res - - res="$(chacha20_Qround "$s0" "$s4" "$s8" "$s12")" - read -r s0 s4 s8 s12 <<< "$res" - res="$(chacha20_Qround "$s1" "$s5" "$s9" "$s13")" - read -r s1 s5 s9 s13 <<< "$res" - res="$(chacha20_Qround "$s2" "$s6" "$s10" "$s14")" - read -r s2 s6 s10 s14 <<< "$res" - res="$(chacha20_Qround "$s3" "$s7" "$s11" "$s15")" - read -r s3 s7 s11 s15 <<< "$res" - res="$(chacha20_Qround "$s0" "$s5" "$s10" "$s15")" - read -r s0 s5 s10 s15 <<< "$res" - res="$(chacha20_Qround "$s1" "$s6" "$s11" "$s12")" - read -r s1 s6 s11 s12 <<< "$res" - res="$(chacha20_Qround "$s2" "$s7" "$s8" "$s13")" - read -r s2 s7 s8 s13 <<< "$res" - res="$(chacha20_Qround "$s3" "$s4" "$s9" "$s14")" - read -r s3 s4 s9 s14 <<< "$res" - - tm_out "$s0 $s1 $s2 $s3 $s4 $s5 $s6 $s7 $s8 $s9 $s10 $s11 $s12 $s13 $s14 $s15" - return 0 -} - -# See RFC 7539, Sections 2.3 and 2.3.1 -chacha20_block() { - local key="$1" - local counter="$2" - local nonce="$3" - local s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12 s13 s14 s15 - local ws0 ws1 ws2 ws3 ws4 ws5 ws6 ws7 ws8 ws9 ws10 ws11 ws12 ws13 ws14 ws15 - local working_state - local -i i - - # create the state variable - s0="61707865"; s1="3320646e"; s2="79622d32"; s3="6b206574" - s4="${key:6:2}${key:4:2}${key:2:2}${key:0:2}" - s5="${key:14:2}${key:12:2}${key:10:2}${key:8:2}" - s6="${key:22:2}${key:20:2}${key:18:2}${key:16:2}" - s7="${key:30:2}${key:28:2}${key:26:2}${key:24:2}" - s8="${key:38:2}${key:36:2}${key:34:2}${key:32:2}" - s9="${key:46:2}${key:44:2}${key:42:2}${key:40:2}" - s10="${key:54:2}${key:52:2}${key:50:2}${key:48:2}" - s11="${key:62:2}${key:60:2}${key:58:2}${key:56:2}" - s12="$counter" - s13="${nonce:6:2}${nonce:4:2}${nonce:2:2}${nonce:0:2}" - s14="${nonce:14:2}${nonce:12:2}${nonce:10:2}${nonce:8:2}" - s15="${nonce:22:2}${nonce:20:2}${nonce:18:2}${nonce:16:2}" - - # Initialize working_state to state - working_state="$s0 $s1 $s2 $s3 $s4 $s5 $s6 $s7 $s8 $s9 $s10 $s11 $s12 $s13 $s14 $s15" - - # compute the 20 rounds (10 calls to inner block function, each of which - # performs 8 quarter rounds). - for (( i=0 ; i < 10; i++ )); do - working_state="$(chacha20_inner_block $working_state)" - done - read -r ws0 ws1 ws2 ws3 ws4 ws5 ws6 ws7 ws8 ws9 ws10 ws11 ws12 ws13 ws14 ws15 <<< "$working_state" - - # Add working state to state - s0="$(printf "%08X" $(((0x$s0+0x$ws0) & 0xffffffff)))" - s1="$(printf "%08X" $(((0x$s1+0x$ws1) & 0xffffffff)))" - s2="$(printf "%08X" $(((0x$s2+0x$ws2) & 0xffffffff)))" - s3="$(printf "%08X" $(((0x$s3+0x$ws3) & 0xffffffff)))" - s4="$(printf "%08X" $(((0x$s4+0x$ws4) & 0xffffffff)))" - s5="$(printf "%08X" $(((0x$s5+0x$ws5) & 0xffffffff)))" - s6="$(printf "%08X" $(((0x$s6+0x$ws6) & 0xffffffff)))" - s7="$(printf "%08X" $(((0x$s7+0x$ws7) & 0xffffffff)))" - s8="$(printf "%08X" $(((0x$s8+0x$ws8) & 0xffffffff)))" - s9="$(printf "%08X" $(((0x$s9+0x$ws9) & 0xffffffff)))" - s10="$(printf "%08X" $(((0x$s10+0x$ws10) & 0xffffffff)))" - s11="$(printf "%08X" $(((0x$s11+0x$ws11) & 0xffffffff)))" - s12="$(printf "%08X" $(((0x$s12+0x$ws12) & 0xffffffff)))" - s13="$(printf "%08X" $(((0x$s13+0x$ws13) & 0xffffffff)))" - s14="$(printf "%08X" $(((0x$s14+0x$ws14) & 0xffffffff)))" - s15="$(printf "%08X" $(((0x$s15+0x$ws15) & 0xffffffff)))" - - # serialize the state - s0="${s0:6:2}${s0:4:2}${s0:2:2}${s0:0:2}" - s1="${s1:6:2}${s1:4:2}${s1:2:2}${s1:0:2}" - s2="${s2:6:2}${s2:4:2}${s2:2:2}${s2:0:2}" - s3="${s3:6:2}${s3:4:2}${s3:2:2}${s3:0:2}" - s4="${s4:6:2}${s4:4:2}${s4:2:2}${s4:0:2}" - s5="${s5:6:2}${s5:4:2}${s5:2:2}${s5:0:2}" - s6="${s6:6:2}${s6:4:2}${s6:2:2}${s6:0:2}" - s7="${s7:6:2}${s7:4:2}${s7:2:2}${s7:0:2}" - s8="${s8:6:2}${s8:4:2}${s8:2:2}${s8:0:2}" - s9="${s9:6:2}${s9:4:2}${s9:2:2}${s9:0:2}" - s10="${s10:6:2}${s10:4:2}${s10:2:2}${s10:0:2}" - s11="${s11:6:2}${s11:4:2}${s11:2:2}${s11:0:2}" - s12="${s12:6:2}${s12:4:2}${s12:2:2}${s12:0:2}" - s13="${s13:6:2}${s13:4:2}${s13:2:2}${s13:0:2}" - s14="${s14:6:2}${s14:4:2}${s14:2:2}${s14:0:2}" - s15="${s15:6:2}${s15:4:2}${s15:2:2}${s15:0:2}" - - tm_out "$s0$s1$s2$s3$s4$s5$s6$s7$s8$s9$s10$s11$s12$s13$s14$s15" - return 0 -} - -# See RFC 7539, Section 2.4 -chacha20() { - local key="$1" - local -i counter=1 - local nonce="$2" - local ciphertext="$3" - local -i i ciphertext_len num_blocks mod_check - local -i i1 i2 i3 i4 i5 i6 i7 i8 i9 i10 i11 i12 i13 i14 i15 i16 - local keystream plaintext="" - - ciphertext_len=${#ciphertext} - num_blocks=$ciphertext_len/128 - - for (( i=0; i < num_blocks; i++)); do - i1=128*$i; i2=$i1+8; i3=$i1+16; i4=$i1+24; i5=$i1+32; i6=$i1+40; i7=$i1+48; i8=$i1+56 - i9=$i1+64; i10=$i1+72; i11=$i1+80; i12=$i1+88; i13=$i1+96; i14=$i1+104; i15=$i1+112; i16=$i1+120 - keystream="$(chacha20_block "$key" "$(printf "%08X" $counter)" "$nonce")" - plaintext+="$(printf "%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X" \ - "$((0x${ciphertext:i1:8} ^ 0x${keystream:0:8}))" \ - "$((0x${ciphertext:i2:8} ^ 0x${keystream:8:8}))" \ - "$((0x${ciphertext:i3:8} ^ 0x${keystream:16:8}))" \ - "$((0x${ciphertext:i4:8} ^ 0x${keystream:24:8}))" \ - "$((0x${ciphertext:i5:8} ^ 0x${keystream:32:8}))" \ - "$((0x${ciphertext:i6:8} ^ 0x${keystream:40:8}))" \ - "$((0x${ciphertext:i7:8} ^ 0x${keystream:48:8}))" \ - "$((0x${ciphertext:i8:8} ^ 0x${keystream:56:8}))" \ - "$((0x${ciphertext:i9:8} ^ 0x${keystream:64:8}))" \ - "$((0x${ciphertext:i10:8} ^ 0x${keystream:72:8}))" \ - "$((0x${ciphertext:i11:8} ^ 0x${keystream:80:8}))" \ - "$((0x${ciphertext:i12:8} ^ 0x${keystream:88:8}))" \ - "$((0x${ciphertext:i13:8} ^ 0x${keystream:96:8}))" \ - "$((0x${ciphertext:i14:8} ^ 0x${keystream:104:8}))" \ - "$((0x${ciphertext:i15:8} ^ 0x${keystream:112:8}))" \ - "$((0x${ciphertext:i16:8} ^ 0x${keystream:120:8}))")" - counter+=1 - done - - mod_check=$ciphertext_len%128 - if [[ $mod_check -ne 0 ]]; then - keystream="$(chacha20_block "$key" "$(printf "%08X" $counter)" "$nonce")" - i1=128*$num_blocks - for (( i=0; i < mod_check; i=i+2 )); do - plaintext+="$(printf "%02X" "$((0x${ciphertext:i1:2} ^ 0x${keystream:i:2}))")" - i1+=2 - done - fi - tm_out "$plaintext" - return 0 -} - -# arg1: TLS cipher -# arg2: key -# arg3: nonce (must be 96 bits in length) -# arg4: ciphertext -sym-decrypt() { - local cipher="$1" - local key="$2" nonce="$3" - local ciphertext="$4" - local ossl_cipher - local plaintext - local -i ciphertext_len tag_len - - case "$cipher" in - *CCM_8*) - tag_len=16 ;; - *CCM*|*GCM*|*CHACHA20_POLY1305*) - tag_len=32 ;; - *) - return 7 ;; - esac - - # The final $tag_len characters of the ciphertext are the authentication tag - ciphertext_len=${#ciphertext} - [[ $ciphertext_len -lt $tag_len ]] && return 7 - ciphertext_len=$ciphertext_len-$tag_len - - if [[ "$cipher" =~ CHACHA20_POLY1305 ]]; then - if "$HAS_CHACHA20"; then - plaintext="$(asciihex_to_binary "${ciphertext:0:ciphertext_len}" | \ - $OPENSSL enc -chacha20 -K "$key" -iv "01000000$nonce" 2>/dev/null | hexdump -v -e '16/1 "%02X"')" - plaintext="$(strip_spaces "$plaintext")" - else - plaintext="$(chacha20 "$key" "$nonce" "${ciphertext:0:ciphertext_len}")" - fi - elif [[ "$cipher" == TLS_AES_128_GCM_SHA256 ]] && "$HAS_AES128_GCM"; then - plaintext="$(asciihex_to_binary "${ciphertext:0:ciphertext_len}" | \ - $OPENSSL enc -aes-128-gcm -K "$key" -iv "$nonce" 2>/dev/null | hexdump -v -e '16/1 "%02X"')" - plaintext="$(strip_spaces "$plaintext")" - elif [[ "$cipher" == TLS_AES_256_GCM_SHA384 ]] && "$HAS_AES256_GCM"; then - plaintext="$(asciihex_to_binary "${ciphertext:0:ciphertext_len}" | \ - $OPENSSL enc -aes-256-gcm -K "$key" -iv "$nonce" 2>/dev/null | hexdump -v -e '16/1 "%02X"')" - plaintext="$(strip_spaces "$plaintext")" - else - if [[ "$cipher" =~ AES_128 ]]; then - ossl_cipher="-aes-128-ecb" - elif [[ "$cipher" =~ AES_256 ]]; then - ossl_cipher="-aes-256-ecb" - else - return 7 - fi - if [[ "$cipher" =~ CCM ]]; then - plaintext="$(ccm-gcm-decrypt "$ossl_cipher" "$key" "02${nonce}000001" "${ciphertext:0:ciphertext_len}")" - else # GCM - plaintext="$(ccm-gcm-decrypt "$ossl_cipher" "$key" "${nonce}00000002" "${ciphertext:0:ciphertext_len}")" - fi - fi - [[ $? -ne 0 ]] && return 7 - - tm_out "$plaintext" - return 0 -} - -# arg1: iv -# arg2: sequence number -get-nonce() { - local iv="$1" - local -i seq_num="$2" - local -i len lsb - local msb nonce - - len=${#iv} - [[ $len -lt 8 ]] && return 7 - i=$len-8 - msb="${iv:0:i}" - lsb="0x${iv:i:8}" - nonce="${msb}$(printf "%08X" "$((lsb ^ seq_num))")" - tm_out "$nonce" - return 0 -} - -# Return: -# 0 if arg1 contains the entire server response. -# 1 if arg1 does not contain the entire server response. -# 2 if the response is malformed. -# 3 if (a) the response version is TLSv1.3; -# (b) arg1 contains the entire ServerHello (and appears to contain the entire response); -# (c) the entire response is supposed to be parsed; and -# (d) the key and IV have not been provided to decrypt the response. -# arg1: ASCII-HEX encoded reply -# arg2: whether to process the full request ("all") or just the basic request plus the ephemeral key if any ("ephemeralkey"). -# arg3: TLS cipher for decrypting TLSv1.3 response -# arg4: key and IV for decrypting TLSv1.3 response -check_tls_serverhellodone() { - local tls_hello_ascii="$1" - local process_full="$2" - local cipher="$3" - local key_and_iv="$4" - local tls_handshake_ascii="" tls_alert_ascii="" - local -i i tls_hello_ascii_len tls_handshake_ascii_len tls_alert_ascii_len - local -i msg_len remaining tls_serverhello_ascii_len sid_len - local -i j offset tls_extensions_len extension_len - local tls_content_type tls_protocol tls_handshake_type tls_msg_type extension_type - local tls_err_level - local key iv - local -i seq_num=0 plaintext_len - local plaintext decrypted_response="" - - DETECTED_TLS_VERSION="" - - [[ -n "$key_and_iv" ]] && read -r key iv <<< "$key_and_iv" - - if [[ -z "$tls_hello_ascii" ]]; then - return 0 # no server hello received - fi - - tls_hello_ascii_len=${#tls_hello_ascii} - for (( i=0; i<tls_hello_ascii_len; i=i+msg_len )); do - remaining=$tls_hello_ascii_len-$i - [[ $remaining -lt 10 ]] && return 1 - - tls_content_type="${tls_hello_ascii:i:2}" - [[ "$tls_content_type" != 14 ]] && [[ "$tls_content_type" != 15 ]] && \ - [[ "$tls_content_type" != 16 ]] && [[ "$tls_content_type" != 17 ]] && return 2 - i=$i+2 - tls_protocol="${tls_hello_ascii:i:4}" - [[ -z "$DETECTED_TLS_VERSION" ]] && DETECTED_TLS_VERSION="$tls_protocol" - [[ "${tls_protocol:0:2}" != 03 ]] && return 2 - i=$i+4 - msg_len=2*$(hex2dec "${tls_hello_ascii:i:4}") - i=$i+4 - remaining=$tls_hello_ascii_len-$i - [[ $msg_len -gt $remaining ]] && return 1 - - if [[ "$tls_content_type" == 16 ]]; then - tls_handshake_ascii+="${tls_hello_ascii:i:msg_len}" - tls_handshake_ascii_len=${#tls_handshake_ascii} - decrypted_response+="$tls_content_type$tls_protocol$(printf "%04X" $((msg_len/2)))${tls_hello_ascii:i:msg_len}" - # the ServerHello MUST be the first handshake message - [[ $tls_handshake_ascii_len -ge 2 ]] && [[ "${tls_handshake_ascii:0:2}" != 02 ]] && return 2 - if [[ $tls_handshake_ascii_len -ge 12 ]]; then - DETECTED_TLS_VERSION="${tls_handshake_ascii:8:4}" - - # In TLSv1.3 (starting with draft 22), the version field specifies TLSv1.2, but - # there is a supported_versions extension that specifies the actual version. So, - # if the version field specifies TLSv1.2, then check to see if there is a - # supported_versions extension. - if [[ "$DETECTED_TLS_VERSION" == 0303 ]]; then - tls_serverhello_ascii_len=2*$(hex2dec "${tls_handshake_ascii:2:6}") - sid_len=2*$(hex2dec "${tls_handshake_ascii:76:2}") - if [[ $tls_serverhello_ascii_len -gt 76+$sid_len ]]; then - # ServerHello contains extensions, so check for supported_versions extension - offset=84+$sid_len - tls_extensions_len=2*$(hex2dec "${tls_handshake_ascii:offset:4}") - [[ $tls_extensions_len -ne $tls_serverhello_ascii_len-$sid_len-80 ]] && return 2 - for (( j=0; j<tls_extensions_len; j=j+8+extension_len )); do - [[ $tls_extensions_len-$j -lt 8 ]] && return 2 - offset=88+$sid_len+$j - extension_type="${tls_handshake_ascii:offset:4}" - offset=92+$sid_len+$j - extension_len=2*$(hex2dec "${tls_handshake_ascii:offset:4}") - [[ $extension_len -gt $tls_extensions_len-$j-8 ]] && return 2 - if [[ "$extension_type" == 002B ]]; then # supported_versions - [[ $extension_len -ne 4 ]] && return 2 - offset=96+$sid_len+$j - DETECTED_TLS_VERSION="${tls_handshake_ascii:offset:4}" - fi - done - fi - fi - # A version of {0x7F, xx} represents an implementation of a draft version of TLS 1.3 - [[ "${DETECTED_TLS_VERSION:0:2}" == 7F ]] && DETECTED_TLS_VERSION=0304 - if [[ 0x$DETECTED_TLS_VERSION -ge 0x0304 ]] && [[ "$process_full" == ephemeralkey ]]; then - tls_serverhello_ascii_len=2*$(hex2dec "${tls_handshake_ascii:2:6}") - if [[ $tls_handshake_ascii_len -ge $tls_serverhello_ascii_len+8 ]]; then - tm_out "" - return 0 # The entire ServerHello message has been received (and the rest isn't needed) - fi - fi - fi - elif [[ "$tls_content_type" == 15 ]]; then # TLS ALERT - tls_alert_ascii+="${tls_hello_ascii:i:msg_len}" - decrypted_response+="$tls_content_type$tls_protocol$(printf "%04X" $((msg_len/2)))${tls_hello_ascii:i:msg_len}" - elif [[ "$tls_content_type" == 17 ]] && [[ -n "$key_and_iv" ]]; then # encrypted data - nonce="$(get-nonce "$iv" "$seq_num")" - [[ $? -ne 0 ]] && return 2 - plaintext="$(sym-decrypt "$cipher" "$key" "$nonce" "${tls_hello_ascii:i:msg_len}")" - [[ $? -ne 0 ]] && return 2 - seq_num+=1 - - # Remove zeros from end of plaintext, if any - plaintext_len=${#plaintext}-2 - while [[ "${plaintext:plaintext_len:2}" == 00 ]]; do - plaintext_len=$plaintext_len-2 - done - tls_content_type="${plaintext:plaintext_len:2}" - decrypted_response+="${tls_content_type}0301$(printf "%04X" $((plaintext_len/2)))${plaintext:0:plaintext_len}" - if [[ "$tls_content_type" == 16 ]]; then - tls_handshake_ascii+="${plaintext:0:plaintext_len}" - elif [[ "$tls_content_type" == 15 ]]; then - tls_alert_ascii+="${plaintext:0:plaintext_len}" - else - return 2 - fi - fi - done - - # If there is a fatal alert, then we are done. - tls_alert_ascii_len=${#tls_alert_ascii} - for (( i=0; i<tls_alert_ascii_len; i=i+4 )); do - remaining=$tls_alert_ascii_len-$i - [[ $remaining -lt 4 ]] && return 1 - tls_err_level=${tls_alert_ascii:i:2} # 1: warning, 2: fatal - [[ $tls_err_level == 02 ]] && DETECTED_TLS_VERSION="" && tm_out "" && return 0 - done - - # If there is a serverHelloDone or Finished, then we are done. - tls_handshake_ascii_len=${#tls_handshake_ascii} - for (( i=0; i<tls_handshake_ascii_len; i=i+msg_len )); do - remaining=$tls_handshake_ascii_len-$i - [[ $remaining -lt 8 ]] && return 1 - tls_msg_type="${tls_handshake_ascii:i:2}" - i=$i+2 - msg_len=2*$(hex2dec "${tls_handshake_ascii:i:6}") - i=$i+6 - remaining=$tls_handshake_ascii_len-$i - [[ $msg_len -gt $remaining ]] && return 1 - - # For SSLv3 - TLS1.2 look for a ServerHelloDone message. - # For TLS 1.3 look for a Finished message. - [[ $tls_msg_type == 0E ]] && tm_out "" && return 0 - [[ $tls_msg_type == 14 ]] && tm_out "$decrypted_response" && return 0 - done - # If the response is TLSv1.3 and the full response is to be processed, but the - # key and IV have not been provided to decrypt the response, then return 3 if - # the entire ServerHello has been received. - if [[ "$DETECTED_TLS_VERSION" == 0304 ]] && [[ "$process_full" =~ all ]] && \ - [[ -z "$key_and_iv" ]] && [[ $tls_handshake_ascii_len -gt 0 ]]; then - return 3 - fi - # If we haven't encountered a fatal alert or a server hello done, - # then there must be more data to retrieve. - return 1 -} - -# arg1: tls alert error/warning code -# returns: description -tls_alert() { - local tls_alert_text="" - - case "$1" in - 00) tls_alert_text="close notify" ;; - 0A) tls_alert_text="unexpected message" ;; - 14) tls_alert_text="bad record mac" ;; - 15) tls_alert_text="decryption failed" ;; - 16) tls_alert_text="record overflow" ;; - 1E) tls_alert_text="decompression failure" ;; - 28) tls_alert_text="handshake failure" ;; - 29) tls_alert_text="no certificate RESERVED" ;; - 2A) tls_alert_text="bad certificate" ;; - 2B) tls_alert_text="unsupported certificate" ;; - 2C) tls_alert_text="certificate revoked" ;; - 2D) tls_alert_text="certificate expired" ;; - 2E) tls_alert_text="certificate unknown" ;; - 2F) tls_alert_text="illegal parameter" ;; - 30) tls_alert_text="unknown ca" ;; - 31) tls_alert_text="access denied" ;; - 32) tls_alert_text="decode error" ;; - 33) tls_alert_text="decrypt error" ;; - 3C) tls_alert_text="export restriction RESERVED" ;; - 46) tls_alert_text="protocol version" ;; - 47) tls_alert_text="insufficient security" ;; - 50) tls_alert_text="internal error" ;; - 56) tls_alert_text="inappropriate fallback" ;; - 5A) tls_alert_text="user canceled" ;; - 64) tls_alert_text="no renegotiation" ;; - 6D) tls_alert_text="missing extension" ;; - 6E) tls_alert_text="unsupported extension" ;; - 6F) tls_alert_text="certificate unobtainable" ;; - 70) tls_alert_text="unrecognized name" ;; - 71) tls_alert_text="bad certificate status response" ;; - 72) tls_alert_text="bad certificate hash value" ;; - 73) tls_alert_text="unknown psk identity" ;; - 74) tls_alert_text="certificate required" ;; - 78) tls_alert_text="no application protocol" ;; - *) tls_alert_text="$(hex2dec "$1")";; - esac - echo "$tls_alert_text" - return 0 -} - -# arg1: ASCII-HEX encoded reply -# arg2: (optional): "all" or "all+" - process full response (including Certificate and certificate_status handshake messages) -# "ephemeralkey" - extract the server's ephemeral key (if any) -# arg3: (optional): CIPHER_SUITES string (lowercase, and in the format output by code2network()) -# If present, parse_tls_serverhello() will check that the cipher in the ServerHello appears in -# the CIPHER_SUITES string. -parse_tls_serverhello() { - local tls_hello_ascii="$1" - local process_full="$2" - local cipherlist="$3" - local tls_handshake_ascii="" tls_alert_ascii="" - local -i tls_hello_ascii_len tls_handshake_ascii_len tls_alert_ascii_len msg_len - local tls_serverhello_ascii="" tls_certificate_ascii="" - local tls_serverkeyexchange_ascii="" tls_certificate_status_ascii="" - local tls_encryptedextensions_ascii="" tls_revised_certificate_msg="" - local -i tls_serverhello_ascii_len=0 tls_certificate_ascii_len=0 - local -i tls_serverkeyexchange_ascii_len=0 tls_certificate_status_ascii_len=0 - local -i tls_encryptedextensions_ascii_len=0 - local added_encrypted_extensions=false - local tls_alert_descrip tls_sid_len_hex issuerDN subjectDN CAissuerDN CAsubjectDN - local -i tls_sid_len offset extns_offset nr_certs=0 - local tls_msg_type tls_content_type tls_protocol tls_protocol2 tls_hello_time - local tls_err_level tls_err_descr_no tls_cipher_suite rfc_cipher_suite tls_compression_method - local tls_extensions="" extension_type named_curve_str="" named_curve_oid - local -i i j extension_len extn_len tls_extensions_len ocsp_response_len=0 ocsp_response_list_len ocsp_resp_offset - local -i certificate_list_len certificate_len cipherlist_len - local -i curve_type named_curve - local -i dh_bits=0 msb mask - local hostcert_issuer="" - local len1 len2 len3 key_bitstring="" pem_certificate - local dh_p dh_param ephemeral_param rfc7919_param - local -i dh_p_len dh_param_len - - DETECTED_TLS_VERSION="" - [[ $DEBUG -ge 1 ]] && echo > $TMPFILE - - [[ "$DEBUG" -ge 5 ]] && echo $tls_hello_ascii # one line without any blanks - - # Client messages, including handshake messages, are carried by the record layer. - # First, extract the handshake and alert messages. - # see https://en.wikipedia.org/wiki/Transport_Layer_Security-SSL#TLS_record - # byte 0: content type: 0x14=CCS, 0x15=TLS alert x16=Handshake, 0x17 Application, 0x18=HB - # byte 1+2: TLS version word, major is 03, minor 00=SSL3, 01=TLS1 02=TLS1.1 03=TLS 1.2 - # byte 3+4: fragment length - # bytes 5...: message fragment - tls_hello_ascii_len=${#tls_hello_ascii} - if [[ $DEBUG -ge 3 ]] && [[ $tls_hello_ascii_len -gt 0 ]]; then - echo "TLS message fragments:" - fi - for (( i=0; i<tls_hello_ascii_len; i=i+msg_len )); do - if [[ $tls_hello_ascii_len-$i -lt 10 ]]; then - if [[ "$process_full" =~ all ]]; then - # The entire server response should have been retrieved. - debugme tmln_warning "Malformed message." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - else - # This could just be a result of the server's response being - # split across two or more packets. - break - fi - fi - tls_content_type="${tls_hello_ascii:i:2}" - i=$i+2 - tls_protocol="${tls_hello_ascii:i:4}" - i=$i+4 - msg_len=2*$(hex2dec "${tls_hello_ascii:i:4}") - i=$i+4 - - if [[ $DEBUG -ge 3 ]]; then - echo " protocol (rec. layer): 0x$tls_protocol" - echo -n " tls_content_type: 0x$tls_content_type" - case $tls_content_type in - 14) tmln_out " (change cipher spec)" ;; - 15) tmln_out " (alert)" ;; - 16) tmln_out " (handshake)" ;; - 17) tmln_out " (application data)" ;; - *) tmln_out ;; - esac - echo " msg_len: $((msg_len/2))" - tmln_out - fi - - if "$do_starttls" ; then - if [[ $tls_content_type == 35 ]] || [[ $tls_content_type == 34 ]]; then - # STARTTLS handshake failed and server replied plaintext with a 5xx or 4xx - [[ $DEBUG -ge 2 ]] && printf "%s\n" "400/500: $(hex2ascii "$tls_hello_ascii" 2>/dev/null)" - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 4 - elif [[ "$tls_hello_ascii" =~ 6130303220 ]]; then - [[ $DEBUG -ge 2 ]] && printf "%s\n" "probably IMAP plaintext reply \"$(hex2ascii "${tls_hello_ascii:0:32}" 2>/dev/null)\"" - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 3 - fi - fi - if [[ $tls_content_type != 14 ]] && [[ $tls_content_type != 15 ]] && \ - [[ $tls_content_type != 16 ]] && [[ $tls_content_type != 17 ]]; then - debugme tmln_warning "Content type other than alert, handshake, change cipher spec, or application data detected." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - elif [[ "${tls_protocol:0:2}" != 03 ]]; then - debugme tmln_warning "Protocol record_version.major is not 03." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - DETECTED_TLS_VERSION=$tls_protocol - - if [[ $msg_len -gt $tls_hello_ascii_len-$i ]]; then - if [[ "$process_full" =~ all ]]; then - debugme tmln_warning "Malformed message." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 7 - else - # This could just be a result of the server's response being split - # across two or more packets. Just grab the part that is available. - msg_len=$tls_hello_ascii_len-$i - fi - fi - - if [[ $tls_content_type == 16 ]]; then - tls_handshake_ascii="$tls_handshake_ascii${tls_hello_ascii:i:msg_len}" - elif [[ $tls_content_type == 15 ]]; then # TLS ALERT - tls_alert_ascii="$tls_alert_ascii${tls_hello_ascii:i:msg_len}" - fi - done - - # Now check the alert messages. - tls_alert_ascii_len=${#tls_alert_ascii} - if [[ "$process_full" =~ all ]] && [[ $tls_alert_ascii_len%4 -ne 0 ]]; then - debugme tmln_warning "Malformed message." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - - if [[ $tls_alert_ascii_len -gt 0 ]]; then - debugme echo "TLS alert messages:" - for (( i=0; i+3 < tls_alert_ascii_len; i=i+4 )); do - tls_err_level=${tls_alert_ascii:i:2} # 1: warning, 2: fatal - j=$i+2 - tls_err_descr_no=${tls_alert_ascii:j:2} - if [[ $DEBUG -ge 1 ]]; then - debugme tm_out " tls_err_descr_no: 0x${tls_err_descr_no} / = $(hex2dec ${tls_err_descr_no})" - tls_alert_descrip="$(tls_alert "$tls_err_descr_no")" - if [[ $DEBUG -ge 2 ]]; then - tmln_out " ($tls_alert_descrip)" - tm_out " tls_err_level: ${tls_err_level}" - fi - case $tls_err_level in - 01) echo -n "warning " >> $TMPFILE - debugme tmln_out " (warning)" ;; - 02) echo -n "fatal " >> $TMPFILE - debugme tmln_out " (fatal)" ;; - esac - echo "alert $tls_alert_descrip" >> $TMPFILE - echo "===============================================================================" >> $TMPFILE - fi - - if [[ "$tls_err_level" != 01 ]] && [[ "$tls_err_level" != 02 ]]; then - debugme tmln_warning "Unexpected AlertLevel (0x$tls_err_level)." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - elif [[ "$tls_err_level" == 02 ]]; then - # Fatal alert - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - done - fi - - # Now extract just the server hello, certificate, certificate status, - # and server key exchange handshake messages. - tls_handshake_ascii_len=${#tls_handshake_ascii} - if [[ $DEBUG -ge 3 ]] && [[ $tls_handshake_ascii_len -gt 0 ]]; then - echo "TLS handshake messages:" - fi - for (( i=0; i<tls_handshake_ascii_len; i=i+msg_len )); do - if [[ $tls_handshake_ascii_len-$i -lt 8 ]]; then - if [[ "$process_full" =~ all ]]; then - # The entire server response should have been retrieved. - debugme tmln_warning "Malformed message." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - else - # This could just be a result of the server's response being - # split across two or more packets. - continue - fi - fi - tls_msg_type="${tls_handshake_ascii:i:2}" - i=$i+2 - msg_len=2*$(hex2dec "${tls_handshake_ascii:i:6}") - i=$i+6 - if [[ $DEBUG -ge 3 ]]; then - tm_out " handshake type: 0x${tls_msg_type}" - case $tls_msg_type in - 00) tmln_out " (hello_request)" ;; - 01) tmln_out " (client_hello)" ;; - 02) tmln_out " (server_hello)" ;; - 03) tmln_out " (hello_verify_request)" ;; - 04) tmln_out " (new_session_ticket)" ;; - 05) tmln_out " (end_of_early_data)" ;; - 06) tmln_out " (hello_retry_request)" ;; - 08) tmln_out " (encrypted_extensions)" ;; - 0B) tmln_out " (certificate)" ;; - 0C) tmln_out " (server_key_exchange)" ;; - 0D) tmln_out " (certificate_request)" ;; - 0E) tmln_out " (server_hello_done)" ;; - 0F) tmln_out " (certificate_verify)" ;; - 10) tmln_out " (client_key_exchange)" ;; - 14) tmln_out " (finished)" ;; - 15) tmln_out " (certificate_url)" ;; - 16) tmln_out " (certificate_status)" ;; - 17) tmln_out " (supplemental_data)" ;; - 18) tmln_out " (key_update)" ;; - 19) tmln_out " (compressed_certificate)" ;; - FE) tmln_out " (message_hash)" ;; - *) tmln_out ;; - esac - echo " msg_len: $((msg_len/2))" - tmln_out - fi - if [[ $msg_len -gt $tls_handshake_ascii_len-$i ]]; then - if [[ "$process_full" =~ all ]]; then - debugme tmln_warning "Malformed message." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - else - # This could just be a result of the server's response being - # split across two or more packets. Just grab the part that - # is available. - msg_len=$tls_handshake_ascii_len-$i - fi - fi - - if [[ "$tls_msg_type" == 02 ]]; then - if [[ -n "$tls_serverhello_ascii" ]]; then - debugme tmln_warning "Response contained more than one ServerHello handshake message." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - tls_serverhello_ascii="${tls_handshake_ascii:i:msg_len}" - tls_serverhello_ascii_len=$msg_len - elif [[ "$process_full" =~ all ]] && [[ "$tls_msg_type" == 08 ]]; then - # Add excrypted extensions (now decrypted) to end of extensions in SeverHello - tls_encryptedextensions_ascii="${tls_handshake_ascii:i:msg_len}" - tls_encryptedextensions_ascii_len=$msg_len - if [[ $msg_len -lt 2 ]]; then - debugme tmln_warning "Response contained a malformed encrypted extensions message" - return 1 - fi - elif [[ "$process_full" =~ all ]] && [[ "$tls_msg_type" == 0B ]]; then - if [[ -n "$tls_certificate_ascii" ]]; then - debugme tmln_warning "Response contained more than one Certificate handshake message." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - tls_certificate_ascii="${tls_handshake_ascii:i:msg_len}" - tls_certificate_ascii_len=$msg_len - elif ( [[ "$process_full" =~ all ]] || [[ "$process_full" == ephemeralkey ]] ) && [[ "$tls_msg_type" == 0C ]]; then - if [[ -n "$tls_serverkeyexchange_ascii" ]]; then - debugme tmln_warning "Response contained more than one ServerKeyExchange handshake message." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - tls_serverkeyexchange_ascii="${tls_handshake_ascii:i:msg_len}" - tls_serverkeyexchange_ascii_len=$msg_len - elif [[ "$process_full" =~ all ]] && [[ "$tls_msg_type" == 16 ]]; then - if [[ -n "$tls_certificate_status_ascii" ]]; then - debugme tmln_warning "Response contained more than one certificate_status handshake message." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - tls_certificate_status_ascii="${tls_handshake_ascii:i:msg_len}" - tls_certificate_status_ascii_len=$msg_len - elif [[ "$tls_msg_type" == 19 ]]; then - if [[ -n "$tls_certificate_ascii" ]]; then - debugme tmln_warning "Response contained more than one Certificate handshake message." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - if [[ $DEBUG -ge 3 ]]; then - tm_out " Certificate Compression Algorithm: ${tls_handshake_ascii:i:4}" - case ${tls_handshake_ascii:i:4} in - 0001) tmln_out " (ZLIB)" ;; - 0002) tmln_out " (Brotli)" ;; - 0003) tmln_out " (Zstandard)" ;; - *) tmln_out ;; - esac - offset=$((i+4)) - tmln_out " Uncompressed certificate length: $(printf "%d" 0x${tls_handshake_ascii:offset:6})" - tmln_out - fi - if [[ "$process_full" =~ all ]] && "$HAS_ZLIB" && [[ "${tls_handshake_ascii:i:4}" == 0001 ]]; then - offset=$((i+4)) - tls_certificate_ascii_len=2*0x${tls_handshake_ascii:offset:6} - offset=$((i+16)) - len1=$((msg_len-16)) - tls_certificate_ascii="$(asciihex_to_binary "${tls_handshake_ascii:offset:len1}" | $OPENSSL zlib -d 2>/dev/null | hexdump -v -e '16/1 "%02X"')" - tls_certificate_ascii="${tls_certificate_ascii%%[!0-9A-F]*}" - if [[ ${#tls_certificate_ascii} -ne $tls_certificate_ascii_len ]]; then - debugme tmln_warning "Length of uncompressed certificates did not match specified length." - return 1 - fi - fi - fi - done - - if [[ $tls_serverhello_ascii_len -eq 0 ]]; then - debugme echo "server hello empty, TCP connection closed" - DETECTED_TLS_VERSION="closed TCP connection " - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 # no server hello received - elif [[ $tls_serverhello_ascii_len -lt 76 ]]; then - DETECTED_TLS_VERSION="reply malformed" - debugme echo "Malformed response" - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - elif [[ "${tls_handshake_ascii:0:2}" != 02 ]]; then - # the ServerHello MUST be the first handshake message - DETECTED_TLS_VERSION="reply contained no ServerHello" - debugme tmln_warning "The first handshake protocol message is not a ServerHello." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - if [[ $DEBUG -eq 0 ]]; then - echo "CONNECTED(00000003)" > $TMPFILE - else - echo "CONNECTED(00000003)" >> $TMPFILE - fi - - # First parse the server hello handshake message - # byte 0+1: 03, TLS version word see byte 1+2 - # byte 2-5: TLS timestamp for OpenSSL <1.01f - # byte 6-33: random, 28 bytes - # byte 34: session id length - # byte 35+36+sid-len: cipher suite! - # byte 37+sid-len: compression method: 00: none, 01: deflate, 64: LZS - # byte 38+39+sid-len: extension length - tls_protocol2="${tls_serverhello_ascii:0:4}" - DETECTED_TLS_VERSION="$tls_protocol2" - [[ "${DETECTED_TLS_VERSION:0:2}" == 7F ]] && DETECTED_TLS_VERSION="0304" - if [[ "${DETECTED_TLS_VERSION:0:2}" != 03 ]]; then - debugme tmln_warning "server_version.major in ServerHello is not 03." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - - if [[ "0x${DETECTED_TLS_VERSION:2:2}" -le "0x03" ]]; then - tls_hello_time="${tls_serverhello_ascii:4:8}" - [[ "$TLS_DIFFTIME_SET" || "$DEBUG" ]] && TLS_TIME=$(hex2dec "$tls_hello_time") - tls_sid_len_hex="${tls_serverhello_ascii:68:2}" - tls_sid_len=2*$(hex2dec "$tls_sid_len_hex") - offset=$((tls_sid_len+70)) - if [[ $tls_serverhello_ascii_len -lt 76+$tls_sid_len ]]; then - debugme echo "Malformed response" - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - else - offset=68 - fi - - tls_cipher_suite="${tls_serverhello_ascii:offset:4}" - - if [[ "0x${DETECTED_TLS_VERSION:2:2}" -le "0x03" ]]; then - offset=$((tls_sid_len+74)) - tls_compression_method="${tls_serverhello_ascii:offset:2}" - extns_offset=$((tls_sid_len+76)) - else - extns_offset=72 - fi - - if [[ $tls_serverhello_ascii_len -gt $extns_offset ]] && \ - ( [[ "$process_full" =~ all ]] || [[ "$DETECTED_TLS_VERSION" == 0303 ]] || \ - ( [[ "$process_full" == ephemeralkey ]] && [[ "0x${DETECTED_TLS_VERSION:2:2}" -gt "0x03" ]] ) ); then - if [[ $tls_serverhello_ascii_len -lt $extns_offset+4 ]]; then - debugme echo "Malformed response" - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - tls_extensions_len=$(hex2dec "${tls_serverhello_ascii:extns_offset:4}")*2 - if [[ $tls_extensions_len -ne $tls_serverhello_ascii_len-$extns_offset-4 ]]; then - debugme tmln_warning "Malformed message." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - for (( i=0; i<tls_extensions_len; i=i+8+extension_len )); do - if [[ $tls_extensions_len-$i -lt 8 ]]; then - debugme echo "Malformed response" - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - offset=$((extns_offset+i+4)) - extension_type="${tls_serverhello_ascii:offset:4}" - offset=$((extns_offset+i+8)) - extension_len=2*$(hex2dec "${tls_serverhello_ascii:offset:4}") - if [[ $extension_len -gt $tls_extensions_len-$i-8 ]]; then - debugme echo "Malformed response" - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - case $extension_type in - 0000) tls_extensions+="TLS server extension \"server name\" (id=0), len=$extension_len\n" ;; - 0001) tls_extensions+="TLS server extension \"max fragment length\" (id=1), len=$extension_len\n" ;; - 0002) tls_extensions+="TLS server extension \"client certificate URL\" (id=2), len=$extension_len\n" ;; - 0003) tls_extensions+="TLS server extension \"trusted CA keys\" (id=3, len=$extension_len\n)" ;; - 0004) tls_extensions+="TLS server extension \"truncated HMAC\" (id=4), len=$extension_len\n" ;; - 0005) tls_extensions+="TLS server extension \"status request\" (id=5), len=$extension_len\n" - if [[ $extension_len -gt 0 ]] && [[ "$process_full" =~ all ]]; then - # In TLSv1.3 the status_request extension contains the CertificateStatus message, unlike - # TLSv1.2 and below where CertificateStatus appears in its own handshake message. So, if - # the status_request extension is not empty, extract the value and place it in - # $tls_certificate_status_ascii. - tls_certificate_status_ascii_len=$extension_len - offset=$((extns_offset+12+i)) - tls_certificate_status_ascii="${tls_serverhello_ascii:offset:tls_certificate_status_ascii_len}" - fi - ;; - 0006) tls_extensions+="TLS server extension \"user mapping\" (id=6), len=$extension_len\n" ;; - 0007) tls_extensions+="TLS server extension \"client authz\" (id=7), len=$extension_len\n" ;; - 0008) tls_extensions+="TLS server extension \"server authz\" (id=8), len=$extension_len\n" ;; - 0009) tls_extensions+="TLS server extension \"cert type\" (id=9), len=$extension_len\n" ;; - 000A) tls_extensions+="TLS server extension \"supported_groups\" (id=10), len=$extension_len\n" - if [[ "$process_full" =~ all ]]; then - if [[ $extension_len -lt 4 ]]; then - debugme tmln_warning "Malformed supported groups extension." - return 1 - fi - echo -n "Supported groups: " >> $TMPFILE - offset=$((extns_offset+12+i)) - len1=2*$(hex2dec "${tls_serverhello_ascii:offset:4}") - if [[ $extension_len -lt $len1+4 ]] || [[ $len1 -lt 4 ]]; then - debugme tmln_warning "Malformed supported groups extension." - return 1 - fi - offset=$((offset+4)) - for (( j=0; j < len1; j=j+4 )); do - [[ $j -ne 0 ]] && echo -n ", " >> $TMPFILE - case "${tls_serverhello_ascii:offset:4}" in - "0017") echo -n "secp256r1" >> $TMPFILE ;; - "0018") echo -n "secp384r1" >> $TMPFILE ;; - "0019") echo -n "secp521r1" >> $TMPFILE ;; - "001D") echo -n "X25519" >> $TMPFILE ;; - "001E") echo -n "X448" >> $TMPFILE ;; - "0100") echo -n "ffdhe2048" >> $TMPFILE ;; - "0101") echo -n "ffdhe3072" >> $TMPFILE ;; - "0102") echo -n "ffdhe4096" >> $TMPFILE ;; - "0103") echo -n "ffdhe6144" >> $TMPFILE ;; - "0104") echo -n "ffdhe8192" >> $TMPFILE ;; - *) echo -n "unknown (${tls_serverhello_ascii:offset:4})" >> $TMPFILE ;; - esac - offset=$((offset+4)) - done - echo "" >> $TMPFILE - fi - ;; - 000B) tls_extensions+="TLS server extension \"EC point formats\" (id=11), len=$extension_len\n" ;; - 000C) tls_extensions+="TLS server extension \"SRP\" (id=12), len=$extension_len\n" ;; - 000D) tls_extensions+="TLS server extension \"signature algorithms\" (id=13), len=$extension_len\n" ;; - 000E) tls_extensions+="TLS server extension \"use SRTP\" (id=14), len=$extension_len\n" ;; - 000F) tls_extensions+="TLS server extension \"heartbeat\" (id=15), len=$extension_len\n" ;; - 0010) tls_extensions+="TLS server extension \"application layer protocol negotiation\" (id=16), len=$extension_len\n" - if [[ "$process_full" =~ all ]]; then - if [[ $extension_len -lt 4 ]]; then - debugme echo "Malformed application layer protocol negotiation extension." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - echo -n "ALPN protocol: " >> $TMPFILE - offset=$((extns_offset+12+i)) - j=2*$(hex2dec "${tls_serverhello_ascii:offset:4}") - if [[ $extension_len -ne $j+4 ]] || [[ $j -lt 2 ]]; then - debugme echo "Malformed application layer protocol negotiation extension." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - offset=$((offset+4)) - j=2*$(hex2dec "${tls_serverhello_ascii:offset:2}") - if [[ $extension_len -ne $j+6 ]]; then - debugme echo "Malformed application layer protocol negotiation extension." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - offset=$((offset+2)) - asciihex_to_binary "${tls_serverhello_ascii:offset:j}" >> "$TMPFILE" - echo "" >> $TMPFILE - echo "===============================================================================" >> $TMPFILE - fi - ;; - 0011) tls_extensions+="TLS server extension \"certificate status version 2\" (id=17), len=$extension_len\n" ;; - 0012) tls_extensions+="TLS server extension \"signed certificate timestamps\" (id=18), len=$extension_len\n" ;; - 0013) tls_extensions+="TLS server extension \"client certificate type\" (id=19), len=$extension_len\n" ;; - 0014) tls_extensions+="TLS server extension \"server certificate type\" (id=20), len=$extension_len\n" ;; - 0015) tls_extensions+="TLS server extension \"TLS padding\" (id=21), len=$extension_len\n" ;; - 0016) tls_extensions+="TLS server extension \"encrypt-then-mac\" (id=22), len=$extension_len\n" ;; - 0017) tls_extensions+="TLS server extension \"extended master secret\" (id=23), len=$extension_len\n" ;; - 0018) tls_extensions+="TLS server extension \"token binding\" (id=24), len=$extension_len\n" ;; - 0019) tls_extensions+="TLS server extension \"cached info\" (id=25), len=$extension_len\n" ;; - 0023) tls_extensions+="TLS server extension \"session ticket\" (id=35), len=$extension_len\n" ;; - 0028|0033) - # The key share extension was renumbered from 40 to 51 in TLSv1.3 draft 23 since a few - # implementations have been using 40 for the extended_random extension. Since the - # server's version may not yet have been determined, assume that both values represent the - # key share extension. - if [[ "$extension_type" == "00$KEY_SHARE_EXTN_NR" ]]; then - tls_extensions+="TLS server extension \"key share\"" - else - tls_extensions+="TLS server extension \"unrecognized extension\"" - fi - if [[ "$extension_type" == 0028 ]]; then - tls_extensions+=" (id=40), len=$extension_len\n" - else - tls_extensions+=" (id=51), len=$extension_len\n" - fi - if [[ "$process_full" =~ all ]] || [[ "$process_full" == ephemeralkey ]]; then - if [[ $extension_len -lt 4 ]]; then - debugme tmln_warning "Malformed key share extension." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - offset=$((extns_offset+12+i)) - named_curve=$(hex2dec "${tls_serverhello_ascii:offset:4}") - offset=$((extns_offset+16+i)) - msg_len=2*"$(hex2dec "${tls_serverhello_ascii:offset:4}")" - if [[ $msg_len -ne $extension_len-8 ]]; then - debugme tmln_warning "Malformed key share extension." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - case $named_curve in - 21) dh_bits=224 ; named_curve_str="P-224" ; named_curve_oid="06052b81040021" ;; - 23) dh_bits=256 ; named_curve_str="P-256" ; named_curve_oid="06082a8648ce3d030107" ;; - 24) dh_bits=384 ; named_curve_str="P-384" ; named_curve_oid="06052b81040022" ;; - 25) dh_bits=521 ; named_curve_str="P-521" ; named_curve_oid="06052b81040023" ;; - 29) dh_bits=253 ; named_curve_str="X25519" ;; - 30) dh_bits=448 ; named_curve_str="X448" ;; - 256) dh_bits=2048 ; named_curve_str="ffdhe2048" ;; - 257) dh_bits=3072 ; named_curve_str="ffdhe3072" ;; - 258) dh_bits=4096 ; named_curve_str="ffdhe4096" ;; - 259) dh_bits=6144 ; named_curve_str="ffdhe6144" ;; - 260) dh_bits=8192 ; named_curve_str="ffdhe8192" ;; - *) named_curve_str="" ; named_curve_oid="" ;; - esac - offset=$((extns_offset+20+i)) - if ! "$HAS_PKEY"; then - # The key can't be extracted without the pkey utility. - key_bitstring="" - elif [[ $named_curve -eq 29 ]]; then - key_bitstring="302a300506032b656e032100${tls_serverhello_ascii:offset:msg_len}" - elif [[ $named_curve -eq 30 ]]; then - key_bitstring="3042300506032b656f033900${tls_serverhello_ascii:offset:msg_len}" - elif [[ $named_curve -lt 256 ]] && [[ -n "$named_curve_oid" ]]; then - len1="$(printf "%02x" $((msg_len/2+1)))" - [[ "0x${len1}" -ge "0x80" ]] && len1="81${len1}" - key_bitstring="03${len1}00${tls_serverhello_ascii:offset:msg_len}" - len2="$(printf "%02x" $((${#named_curve_oid}/2+9)))" - len3="$(printf "%02x" $((${#named_curve_oid}/2+${#key_bitstring}/2+11)))" - [[ "0x${len3}" -ge "0x80" ]] && len3="81${len3}" - key_bitstring="30${len3}30${len2}06072a8648ce3d0201${named_curve_oid}${key_bitstring}" - elif [[ "$named_curve_str" =~ "ffdhe" ]] && [[ "${TLS13_KEY_SHARES[named_curve]}" =~ "BEGIN" ]]; then - dh_param="$($OPENSSL pkey -pubout -outform DER 2>>$ERRFILE <<< "${TLS13_KEY_SHARES[named_curve]}" | hexdump -v -e '16/1 "%02X"')" - - # First is the length of the public-key SEQUENCE, and it is always encoded in four bytes (3082xxxx) - # Next is the length of the parameters SEQUENCE, and it is also always encoded in four bytes (3082xxxx) - dh_param_len=8+2*"$(hex2dec "${dh_param:12:4}")" - dh_param="${dh_param:8:dh_param_len}" - if [[ "0x${tls_serverhello_ascii:offset:2}" -ge 0x80 ]]; then - key_bitstring="00${tls_serverhello_ascii:offset:msg_len}" - msg_len+=2 - else - key_bitstring="${tls_serverhello_ascii:offset:msg_len}" - fi - len1="$(printf "%04x" $((msg_len/2)))" - key_bitstring="0282${len1}$key_bitstring" - len1="$(printf "%04x" $((${#key_bitstring}/2+1)))" - key_bitstring="${dh_param}0382${len1}00$key_bitstring" - len1="$(printf "%04x" $((${#key_bitstring}/2)))" - key_bitstring="3082${len1}$key_bitstring" - fi - if [[ -n "$key_bitstring" ]]; then - key_bitstring="$(asciihex_to_binary "$key_bitstring" | $OPENSSL pkey -pubin -inform DER 2>$ERRFILE)" - if [[ -z "$key_bitstring" ]] && [[ $DEBUG -ge 2 ]]; then - if [[ -n "$named_curve_str" ]]; then - prln_warning "Your $OPENSSL doesn't support $named_curve_str" - else - prln_warning "Your $OPENSSL doesn't support named curve $named_curve" - fi - fi - fi - fi - ;; - 0029) tls_extensions+="TLS server extension \"pre-shared key\" (id=41), len=$extension_len\n" ;; - 002A) tls_extensions+="TLS server extension \"early data\" (id=42), len=$extension_len\n" ;; - 002B) tls_extensions+="TLS server extension \"supported versions\" (id=43), len=$extension_len\n" - if [[ $extension_len -ne 4 ]]; then - debugme tmln_warning "Malformed supported versions extension." - return 1 - fi - offset=$((extns_offset+12+i)) - tls_protocol2="${tls_serverhello_ascii:offset:4}" - DETECTED_TLS_VERSION="$tls_protocol2" - [[ "${DETECTED_TLS_VERSION:0:2}" == 7F ]] && DETECTED_TLS_VERSION="0304" - ;; - 002C) tls_extensions+="TLS server extension \"cookie\" (id=44), len=$extension_len\n" ;; - 002D) tls_extensions+="TLS server extension \"psk key exchange modes\" (id=45), len=$extension_len\n" ;; - 002E) tls_extensions+="TLS server extension \"ticket early data info\" (id=46), len=$extension_len\n" ;; - 002F) tls_extensions+="TLS server extension \"certificate authorities\" (id=47), len=$extension_len\n" ;; - 0030) tls_extensions+="TLS server extension \"oid filters\" (id=48), len=$extension_len\n" ;; - 0031) tls_extensions+="TLS server extension \"post handshake auth\" (id=49), len=$extension_len\n" ;; - 3374) tls_extensions+="TLS server extension \"next protocol\" (id=13172), len=$extension_len\n" - if [[ "$process_full" =~ all ]]; then - local -i protocol_len - echo -n "Protocols advertised by server: " >> $TMPFILE - offset=$((extns_offset+12+i)) - for (( j=0; j<extension_len; j=j+protocol_len+2 )); do - if [[ $extension_len -lt $j+2 ]]; then - debugme echo "Malformed next protocol extension." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - protocol_len=2*$(hex2dec "${tls_serverhello_ascii:offset:2}") - if [[ $extension_len -lt $j+$protocol_len+2 ]]; then - debugme echo "Malformed next protocol extension." - [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - offset=$((offset+2)) - asciihex_to_binary "${tls_serverhello_ascii:offset:protocol_len}" >> "$TMPFILE" - offset=$((offset+protocol_len)) - [[ $j+$protocol_len+2 -lt $extension_len ]] && echo -n ", " >> $TMPFILE - done - echo "" >> $TMPFILE - echo "===============================================================================" >> $TMPFILE - fi - ;; - FF01) tls_extensions+="TLS server extension \"renegotiation info\" (id=65281), len=$extension_len\n" ;; - *) tls_extensions+="TLS server extension \"unrecognized extension\" (id=$(printf "%d\n\n" "0x$extension_type")), len=$extension_len\n" ;; - esac - # After processing all of the extensions in the ServerHello message, - # if it has been determined that the response is TLSv1.3 and the - # response was decrypted, then modify $tls_serverhello_ascii by adding - # the extensions from the EncryptedExtensions and Certificate messages - # and then process them. - if ! "$added_encrypted_extensions" && [[ "$DETECTED_TLS_VERSION" == "0304" ]] && \ - [[ $((i+8+extension_len)) -eq $tls_extensions_len ]]; then - # Note that the encrypted extensions have been added so that - # the aren't added a second time. - added_encrypted_extensions=true - if [[ -n "$tls_encryptedextensions_ascii" ]]; then - tls_serverhello_ascii_len+=$tls_encryptedextensions_ascii_len-4 - tls_extensions_len+=$tls_encryptedextensions_ascii_len-4 - tls_encryptedextensions_ascii_len=$tls_encryptedextensions_ascii_len/2-2 - offset=$((extns_offset+4)) - tls_serverhello_ascii="${tls_serverhello_ascii:0:extns_offset}$(printf "%04X" $((0x${tls_serverhello_ascii:extns_offset:4}+$tls_encryptedextensions_ascii_len)))${tls_serverhello_ascii:offset}${tls_encryptedextensions_ascii:4}" - fi - if [[ -n "$tls_certificate_ascii" ]]; then - # In TLS 1.3, the Certificate message begins with a zero length certificate_request_context. - # In addition, certificate_list is now a list of (certificate, extension) pairs rather than - # just certificates. So, extract the extensions and add them to $tls_serverhello_ascii and - # create a new $tls_certificate_ascii that only contains a list of certificates. - if [[ -n "$tls_certificate_ascii" ]]; then - if [[ "${tls_certificate_ascii:0:2}" != "00" ]]; then - debugme tmln_warning "Malformed Certificate Handshake message in ServerHello." - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - if [[ $tls_certificate_ascii_len -lt 8 ]]; then - debugme tmln_warning "Malformed Certificate Handshake message in ServerHello." - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - certificate_list_len=2*$(hex2dec "${tls_certificate_ascii:2:6}") - if [[ $certificate_list_len -ne $tls_certificate_ascii_len-8 ]]; then - debugme tmln_warning "Malformed Certificate Handshake message in ServerHello." - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - for (( j=8; j < tls_certificate_ascii_len; j=j+extn_len )); do - if [[ $tls_certificate_ascii_len-$j -lt 6 ]]; then - debugme tmln_warning "Malformed Certificate Handshake message in ServerHello." - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - certificate_len=2*$(hex2dec "${tls_certificate_ascii:j:6}") - if [[ $certificate_len -gt $tls_certificate_ascii_len-$j-6 ]]; then - debugme tmln_warning "Malformed Certificate Handshake message in ServerHello." - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - len1=$certificate_len+6 - tls_revised_certificate_msg+="${tls_certificate_ascii:j:len1}" - j+=$len1 - extn_len=2*$(hex2dec "${tls_certificate_ascii:j:4}") - j+=4 - # TODO: Should only the extensions associated with the EE certificate be added to $tls_serverhello_ascii? - tls_serverhello_ascii_len+=$extn_len - tls_extensions_len+=$extn_len - offset=$((extns_offset+4)) - tls_serverhello_ascii="${tls_serverhello_ascii:0:extns_offset}$(printf "%04X" $(( 0x${tls_serverhello_ascii:extns_offset:4}+extn_len/2)) )${tls_serverhello_ascii:offset}${tls_certificate_ascii:j:extn_len}" - done - tls_certificate_ascii_len=${#tls_revised_certificate_msg}+6 - tls_certificate_ascii="$(printf "%06X" $(( tls_certificate_ascii_len/2-3)) )$tls_revised_certificate_msg" - fi - fi - fi - done - fi - - if [[ "$DETECTED_TLS_VERSION" == "0300" ]]; then - echo "Protocol : SSLv3" >> $TMPFILE - else - echo "Protocol : TLSv1.$((0x$DETECTED_TLS_VERSION-0x0301))" >> $TMPFILE - fi - echo "===============================================================================" >> $TMPFILE - if [[ $TLS_NR_CIPHERS -ne 0 ]]; then - if [[ "${tls_cipher_suite:0:2}" == "00" ]]; then - rfc_cipher_suite="$(show_rfc_style "x${tls_cipher_suite:2:2}")" - else - rfc_cipher_suite="$(show_rfc_style "x${tls_cipher_suite:0:4}")" - fi - elif "$HAS_CIPHERSUITES"; then - rfc_cipher_suite="$($OPENSSL ciphers -V -ciphersuites "$TLS13_OSSL_CIPHERS" 'ALL:COMPLEMENTOFALL' | grep -i " 0x${tls_cipher_suite:0:2},0x${tls_cipher_suite:2:2} " | awk '{ print $3 }')" - else - rfc_cipher_suite="$($OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL' | grep -i " 0x${tls_cipher_suite:0:2},0x${tls_cipher_suite:2:2} " | awk '{ print $3 }')" - fi - echo "Cipher : $rfc_cipher_suite" >> $TMPFILE - if [[ $dh_bits -ne 0 ]]; then - if [[ "$named_curve_str" =~ "ffdhe" ]]; then - echo "Server Temp Key: DH, $named_curve_str, $dh_bits bits" >> $TMPFILE - elif [[ "$named_curve_str" == "X25519" ]] || [[ "$named_curve_str" == "X448" ]]; then - echo "Server Temp Key: $named_curve_str, $dh_bits bits" >> $TMPFILE - else - echo "Server Temp Key: ECDH, $named_curve_str, $dh_bits bits" >> $TMPFILE - fi - fi - if [[ -n "$key_bitstring" ]]; then - echo "$key_bitstring" >> $TMPFILE - [[ "${TLS13_KEY_SHARES[named_curve]}" =~ "BEGIN" ]] && \ - echo "${TLS13_KEY_SHARES[named_curve]}" >> $TMPFILE - fi - echo "===============================================================================" >> $TMPFILE - if [[ "0x${DETECTED_TLS_VERSION:2:2}" -le "0x03" ]]; then - case $tls_compression_method in - 00) echo "Compression: NONE" >> $TMPFILE ;; - 01) echo "Compression: zlib compression" >> $TMPFILE ;; - 40) echo "Compression: LZS compression" >> $TMPFILE ;; - *) echo "Compression: unrecognized compression method" >> $TMPFILE ;; - esac - echo "===============================================================================" >> $TMPFILE - fi - [[ -n "$tls_extensions" ]] && echo -e "$tls_extensions" >> $TMPFILE - - if [[ $DEBUG -ge 3 ]]; then - echo "TLS server hello message:" - if [[ $DEBUG -ge 4 ]]; then - echo " tls_protocol: 0x$tls_protocol2" - [[ "0x${DETECTED_TLS_VERSION:2:2}" -le "0x03" ]] && echo " tls_sid_len: 0x$tls_sid_len_hex / = $((tls_sid_len/2))" - fi - if [[ "0x${DETECTED_TLS_VERSION:2:2}" -le "0x03" ]]; then - echo -n " tls_hello_time: 0x$tls_hello_time " - parse_date "$TLS_TIME" "+%Y-%m-%d %r" "%s" # in debugging mode we don't mind the cycles and don't use TLS_DIFFTIME_SET - fi - echo -n " tls_cipher_suite: 0x$tls_cipher_suite" - if [[ -n "$rfc_cipher_suite" ]]; then - echo " ($rfc_cipher_suite)" - else - echo "" - fi - if [[ $dh_bits -ne 0 ]]; then - if [[ "$named_curve_str" =~ "ffdhe" ]]; then - echo " dh_bits: DH, $named_curve_str, $dh_bits bits" - elif [[ "$named_curve_str" == "X25519" ]] || [[ "$named_curve_str" == "X448" ]]; then - echo " dh_bits: $named_curve_str, $dh_bits bits" - else - echo " dh_bits: ECDH, $named_curve_str, $dh_bits bits" - fi - fi - if [[ "0x${DETECTED_TLS_VERSION:2:2}" -le "0x03" ]]; then - echo -n " tls_compression_method: 0x$tls_compression_method " - case $tls_compression_method in - 00) echo "(NONE)" ;; - 01) echo "(zlib compression)" ;; - 40) echo "(LZS compression)" ;; - *) echo "(unrecognized compression method)" ;; - esac - fi - if [[ -n "$tls_extensions" ]]; then - echo -n " tls_extensions: " - newline_to_spaces "$(grep -a 'TLS server extension ' $TMPFILE | \ - sed -e 's/TLS server extension //g' -e 's/\" (id=/\/#/g' \ - -e 's/,.*$/,/g' -e 's/),$/\"/g' \ - -e 's/elliptic curves\/#10/supported_groups\/#10/g')" - echo "" - if [[ "$tls_extensions" =~ supported_groups ]]; then - echo " Supported Groups: $(grep "Supported groups:" "$TMPFILE" | sed 's/Supported groups: //')" - fi - if [[ "$tls_extensions" =~ application\ layer\ protocol\ negotiation ]]; then - echo " ALPN protocol: $(grep "ALPN protocol:" "$TMPFILE" | sed 's/ALPN protocol: //')" - fi - if [[ "$tls_extensions" =~ next\ protocol ]]; then - echo " NPN protocols: $(grep "Protocols advertised by server:" "$TMPFILE" | sed 's/Protocols advertised by server: //')" - fi - fi - tmln_out - fi - - # If a CIPHER_SUITES string was provided, then check that $tls_cipher_suite is in the string. - # this appeared in yassl + MySQL (https://github.com/drwetter/testssl.sh/pull/784) but adds robustness - # to the implementation - if [[ -n "$cipherlist" ]]; then - tls_cipher_suite="$(tolower "$tls_cipher_suite")" - tls_cipher_suite="${tls_cipher_suite:0:2}\\x${tls_cipher_suite:2:2}" - cipherlist_len=${#cipherlist} - for (( i=0; i < cipherlist_len; i=i+8 )); do - # At the right hand side we need the quotes here! - [[ "${cipherlist:i:6}" == "$tls_cipher_suite" ]] && break - done - if [[ $i -ge $cipherlist_len ]]; then - BAD_SERVER_HELLO_CIPHER=true - debugme echo "The ServerHello specifies a cipher suite that wasn't included in the ClientHello." - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - fi - - # If the ClientHello included a supported_versions extension, then check that the - # $DETECTED_TLS_VERSION appeared in the list offered in the ClientHello. - if [[ "${TLS_CLIENT_HELLO:0:2}" == 01 ]]; then - # get position of cipher lists (just after session id) - offset=78+2*$(hex2dec "${TLS_CLIENT_HELLO:76:2}") - # get position of compression methods - offset+=4+2*$(hex2dec "${TLS_CLIENT_HELLO:offset:4}") - # get position of extensions - extns_offset=$offset+6+2*$(hex2dec "${TLS_CLIENT_HELLO:offset:2}") - len1=${#TLS_CLIENT_HELLO} - for (( i=extns_offset; i < len1; i=i+8+extension_len )); do - extension_type="${TLS_CLIENT_HELLO:i:4}" - offset=4+$i - extension_len=2*$(hex2dec "${TLS_CLIENT_HELLO:offset:4}") - if [[ "$extension_type" == 002b ]]; then - offset+=6 - tls_protocol2="$(tolower "$tls_protocol2")" - for (( j=0; j < extension_len-2; j=j+4 )); do - [[ "${TLS_CLIENT_HELLO:offset:4}" == $tls_protocol2 ]] && break - offset+=4 - done - if [[ $j -eq $extension_len-2 ]]; then - debugme echo "The ServerHello specifies a version that wasn't offered in the ClientHello." - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - break - fi - done - fi - - # Now parse the Certificate message. - if [[ "$process_full" =~ all ]]; then - # not sure why we need this - [[ -e "$HOSTCERT" ]] && rm "$HOSTCERT" - [[ -e "$TEMPDIR/intermediatecerts.pem" ]] && > "$TEMPDIR/intermediatecerts.pem" - fi - if [[ $tls_certificate_ascii_len -ne 0 ]]; then - # The first certificate is the server's certificate. If there are anything - # subsequent certificates, they are intermediate certificates. - if [[ $tls_certificate_ascii_len -lt 12 ]]; then - debugme echo "Malformed Certificate Handshake message in ServerHello." - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - certificate_list_len=2*$(hex2dec "${tls_certificate_ascii:0:6}") - if [[ $certificate_list_len -ne $tls_certificate_ascii_len-6 ]]; then - debugme echo "Malformed Certificate Handshake message in ServerHello." - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - - # Place server's certificate in $HOSTCERT - certificate_len=2*$(hex2dec "${tls_certificate_ascii:6:6}") - if [[ $certificate_len -gt $tls_certificate_ascii_len-12 ]]; then - debugme echo "Malformed Certificate Handshake message in ServerHello." - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - asciihex_to_binary "${tls_certificate_ascii:12:certificate_len}" | \ - $OPENSSL x509 -inform DER -outform PEM -out "$HOSTCERT" 2>$ERRFILE - if [[ $? -ne 0 ]]; then - debugme echo "Malformed certificate in Certificate Handshake message in ServerHello." - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - get_pub_key_size - echo "===============================================================================" >> $TMPFILE - echo "---" >> $TMPFILE - echo "Certificate chain" >> $TMPFILE - subjectDN="$($OPENSSL x509 -in $HOSTCERT -noout -subject 2>>$ERRFILE)" - issuerDN="$($OPENSSL x509 -in $HOSTCERT -noout -issuer 2>>$ERRFILE)" - echo " $nr_certs s:${subjectDN:9}" >> $TMPFILE - echo " i:${issuerDN:8}" >> $TMPFILE - cat "$HOSTCERT" >> $TMPFILE - - echo "" > "$TEMPDIR/intermediatecerts.pem" - # Place any additional certificates in $TEMPDIR/intermediatecerts.pem - CERTIFICATE_LIST_ORDERING_PROBLEM=false - CAissuerDN="$issuerDN" - for (( i=12+certificate_len; i<tls_certificate_ascii_len; i=i+certificate_len )); do - if [[ $tls_certificate_ascii_len-$i -lt 6 ]]; then - debugme echo "Malformed Certificate Handshake message in ServerHello." - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - certificate_len=2*$(hex2dec "${tls_certificate_ascii:i:6}") - i+=6 - if [[ $certificate_len -gt $tls_certificate_ascii_len-$i ]]; then - debugme echo "Malformed certificate in Certificate Handshake message in ServerHello." - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - pem_certificate="$(asciihex_to_binary "${tls_certificate_ascii:i:certificate_len}" | \ - $OPENSSL x509 -inform DER -outform PEM 2>$ERRFILE)" - if [[ $? -ne 0 ]]; then - debugme echo "Malformed certificate in Certificate Handshake message in ServerHello." - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - nr_certs+=1 - CAsubjectDN="$($OPENSSL x509 -noout -subject 2>>$ERRFILE <<< "$pem_certificate")" - # Check that this certificate certifies the one immediately preceding it. - [[ "${CAsubjectDN:9}" != "${CAissuerDN:8}" ]] && CERTIFICATE_LIST_ORDERING_PROBLEM=true - CAissuerDN="$($OPENSSL x509 -noout -issuer 2>>$ERRFILE <<< "$pem_certificate")" - echo " $nr_certs s:${CAsubjectDN:9}" >> $TMPFILE - echo " i:${CAissuerDN:8}" >> $TMPFILE - echo "$pem_certificate" >> $TMPFILE - echo "$pem_certificate" >> "$TEMPDIR/intermediatecerts.pem" - if [[ -z "$hostcert_issuer" ]] && [[ "${CAsubjectDN:9}" == "${issuerDN:8}" ]]; then - # The issuer's certificate is needed if there is a stapled OCSP response, - # and it may be needed if check_revocation_ocsp() will later be called - # with the OCSP URI in the server's certificate. - hostcert_issuer="$TEMPDIR/hostcert_issuer.pem" - echo "$pem_certificate" > "$hostcert_issuer" - fi - done - echo "---" >> $TMPFILE - echo "Server certificate" >> $TMPFILE - echo "subject=${subjectDN:9}" >> $TMPFILE - echo "issuer=${issuerDN:8}" >> $TMPFILE - echo "---" >> $TMPFILE - fi - - # Now parse the certificate status message - if [[ $tls_certificate_status_ascii_len -ne 0 ]] && [[ $tls_certificate_status_ascii_len -lt 8 ]]; then - debugme echo "Malformed certificate status Handshake message in ServerHello." - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - elif [[ $tls_certificate_status_ascii_len -ne 0 ]] && [[ "${tls_certificate_status_ascii:0:2}" == "01" ]]; then - # This is a certificate status message of type "ocsp" - ocsp_response_len=2*$(hex2dec "${tls_certificate_status_ascii:2:6}") - if [[ $ocsp_response_len -ne $tls_certificate_status_ascii_len-8 ]]; then - debugme echo "Malformed certificate status Handshake message in ServerHello." - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - ocsp_resp_offset=8 - elif [[ $tls_certificate_status_ascii_len -ne 0 ]] && [[ "${tls_certificate_status_ascii:0:2}" == "02" ]]; then - # This is a list of OCSP responses, but only the first one is needed - # since the first one corresponds to the server's certificate. - ocsp_response_list_len=2*$(hex2dec "${tls_certificate_status_ascii:2:6}") - if [[ $ocsp_response_list_len -ne $tls_certificate_status_ascii_len-8 ]] || [[ $ocsp_response_list_len -lt 6 ]]; then - debugme echo "Malformed certificate status Handshake message in ServerHello." - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - ocsp_response_len=2*$(hex2dec "${tls_certificate_status_ascii:8:6}") - if [[ $ocsp_response_len -gt $ocsp_response_list_len-6 ]]; then - debugme echo "Malformed certificate status Handshake message in ServerHello." - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - ocsp_resp_offset=14 - fi - STAPLED_OCSP_RESPONSE="" - if [[ $ocsp_response_len -ne 0 ]]; then - STAPLED_OCSP_RESPONSE="${tls_certificate_status_ascii:ocsp_resp_offset:ocsp_response_len}" - echo "OCSP response:" >> $TMPFILE - echo "===============================================================================" >> $TMPFILE - if [[ -n "$hostcert_issuer" ]]; then - asciihex_to_binary "$STAPLED_OCSP_RESPONSE" | \ - $OPENSSL ocsp -no_nonce -CAfile $TEMPDIR/intermediatecerts.pem -issuer $hostcert_issuer -cert $HOSTCERT -respin /dev/stdin -resp_text >> $TMPFILE 2>$ERRFILE - else - asciihex_to_binary "$STAPLED_OCSP_RESPONSE" | \ - $OPENSSL ocsp -respin /dev/stdin -resp_text >> $TMPFILE 2>$ERRFILE - fi - echo "===============================================================================" >> $TMPFILE - elif [[ "$process_full" =~ all ]]; then - echo "OCSP response: no response sent" >> $TMPFILE - echo "===============================================================================" >> $TMPFILE - fi - - # Now parse the server key exchange message - if [[ $tls_serverkeyexchange_ascii_len -ne 0 ]]; then - if [[ $rfc_cipher_suite =~ TLS_ECDHE_ ]] || [[ $rfc_cipher_suite =~ TLS_ECDH_anon ]] || \ - [[ $rfc_cipher_suite == ECDHE* ]] || [[ $rfc_cipher_suite == AECDH* ]]; then - if [[ $tls_serverkeyexchange_ascii_len -lt 6 ]]; then - debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - curve_type=$(hex2dec "${tls_serverkeyexchange_ascii:0:2}") - if [[ $curve_type -eq 3 ]]; then - # named_curve - the curve is identified by a 2-byte number - named_curve=$(hex2dec "${tls_serverkeyexchange_ascii:2:4}") - # https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8 - case $named_curve in - 1) dh_bits=163 ; named_curve_str="K-163" ;; - 2) dh_bits=162 ; named_curve_str="sect163r1" ;; - 3) dh_bits=163 ; named_curve_str="B-163" ;; - 4) dh_bits=193 ; named_curve_str="sect193r1" ;; - 5) dh_bits=193 ; named_curve_str="sect193r2" ;; - 6) dh_bits=232 ; named_curve_str="K-233" ;; - 7) dh_bits=233 ; named_curve_str="B-233" ;; - 8) dh_bits=238 ; named_curve_str="sect239k1" ;; - 9) dh_bits=281 ; named_curve_str="K-283" ;; - 10) dh_bits=282 ; named_curve_str="B-283" ;; - 11) dh_bits=407 ; named_curve_str="K-409" ;; - 12) dh_bits=409 ; named_curve_str="B-409" ;; - 13) dh_bits=570 ; named_curve_str="K-571" ;; - 14) dh_bits=570 ; named_curve_str="B-571" ;; - 15) dh_bits=161 ; named_curve_str="secp160k1" ;; - 16) dh_bits=161 ; named_curve_str="secp160r1" ;; - 17) dh_bits=161 ; named_curve_str="secp160r2" ;; - 18) dh_bits=192 ; named_curve_str="secp192k1" ;; - 19) dh_bits=192 ; named_curve_str="P-192" ;; - 20) dh_bits=225 ; named_curve_str="secp224k1" ;; - 21) dh_bits=224 ; named_curve_str="P-224" ;; - 22) dh_bits=256 ; named_curve_str="secp256k1" ;; - 23) dh_bits=256 ; named_curve_str="P-256" ;; - 24) dh_bits=384 ; named_curve_str="P-384" ;; - 25) dh_bits=521 ; named_curve_str="P-521" ;; - 26) dh_bits=256 ; named_curve_str="brainpoolP256r1" ;; - 27) dh_bits=384 ; named_curve_str="brainpoolP384r1" ;; - 28) dh_bits=512 ; named_curve_str="brainpoolP512r1" ;; - 29) dh_bits=253 ; named_curve_str="X25519" ;; - 30) dh_bits=448 ; named_curve_str="X448" ;; - esac - fi - if [[ $dh_bits -ne 0 ]] && [[ $named_curve -ne 29 ]] && [[ $named_curve -ne 30 ]]; then - [[ $DEBUG -ge 3 ]] && echo -e " dh_bits: ECDH, $named_curve_str, $dh_bits bits\n" - echo "Server Temp Key: ECDH, $named_curve_str, $dh_bits bits" >> $TMPFILE - elif [[ $dh_bits -ne 0 ]]; then - [[ $DEBUG -ge 3 ]] && echo -e " dh_bits: $named_curve_str, $dh_bits bits\n" - echo "Server Temp Key: $named_curve_str, $dh_bits bits" >> $TMPFILE - fi - elif [[ $rfc_cipher_suite =~ TLS_DHE_ ]] || [[ $rfc_cipher_suite =~ TLS_DH_anon ]] || \ - [[ $rfc_cipher_suite == "DHE-"* ]] || [[ $rfc_cipher_suite == "EDH-"* ]] || \ - [[ $rfc_cipher_suite == "EXP1024-DHE-"* ]]; then - # For DH ephemeral keys the first field is p, and the length of - # p is the same as the length of the public key. - if [[ $tls_serverkeyexchange_ascii_len -lt 4 ]]; then - debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - dh_p_len=2*$(hex2dec "${tls_serverkeyexchange_ascii:0:4}") - offset=4+$dh_p_len - if [[ $tls_serverkeyexchange_ascii_len -lt $offset ]]; then - debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - - # Subtract any leading 0 bytes - for (( i=4; i < offset; i=i+2 )); do - [[ "${tls_serverkeyexchange_ascii:i:2}" != "00" ]] && break - dh_p_len=$dh_p_len-2 - done - if [[ $i -ge $offset ]]; then - debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." - tmpfile_handle ${FUNCNAME[0]}.txt - return 1 - fi - dh_p="${tls_serverkeyexchange_ascii:i:dh_p_len}" - - dh_bits=4*$dh_p_len - msb=$(hex2dec "${tls_serverkeyexchange_ascii:i:2}") - for (( mask=128; msb < mask; mask/=2 )); do - dh_bits=$dh_bits-1 - done - - key_bitstring="$(get_dh_ephemeralkey "$tls_serverkeyexchange_ascii")" - [[ $? -eq 0 ]] && echo "$key_bitstring" >> $TMPFILE - - # Check to see whether the ephemeral public key uses one of the groups from - # RFC 7919 for parameters - case $dh_bits in - 2048) named_curve=256; named_curve_str=" ffdhe2048," ;; - 3072) named_curve=257; named_curve_str=" ffdhe3072," ;; - 4096) named_curve=258; named_curve_str=" ffdhe4096," ;; - 6144) named_curve=259; named_curve_str=" ffdhe6144," ;; - 8192) named_curve=260; named_curve_str=" ffdhe8192," ;; - *) named_curve=0; named_curve_str="" ;; - esac - [[ -z "$key_bitstring" ]] && named_curve=0 && named_curve_str="" - if "$HAS_PKEY" && [[ $named_curve -ne 0 ]] && [[ "${TLS13_KEY_SHARES[named_curve]}" =~ BEGIN ]]; then - ephemeral_param="$($OPENSSL pkey -pubin -text_pub -noout 2>>$ERRFILE <<< "$key_bitstring")" - # OpenSSL 3.0.0 outputs the group name rather than the actual parameter values for some named groups. - if [[ "$ephemeral_param" =~ GROUP: ]]; then - ephemeral_param="${ephemeral_param#*GROUP: }" - rfc7919_param="${named_curve_str# }" - rfc7919_param="${rfc7919_param%,}" - [[ "$ephemeral_param" =~ $rfc7919_param ]] || named_curve_str="" - else - ephemeral_param="$(grep -EA 1000 "prime:|P:" <<< "$ephemeral_param")" - rfc7919_param="$($OPENSSL pkey -text_pub -noout 2>>$ERRFILE <<< "${TLS13_KEY_SHARES[named_curve]}" | grep -EA 1000 "prime:|P:")" - [[ "$ephemeral_param" != "$rfc7919_param" ]] && named_curve_str="" - fi - fi - - [[ $DEBUG -ge 3 ]] && [[ $dh_bits -ne 0 ]] && echo -e " dh_bits: DH,$named_curve_str $dh_bits bits\n" - [[ $dh_bits -ne 0 ]] && echo "Server Temp Key: DH,$named_curve_str $dh_bits bits" >> $TMPFILE - fi - fi - tmpfile_handle ${FUNCNAME[0]}.txt - - TLS_SERVER_HELLO="02$(printf "%06x" $(( tls_serverhello_ascii_len/2)) )${tls_serverhello_ascii}" - return 0 -} - - -#arg1 (optional): list of ciphers suites or empty -#arg2 (optional): "true" if full server response should be parsed. -# return: 6: couldn't open socket, 3(!): sslv2 handshake succeeded, 0=no SSLv2 -# 1,4,6,7: see return value of parse_sslv2_serverhello() -sslv2_sockets() { - local ret - local cipher_suites="$1" - local client_hello len_client_hello - local len_ciph_suites_byte len_ciph_suites - local server_hello sock_reply_file2 foo - local -i response_len server_hello_len - local parse_complete=false - - # this could be empty so we use '==' - if [[ "$2" == true ]]; then - parse_complete=true - fi - if [[ -z "$cipher_suites" ]]; then - cipher_suites=" - 05,00,80, # 1st cipher 9 cipher specs, only classical V2 ciphers are used here, see FIXME below - 03,00,80, # 2nd there are v3 in v2!!! : https://tools.ietf.org/html/rfc6101#appendix-E - 01,00,80, # 3rd Cipher specifications introduced in version 3.0 can be included in version 2.0 client hello messages using - 07,00,c0, # 4th the syntax below. [..] # V2CipherSpec (see Version 3.0 name) = { 0x00, CipherSuite }; !!!! - 08,00,80, # 5th - 06,00,40, # 6th - 04,00,80, # 7th - 02,00,80, # 8th - 06,01,40, # 9 - 07,01,c0, # 10 - FF,80,00, # 11 - FF,80,10, # 12 - 00,00,00" # 13 - # FIXME: http://max.euston.net/d/tip_sslciphers.html <-- also SSLv3 ciphers - fi - - code2network "$cipher_suites" # convert CIPHER_SUITES - cipher_suites="$NW_STR" # we don't have the leading \x here so string length is two byte less, see next - len_ciph_suites_byte=${#cipher_suites} - - let "len_ciph_suites_byte += 2" - len_ciph_suites=$(printf "%02x\n" $(( len_ciph_suites_byte / 4 ))) - len_client_hello=$(printf "%02x\n" $((0x$len_ciph_suites + 0x19))) - - client_hello=" - ,80,$len_client_hello # length - ,01 # Client Hello - ,00,02 # SSLv2 - ,00,$len_ciph_suites # cipher spec length - ,00,00 # session ID length - ,00,10 # challenge length - ,$cipher_suites - ,29,22,be,b3,5a,01,8b,04,fe,5f,80,03,a0,13,eb,c4" # Challenge - # https://idea.popcount.org/2012-06-16-dissecting-ssl-handshake/ (client) - - fd_socket 5 || return 6 - debugme echo -n "sending client hello... " - socksend_clienthello "$client_hello" - - sockread_serverhello 32768 - if "$parse_complete"; then - if [[ -s "$SOCK_REPLY_FILE" ]]; then - server_hello=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE") - server_hello_len=$((2 + $(hex2dec "${server_hello:1:3}") )) - foo="$(wc -c "$SOCK_REPLY_FILE")" - response_len="${foo% *}" - for (( 1; response_len < server_hello_len; 1 )); do - sock_reply_file2=${SOCK_REPLY_FILE}.2 - mv "$SOCK_REPLY_FILE" "$sock_reply_file2" - - debugme echo -n "requesting more server hello data... " - socksend "" $USLEEP_SND - sockread_serverhello 32768 - - [[ ! -s "$SOCK_REPLY_FILE" ]] && break - cat "$SOCK_REPLY_FILE" >> "$sock_reply_file2" - mv "$sock_reply_file2" "$SOCK_REPLY_FILE" - foo="$(wc -c "$SOCK_REPLY_FILE")" - response_len="${foo% *}" - done - fi - fi - - debugme echo "reading server hello... " - if [[ "$DEBUG" -ge 4 ]]; then - hexdump -C "$SOCK_REPLY_FILE" | head -6 - tmln_out - fi - - parse_sslv2_serverhello "$SOCK_REPLY_FILE" "$parse_complete" - ret=$? - - close_socket - tmpfile_handle ${FUNCNAME[0]}.dd $SOCK_REPLY_FILE - return $ret -} - - -# arg1: supported groups extension -# arg2: "all" - process full response (including Certificate and certificate_status handshake messages) -# "ephemeralkey" - extract the server's ephemeral key (if any) -# Given the supported groups extension, create a key_share extension that includes a key share for -# each group listed in the supported groups extension. -generate_key_share_extension() { - local supported_groups - local -i i len supported_groups_len group - local extn_len list_len - local key_share key_shares="" - local -i nr_key_shares=0 - - supported_groups="${1//\\x/}" - [[ "${supported_groups:0:4}" != "000a" ]] && return 1 - - supported_groups_len=${#supported_groups} - [[ $supported_groups_len -lt 16 ]] && return 1 - - len=2*$(hex2dec "${supported_groups:4:4}") - [[ $len+8 -ne $supported_groups_len ]] && return 1 - - len=2*$(hex2dec "${supported_groups:8:4}") - [[ $len+12 -ne $supported_groups_len ]] && return 1 - - for (( i=12; i<supported_groups_len; i=i+4 )); do - group=$(hex2dec "${supported_groups:i:4}") - # If the Supported groups extensions lists more than one group, - # then don't include the larger key shares in the extension. - [[ $i -gt 12 ]] && [[ $group -gt 256 ]] && continue - - # Versions of OpenSSL prior to 1.1.0 cannot perform operations - # with X25519 keys, so don't include the X25519 key share - # if the server's response needs to be decrypted and an - # older version of OpenSSL is being used. - if [[ $i -gt 12 ]] && [[ $group -eq 29 ]] && [[ "$2" == all ]]; then - [[ "$OSSL_NAME" =~ LibreSSL ]] && continue - if [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR != 1.1.0* ]] && \ - [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR != 1.1.1* ]] && \ - [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR != 3.0.0* ]]; then - continue - fi - fi - - # Versions of OpenSSL prior to 1.1.1 cannot perform operations - # with X448 keys, so don't include the X448 key share - # if the server's response needs to be decrypted and an - # older version of OpenSSL is being used. - if [[ $i -gt 12 ]] && [[ $group -eq 30 ]] && [[ "$2" == all ]]; then - [[ "$OSSL_NAME" =~ LibreSSL ]] && continue - if [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR != 1.1.1* ]] && \ - [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR != 3.0.0* ]]; then - continue - fi - fi - - # NOTE: The public keys could be extracted from the private keys - # (TLS13_KEY_SHARES) using $OPENSSL, but only OpenSSL 1.1.0 and newer can - # extract the public key from an X25519 private key, and only - # OpenSSL 1.1.1 can extract the public key from an X448 private key. - key_share="${TLS13_PUBLIC_KEY_SHARES[group]}" - if [[ ${#key_share} -gt 4 ]]; then - key_shares+=",$key_share" - nr_key_shares+=1 - # Don't include more than two keys, so that the extension isn't too large. - [[ $nr_key_shares -ge 2 ]] && break - fi - done - [[ -z "$key_shares" ]] && tm_out "" && return 0 - - len=${#key_shares}/3 - list_len="$(printf "%04x" "$len")" - len+=2 - extn_len="$(printf "%04x" "$len")" - tm_out "00,$KEY_SHARE_EXTN_NR,${extn_len:0:2},${extn_len:2:2},${list_len:0:2},${list_len:2:2}$key_shares" - return 0 -} - -# ARG1: TLS version low byte (00: SSLv3, 01: TLS 1.0, 02: TLS 1.1, 03: TLS 1.2) -# ARG2: CIPHER_SUITES string (lowercase, and in the format output by code2network()) -# ARG3: "all" - process full response (including Certificate and certificate_status handshake messages) -# "all+" - same as "all", but do not offer any curves with TLSv1.3 that are not supported by -# $OPENSSL, since response MUST be decrypted. -# "ephemeralkey" - extract the server's ephemeral key (if any) -# ARG4: (optional) additional request extensions -# ARG5: (optional): "true" if ClientHello should advertise compression methods other than "NULL" -# ARG6: (optional): "false" if prepare_tls_clienthello() should not open a new socket -# -prepare_tls_clienthello() { - local tls_low_byte="$1" tls_legacy_version="$1" - local process_full="$3" - local new_socket=true - local tls_word_reclayer="03, 01" # the first TLS version number is the record layer and always 0301 - # -- except: SSLv3 and second ClientHello after HelloRetryRequest - local servername_hexstr len_servername len_servername_hex - local hexdump_format_str part1 part2 - local all_extensions="" - local -i i j len_extension len_padding_extension len_all len_session_id - local len_sni_listlen len_sni_ext len_extension_hex len_padding_extension_hex - local cipher_suites len_ciph_suites len_ciph_suites_byte len_ciph_suites_word - local len_client_hello_word len_all_word - local ecc_cipher_suite_found=false - local extension_signature_algorithms extension_heartbeat session_id - local extension_session_ticket extension_next_protocol extension_padding - local extension_supported_groups="" extension_supported_point_formats="" - local extensions_key_share="" extn_type supported_groups_c2n="" extn_psk_mode="" - local extra_extensions extra_extensions_list="" extension_supported_versions="" - local offer_compression=false compression_methods - - # TLSv1.3 ClientHello messages MUST specify only the NULL compression method. - [[ "$5" == true ]] && [[ "0x$tls_low_byte" -le "0x03" ]] && offer_compression=true - [[ "$6" == false ]] && new_socket=false - - cipher_suites="$2" # we don't have the leading \x here so string length is two byte less, see next - len_ciph_suites_byte=${#cipher_suites} - let "len_ciph_suites_byte += 2" - - # we have additional 2 chars \x in each 2 byte string and 2 byte ciphers, so we need to divide by 4: - len_ciph_suites=$(printf "%02x\n" $(( len_ciph_suites_byte / 4 ))) - len2twobytes "$len_ciph_suites" - len_ciph_suites_word="$LEN_STR" - #[[ $DEBUG -ge 3 ]] && echo $len_ciph_suites_word - - if [[ "$tls_low_byte" != "00" ]]; then - # Add extensions - - # Check to see if any ECC cipher suites are included in cipher_suites - # (not needed for TLSv1.3) - if [[ "0x$tls_low_byte" -le "0x03" ]]; then - for (( i=0; i<len_ciph_suites_byte; i=i+8 )); do - j=$i+4 - part1="0x${cipher_suites:$i:2}" - part2="0x${cipher_suites:$j:2}" - if [[ "$part1" == 0xc0 ]]; then - if [[ "$part2" -ge 0x01 ]] && [[ "$part2" -le 0x19 ]]; then - ecc_cipher_suite_found=true && break - elif [[ "$part2" -ge 0x23 ]] && [[ "$part2" -le 0x3b ]]; then - ecc_cipher_suite_found=true && break - elif [[ "$part2" -ge 0x48 ]] && [[ "$part2" -le 0x4f ]]; then - ecc_cipher_suite_found=true && break - elif [[ "$part2" -ge 0x5c ]] && [[ "$part2" -le 0x63 ]]; then - ecc_cipher_suite_found=true && break - elif [[ "$part2" -ge 0x70 ]] && [[ "$part2" -le 0x79 ]]; then - ecc_cipher_suite_found=true && break - elif [[ "$part2" -ge 0x86 ]] && [[ "$part2" -le 0x8d ]]; then - ecc_cipher_suite_found=true && break - elif [[ "$part2" -ge 0x9a ]] && [[ "$part2" -le 0x9b ]]; then - ecc_cipher_suite_found=true && break - elif [[ "$part2" -ge 0xac ]] && [[ "$part2" -le 0xaf ]]; then - ecc_cipher_suite_found=true && break - fi - elif [[ "$part1" == 0xcc ]]; then - if [[ "$part2" == 0xa8 ]] || [[ "$part2" == 0xa9 ]] || \ - [[ "$part2" == 0xac ]] || [[ "$part2" == 0x13 ]] || \ - [[ "$part2" == 0x14 ]]; then - ecc_cipher_suite_found=true && break - fi - fi - done - fi - - if [[ -n "$SNI" ]]; then - #formatted example for SNI - #00 00 # extension server_name - #00 1a # length = the following +2 = server_name length + 5 - #00 18 # server_name list_length = server_name length +3 - #00 # server_name type (hostname) - #00 15 # server_name length - #66 66 66 66 66 66 2e 66 66 66 66 66 66 66 66 66 66 2e 66 66 66 target.mydomain1.tld # server_name target - len_servername=${#NODE} - hexdump_format_str="$len_servername/1 \"%02x,\"" - servername_hexstr=$(printf $NODE | hexdump -v -e "${hexdump_format_str}" | sed 's/,$//') - # convert lengths we need to fill in from dec to hex: - len_servername_hex=$(printf "%02x\n" $len_servername) - len_sni_listlen=$(printf "%02x\n" $((len_servername+3))) - len_sni_ext=$(printf "%02x\n" $((len_servername+5))) - fi - - if [[ 0x$tls_low_byte -le 0x03 ]]; then - extension_signature_algorithms=" - 00, 0d, # Type: signature_algorithms , see RFC 5246 - 00, 20, 00,1e, # lengths - 06,01, 06,02, 06,03, 05,01, 05,02, 05,03, 04,01, 04,02, 04,03, - 03,01, 03,02, 03,03, 02,01, 02,02, 02,03" - else - extension_signature_algorithms=" - 00, 0d, # Type: signature_algorithms , see RFC 8446 - 00, 22, 00, 20, # lengths - 04,03, 05,03, 06,03, 08,04, 08,05, 08,06, - 04,01, 05,01, 06,01, 08,09, 08,0a, 08,0b, - 08,07, 08,08, 02,01, 02,03" - fi - - extension_heartbeat=" - 00, 0f, 00, 01, 01" - - extension_session_ticket=" - 00, 23, 00, 00" - - extension_next_protocol=" - 33, 74, 00, 00" - - extn_psk_mode=" - 00, 2d, 00, 02, 01, 01" - - if "$ecc_cipher_suite_found"; then - # Supported Groups Extension - extension_supported_groups=" - 00, 0a, # Type: Supported Elliptic Curves , see RFC 4492 - 00, 3e, 00, 3c, # lengths - 00, 0e, 00, 0d, 00, 19, 00, 1c, 00, 1e, 00, 0b, 00, 0c, 00, 1b, - 00, 18, 00, 09, 00, 0a, 00, 1a, 00, 16, 00, 17, 00, 1d, 00, 08, - 00, 06, 00, 07, 00, 14, 00, 15, 00, 04, 00, 05, 00, 12, 00, 13, - 00, 01, 00, 02, 00, 03, 00, 0f, 00, 10, 00, 11" - elif [[ 0x$tls_low_byte -gt 0x03 ]]; then - # Supported Groups Extension - if [[ ! "$process_full" =~ all ]] || ( [[ ! "$OSSL_NAME" =~ LibreSSL ]] && \ - ( [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 1.1.1* ]] || \ - [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 3.0.0* ]] ) ); then - extension_supported_groups=" - 00,0a, # Type: Supported Groups, see RFC 8446 - 00,10, 00,0e, # lengths - 00,1d, 00,17, 00,1e, 00,18, 00,19, - 01,00, 01,01" - # OpenSSL prior to 1.1.1 does not support X448, so list it as the least - # preferred option if the response needs to be decrypted, and do not - # list it at all if the response MUST be decrypted. - elif [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 1.1.0* ]] && [[ "$process_full" == all+ ]]; then - extension_supported_groups=" - 00,0a, # Type: Supported Groups, see RFC 8446 - 00,0e, 00,0c, # lengths - 00,1d, 00,17, 00,18, 00,19, - 01,00, 01,01" - elif [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == "1.1.0"* ]]; then - extension_supported_groups=" - 00,0a, # Type: Supported Groups, see RFC 8446 - 00,10, 00,0e, # lengths - 00,1d, 00,17, 00,18, 00,19, - 01,00, 01,01, 00,1e" - # OpenSSL prior to 1.1.0 does not support either X25519 or X448, - # so list them as the least referred options if the response - # needs to be decrypted, and do not list them at all if the - # response MUST be decrypted. - elif [[ "$process_full" == all+ ]]; then - extension_supported_groups=" - 00,0a, # Type: Supported Groups, see RFC 8446 - 00,0c, 00,0a, # lengths - 00,17, 00,18, 00,19, - 01,00, 01,01" - else - extension_supported_groups=" - 00,0a, # Type: Supported Groups, see RFC 8446 - 00,10, 00,0e, # lengths - 00,17, 00,18, 00,19, - 01,00, 01,01, 00,1d, 00,1e" - fi - - code2network "$extension_supported_groups" - supported_groups_c2n="$NW_STR" - fi - - if "$ecc_cipher_suite_found" || [[ 0x$tls_low_byte -gt 0x03 ]]; then - # Supported Point Formats Extension. - extension_supported_point_formats=" - 00, 0b, # Type: Supported Point Formats , see RFC 4492 - 00, 02, # len - 01, 00" - fi - - # Each extension should appear in the ClientHello at most once. So, - # find out what extensions were provided as an argument and only use - # the provided values for those extensions. - extra_extensions="$(tolower "$4")" - code2network "$extra_extensions" - len_all=${#NW_STR} - for (( i=0; i < len_all; i=i+16+4*0x$len_extension_hex )); do - part2=$i+4 - extn_type="${NW_STR:i:2}${NW_STR:part2:2}" - extra_extensions_list+=" $extn_type " - j=$i+8 - part2=$j+4 - len_extension_hex="${NW_STR:j:2}${NW_STR:part2:2}" - if [[ "$extn_type" == "000a" ]] && [[ 0x$tls_low_byte -gt 0x03 ]]; then - j=14+4*0x$len_extension_hex - supported_groups_c2n="${NW_STR:i:j}" - fi - done - if [[ 0x$tls_low_byte -gt 0x03 ]]; then - extensions_key_share="$(generate_key_share_extension "$supported_groups_c2n" "$process_full")" - [[ $? -ne 0 ]] && return 1 - fi - - if [[ -n "$SNI" ]] && [[ ! "$extra_extensions_list" =~ " 0000 " ]]; then - all_extensions=" - 00, 00 # extension server_name - ,00, $len_sni_ext # length SNI EXT - ,00, $len_sni_listlen # server_name list_length - ,00 # server_name type (hostname) - ,00, $len_servername_hex # server_name length. We assume len(hostname) < FF - 9 - ,$servername_hexstr" # server_name target - fi - if [[ 0x$tls_low_byte -ge 0x04 ]] && [[ ! "$extra_extensions_list" =~ " 002b " ]]; then - # Add supported_versions extension listing all TLS/SSL versions - # from the one specified in $tls_low_byte to SSLv3. - for (( i=0x$tls_low_byte; i >=0; i=i-1 )); do - if [[ 0x$i -eq 4 ]]; then - # FIXME: The ClientHello currently advertises support for various - # draft versions of TLSv1.3. Eventually it should only adversize - # support for the final version (0304). - if [[ "$KEY_SHARE_EXTN_NR" == 33 ]]; then - extension_supported_versions+=", 03, 04, 7f, 1c, 7f, 1b, 7f, 1a, 7f, 19, 7f, 18, 7f, 17" - else - extension_supported_versions+=", 7f, 16, 7f, 15, 7f, 14, 7f, 13, 7f, 12" - fi - else - extension_supported_versions+=", 03, $(printf "%02x" $i)" - fi - done - [[ -n "$all_extensions" ]] && all_extensions+="," - # FIXME: Adjust the lengths ("+15" and "+14") when the draft versions of TLSv1.3 are removed. - if [[ "$KEY_SHARE_EXTN_NR" == "33" ]]; then - all_extensions+="00, 2b, 00, $(printf "%02x" $((2*0x$tls_low_byte+15))), $(printf "%02x" $((2*0x$tls_low_byte+14)))$extension_supported_versions" - else - all_extensions+="00, 2b, 00, $(printf "%02x" $((2*0x$tls_low_byte+11))), $(printf "%02x" $((2*0x$tls_low_byte+10)))$extension_supported_versions" - fi - fi - - # There does not seem to be any reason to include this extension. However, it appears that - # OpenSSL, Firefox, and Chrome include it in TLS 1.3 ClientHello messages, and there is at - # least one server that will fail the connection if it is absent - # (see https://github.com/drwetter/testssl.sh/issues/990). - if [[ "0x$tls_low_byte" -ge 0x04 ]] && [[ ! "$extra_extensions_list" =~ " 002d " ]]; then - [[ -n "$all_extensions" ]] && all_extensions+="," - all_extensions+="$extn_psk_mode" - fi - - if [[ ! "$extra_extensions_list" =~ " 0023 " ]]; then - [[ -n "$all_extensions" ]] && all_extensions+="," - all_extensions+="$extension_session_ticket" - fi - - # If the ClientHello will include the ALPN extension, then don't include the NPN extension. - if [[ ! "$extra_extensions_list" =~ " 3374 " ]] && [[ ! "$extra_extensions_list" =~ " 0010 " ]]; then - [[ -n "$all_extensions" ]] && all_extensions+="," - all_extensions+="$extension_next_protocol" - fi - - # RFC 5246 says that clients MUST NOT offer the signature algorithms - # extension if they are offering TLS versions prior to 1.2. - if [[ "0x$tls_low_byte" -ge 0x03 ]] && [[ ! "$extra_extensions_list" =~ " 000d " ]]; then - [[ -n "$all_extensions" ]] && all_extensions+="," - all_extensions+="$extension_signature_algorithms" - fi - - if [[ -n "$extension_supported_groups" ]] && [[ ! "$extra_extensions_list" =~ " 000a " ]]; then - [[ -n "$all_extensions" ]] && all_extensions+="," - all_extensions+="$extension_supported_groups" - fi - - if [[ -n "$extensions_key_share" ]] && [[ ! "$extra_extensions_list" =~ " 00$KEY_SHARE_EXTN_NR " ]]; then - [[ -n "$all_extensions" ]] && all_extensions+="," - all_extensions+="$extensions_key_share" - fi - - if [[ -n "$extension_supported_point_formats" ]] && [[ ! "$extra_extensions_list" =~ " 000b " ]]; then - [[ -n "$all_extensions" ]] && all_extensions+="," - all_extensions+="$extension_supported_point_formats" - fi - - if [[ -n "$extra_extensions" ]]; then - [[ -n "$all_extensions" ]] && all_extensions+="," - all_extensions+="$extra_extensions" - fi - - # Make sure that a non-empty extension goes last (either heartbeat or padding). - # See PR #792 and https://www.ietf.org/mail-archive/web/tls/current/msg19720.html. - if [[ ! "$extra_extensions_list" =~ " 000f " ]]; then - [[ -n "$all_extensions" ]] && all_extensions+="," - all_extensions+="$extension_heartbeat" - fi - - code2network "$all_extensions" # convert extensions - all_extensions="$NW_STR" # we don't have the leading \x here so string length is two byte less, see next - len_extension=${#all_extensions} - len_extension+=2 - len_extension=$len_extension/4 - len_extension_hex=$(printf "%02x\n" $len_extension) - - # If the length of the Client Hello would be between 256 and 511 bytes, - # then add a padding extension (see RFC 7685) - len_all=$((0x$len_ciph_suites + 0x2b + 0x$len_extension_hex + 0x2)) - "$offer_compression" && len_all+=2 - [[ 0x$tls_low_byte -gt 0x03 ]] && len_all+=32 # TLSv1.3 ClientHello includes a 32-byte session id - if [[ $len_all -ge 256 ]] && [[ $len_all -le 511 ]] && [[ ! "$extra_extensions_list" =~ " 0015 " ]]; then - if [[ $len_all -ge 508 ]]; then - len_padding_extension=1 # Final extension cannot be empty: see PR #792 - else - len_padding_extension=$((508 - len_all)) - fi - len_padding_extension_hex=$(printf "%02x\n" $len_padding_extension) - len2twobytes "$len_padding_extension_hex" - all_extensions="$all_extensions\\x00\\x15\\x${LEN_STR:0:2}\\x${LEN_STR:4:2}" - for (( i=0; i<len_padding_extension; i++ )); do - all_extensions="$all_extensions\\x00" - done - len_extension=$len_extension+$len_padding_extension+0x4 - len_extension_hex=$(printf "%02x\n" $len_extension) - elif [[ ! "$extra_extensions_list" =~ " 0015 " ]] && ( [[ $((len_all%256)) -eq 10 ]] || [[ $((len_all%256)) -eq 14 ]] ); then - # Some servers fail if the length of the ClientHello is 522, 778, 1034, 1290, ... bytes. - # A few servers also fail if the length is 526, 782, 1038, 1294, ... bytes. - # So, if the ClientHello would be one of these length, add a 5-byte padding extension. - all_extensions="$all_extensions\\x00\\x15\\x00\\x01\\x00" - len_extension+=5 - len_extension_hex=$(printf "%02x\n" $len_extension) - fi - len2twobytes "$len_extension_hex" - all_extensions=" - ,$LEN_STR # first the len of all extensions. - ,$all_extensions" - fi - - if [[ 0x$tls_low_byte -gt 0x03 ]]; then - # TLSv1.3 calls for sending a random 32-byte session id in middlebox compatibility mode. - session_id="20,44,b8,92,56,af,74,52,9e,d8,cf,52,14,c8,af,d8,34,0b,e7,7f,eb,86,01,84,50,5d,e4,a1,6a,09,3b,bf,6e" - len_session_id=32 - else - session_id="00" - len_session_id=0 - fi - - # RFC 3546 doesn't specify SSLv3 to have SNI, openssl just ignores the switch if supplied - if [[ "$tls_low_byte" == "00" ]]; then - len_all=$((0x$len_ciph_suites + len_session_id + 0x27)) - else - len_all=$((0x$len_ciph_suites + len_session_id + 0x27 + 0x$len_extension_hex + 0x2)) - fi - "$offer_compression" && len_all+=2 - len2twobytes $(printf "%02x\n" $len_all) - len_client_hello_word="$LEN_STR" - #[[ $DEBUG -ge 3 ]] && echo $len_client_hello_word - - if [[ "$tls_low_byte" == 00 ]]; then - len_all=$((0x$len_ciph_suites + len_session_id + 0x2b)) - else - len_all=$((0x$len_ciph_suites + len_session_id + 0x2b + 0x$len_extension_hex + 0x2)) - fi - "$offer_compression" && len_all+=2 - len2twobytes $(printf "%02x\n" $len_all) - len_all_word="$LEN_STR" - #[[ $DEBUG -ge 3 ]] && echo $len_all_word - - # if we have SSLv3, the first occurrence of TLS protocol -- record layer -- is SSLv3, otherwise TLS 1.0, - # except in the case of a second ClientHello in TLS 1.3, in which case it is TLS 1.2. - [[ $tls_low_byte == "00" ]] && tls_word_reclayer="03, 00" - - [[ 0x$tls_legacy_version -ge 0x04 ]] && tls_legacy_version="03" - - if "$offer_compression"; then - # See https://www.iana.org/assignments/comp-meth-ids/comp-meth-ids.xhtml#comp-meth-ids-2 - compression_methods="03,01,40,00" # Offer NULL, DEFLATE, and LZS compression - else - compression_methods="01,00" # Only offer NULL compression (0x00) - fi - - TLS_CLIENT_HELLO=" - # TLS header ( 5 bytes) - ,16, $tls_word_reclayer # TLS Version: in wireshark this is always 01 for TLS 1.0-1.2 - ,$len_all_word # Length <--- - # Handshake header: - ,01 # Type (x01 for ClientHello) - ,00, $len_client_hello_word # Length ClientHello - ,03, $tls_legacy_version # TLS version ClientHello - ,54, 51, 1e, 7a # Unix time since see www.moserware.com/2009/06/first-few-milliseconds-of-https.html - ,de, ad, be, ef # Random 28 bytes - ,31, 33, 07, 00, 00, 00, 00, 00 - ,cf, bd, 39, 04, cc, 16, 0b, 85 - ,03, 90, 9f, 77, 04, 33, d4, de - ,$session_id - ,$len_ciph_suites_word # Cipher suites length - ,$cipher_suites - ,$compression_methods" - - if "$new_socket"; then - fd_socket 5 || return 6 - fi - - debugme echo -n "sending client hello... " - socksend_clienthello "$TLS_CLIENT_HELLO$all_extensions" $USLEEP_SND - - if [[ "$tls_low_byte" -gt 0x03 ]]; then - TLS_CLIENT_HELLO="$(tolower "$NW_STR")" - TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\x0\\/\\x00\\}" - TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\x1\\/\\x01\\}" - TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\x2\\/\\x02\\}" - TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\x3\\/\\x03\\}" - TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\x4\\/\\x04\\}" - TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\x5\\/\\x05\\}" - TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\x6\\/\\x06\\}" - TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\x7\\/\\x07\\}" - TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\x8\\/\\x08\\}" - TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\x9\\/\\x09\\}" - TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\xa\\/\\x0a\\}" - TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\xb\\/\\x0b\\}" - TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\xc\\/\\x0c\\}" - TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\xd\\/\\x0d\\}" - TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\xe\\/\\x0e\\}" - TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\xf\\/\\x0f\\}" - TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\x/}" - TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO:10}" - fi - - return 0 -} - -# arg1: The original ClientHello -# arg2: The server's response -# Return 0 if the response is not a HelloRetryRequest. -# Return 1 if the response is a malformed HelloRetryRequest or if a new ClientHello cannot be sent. -# Return 2 if the response is a HelloRetryRequest, and sending a new ClientHello succeeded. -# Return 6 if the response is a HelloRetryRequest, and sending a new ClientHello failed. -resend_if_hello_retry_request() { - local original_clienthello="$1" - local tls_hello_ascii="$2" - local msg_type tls_low_byte server_version cipher_suite rfc_cipher_suite - local key_share="" new_key_share="" cookie="" second_clienthello data="" - local -i i j msg_len tls_hello_ascii_len sid_len - local -i extns_offset hrr_extns_len extra_extensions_len len_extn - local extra_extensions extn_type part2 new_extra_extns="" - local sha256_hrr="CF21AD74E59A6111BE1D8C021E65B891C2A211167ABB8C5E079E09E2C8A8339C" - - tls_hello_ascii_len=${#tls_hello_ascii} - # A HelloRetryRequest is at least 13 bytes long - [[ $tls_hello_ascii_len -lt 26 ]] && return 0 - # A HelloRetryRequest is a handshake message (16) with a major record version of 03. - [[ "${tls_hello_ascii:0:4}" != 1603 ]] && return 0 - msg_type="${tls_hello_ascii:10:2}" - if [[ "$msg_type" == 02 ]]; then - # A HRR is a ServerHello with a Random value equal to the - # SHA-256 hash of "HelloRetryRequest" - [[ $tls_hello_ascii_len -lt 76 ]] && return 0 - [[ "${tls_hello_ascii:22:64}" != $sha256_hrr ]] && return 0 - elif [[ "$msg_type" != 06 ]]; then - # The handshake type for hello_retry_request in draft versions was 06. - return 0 - fi - - # This appears to be a HelloRetryRequest message. - debugme echo "reading hello retry request... " - if [[ "$DEBUG" -ge 4 ]]; then - hexdump -C $SOCK_REPLY_FILE | head -6 - echo - [[ "$DEBUG" -ge 5 ]] && echo "$tls_hello_ascii" # one line without any blanks - fi - - # Check the length of the handshake message - msg_len=2*$(hex2dec "${tls_hello_ascii:6:4}") - if [[ $msg_len -gt $tls_hello_ascii_len-10 ]]; then - debugme echo "malformed HelloRetryRequest" - return 1 - fi - # The HelloRetryRequest message may be followed by something - # else (e.g., a change cipher spec message). Ignore anything - # that follows. - tls_hello_ascii_len=$msg_len+10 - - # Check the length of the HelloRetryRequest message. - msg_len=2*$(hex2dec "${tls_hello_ascii:12:6}") - if [[ $msg_len -ne $tls_hello_ascii_len-18 ]]; then - debugme echo "malformed HelloRetryRequest" - return 1 - fi - - if [[ "$msg_type" == 06 ]]; then - server_version="${tls_hello_ascii:18:4}" - if [[ 0x$server_version -ge 0x7f13 ]]; then - # Starting with TLSv1.3 draft 19, a HelloRetryRequest is at least 15 bytes long - [[ $tls_hello_ascii_len -lt 30 ]] && return 0 - cipher_suite="${tls_hello_ascii:22:2},${tls_hello_ascii:24:2}" - extns_offset=26 - else - extns_offset=22 - fi - else - sid_len=2*$(hex2dec "${tls_hello_ascii:86:2}") - i=88+$sid_len - j=90+$sid_len - cipher_suite="${tls_hello_ascii:i:2},${tls_hello_ascii:j:2}" - extns_offset=94+$sid_len - fi - - # Check the length of the extensions. - hrr_extns_len=2*$(hex2dec "${tls_hello_ascii:extns_offset:4}") - if [[ $hrr_extns_len -ne $tls_hello_ascii_len-$extns_offset-4 ]]; then - debugme echo "malformed HelloRetryRequest" - return 1 - fi - - # Parse HelloRetryRequest extensions - for (( i=extns_offset+4; i < tls_hello_ascii_len; i=i+8+len_extn )); do - extn_type="${tls_hello_ascii:i:4}" - j=$i+4 - len_extn=2*$(hex2dec "${tls_hello_ascii:j:4}") - j+=4 - if [[ $len_extn -gt $tls_hello_ascii_len-$j ]]; then - debugme echo "malformed HelloRetryRequest" - return 1 - fi - if [[ "$extn_type" == 002C ]]; then - # If the HRR includes a cookie extension, then it needs to be - # included in the next ClientHello. - j=8+$len_extn - cookie="${tls_hello_ascii:i:j}" - elif [[ "$extn_type" == 00$KEY_SHARE_EXTN_NR ]]; then - # If the HRR includes a key_share extension, then it specifies the - # group to be used in the next ClientHello. So, create a key_share - # extension that specifies this group. - if [[ $len_extn -ne 4 ]]; then - debugme echo "malformed key share extension in HelloRetryRequest" - return 1 - fi - key_share="${tls_hello_ascii:j:4}" - new_key_share="$(generate_key_share_extension "000a00040002$key_share" "ephemeralkey")" - [[ $? -ne 0 ]] && return 1 - [[ -z "$new_key_share" ]] && return 1 - new_key_share="${new_key_share//,/}" - elif [[ "$extn_type" == 002B ]]; then - if [[ $len_extn -ne 4 ]]; then - debugme echo "malformed supported versions extension in HelloRetryRequest" - return 1 - fi - server_version="${tls_hello_ascii:j:4}" - fi - done - - if [[ $DEBUG -ge 3 ]]; then - echo "TLS message fragments:" - echo " tls_protocol (reclyr): 0x${tls_hello_ascii:2:4}" - echo " tls_content_type: 0x16 (handshake)" - echo " msg_len: $(hex2dec "${tls_hello_ascii:6:4}")" - echo - echo "TLS handshake message:" - echo -n " handshake type: 0x$msg_type " - case "$msg_type" in - 02) echo "(hello_retry_request formatted as server_hello)" ;; - 06) echo "(hello_retry_request)" ;; - esac - echo " msg_len: $(hex2dec "${tls_hello_ascii:12:6}")" - echo - echo "TLS hello retry request message:" - echo " server version: $server_version" - if [[ "$server_version" == 0304 ]] || [[ 0x$server_version -ge 0x7f13 ]]; then - echo -n " cipher suite: $cipher_suite" - if [[ $TLS_NR_CIPHERS -ne 0 ]]; then - if [[ "${cipher_suite:0:2}" == "00" ]]; then - rfc_cipher_suite="$(show_rfc_style "x${cipher_suite:3:2}")" - else - rfc_cipher_suite="$(show_rfc_style "x${cipher_suite:0:2}${cipher_suite:3:2}")" - fi - elif "$HAS_CIPHERSUITES"; then - rfc_cipher_suite="$($OPENSSL ciphers -V -ciphersuites "$TLS13_OSSL_CIPHERS" 'ALL:COMPLEMENTOFALL' | grep -i " 0x${cipher_suite:0:2},0x${cipher_suite:3:2} " | awk '{ print $3 }')" - else - rfc_cipher_suite="$($OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL' | grep -i " 0x${cipher_suite:0:2},0x${cipher_suite:3:2} " | awk '{ print $3 }')" - fi - if [[ -n "$rfc_cipher_suite" ]]; then - echo " ($rfc_cipher_suite)" - else - echo "" - fi - fi - [[ -n "$key_share" ]] && echo " key share: 0x$key_share" - [[ -n "$cookie" ]] && echo " cookie: $cookie" - fi - - # Starting with TLSv1.3 draft 24, the second ClientHello should specify a record layer version of 0x0303 - if [[ "$server_version" == 0304 ]] || [[ 0x$server_version -ge 0x7f18 ]]; then - original_clienthello="160303${original_clienthello:6}" - fi - - if [[ "$server_version" == 0304 ]] || [[ 0x$server_version -ge 0x7f16 ]]; then - # Send a dummy change cipher spec for middlebox compatibility. - debugme echo -en "\nsending dummy change cipher spec... " - socksend ", x14, x03, x03 ,x00, x01, x01" 0 - fi - debugme echo -en "\nsending second client hello... " - second_clienthello="$(modify_clienthello "$original_clienthello" "$new_key_share" "$cookie")" - TLS_CLIENT_HELLO="${second_clienthello:10}" - msg_len=${#second_clienthello} - for (( i=0; i < msg_len; i=i+2 )); do - data+=", ${second_clienthello:i:2}" - done - debugme echo -n "sending client hello... " - socksend_clienthello "$data" $USLEEP_SND - sockread_serverhello 32768 - return 2 -} - -# arg1: TLS version low byte -# (00: SSLv3, 01: TLS 1.0, 02: TLS 1.1, 03: TLS 1.2) -# arg2: (optional) list of cipher suites -# arg3: (optional): "all" - process full response (including Certificate and certificate_status handshake messages) -# "all+" - same as "all", but do not offer any curves with TLSv1.3 that are not supported by -# $OPENSSL, since response MUST be decrypted. -# "ephemeralkey" - extract the server's ephemeral key (if any) -# arg4: (optional) additional request extensions -# arg5: (optional) "true" if ClientHello should advertise compression methods other than "NULL" -# arg6: (optional) "false" if the connection should not be closed before the function returns. -# return: 0: successful connect | 1: protocol or cipher not available | 2: as (0) but downgraded -# 6: couldn't open socket | 7: couldn't open temp file -tls_sockets() { - local -i ret=0 - local -i save=0 - local lines - local tls_low_byte - local cipher_list_2send - local sock_reply_file2 sock_reply_file3 - local tls_hello_ascii next_packet - local clienthello1 original_clienthello hrr="" - local process_full="$3" offer_compression=false skip=false - local close_connection=true - local -i hello_done=0 - local cipher="" key_and_iv="" decrypted_response - - [[ "$5" == true ]] && offer_compression=true - [[ "$6" == false ]] && close_connection=false - tls_low_byte="$1" - if [[ -n "$2" ]]; then # use supplied string in arg2 if there is one - cipher_list_2send="$2" - else # otherwise use std ciphers then - if [[ "$tls_low_byte" == 03 ]]; then - cipher_list_2send="$TLS12_CIPHER" - else - cipher_list_2send="$TLS_CIPHER" - fi - fi - code2network "$(tolower "$cipher_list_2send")" # convert CIPHER_SUITES to a "standardized" format - cipher_list_2send="$NW_STR" - - debugme echo -en "\nsending client hello... " - prepare_tls_clienthello "$tls_low_byte" "$cipher_list_2send" "$process_full" "$4" "$offer_compression" - ret=$? # 6 means opening socket didn't succeed, e.g. timeout - - # if sending didn't succeed we don't bother - if [[ $ret -eq 0 ]]; then - clienthello1="$TLS_CLIENT_HELLO" - sockread_serverhello 32768 - "$TLS_DIFFTIME_SET" && TLS_NOW=$(LC_ALL=C date "+%s") - - tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE") - tls_hello_ascii="${tls_hello_ascii%%[!0-9A-F]*}" - tls_hello_ascii="${tls_hello_ascii%%140303000101}" - - # Check if the response is a HelloRetryRequest. - original_clienthello="160301$(printf "%04x" "${#clienthello1}")$clienthello1" - resend_if_hello_retry_request "$original_clienthello" "$tls_hello_ascii" - ret=$? - if [[ $ret -eq 2 ]]; then - hrr="${tls_hello_ascii:10}" - tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE") - tls_hello_ascii="${tls_hello_ascii%%[!0-9A-F]*}" - elif [[ $ret -eq 1 ]] || [[ $ret -eq 6 ]]; then - close_socket - TMPFILE=$SOCK_REPLY_FILE - tmpfile_handle ${FUNCNAME[0]}.dd - return $ret - fi - - # The server's response may span more than one packet. If only the - # first part of the response needs to be processed, this isn't an - # issue. However, if the entire response needs to be processed or - # if the ephemeral key is needed (which comes last for TLS 1.2 and - # below), then we need to check if response appears to be complete, - # and if it isn't then try to get another packet from the server. - if [[ "$process_full" =~ all ]] || [[ "$process_full" == ephemeralkey ]]; then - hello_done=1; skip=true - fi - for (( 1 ; hello_done==1; 1 )); do - if ! "$skip"; then - if [[ $DEBUG -ge 1 ]]; then - sock_reply_file2=$(mktemp $TEMPDIR/ddreply.XXXXXX) || return 7 - mv "$SOCK_REPLY_FILE" "$sock_reply_file2" - fi - - debugme echo -n "requesting more server hello data... " - socksend "" $USLEEP_SND - sockread_serverhello 32768 - - next_packet=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE") - next_packet="${next_packet%%[!0-9A-F]*}" - - if [[ ${#next_packet} -eq 0 ]]; then - # This shouldn't be necessary. However, it protects against - # getting into an infinite loop if the server has nothing - # left to send and check_tls_serverhellodone doesn't - # correctly catch it. - [[ $DEBUG -ge 1 ]] && mv "$sock_reply_file2" "$SOCK_REPLY_FILE" - hello_done=0 - else - tls_hello_ascii+="$next_packet" - if [[ $DEBUG -ge 1 ]]; then - sock_reply_file3=$(mktemp $TEMPDIR/ddreply.XXXXXX) || return 7 - mv "$SOCK_REPLY_FILE" "$sock_reply_file3" - mv "$sock_reply_file2" "$SOCK_REPLY_FILE" - cat "$sock_reply_file3" >> "$SOCK_REPLY_FILE" - rm "$sock_reply_file3" - fi - fi - fi - skip=false - if [[ $hello_done -eq 1 ]]; then - decrypted_response="$(check_tls_serverhellodone "$tls_hello_ascii" "$process_full" "$cipher" "$key_and_iv")" - hello_done=$? - [[ "$hello_done" -eq 0 ]] && [[ -n "$decrypted_response" ]] && tls_hello_ascii="$(toupper "$decrypted_response")" - if [[ "$hello_done" -eq 3 ]]; then - hello_done=1; skip=true - debugme echo "reading server hello..." - parse_tls_serverhello "$tls_hello_ascii" "ephemeralkey" - ret=$? - if [[ "$ret" -eq 0 ]] || [[ "$ret" -eq 2 ]]; then - cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") - if [[ -n "$hrr" ]]; then - key_and_iv="$(derive-handshake-traffic-keys "$cipher" "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" "$clienthello1" "$hrr" "$TLS_CLIENT_HELLO" "$TLS_SERVER_HELLO")" - else - key_and_iv="$(derive-handshake-traffic-keys "$cipher" "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" "" "" "$TLS_CLIENT_HELLO" "$TLS_SERVER_HELLO")" - fi - [[ $? -ne 0 ]] && hello_done=2 - else - hello_done=2 - fi - fi - fi - done - - debugme echo "reading server hello..." - if [[ "$DEBUG" -ge 4 ]]; then - hexdump -C $SOCK_REPLY_FILE | head -6 - echo - fi - - parse_tls_serverhello "$tls_hello_ascii" "$process_full" "$cipher_list_2send" - save=$? - if "$close_connection" && [[ $save == 0 ]]; then - send_close_notify "$DETECTED_TLS_VERSION" - fi - - if [[ $DEBUG -ge 2 ]]; then - # see https://secure.wand.net.nz/trac/libprotoident/wiki/SSL - lines=$(count_lines "$(hexdump -C "$SOCK_REPLY_FILE" 2>$ERRFILE)") - tm_out " ($lines lines returned) " - fi - - # determine the return value for higher level, so that they can tell what the result is - if [[ $save -eq 1 ]] || [[ $lines -eq 1 ]]; then - ret=1 # NOT available - elif [[ $save -eq 3 ]]; then - # only for IMAP currently 'a002 NO Starttls' - ret=3 - elif [[ $save -eq 8 ]]; then - # odd return, we just pass this from parse_tls_serverhello() back - ret=8 - elif [[ $save -eq 4 ]]; then - # STARTTLS problem passing back - ret=4 - else - if [[ 03$tls_low_byte -eq $DETECTED_TLS_VERSION ]]; then - ret=0 # protocol available, TLS version returned equal to the one send - else - debugme echo -n "protocol send: 0x03$tls_low_byte, returned: 0x$DETECTED_TLS_VERSION" - ret=2 # protocol NOT available, server downgraded to $DETECTED_TLS_VERSION - fi - fi - debugme echo - else - debugme echo "stuck on sending: $ret" - fi - - "$close_connection" && close_socket - tmpfile_handle ${FUNCNAME[0]}.dd $SOCK_REPLY_FILE - return $ret -} - - -####### Vulnerabilities follow ####### -# General overview which browser "supports" which vulnerability: -# https://en.wikipedia.org/wiki/Transport_Layer_Security-SSL#Web_browsers - -# mainly adapted from https://gist.github.com/takeshixx/10107280 -# -run_heartbleed(){ - local tls_hexcode - local heartbleed_payload - local -i n lines_returned - local append="" - local tls_hello_ascii="" - local jsonID="heartbleed" - local cve="CVE-2014-0160" - local cwe="CWE-119" - local hint="" - - [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for heartbleed vulnerability " && outln - pr_bold " Heartbleed"; out " ($cve) " - - if ( [[ "$STARTTLS_PROTOCOL" =~ ldap ]] || [[ "$STARTTLS_PROTOCOL" =~ irc ]] ); then - prln_local_problem "STARTTLS/$STARTTLS_PROTOCOL and --ssl-native collide here" - return 1 - fi - - [[ -z "$TLS_EXTENSIONS" ]] && determine_tls_extensions - if [[ ! "${TLS_EXTENSIONS}" =~ heartbeat ]]; then - pr_svrty_best "not vulnerable (OK)" - outln ", no heartbeat extension" - fileout "$jsonID" "OK" "not vulnerable, no heartbeat extension" "$cve" "$cwe" - return 0 - fi - - if [[ 0 -eq $(has_server_protocol tls1) ]]; then - tls_hexcode="x03, x01" - elif [[ 0 -eq $(has_server_protocol tls1_1) ]]; then - tls_hexcode="x03, x02" - elif [[ 0 -eq $(has_server_protocol tls1_2) ]]; then - tls_hexcode="x03, x03" - elif [[ 0 -eq $(has_server_protocol ssl3) ]]; then - tls_hexcode="x03, x00" - else # no protocol for some reason defined, determine TLS versions offered with a new handshake - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY") >$TMPFILE 2>$ERRFILE </dev/null - case "$(get_protocol $TMPFILE)" in - *1.2) tls_hexcode="x03, x03" ; add_tls_offered tls1_2 yes ;; - *1.1) tls_hexcode="x03, x02" ; add_tls_offered tls1_1 yes ;; - TLSv1) tls_hexcode="x03, x01" ; add_tls_offered tls1 yes ;; - SSLv3) tls_hexcode="x03, x00" ; add_tls_offered ssl3 yes ;; - esac - fi - debugme echo "using protocol $tls_hexcode" - - heartbleed_payload=", x18, $tls_hexcode, x00, x03, x01, x40, x00" - tls_sockets "${tls_hexcode:6:2}" "" "ephemeralkey" "" "" "false" - - [[ $DEBUG -ge 4 ]] && tmln_out "\nsending payload with TLS version $tls_hexcode:" - socksend "$heartbleed_payload" 1 - sockread_serverhello 16384 $HEARTBLEED_MAX_WAITSOCK - if [[ $? -eq 3 ]]; then - append=", timed out" - pr_svrty_best "not vulnerable (OK)"; out "$append" - fileout "$jsonID" "OK" "not vulnerable $append" "$cve" "$cwe" - else - - # server reply should be (>=SSLv3): 18030x in case of a heartBEAT reply -- which we take as a positive result - tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE") - debugme echo "tls_content_type: ${tls_hello_ascii:0:2}" - debugme echo "tls_protocol: ${tls_hello_ascii:2:4}" - - lines_returned=$(count_lines "$(hexdump -ve '16/1 "%02x " " \n"' "$SOCK_REPLY_FILE")") - debugme echo "lines HB reply: $lines_returned" - - if [[ $DEBUG -ge 3 ]]; then - tmln_out "\nheartbleed reply: " - hexdump -C "$SOCK_REPLY_FILE" | head -20 - [[ $lines_returned -gt 20 ]] && tmln_out "[...]" - tmln_out - fi - - if [[ $lines_returned -gt 1 ]] && [[ "${tls_hello_ascii:0:4}" == 1803 ]]; then - if [[ "$STARTTLS_PROTOCOL" =~ ftp ]]; then - # check possibility of weird vsftpd reply, see #426, despite "1803" seems very unlikely... - if grep -q '500 OOPS' "$SOCK_REPLY_FILE" ; then - append=", successful weeded out vsftpd false positive" - pr_svrty_best "not vulnerable (OK)"; out "$append" - fileout "$jsonID" "OK" "not vulnerable $append" "$cve" "$cwe" - else - out "likely " - pr_svrty_critical "VULNERABLE (NOT ok)" - [[ $DEBUG -lt 3 ]] && tm_out ", use debug >=3 to confirm" - fileout "$jsonID" "CRITICAL" "VULNERABLE" "$cve" "$cwe" "$hint" - fi - else - pr_svrty_critical "VULNERABLE (NOT ok)" - fileout "$jsonID" "CRITICAL" "VULNERABLE" "$cve" "$cwe" "$hint" - fi - else - pr_svrty_best "not vulnerable (OK)" - fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" - fi - fi - outln - tmpfile_handle ${FUNCNAME[0]}.dd $SOCK_REPLY_FILE - close_socket - return 0 -} - -# helper function -ok_ids(){ - prln_svrty_best "\n ok -- something reset our ccs packets" - return 0 -} - -# see https://www.openssl.org/news/secadv_20140605.txt -# mainly adapted from Ramon de C Valle's C code from https://gist.github.com/rcvalle/71f4b027d61a78c42607 -#FIXME: At a certain point ccs needs to be changed and make use of code2network using a file, then tls_sockets -# -run_ccs_injection(){ - local tls_hexcode ccs_message client_hello byte6 - local -i retval ret=0 - local tls_hello_ascii="" - local jsonID="CCS" - local cve="CVE-2014-0224" - local cwe="CWE-310" - local hint="" - - [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for CCS injection vulnerability " && outln - pr_bold " CCS"; out " ($cve) " - - if ( [[ "$STARTTLS_PROTOCOL" =~ ldap ]] || [[ "$STARTTLS_PROTOCOL" =~ irc ]] ); then - prln_local_problem "STARTTLS/$STARTTLS_PROTOCOL and --ssl-native collide here" - return 1 - fi - - if [[ 0 -eq $(has_server_protocol tls1) ]]; then - tls_hexcode="x03, x01" - elif [[ 0 -eq $(has_server_protocol tls1_1) ]]; then - tls_hexcode="x03, x02" - elif [[ 0 -eq $(has_server_protocol tls1_2) ]]; then - tls_hexcode="x03, x03" - elif [[ 0 -eq $(has_server_protocol ssl3) ]]; then - tls_hexcode="x03, x00" - else # no protocol for some reason defined, determine TLS versions offered with a new handshake - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY") >$TMPFILE 2>$ERRFILE </dev/null - case "$(get_protocol $TMPFILE)" in - *1.2) tls_hexcode="x03, x03" ; add_tls_offered tls1_2 yes ;; - *1.1) tls_hexcode="x03, x02" ; add_tls_offered tls1_1 yes ;; - TLSv1) tls_hexcode="x03, x01" ; add_tls_offered tls1 yes ;; - SSLv3) tls_hexcode="x03, x00" ; add_tls_offered ssl3 yes ;; - esac - fi - debugme echo "using protocol $tls_hexcode" - - ccs_message=", x14, $tls_hexcode ,x00, x01, x01" - - client_hello=" - # TLS header (5 bytes) - ,x16, # content type (x16 for handshake) - x03, x01, # TLS version in record layer is always TLS 1.0 (except SSLv3) - x00, x93, # length - # Handshake header - x01, # type (x01 for ClientHello) - x00, x00, x8f, # length - $tls_hexcode, # TLS version - # Random (32 byte) - x53, x43, x5b, x90, x9d, x9b, x72, x0b, - xbc, x0c, xbc, x2b, x92, xa8, x48, x97, - xcf, xbd, x39, x04, xcc, x16, x0b, x85, - x03, x90, x9f, x77, x04, x33, xd4, xde, - x00, # session ID length - x00, x68, # cipher suites length - # Cipher suites (51 suites) - xc0, x13, xc0, x12, xc0, x11, xc0, x10, - xc0, x0f, xc0, x0e, xc0, x0d, xc0, x0c, - xc0, x0b, xc0, x0a, xc0, x09, xc0, x08, - xc0, x07, xc0, x06, xc0, x05, xc0, x04, - xc0, x03, xc0, x02, xc0, x01, x00, x39, - x00, x38, x00, x37, x00, x36, x00, x35, x00, x34, - x00, x33, x00, x32, x00, x31, x00, x30, - x00, x2f, x00, x16, x00, x15, x00, x14, - x00, x13, x00, x12, x00, x11, x00, x10, - x00, x0f, x00, x0e, x00, x0d, x00, x0c, - x00, x0b, x00, x0a, x00, x09, x00, x08, - x00, x07, x00, x06, x00, x05, x00, x04, - x00, x03, x00, x02, x00, x01, x01, x00" - - fd_socket 5 || return 1 - -# we now make a standard handshake ... - debugme echo -n "sending client hello... " - socksend "$client_hello" 1 - - debugme echo "reading server hello... " - sockread_serverhello 32768 - if [[ $DEBUG -ge 4 ]]; then - hexdump -C "$SOCK_REPLY_FILE" | head -20 - tmln_out "[...]" - tm_out "\nsending payload #1 with TLS version $tls_hexcode: " - fi - rm "$SOCK_REPLY_FILE" -# ... and then send the a change cipher spec message - socksend "$ccs_message" 1 || ok_ids - sockread_serverhello 4096 $CCS_MAX_WAITSOCK - if [[ $DEBUG -ge 3 ]]; then - tmln_out "\n1st reply: " - hexdump -C "$SOCK_REPLY_FILE" | head -20 - tmln_out - tm_out "sending payload #2 with TLS version $tls_hexcode: " - fi - rm "$SOCK_REPLY_FILE" - - socksend "$ccs_message" 2 || ok_ids - sockread_serverhello 4096 $CCS_MAX_WAITSOCK - retval=$? - - tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE") - byte6="${tls_hello_ascii:12:2}" - debugme echo "tls_content_type: ${tls_hello_ascii:0:2} | tls_protocol: ${tls_hello_ascii:2:4} | byte6: $byte6" - - if [[ $DEBUG -ge 3 ]]; then - tmln_out "\n2nd reply: " - hexdump -C "$SOCK_REPLY_FILE" - tmln_out - fi - -# in general, see https://en.wikipedia.org/wiki/Transport_Layer_Security#Alert_protocol -# https://tools.ietf.org/html/rfc5246#section-7.2 -# -# not ok for CCSI: 15 | 0301 | 00 02 | 02 15 -# ALERT | TLS 1.0 | Length=2 | Decryption failed (21) -# -# ok: nothing: ==> RST -# -# 0A: Unexpected message -# 28: Handshake failure - if [[ -z "${tls_hello_ascii:0:12}" ]]; then - # empty reply - pr_svrty_best "not vulnerable (OK)" - if [[ $retval -eq 3 ]]; then - fileout "$jsonID" "OK" "not vulnerable (timed out)" "$cve" "$cwe" - else - fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" - fi - elif [[ "${tls_hello_ascii:0:4}" == "1503" ]]; then - if [[ ! "${tls_hello_ascii:5:2}" =~ [03|02|01|00] ]]; then - pr_warning "test failed " - out "no proper TLS repy (debug info: protocol sent: 1503${tls_hexcode#x03, x}, reply: ${tls_hello_ascii:0:14}" - fileout "$jsonID" "DEBUG" "test failed, around line $LINENO, debug info (${tls_hello_ascii:0:14})" "$cve" "$cwe" "$hint" - ret=1 - elif [[ "$byte6" == "15" ]]; then - # decryption failed received - pr_svrty_critical "VULNERABLE (NOT ok)" - fileout "$jsonID" "CRITICAL" "VULNERABLE" "$cve" "$cwe" "$hint" - elif [[ "$byte6" == "0A" ]] || [[ "$byte6" == "28" ]]; then - # Unexpected message / Handshake failure received - pr_warning "likely " - out "not vulnerable (OK)" - out " - alert description type: $byte6" - fileout "$jsonID" "WARN" "probably not vulnerable but received 0x${byte6} instead of 0x15" "$cve" "$cwe" "$hint" - elif [[ "$byte6" == "14" ]]; then - # bad_record_mac -- this is not "not vulnerable" - out "likely " - pr_svrty_critical "VULNERABLE (NOT ok)" - out ", suspicious \"bad_record_mac\" ($byte6)" - fileout "$jsonID" "CRITICAL" "likely VULNERABLE" "$cve" "$cwe" "$hint" - else - # other errors, see https://tools.ietf.org/html/rfc5246#section-7.2 - out "likely " - pr_svrty_critical "VULNERABLE (NOT ok)" - out ", suspicious error code \"$byte6\" returned. Please report" - fileout "$jsonID" "CRITICAL" "likely VULNERABLE with $byte6" "$cve" "$cwe" "$hint" - fi - elif [[ $STARTTLS_PROTOCOL == "mysql" ]] && [[ "${tls_hello_ascii:14:12}" == "233038533031" ]]; then - # MySQL community edition (yaSSL) returns a MySQL error instead of a TLS Alert - # Error: #08S01 Bad handshake - pr_svrty_best "not vulnerable (OK)" - out ", looks like MySQL community edition (yaSSL)" - fileout "$jsonID" "OK" "not vulnerable (MySQL community edition (yaSSL) detected)" "$cve" "$cwe" - elif [[ "$byte6" == [0-9a-f][0-9a-f] ]] && [[ "${tls_hello_ascii:2:2}" != "03" ]]; then - pr_warning "test failed" - out ", probably read buffer too small (${tls_hello_ascii:0:14})" - fileout "$jsonID" "DEBUG" "test failed, probably read buffer too small (${tls_hello_ascii:0:14})" "$cve" "$cwe" "$hint" - ret=1 - else - pr_warning "test failed " - out "around line $LINENO (debug info: ${tls_hello_ascii:0:12},$byte6)" - fileout "$jsonID" "DEBUG" "test failed, around line $LINENO, debug info (${tls_hello_ascii:0:12},$byte6)" "$cve" "$cwe" "$hint" - ret=1 - fi - outln - - tmpfile_handle ${FUNCNAME[0]}.dd $SOCK_REPLY_FILE - close_socket - return $ret -} - -sub_session_ticket_tls() { - local tls_proto="$1" - local sessticket_tls="" - #FIXME: we likely have done this already before (either @ run_server_defaults() or at least the output - # from a previous handshake) --> would save 1x connect. We have TLS_TICKET but not yet the ticket itself #FIXME - #ATTENTION: we DO NOT use SNI here as we assume ticketbleed is a vulnerability of the TLS stack. If we'd do SNI here, we'd also need - # it in the ClientHello of run_ticketbleed() otherwise the ticket will be different and the whole thing won't work! - # - sessticket_tls="$($OPENSSL s_client $(s_client_options "$BUGS $tls_proto $PROXY -connect $NODEIP:$PORT") </dev/null 2>$ERRFILE | awk '/TLS session ticket:/,/^$/' | awk '!/TLS session ticket/')" - sessticket_tls="$(sed -e 's/^.* - /x/g' -e 's/ .*$//g' <<< "$sessticket_tls" | tr '\n' ',')" - sed -e 's/ /,x/g' -e 's/-/,x/g' <<< "$sessticket_tls" - -} - - -# see https://blog.filippo.io/finding-ticketbleed/ | https://ticketbleed.com/ -run_ticketbleed() { - local tls_hexcode tls_proto="" - local session_tckt_tls="" - local -i len_ch=300 # fixed len of prepared clienthello below - local sid="x00,x0B,xAD,xC0,xDE,x00," # some abitratry bytes - local len_sid="$(( ${#sid} / 4))" - local xlen_sid="$(dec02hex $len_sid)" - local -i len_tckt_tls=0 nr_sid_detected=0 - local xlen_tckt_tls="" xlen_handshake_record_layer="" xlen_handshake_ssl_layer="" - local -i len_handshake_record_layer=0 - local i - local -a memory sid_detected - local early_exit=true - local -i ret=0 - local jsonID="ticketbleed" - local cve="CVE-2016-9244" - local cwe="CWE-200" - local hint="" - - [[ -n "$STARTTLS" ]] && return 0 - [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for Ticketbleed vulnerability " && outln - pr_bold " Ticketbleed"; out " ($cve), experiment. " - - if [[ "$SERVICE" != HTTP ]] && ! "$CLIENT_AUTH"; then - outln "-- (applicable only for HTTPS)" - fileout "$jsonID" "INFO" "not applicable, not HTTP" "$cve" "$cwe" - return 0 - fi - - # highly unlikely that it is NOT supported. We may loose time here but it's more solid - [[ -z "$TLS_EXTENSIONS" ]] && determine_tls_extensions - if [[ ! "${TLS_EXTENSIONS}" =~ "session ticket" ]]; then - pr_svrty_best "not vulnerable (OK)" - outln ", no session ticket extension" - fileout "$jsonID" "OK" "no session ticket extension" "$cve" "$cwe" - return 0 - fi - - if [[ 0 -eq $(has_server_protocol tls1) ]]; then - tls_hexcode="x03, x01"; tls_proto="-tls1" - elif [[ 0 -eq $(has_server_protocol tls1_1) ]]; then - tls_hexcode="x03, x02"; tls_proto="-tls1_1" - elif [[ 0 -eq $(has_server_protocol tls1_2) ]]; then - tls_hexcode="x03, x03"; tls_proto="-tls1_2" - elif [[ 0 -eq $(has_server_protocol ssl3) ]]; then - tls_hexcode="x03, x00"; tls_proto="-ssl3" - else # no protocol for some reason defined, determine TLS versions offered with a new handshake - "$HAS_TLS13" && tls_proto="-no_tls1_3" - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS $tls_proto -connect $NODEIP:$PORT $PROXY") >$TMPFILE 2>$ERRFILE </dev/null - case "$(get_protocol $TMPFILE)" in - *1.2) tls_hexcode="x03, x03"; tls_proto="-tls1_2" ; add_tls_offered tls1_2 yes ;; - *1.1) tls_hexcode="x03, x02"; tls_proto="-tls1_1" ; add_tls_offered tls1_1 yes ;; - TLSv1) tls_hexcode="x03, x01"; tls_proto="-tls1" ; add_tls_offered tls1 yes ;; - SSLv3) tls_hexcode="x03, x00"; tls_proto="-ssl3" ; add_tls_offered ssl3 yes ;; - esac - fi - debugme echo "using protocol $tls_hexcode" - - session_tckt_tls="$(sub_session_ticket_tls "$tls_proto")" - if [[ "$session_tckt_tls" == "," ]]; then - pr_svrty_best "not vulnerable (OK)" - outln ", no session tickets" - fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" - debugme echo " session ticket TLS \"$session_tckt_tls\"" - return 0 - fi - - len_tckt_tls=${#session_tckt_tls} - len_tckt_tls=$(( len_tckt_tls / 4)) - xlen_tckt_tls="$(dec02hex $len_tckt_tls)" - len_handshake_record_layer="$(( len_sid + len_ch + len_tckt_tls ))" - xlen_handshake_record_layer="$(dec04hex "$len_handshake_record_layer")" - len_handshake_ssl_layer="$(( len_handshake_record_layer + 4 ))" - xlen_handshake_ssl_layer="$(dec04hex "$len_handshake_ssl_layer")" - - if [[ "$DEBUG" -ge 4 ]]; then - echo "len_tckt_tls (hex): $len_tckt_tls ($xlen_tckt_tls)" - echo "sid: $sid" - echo "len_sid (hex) $len_sid ($xlen_sid)" - echo "len_handshake_record_layer: $len_handshake_record_layer ($xlen_handshake_record_layer)" - echo "len_handshake_ssl_layer: $len_handshake_ssl_layer ($xlen_handshake_ssl_layer)" - echo "session_tckt_tls: $session_tckt_tls" - fi - - client_hello=" - # TLS header (5 bytes) - ,x16, # Content type (x16 for handshake) - x03,x01, # TLS version record layer - # Length Secure Socket Layer follows: - $xlen_handshake_ssl_layer, - # Handshake header - x01, # Type (x01 for ClientHello) - # Length of ClientHello follows: - x00, $xlen_handshake_record_layer, - $tls_hexcode, # TLS Version - # Random (32 byte) Unix time etc, see www.moserware.com/2009/06/first-few-milliseconds-of-https.html - xee, xee, x5b, x90, x9d, x9b, x72, x0b, - xbc, x0c, xbc, x2b, x92, xa8, x48, x97, - xcf, xbd, x39, x04, xcc, x16, x0b, x85, - x03, x90, x9f, x77, x04, x33, xff, xff, - $xlen_sid, # Session ID length - $sid - x00, x6a, # Cipher suites length 106 - # 53 Cipher suites - xc0,x14, xc0,x13, xc0,x0a, xc0,x21, - x00,x39, x00,x38, x00,x88, x00,x87, - xc0,x0f, xc0,x05, x00,x35, x00,x84, - xc0,x12, xc0,x08, xc0,x1c, xc0,x1b, - x00,x16, x00,x13, xc0,x0d, xc0,x03, - x00,x0a, xc0,x13, xc0,x09, xc0,x1f, - xc0,x1e, x00,x33, x00,x32, x00,x9a, - x00,x99, x00,x45, x00,x44, xc0,x0e, - xc0,x04, x00,x2f, x00,x96, x00,x41, - xc0,x11, xc0,x07, xc0,x0c, xc0,x02, - x00,x05, x00,x04, x00,x15, x00,x12, - xc0,x30, xc0,x2f, x00,x9d, x00,x9c, - x00,x3d, x00,x3c, x00,x9f, x00,x9e, - x00,xff, - x01, # Compression methods length - x00, # Compression method (x00 for NULL) - x01,x5b, # Extensions length ####### 10b + x14 + x3c -# Extension Padding - x00,x15, - # length: - x00,x38, - x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, - x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, - x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, -# Extension: ec_point_formats - x00,x0b, - # length: - x00,x04, - # data: - x03,x00, x01,x02, -# Extension: elliptic_curves - x00,x0a, - # length - x00,x34, - x00,x32, - # data: - x00,x0e, x00,x0d, x00,x19, x00,x0b, x00,x0c, - x00,x18, x00,x09, x00,x0a, x00,x16, - x00,x17, x00,x08, x00,x06, x00,x07, - x00,x14, x00,x15, x00,x04, x00,x05, - x00,x12, x00,x13, x00,x01, x00,x02, - x00,x03, x00,x0f, x00,x10, x00,x11, -# Extension: Signature Algorithms - x00,x0d, - # length: - x00,x10, - # data: - x00,x0e ,x04,x01, x05,x01 ,x02,x01, x04,x03, x05,x03, - x02,x03, x02,x02, -# Extension: SessionTicket TLS - x00, x23, - # length of SessionTicket TLS - x00, $xlen_tckt_tls, - # data, Session Ticket - $session_tckt_tls # here we have the comma already -# Extension: Heartbeat - x00, x0f, x00, x01, x01" - - # we do 3 client hellos, then see whether different memory is returned - for i in 1 2 3; do - fd_socket 5 || return 6 - debugme echo -n "sending client hello... " - socksend "$client_hello" 0 - - debugme echo "reading server hello (ticketbleed reply)... " - if "$FAST_SOCKET"; then - tls_hello_ascii=$(sockread_fast 32768) - else - sockread_serverhello 32768 $CCS_MAX_WAITSOCK - tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE") - fi - [[ "$DEBUG" -ge 5 ]] && echo "$tls_hello_ascii" - if [[ "$DEBUG" -ge 4 ]]; then - echo "=============================" - echo "$tls_hello_ascii" - echo "=============================" - fi - - if [[ "${tls_hello_ascii:0:2}" == 15 ]]; then - debugme echo -n "TLS Alert ${tls_hello_ascii:10:4} (TLS version: ${tls_hello_ascii:2:4}) -- " - pr_svrty_best "not vulnerable (OK)" - fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" - send_close_notify "${tls_hello_ascii:18:4}" - close_socket - break - elif [[ -z "${tls_hello_ascii:0:2}" ]]; then - pr_svrty_best "not vulnerable (OK)" - out ", reply empty" - fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" - send_close_notify "${tls_hello_ascii:18:4}" - close_socket - break - elif [[ "${tls_hello_ascii:0:2}" == 16 ]]; then - early_exit=false - debugme echo -n "Handshake (TLS version: ${tls_hello_ascii:2:4}), " - if [[ "${tls_hello_ascii:10:6}" == 020000 ]]; then - debugme echo -n "ServerHello -- " - else - debugme echo -n "Message type: ${tls_hello_ascii:10:6} -- " - fi - sid_input=$(sed -e 's/x//g' -e 's/,//g' <<< "$sid") - sid_detected[i]="${tls_hello_ascii:88:32}" - memory[i]="${tls_hello_ascii:$((88+ len_sid*2)):$((32 - len_sid*2))}" - if [[ "$DEBUG" -ge 3 ]]; then - echo - echo "TLS version, record layer: ${tls_hello_ascii:18:4}" - echo "Session ID: ${sid_detected[i]}" - echo "memory: ${memory[i]}" - echo -n "$sid_input in SID: " ; - [[ "${sid_detected[i]}" =~ $sid_input ]] && echo "yes" || echo "no" - fi - [[ "$DEBUG" -ge 1 ]] && echo $tls_hello_ascii >$TEMPDIR/${FUNCNAME[0]}.tls_hello_ascii${i}.txt - else - ret=1 - pr_warning "test failed" - out " around line $LINENO (debug info: ${tls_hello_ascii:0:2}, ${tls_hello_ascii:2:10})" - fileout "$jsonID" "DEBUG" "test failed, around $LINENO (debug info: ${tls_hello_ascii:0:2}, ${tls_hello_ascii:2:10})" "$cve" "$cwe" - send_close_notify "${tls_hello_ascii:18:4}" - close_socket - break - fi - send_close_notify "${tls_hello_ascii:18:4}" - close_socket - done - - if ! "$early_exit"; then - # here we test the replies if a TLS server hello was received >1x - for i in 1 2 3 ; do - if [[ "${sid_detected[i]}" =~ $sid_input ]]; then - # was our faked TLS SID returned? - nr_sid_detected+=1 - fi - done - if [[ $nr_sid_detected -eq 3 ]]; then - if [[ ${memory[1]} != ${memory[2]} ]] && [[ ${memory[2]} != ${memory[3]} ]]; then - pr_svrty_critical "VULNERABLE (NOT ok)" - fileout "$jsonID" "CRITICAL" "VULNERABLE" "$cve" "$cwe" "$hint" - else - pr_svrty_best "not vulnerable (OK)" - out ", session IDs were returned but potential memory fragments do not differ" - fileout "$jsonID" "OK" "not vulnerable, returned potential memory fragments do not differ" "$cve" "$cwe" - fi - else - if [[ "$DEBUG" -ge 2 ]]; then - echo - pr_warning "test failed, non reproducible results!" - else - pr_warning "test failed, non reproducible results!" - out " Please run again w \"--debug=2\" (# of faked TLS SIDs detected: $nr_sid_detected)" - fi - fileout "$jsonID" "DEBUG" "test failed, non reproducible results. $nr_sid_detected TLS Session IDs $nr_sid_detected, ${sid_detected[1]},${sid_detected[2]},${sid_detected[3]}" "$cve" "$cwe" - ret=1 - fi - fi - outln - return $ret -} - -# Overview @ https://www.exploresecurity.com/wp-content/uploads/custom/SSL_manual_cheatsheet.html -# -run_renego() { - local legacycmd="" proto="$OPTIMAL_PROTO" - local sec_renego sec_client_renego - local -i ret=0 - local cve="" - local cwe="CWE-310" - local hint="" - local jsonID="" - # No SNI needed here as there won't be two different SSL stacks for one IP - - "$HAS_TLS13" && [[ -z "$proto" ]] && proto="-no_tls1_3" - - [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for Renegotiation vulnerabilities " && outln - - pr_bold " Secure Renegotiation (RFC 5746) " - jsonID="secure_renego" - - if "$TLS13_ONLY"; then - # https://www.openssl.org/blog/blog/2018/02/08/tlsv1.3/ - pr_svrty_best "not vulnerable (OK)" - [[ $DEBUG -ge 1 ]] && out ", no renegotiation support in TLS 1.3 only servers" - outln - fileout "$jsonID" "OK" "TLS 1.3 only server" "$cve" "$cwe" - else - # first fingerprint for the Line "Secure Renegotiation IS NOT" or "Secure Renegotiation IS " - $OPENSSL s_client $(s_client_options "$proto $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY") 2>&1 </dev/null >$TMPFILE 2>$ERRFILE - if sclient_connect_successful $? $TMPFILE; then - grep -iaq "Secure Renegotiation IS NOT" $TMPFILE - sec_renego=$? # 0= Secure Renegotiation IS NOT supported - # grep -iaq "Secure Renegotiation IS supported" - #FIXME: didn't occur to me yet but why not also to check on "Secure Renegotiation IS supported" - case $sec_renego in - 0) prln_svrty_critical "Not supported / VULNERABLE (NOT ok)" - fileout "$jsonID" "CRITICAL" "VULNERABLE" "$cve" "$cwe" "$hint" - ;; - 1) prln_svrty_best "supported (OK)" - fileout "$jsonID" "OK" "supported" "$cve" "$cwe" - ;; - *) prln_warning "FIXME (bug): $sec_renego" - fileout "$jsonID" "WARN" "FIXME (bug) $sec_renego" "$cve" "$cwe" - ;; - esac - else - prln_warning "OpenSSL handshake didn't succeed" - fileout "$jsonID" "WARN" "OpenSSL handshake didn't succeed" "$cve" "$cwe" - fi - fi - - # FIXME: Basically this can be done with sockets and we might have that information already - # see https://tools.ietf.org/html/rfc5746#section-3.4: 'The client MUST include either an empty "renegotiation_info" - # extension, or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the ClientHello. [..] - # When a ServerHello is received, the client MUST check if it includes the "renegotiation_info" extension: - # If the extension is not present, the server does not support secure renegotiation' - - - pr_bold " Secure Client-Initiated Renegotiation " - jsonID="secure_client_renego" - cve="CVE-2011-1473" - # see: https://blog.qualys.com/ssllabs/2011/10/31/tls-renegotiation-and-denial-of-service-attacks - # https://blog.ivanristic.com/2009/12/testing-for-ssl-renegotiation.html -- head/get doesn't seem to be needed though - # https://archive.fo/20130415224936/http://www.thc.org/thc-ssl-dos/ - # https://vincent.bernat.ch/en/blog/2011-ssl-dos-mitigation - case "$OSSL_VER" in - 0.9.8*) # we need this for Mac OSX unfortunately - case "$OSSL_VER_APPENDIX" in - [a-l]) - prln_local_problem " Your $OPENSSL cannot test this secure renegotiation vulnerability" - fileout "$jsonID" "WARN" "your $OPENSSL cannot test this secure renegotiation vulnerability" "$cve" "$cwe" - return 1 - ;; - [m-z]) - ;; # all ok - esac - ;; - 1.0.1*|1.0.2*) - legacycmd="-legacy_renegotiation" - ;; - 0.9.9*|1.0*|1.1*) - ;; # all ok - esac - - if "$TLS13_ONLY"; then - pr_svrty_best "not vulnerable (OK)" - [[ $DEBUG -ge 1 ]] && out ", no renegotiation support in TLS 1.3 only servers" - outln - fileout "$jsonID" "OK" "not vulnerable, TLS 1.3 only" "$cve" "$cwe" - elif "$CLIENT_AUTH"; then - prln_warning "client x509-based authentication prevents this from being tested" - fileout "$jsonID" "WARN" "client x509-based authentication prevents this from being tested" - sec_client_renego=1 - else - # We need up to two tries here, as some LiteSpeed servers don't answer on "R" and block. Thus first try in the background - # msg enables us to look deeper into it while debugging - echo R | $OPENSSL s_client $(s_client_options "$proto $BUGS $legacycmd $STARTTLS -connect $NODEIP:$PORT $PROXY") >$TMPFILE 2>>$ERRFILE & - wait_kill $! $HEADER_MAXSLEEP - if [[ $? -eq 3 ]]; then - pr_svrty_good "likely not vulnerable (OK)"; outln ", timed out" # it hung - fileout "$jsonID" "OK" "likely not vulnerable (timed out)" "$cve" "$cwe" - sec_client_renego=1 - else - # second try in the foreground as we are sure now it won't hang - echo R | $OPENSSL s_client $(s_client_options "$proto $legacycmd $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY") >$TMPFILE 2>>$ERRFILE - sec_client_renego=$? - # 0 means client is renegotiating & doesn't return an error --> vuln! - # 1 means client tried to renegotiating but the server side errored then. You still see RENEGOTIATING in the output - if tail -5 $TMPFILE| grep -qa '^closed'; then - # Exemption from above: server closed the connection but return value was zero - # See https://github.com/drwetter/testssl.sh/issues/1725 and referenced issue @haproxy - sec_client_renego=1 - fi - case "$sec_client_renego" in - 0) # We try again if server is HTTP. This could be either a node.js server or something else. - # node.js has a mitigation which allows 3x R and then blocks. So we test 4x - # This way we save a couple seconds as we weeded out the ones which are more robust - if [[ $SERVICE != HTTP ]]; then - pr_svrty_medium "VULNERABLE (NOT ok)"; outln ", potential DoS threat" - fileout "$jsonID" "MEDIUM" "VULNERABLE, potential DoS threat" "$cve" "$cwe" "$hint" - else - (for i in {1..4}; do echo R; sleep 1; done) | \ - $OPENSSL s_client $(s_client_options "$proto $legacycmd $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY") >$TMPFILE 2>>$ERRFILE - case $? in - 0) pr_svrty_high "VULNERABLE (NOT ok)"; outln ", DoS threat" - fileout "$jsonID" "HIGH" "VULNERABLE, DoS threat" "$cve" "$cwe" "$hint" - ;; - 1) pr_svrty_good "not vulnerable (OK)" - outln " -- mitigated" - fileout "$jsonID" "OK" "not vulnerable, mitigated" "$cve" "$cwe" - ;; - *) prln_warning "FIXME (bug): $sec_client_renego (4 tries)" - fileout "$jsonID" "DEBUG" "FIXME (bug 4 tries) $sec_client_renego" "$cve" "$cwe" - ret=1 - ;; - esac - fi - ;; - 1) - prln_svrty_good "not vulnerable (OK)" - fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" - ;; - *) - prln_warning "FIXME (bug): $sec_client_renego" - fileout "$jsonID" "DEBUG" "FIXME (bug) $sec_client_renego - Please report" "$cve" "$cwe" - ret=1 - ;; - esac - fi - fi - - #pr_bold " Insecure Client-Initiated Renegotiation " # pre-RFC 5746, CVE-2009-3555 - #jsonID="insecure_client_renego" - # - # https://www.openssl.org/news/vulnerabilities.html#y2009. It can only be tested with OpenSSL <=0.9.8k - # Insecure Client-Initiated Renegotiation is missing ==> sockets. When we complete the handshake ;-) - - tmpfile_handle ${FUNCNAME[0]}.txt - return $ret -} - -run_crime() { - local -i ret=0 sclient_success - local addcmd="" - local cve="CVE-2012-4929" - local cwe="CWE-310" - local hint="" - - # In a nutshell: don't offer TLS/SPDY compression. This tests for CRIME Vulnerability on HTTPS only, - # not SPDY or ALPN (yet). Please note that it is an attack where you need client side control, so in - # regular situations this # means anyway "game over", with or without CRIME. - # - # https://blog.qualys.com/ssllabs/2012/09/14/crime-information-leakage-attack-against-ssltls - - [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for CRIME vulnerability " && outln - pr_bold " CRIME, TLS " ; out "($cve) " - - if "$TLS13_ONLY"; then - pr_svrty_best "not vulnerable (OK)" - [[ $DEBUG -ge 1 ]] && out ", no compression in TLS 1.3 only servers" - outln - fileout "$jsonID" "OK" "TLS 1.3 only server" "$cve" "$cwe" - return 0 - fi - - if ! "$HAS_ZLIB"; then - if "$SSL_NATIVE"; then - prln_local_problem "$OPENSSL lacks zlib support" - fileout "CRIME_TLS" "WARN" "CRIME, TLS: Not tested. $OPENSSL lacks zlib support" "$cve" "$cwe" - return 1 - else - tls_sockets "03" "$TLS12_CIPHER" "" "" "true" - sclient_success=$? - [[ $sclient_success -eq 2 ]] && sclient_success=0 - [[ $sclient_success -eq 0 ]] && cp "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" $TMPFILE - fi - else - [[ "$OSSL_VER" == 0.9.8* ]] && addcmd="-no_ssl2" - "$HAS_TLS13" && [[ -z "$OPTIMAL_PROTO" ]] && addcmd+=" -no_tls1_3" - $OPENSSL s_client $(s_client_options "$OPTIMAL_PROTO $BUGS -comp $addcmd $STARTTLS -connect $NODEIP:$PORT $PROXY $SNI") </dev/null &>$TMPFILE - sclient_connect_successful $? $TMPFILE - sclient_success=$? - fi - - if [[ $sclient_success -ne 0 ]]; then - pr_warning "test failed (couldn't connect)" - fileout "CRIME_TLS" "WARN" "Check failed, couldn't connect" "$cve" "$cwe" - ret=1 - elif grep -a Compression $TMPFILE | grep -aq NONE >/dev/null; then - pr_svrty_good "not vulnerable (OK)" - if [[ $SERVICE != HTTP ]] && ! "$CLIENT_AUTH"; then - out " (not using HTTP anyway)" - fileout "CRIME_TLS" "OK" "not vulnerable (not using HTTP anyway)" "$cve" "$cwe" - else - fileout "CRIME_TLS" "OK" "not vulnerable" "$cve" "$cwe" - fi - else - if [[ $SERVICE == HTTP ]] || "$CLIENT_AUTH"; then - pr_svrty_high "VULNERABLE (NOT ok)" - fileout "CRIME_TLS" "HIGH" "VULNERABLE" "$cve" "$cwe" "$hint" - else - pr_svrty_medium "VULNERABLE but not using HTTP: probably no exploit known" - fileout "CRIME_TLS" "MEDIUM" "VULNERABLE, but not using HTTP. Probably no exploit known" "$cve" "$cwe" "$hint" - # not clear whether a protocol != HTTP offers the ability to repeatedly modify the input - # which is done e.g. via javascript in the context of HTTP - fi - fi - outln - -# this needs to be re-done i order to remove the redundant check for spdy - - # weed out starttls, spdy-crime is a web thingy -# if [[ "x$STARTTLS" != "x" ]]; then -# echo -# return $ret -# fi - - # weed out non-webports, spdy-crime is a web thingy. there's a catch thoug, you see it? -# case $PORT in -# 25|465|587|80|110|143|993|995|21) -# echo -# return $ret -# esac - -# if "$HAS_NPN"; then -# $OPENSSL s_client -host $NODE -port $PORT -nextprotoneg $NPN_PROTOs $SNI </dev/null 2>/dev/null >$TMPFILE -# if [[ $? -eq 0 ]]; then -# echo -# pr_bold "CRIME Vulnerability, SPDY " ; outln "($cve): " - -# STR=$(grep Compression $TMPFILE ) -# if echo $STR | grep -q NONE >/dev/null; then -# pr_svrty_best "not vulnerable (OK)" -# ret=$((ret + 0)) -# else -# pr_svrty_critical "VULNERABLE (NOT ok)" -# ret=$((ret + 1)) -# fi -# fi -# fi -# [[ $DEBUG -ge 2 ]] tmln_out "$STR" - tmpfile_handle ${FUNCNAME[0]}.txt - return $ret -} - - -# BREACH is a HTTP-level compression & an attack which works against any cipher suite and is agnostic -# to the version of TLS/SSL, more: http://www.breachattack.com/ . Foreign referrers are the important thing here! -# Mitigation: see https://community.qualys.com/message/20360 -# -run_breach() { - local header - local -i ret=0 - local -i was_killed=0 - local referer useragent - local url="$1" - local spaces=" " - local disclaimer="" - local when_makesense=" Can be ignored for static pages or if no secrets in the page" - local cve="CVE-2013-3587" - local cwe="CWE-310" - local hint="" - local jsonID="BREACH" - - [[ $SERVICE != HTTP ]] && ! "$CLIENT_AUTH" && return 7 - - [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for BREACH (HTTP compression) vulnerability " && outln - pr_bold " BREACH"; out " ($cve) " - if "$CLIENT_AUTH"; then - prln_warning "client x509-based authentication prevents this from being tested" - fileout "$jsonID" "WARN" "client x509-based authentication prevents this from being tested" "$cve" "$cwe" - return 7 - fi - - # if [[ $NR_HEADER_FAIL -ge $MAX_HEADER_FAIL ]]; then - # pr_warning "Retrieving HTTP header failed before. Skipping." - # fileout "$jsonID" "WARN" "HTTP response was wampty before" "$cve" "$cwe" - # outln - # return 1 - # fi - - [[ -z "$url" ]] && url="/" - disclaimer=" - only supplied \"$url\" tested" - - referer="https://google.com/" - [[ "$NODE" =~ google ]] && referer="https://yandex.ru/" # otherwise we have a false positive for google.com - useragent="$UA_STD" - $SNEAKY && useragent="$UA_SNEAKY" - printf "GET $url HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $useragent\r\nReferer: $referer\r\nConnection: Close\r\nAccept-encoding: gzip,deflate,compress,br\r\nAccept: text/*\r\n\r\n" | $OPENSSL s_client $(s_client_options "$OPTIMAL_PROTO $BUGS -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI") 1>$TMPFILE 2>$ERRFILE & - wait_kill $! $HEADER_MAXSLEEP - was_killed=$? # !=0 was killed - result="$(grep -ia Content-Encoding: $TMPFILE)" - result="$(strip_lf "$result")" - result="${result#*:}" - result="$(strip_spaces "$result")" - debugme echo "$result" - if [[ ! -s $TMPFILE ]]; then - pr_warning "failed (HTTP header request stalled or empty return" - if [[ $was_killed -ne 0 ]]; then - pr_warning " and was terminated" - fileout "$jsonID" "WARN" "Test failed as HTTP request stalled and was terminated" "$cve" "$cwe" - else - fileout "$jsonID" "WARN" "Test failed as HTTP response was empty" "$cve" "$cwe" - fi - prln_warning ") " - ret=1 - elif [[ -z $result ]]; then - pr_svrty_good "no HTTP compression (OK) " - outln "$disclaimer" - fileout "$jsonID" "OK" "not vulnerable, no HTTP compression $disclaimer" "$cve" "$cwe" - else - pr_svrty_medium "potentially NOT ok, \"$result\" HTTP compression detected." - outln "$disclaimer" - outln "$spaces$when_makesense" - fileout "$jsonID" "MEDIUM" "potentially VULNERABLE, $result HTTP compression detected $disclaimer" "$cve" "$cwe" "$hint" - fi - # Any URL can be vulnerable. I am testing now only the given URL! - - tmpfile_handle ${FUNCNAME[0]}.txt - return $ret -} - - -# SWEET32 (https://sweet32.info/). Birthday attacks on 64-bit block ciphers. -# In a nutshell: don't use 3DES ciphers anymore (DES, RC2 and IDEA too). -# Please note as opposed to RC4 (stream cipher) RC2 is a block cipher. -# -run_sweet32() { - local -i sclient_success=1 - local sweet32_ciphers="IDEA-CBC-SHA:IDEA-CBC-MD5:RC2-CBC-MD5:KRB5-IDEA-CBC-SHA:KRB5-IDEA-CBC-MD5:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:SRP-DSS-3DES-EDE-CBC-SHA:SRP-RSA-3DES-EDE-CBC-SHA:SRP-3DES-EDE-CBC-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:DH-RSA-DES-CBC3-SHA:DH-DSS-DES-CBC3-SHA:AECDH-DES-CBC3-SHA:ADH-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:DES-CBC3-SHA:DES-CBC3-MD5:DES-CBC3-SHA:RSA-PSK-3DES-EDE-CBC-SHA:PSK-3DES-EDE-CBC-SHA:KRB5-DES-CBC3-SHA:KRB5-DES-CBC3-MD5:ECDHE-PSK-3DES-EDE-CBC-SHA:DHE-PSK-3DES-EDE-CBC-SHA:DES-CFB-M1:EXP1024-DHE-DSS-DES-CBC-SHA:EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:DH-RSA-DES-CBC-SHA:DH-DSS-DES-CBC-SHA:ADH-DES-CBC-SHA:EXP1024-DES-CBC-SHA:DES-CBC-SHA:EXP1024-RC2-CBC-MD5:DES-CBC-MD5:DES-CBC-SHA:KRB5-DES-CBC-SHA:KRB5-DES-CBC-MD5:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA:EXP-ADH-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-RC2-CBC-MD5:EXP-KRB5-RC2-CBC-SHA:EXP-KRB5-DES-CBC-SHA:EXP-KRB5-RC2-CBC-MD5:EXP-KRB5-DES-CBC-MD5:EXP-DH-DSS-DES-CBC-SHA:EXP-DH-RSA-DES-CBC-SHA" - local sweet32_ciphers_hex="00,07, 00,21, 00,25, c0,12, c0,08, c0,1c, c0,1b, c0,1a, 00,16, 00,13, 00,10, 00,0d, c0,17, 00,1b, c0,0d, c0,03, 00,0a, 00,93, 00,8b, 00,1f, 00,23, c0,34, 00,8f, fe,ff, ff,e0, 00,63, 00,15, 00,12, 00,0f, 00,0c, 00,1a, 00,62, 00,09, 00,61, 00,1e, 00,22, fe,fe, ff,e1, 00,14, 00,11, 00,19, 00,08, 00,06, 00,27, 00,26, 00,2a, 00,29, 00,0b, 00,0e" - local ssl2_sweet32_ciphers='RC2-CBC-MD5:EXP-RC2-CBC-MD5:IDEA-CBC-MD5:DES-CBC-MD5:DES-CBC-SHA:DES-CBC3-MD5:DES-CBC3-SHA:DES-CFB-M1' - local ssl2_sweet32_ciphers_hex='03,00,80, 04,00,80, 05,00,80, 06,00,40, 06,01,40, 07,00,C0, 07,01,C0, FF,80,00' - local nr_cipher_minimal=21 - local proto - local cve="CVE-2016-2183 CVE-2016-6329" - local cwe="CWE-327" - local hint="" - local -i nr_sweet32_ciphers=0 nr_supported_ciphers=0 nr_ssl2_sweet32_ciphers=0 nr_ssl2_supported_ciphers=0 - local ssl2_sweet=false - local using_sockets=true - - [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for SWEET32 (Birthday Attacks on 64-bit Block Ciphers) " && outln - pr_bold " SWEET32"; out " (${cve// /, }) " - - if "$TLS13_ONLY"; then - # Unfortunately there's no restriction using TLS 1.2 with $sweet32_ciphers - pr_svrty_best "not vulnerable (OK)" - [[ $DEBUG -ge 1 ]] && out ", TLS 1.3 doesn't offer such ciphers" - outln - fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" - return 0 - fi - - "$SSL_NATIVE" && using_sockets=false - # The openssl binary distributed has almost everything we need (PSK, KRB5 ciphers and feff, ffe0 are typically missing). - # Measurements show that there's little impact whether we use sockets or TLS here, so the default is sockets here. - if "$using_sockets"; then - for proto in 03 02 01 00; do - [[ $(has_server_protocol "$proto") -eq 1 ]] && continue - tls_sockets "$proto" "${sweet32_ciphers_hex}, 00,ff" - sclient_success=$? - [[ $sclient_success -eq 2 ]] && sclient_success=0 - [[ $sclient_success -eq 0 ]] && break - done - if [[ 1 -ne $(has_server_protocol "ssl2") ]]; then - sslv2_sockets "$ssl2_sweet32_ciphers_hex" - case $? in - 3) ssl2_sweet=true - add_tls_offered ssl2 yes ;; - 0) ;; # ssl2_sweet=false - 1|4|6|7) debugme "${FUNCNAME[0]}: test problem we don't handle here" - ;; - esac - fi - else - nr_sweet32_ciphers=$(count_ciphers $sweet32_ciphers) - nr_supported_ciphers=$(count_ciphers $(actually_supported_osslciphers $sweet32_ciphers)) - debugme echo "$nr_sweet32_ciphers / $nr_supported_ciphers" - - nr_ssl2_sweet32_ciphers=$(count_ciphers $ssl2_sweet32_ciphers) - nr_ssl2_supported_ciphers=$(count_ciphers $(actually_supported_osslciphers $ssl2_sweet32_ciphers)) - debugme echo "$nr_ssl2_sweet32_ciphers / $nr_ssl2_supported_ciphers" - - if [[ $(( nr_supported_ciphers + nr_ssl2_supported_ciphers )) -le $nr_cipher_minimal ]]; then - pr_local_problem "Only ${nr_supported_ciphers}+${nr_ssl2_supported_ciphers} \"SWEET32 ciphers\" found in your $OPENSSL." - outln " Test skipped" - fileout "SWEET32" "WARN" "Not tested, lack of local support ($((nr_supported_ciphers + nr_ssl2_supported_ciphers)) ciphers only)" "$cve" "$cwe" "$hint" - return 1 - fi - for proto in -no_ssl2 -tls1_1 -tls1 -ssl3; do - [[ $nr_supported_ciphers -eq 0 ]] && break - ! "$HAS_SSL3" && [[ "$proto" == -ssl3 ]] && continue - if [[ "$proto" != -no_ssl2 ]]; then - "$FAST" && break - [[ $(has_server_protocol "${proto:1}") -eq 1 ]] && continue - fi - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS $proto -cipher $sweet32_ciphers -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE </dev/null - sclient_connect_successful $? $TMPFILE - sclient_success=$? - [[ $DEBUG -ge 2 ]] && grep -Eq "error|failure" $ERRFILE | grep -Eav "unable to get local|verify error" - [[ $sclient_success -eq 0 ]] && break - done - if "$HAS_SSL2"; then - if [[ 1 -ne $(has_server_protocol "ssl2") ]]; then - $OPENSSL s_client $STARTTLS $BUGS -ssl2 -cipher $ssl2_sweet32_ciphers -connect $NODEIP:$PORT $PROXY >$TMPFILE 2>$ERRFILE </dev/null - sclient_connect_successful $? $TMPFILE - if [[ $? -eq 0 ]]; then - ssl2_sweet=true - add_tls_offered ssl2 yes - fi - fi - else - debugme tm_warning "Can't test with SSLv2 here as $OPENSSL lacks support" - # we omit adding a string for DEBUG==0 here as using sockets is the default and the following elif statement becomes ugly - fi - fi - if [[ $sclient_success -eq 0 ]] && "$ssl2_sweet" ; then - pr_svrty_low "VULNERABLE"; out ", uses 64 bit block ciphers for SSLv2 and above" - fileout "SWEET32" "LOW" "uses 64 bit block ciphers for SSLv2 and above" "$cve" "$cwe" "$hint" - elif [[ $sclient_success -eq 0 ]]; then - pr_svrty_low "VULNERABLE"; out ", uses 64 bit block ciphers" - fileout "SWEET32" "LOW" "uses 64 bit block ciphers" "$cve" "$cwe" "$hint" - elif "$ssl2_sweet"; then - pr_svrty_low "VULNERABLE"; out ", uses 64 bit block ciphers with SSLv2 only" - fileout "SWEET32" "LOW" "uses 64 bit block ciphers with SSLv2 only" "$cve" "$cwe" "$hint" - else - pr_svrty_best "not vulnerable (OK)"; - if "$using_sockets"; then - fileout "SWEET32" "OK" "not vulnerable" "$cve" "$cwe" - else - if [[ "$nr_supported_ciphers" -ge 38 ]]; then - # Likely only PSK/KRB5 ciphers are missing: display discrepancy but no warning - if "$HAS_SSL2"; then - out ", $nr_supported_ciphers/$nr_sweet32_ciphers (SSLv2: $nr_ssl2_sweet32_ciphers/$nr_ssl2_supported_ciphers) local ciphers" - fileout "SWEET32" "OK" "not vulnerable ($nr_supported_ciphers of $nr_sweet32_ciphers (SSLv2: $nr_ssl2_sweet32_ciphers/$nr_ssl2_supported_ciphers)) local ciphers" "$cve" "$cwe" - else - out ", $nr_supported_ciphers/$nr_sweet32_ciphers local ciphers" - fileout "SWEET32" "OK" "not vulnerable ($nr_supported_ciphers of $nr_sweet32_ciphers local ciphers" "$cve" "$cwe" - fi - else - if "$HAS_SSL2"; then - pr_warning ", $nr_supported_ciphers/$nr_sweet32_ciphers (SSLv2: $nr_ssl2_sweet32_ciphers/$nr_ssl2_supported_ciphers) local ciphers" - fileout "SWEET32" "WARN" "not vulnerable but ($nr_supported_ciphers of $nr_sweet32_ciphers (SSLv2: $nr_ssl2_sweet32_ciphers/$nr_ssl2_supported_ciphers)) local ciphers only" "$cve" "$cwe" - else - pr_warning ", $nr_supported_ciphers/$nr_sweet32_ciphers local ciphers" - fileout "SWEET32" "WARN" "not vulnerable but ($nr_supported_ciphers of $nr_sweet32_ciphers) local ciphers only" "$cve" "$cwe" - fi - fi - fi - fi - outln - tmpfile_handle ${FUNCNAME[0]}.txt - [[ $sclient_success -ge 6 ]] && return 1 - return 0 -} - - -# Padding Oracle On Downgraded Legacy Encryption, in a nutshell: don't use CBC Ciphers in SSLv3 -run_ssl_poodle() { - local -i sclient_success=0 - local cbc_ciphers="ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:SRP-DSS-AES-256-CBC-SHA:SRP-RSA-AES-256-CBC-SHA:SRP-AES-256-CBC-SHA:DHE-PSK-AES256-CBC-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DH-RSA-AES256-SHA:DH-DSS-AES256-SHA:DHE-RSA-CAMELLIA256-SHA:DHE-DSS-CAMELLIA256-SHA:DH-RSA-CAMELLIA256-SHA:DH-DSS-CAMELLIA256-SHA:AECDH-AES256-SHA:ADH-AES256-SHA:ADH-CAMELLIA256-SHA:ECDH-RSA-AES256-SHA:ECDH-ECDSA-AES256-SHA:AES256-SHA:ECDHE-PSK-AES256-CBC-SHA:CAMELLIA256-SHA:RSA-PSK-AES256-CBC-SHA:PSK-AES256-CBC-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:SRP-DSS-AES-128-CBC-SHA:SRP-RSA-AES-128-CBC-SHA:SRP-AES-128-CBC-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:DH-RSA-AES128-SHA:DH-DSS-AES128-SHA:DHE-RSA-SEED-SHA:DHE-DSS-SEED-SHA:DH-RSA-SEED-SHA:DH-DSS-SEED-SHA:DHE-RSA-CAMELLIA128-SHA:DHE-DSS-CAMELLIA128-SHA:DH-RSA-CAMELLIA128-SHA:DH-DSS-CAMELLIA128-SHA:AECDH-AES128-SHA:ADH-AES128-SHA:ADH-SEED-SHA:ADH-CAMELLIA128-SHA:ECDH-RSA-AES128-SHA:ECDH-ECDSA-AES128-SHA:AES128-SHA:ECDHE-PSK-AES128-CBC-SHA:DHE-PSK-AES128-CBC-SHA:SEED-SHA:CAMELLIA128-SHA:IDEA-CBC-SHA:RSA-PSK-AES128-CBC-SHA:PSK-AES128-CBC-SHA:KRB5-IDEA-CBC-SHA:KRB5-IDEA-CBC-MD5:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:SRP-DSS-3DES-EDE-CBC-SHA:SRP-RSA-3DES-EDE-CBC-SHA:SRP-3DES-EDE-CBC-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:DH-RSA-DES-CBC3-SHA:DH-DSS-DES-CBC3-SHA:AECDH-DES-CBC3-SHA:ADH-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:DES-CBC3-SHA:RSA-PSK-3DES-EDE-CBC-SHA:PSK-3DES-EDE-CBC-SHA:KRB5-DES-CBC3-SHA:KRB5-DES-CBC3-MD5:ECDHE-PSK-3DES-EDE-CBC-SHA:DHE-PSK-3DES-EDE-CBC-SHA:EXP1024-DHE-DSS-DES-CBC-SHA:EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:DH-RSA-DES-CBC-SHA:DH-DSS-DES-CBC-SHA:ADH-DES-CBC-SHA:EXP1024-DES-CBC-SHA:DES-CBC-SHA:KRB5-DES-CBC-SHA:KRB5-DES-CBC-MD5:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA:EXP-ADH-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-KRB5-RC2-CBC-SHA:EXP-KRB5-DES-CBC-SHA:EXP-KRB5-RC2-CBC-MD5:EXP-KRB5-DES-CBC-MD5:EXP-DH-DSS-DES-CBC-SHA:EXP-DH-RSA-DES-CBC-SHA" - local cbc_ciphers_hex="c0,14, c0,0a, c0,22, c0,21, c0,20, 00,91, 00,39, 00,38, 00,37, 00,36, 00,88, 00,87, 00,86, 00,85, c0,19, 00,3a, 00,89, c0,0f, c0,05, 00,35, c0,36, 00,84, 00,95, 00,8d, c0,13, c0,09, c0,1f, c0,1e, c0,1d, 00,33, 00,32, 00,31, 00,30, 00,9a, 00,99, 00,98, 00,97, 00,45, 00,44, 00,43, 00,42, c0,18, 00,34, 00,9b, 00,46, c0,0e, c0,04, 00,2f, c0,35, 00,90, 00,96, 00,41, 00,07, 00,94, 00,8c, 00,21, 00,25, c0,12, c0,08, c0,1c, c0,1b, c0,1a, 00,16, 00,13, 00,10, 00,0d, c0,17, 00,1b, c0,0d, c0,03, 00,0a, 00,93, 00,8b, 00,1f, 00,23, c0,34, 00,8f, 00,63, 00,15, 00,12, 00,0f, 00,0c, 00,1a, 00,62, 00,09, 00,1e, 00,22, 00,14, 00,11, 00,19, 00,08, 00,06, 00,27, 00,26, 00,2a, 00,29, 00,0b, 00,0e" - local hint="" - local -i nr_cbc_ciphers=0 - local using_sockets=true - local cve="CVE-2014-3566" - local cwe="CWE-310" - local jsonID="POODLE_SSL" - - [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for SSLv3 POODLE (Padding Oracle On Downgraded Legacy Encryption) " && outln - pr_bold " POODLE, SSL"; out " ($cve) " - - if "$TLS13_ONLY" || [[ $(has_server_protocol ssl3) -eq 1 ]]; then - # one condition should normally suffice but we don't know when run_poddle() was called - pr_svrty_best "not vulnerable (OK)" - outln ", no SSLv3 support" - fileout "$jsonID" "OK" "not vulnerable, no SSLv3" "$cve" "$cwe" - return 0 - fi - - "$SSL_NATIVE" && using_sockets=false - # The openssl binary distributed has almost everything we need (PSK and KRB5 ciphers are typically missing). - # Measurements show that there's little impact whether we use sockets or TLS here, so the default is sockets here - if "$using_sockets"; then - tls_sockets "00" "$cbc_ciphers_hex, 00,ff" - sclient_success=$? - else - if ! "$HAS_SSL3"; then - prln_local_problem "Your $OPENSSL doesn't support SSLv3" - return 1 - fi - nr_cbc_ciphers=$(count_ciphers $cbc_ciphers) - nr_supported_ciphers=$(count_ciphers $(actually_supported_osslciphers $cbc_ciphers)) - # SNI not needed as SSLv3 has none: - $OPENSSL s_client -ssl3 $STARTTLS $BUGS -cipher $cbc_ciphers -connect $NODEIP:$PORT $PROXY >$TMPFILE 2>$ERRFILE </dev/null - sclient_connect_successful $? $TMPFILE - sclient_success=$? - [[ "$DEBUG" -eq 2 ]] && grep -Eq "error|failure" $ERRFILE | grep -Eav "unable to get local|verify error" - fi - if [[ $sclient_success -eq 0 ]]; then - POODLE=0 - pr_svrty_high "VULNERABLE (NOT ok)"; out ", uses SSLv3+CBC (check TLS_FALLBACK_SCSV mitigation below)" - fileout "$jsonID" "HIGH" "VULNERABLE, uses SSLv3+CBC" "$cve" "$cwe" "$hint" - else - POODLE=1 - pr_svrty_best "not vulnerable (OK)"; - if "$using_sockets"; then - fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" - else - if [[ "$nr_supported_ciphers" -ge 83 ]]; then - # Likely only KRB and PSK cipher are missing: display discrepancy but no warning - out ", $nr_supported_ciphers/$nr_cbc_ciphers local ciphers" - else - pr_warning ", $nr_supported_ciphers/$nr_cbc_ciphers local ciphers" - fi - fileout "$jsonID" "OK" "not vulnerable ($nr_supported_ciphers of $nr_cbc_ciphers local ciphers" "$cve" "$cwe" - fi - fi - outln - tmpfile_handle ${FUNCNAME[0]}.txt - return 0 -} - -# for appliance which use padding, no fallback needed -run_tls_poodle() { - local cve="CVE-2014-8730" - local cwe="CWE-310" - local jsonID="POODLE_TLS" - - pr_bold " POODLE, TLS"; out " ($cve), experimental " - #FIXME - prln_warning "#FIXME" - fileout "$jsonID" "WARN" "Not yet implemented #FIXME" "$cve" "$cwe" - return 0 -} - -#FIXME: fileout needs to be patched according to new scheme. Postponed as otherwise merge fails ?? -# -# This isn't a vulnerability check per se, but checks for the existence of -# the countermeasure to protect against protocol downgrade attacks. -# -run_tls_fallback_scsv() { - local -i ret=0 - local high_proto="" low_proto="" - local p high_proto_str protos_to_try - local jsonID="fallback_SCSV" - - [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for TLS_FALLBACK_SCSV Protection " && outln - pr_bold " TLS_FALLBACK_SCSV"; out " (RFC 7507) " - - # First check we have support for TLS_FALLBACK_SCSV in our local OpenSSL - if ! "$HAS_FALLBACK_SCSV"; then - prln_local_problem "$OPENSSL lacks TLS_FALLBACK_SCSV support" - fileout "$jsonID" "WARN" "$OPENSSL lacks TLS_FALLBACK_SCSV support" - return 1 - fi - - # First determine the highest protocol that the server supports (not including TLSv1.3). - if [[ "$OPTIMAL_PROTO" == -ssl2 ]]; then - prln_svrty_critical "No fallback possible, SSLv2 is the only protocol" - fileout "$jsonID" "CRITICAL" "SSLv2 is the only protocol" - return 0 - fi - for p in tls1_2 tls1_1 tls1 ssl3; do - [[ $(has_server_protocol "$p") -eq 1 ]] && continue - if [[ $(has_server_protocol "$p") -eq 0 ]]; then - high_proto="$p" - break - fi - [[ "$p" == ssl3 ]] && ! "$HAS_SSL3" && continue - $OPENSSL s_client $(s_client_options "-$p $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE </dev/null - if sclient_connect_successful $? $TMPFILE; then - high_proto="$p" - break - fi - done - case "$high_proto" in - "tls1_2") - high_proto_str="TLS 1.2" - protos_to_try="tls1_1 tls1 ssl3" ;; - "tls1_1") - high_proto_str="TLS 1.1" - protos_to_try="tls1 ssl3" ;; - "tls1") - high_proto_str="TLS 1" - protos_to_try="ssl3" ;; - "ssl3") - prln_svrty_high "No fallback possible, SSLv3 is the only protocol" - fileout "$jsonID" "HIGH" "only SSLv3 supported" - return 0 - ;; - *) if [[ $(has_server_protocol tls1_3) -eq 0 ]]; then - # If the server supports TLS 1.3, and does not support TLS 1.2, TLS 1.1, or TLS 1, - # then assume it does not support SSLv3, even if SSLv3 cannot be tested. - pr_svrty_good "No fallback possible (OK)"; outln ", TLS 1.3 is the only protocol" - fileout "$jsonID" "OK" "only TLS 1.3 supported" - elif [[ $(has_server_protocol tls1_3) -eq 1 ]] && \ - ( [[ $(has_server_protocol ssl3) -eq 1 ]] || "$HAS_SSL3" ); then - # TLS 1.3, TLS 1.2, TLS 1.1, TLS 1, and SSLv3 are all not supported. - # This may be an SSLv2-only server, if $OPENSSL does not support SSLv2. - prln_warning "test failed (couldn't connect)" - fileout "$jsonID" "WARN" "Check failed. (couldn't connect)" - return 1 - elif [[ $(has_server_protocol tls1_3) -eq 1 ]]; then - # If the server does not support TLS 1.3, TLS 1.2, TLS 1.1, or TLS 1, and - # support for SSLv3 cannot be tested, then treat it as HIGH severity, since - # it is very likely that SSLv3 is the only supported protocol. - pr_svrty_high "NOT ok, no fallback possible"; outln ", TLS 1.3, 1.2, 1.1 and 1.0 not supported" - fileout "$jsonID" "HIGH" "TLS 1.3, 1.2, 1.1, 1.0 not supported" - else - # TLS 1.2, TLS 1.1, and TLS 1 are not supported, but can't tell whether TLS 1.3 is supported. - # This could be a TLS 1.3 only server, an SSLv3 only server (if SSLv3 support cannot be tested), - # or a server that does not support SSLv3 or any TLS protocol. So, don't report a severity, - # since this could either be good or bad. - outln "No fallback possible, TLS 1.2, TLS 1.1, and TLS 1 not supported" - fileout "$jsonID" "INFO" "TLS 1.2, TLS 1.1, and TLS 1 not supported" - fi - return 0 - esac - - # Next find a second protocol that the server supports. - for p in $protos_to_try; do - [[ $(has_server_protocol "$p") -eq 1 ]] && continue - if [[ $(has_server_protocol "$p") -eq 0 ]]; then - low_proto="$p" - break - fi - [[ "$p" == ssl3 ]] && ! "$HAS_SSL3" && continue - $OPENSSL s_client $(s_client_options "-$p $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE </dev/null - if sclient_connect_successful $? $TMPFILE; then - low_proto="$p" - break - fi - done - - if ! "$HAS_SSL3" && \ - ( [[ "$low_proto" == ssl3 ]] || \ - ( [[ "$high_proto" == tls1 ]] && [[ $(has_server_protocol ssl3) -eq 2 ]] ) ); then - # If the protocol that the server would fall back to is SSLv3, but $OPENSSL does - # not support SSLv3, then the test cannot be performed. So, if $OPENSSL does not - # support SSLv3 and it is known that SSLv3 is the fallback protocol ($low_proto), then - # the test cannot be performed. Similarly, if SSLv3 could be the fallback protocol, but - # support for SSLv3 is unknown, then the test cannot be performed. - # NOTE: This check assumes that any server that supports SSLv3 and either TLS 1.2 or - # TLS 1.1 would also support TLS 1. So, if $high_proto is not TLS 1, then it is assumed - # that either (1) $low_proto has already been set (to TLS1.1 or TLS 1) or (2) no protocol - # lower than $high_proto is offered. - prln_local_problem "Can't test: $OPENSSL does not support SSLv3" - fileout "$jsonID" "WARN" "Can't test: $OPENSSL does not support SSLv3" - return 1 - fi - if [[ -z "$low_proto" ]]; then - case "$high_proto" in - "tls1_2") - pr_svrty_good "No fallback possible (OK)"; outln ", no protocol below $high_proto_str offered" - ;; - *) outln "No fallback possible, no protocol below $high_proto_str offered (OK)" - ;; - esac - fileout "$jsonID" "OK" "no protocol below $high_proto_str offered" - return 0 - fi - case "$low_proto" in - "tls1_1") - p="-no_tls1_2" ;; - "tls1") - p="-no_tls1_2 -no_tls1_1" ;; - "ssl3") - p="-no_tls1_2 -no_tls1_1 -no_tls1" ;; - esac - "$HAS_TLS13" && p+=" -no_tls1_3" - debugme echo "Simulating fallback from $high_proto to $low_proto" - - # ...and do the test (we need to parse the error here!) - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI $p -fallback_scsv") &>$TMPFILE </dev/null - if grep -q "CONNECTED(00" "$TMPFILE"; then - if grep -qa "BEGIN CERTIFICATE" "$TMPFILE"; then - if [[ -z "$POODLE" ]]; then - pr_warning "Rerun including POODLE SSL check. " - pr_svrty_medium "Downgrade attack prevention NOT supported" - fileout "$jsonID" "WARN" "NOT supported. Pls rerun wity POODLE SSL check" - ret=1 - elif [[ "$POODLE" -eq 0 ]]; then - pr_svrty_high "Downgrade attack prevention NOT supported and vulnerable to POODLE SSL" - fileout "$jsonID" "HIGH" "NOT supported and vulnerable to POODLE SSL" - else - pr_svrty_medium "Downgrade attack prevention NOT supported" - fileout "$jsonID" "MEDIUM" "NOT supported" - fi - elif grep -qa "alert inappropriate fallback" "$TMPFILE"; then - pr_svrty_good "Downgrade attack prevention supported (OK)" - fileout "$jsonID" "OK" "supported" - elif grep -qa "alert handshake failure" "$TMPFILE"; then - pr_svrty_good "Probably OK. " - fileout "$jsonID" "OK" "Probably oK" - # see RFC 7507, https://github.com/drwetter/testssl.sh/issues/121 - # other case reported by Nicolas was F5 and at costumer of mine: the same - pr_svrty_medium "But received non-RFC-compliant \"handshake failure\" instead of \"inappropriate fallback\"" - fileout "$jsonID" "MEDIUM" "received non-RFC-compliant \"handshake failure\" instead of \"inappropriate fallback\"" - elif grep -qa "ssl handshake failure" "$TMPFILE"; then - pr_svrty_medium "some unexpected \"handshake failure\" instead of \"inappropriate fallback\"" - fileout "$jsonID" "MEDIUM" "some unexpected \"handshake failure\" instead of \"inappropriate fallback\" (likely: warning)" - else - pr_warning "Check failed, unexpected result " - out ", run $PROG_NAME -Z --debug=1 and look at $TEMPDIR/*tls_fallback_scsv.txt" - fileout "$jsonID" "WARN" "Check failed, unexpected result, run $PROG_NAME -Z --debug=1 and look at $TEMPDIR/*tls_fallback_scsv.txt" - ret=1 - fi - else - pr_warning "test failed (couldn't connect)" - fileout "$jsonID" "WARN" "Check failed. (couldn't connect)" - ret=1 - fi - - outln - tmpfile_handle ${FUNCNAME[0]}.txt - return $ret -} - - -# Factoring RSA Export Keys: don't use EXPORT RSA ciphers, see https://freakattack.com/ -run_freak() { - local -i sclient_success=0 - local -i i nr_supported_ciphers=0 len - # with correct build it should list these 9 ciphers (plus the two latter as SSLv2 ciphers): - local exportrsa_cipher_list="EXP1024-DES-CBC-SHA:EXP1024-RC2-CBC-MD5:EXP1024-RC4-SHA:EXP1024-RC4-MD5:EXP-EDH-RSA-DES-CBC-SHA:EXP-DH-RSA-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-RC4-MD5" - local exportrsa_tls_cipher_list_hex="00,62, 00,61, 00,64, 00,60, 00,14, 00,0E, 00,08, 00,06, 00,03" - local exportrsa_ssl2_cipher_list_hex="04,00,80, 02,00,80, 00,00,00" - local detected_ssl2_ciphers - local addtl_warning="" hexc - local using_sockets=true - local cve="CVE-2015-0204" - local cwe="CWE-310" - local hint="" - local jsonID="FREAK" - - [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for FREAK attack " && outln - pr_bold " FREAK"; out " ($cve) " - - if "$TLS13_ONLY"; then - pr_svrty_best "not vulnerable (OK)" - [[ $DEBUG -ge 1 ]] && out ", TLS 1.3 only server" - outln - fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" - return 0 - fi - - "$SSL_NATIVE" && using_sockets=false - if "$using_sockets"; then - nr_supported_ciphers=$(count_words "$exportrsa_tls_cipher_list_hex")+$(count_words "$exportrsa_ssl2_cipher_list_hex") - else - nr_supported_ciphers=$(count_ciphers $(actually_supported_osslciphers $exportrsa_cipher_list)) - fi - - case $nr_supported_ciphers in - 0) prln_local_problem "$OPENSSL doesn't have any EXPORT RSA ciphers configured" - fileout "$jsonID" "WARN" "Not tested. $OPENSSL doesn't have any EXPORT RSA ciphers configured" "$cve" "$cwe" - return 0 - ;; - 1|2|3) - addtl_warning=" ($magenta""tested only with $nr_supported_ciphers out of 9 ciphers only!$off)" ;; - 4|5|6|7) - addtl_warning=" (tested with $nr_supported_ciphers/9 ciphers)" ;; - 8|9|10|11) - addtl_warning="" ;; - esac - if "$using_sockets"; then - tls_sockets "03" "$exportrsa_tls_cipher_list_hex, 00,ff" - sclient_success=$? - [[ $sclient_success -eq 2 ]] && sclient_success=0 - - # TLS handshake failed with ciphers above. Now we check SSLv2 -- unless we know it's not available - if [[ $sclient_success -ne 0 ]] && [[ $(has_server_protocol ssl2) -ne 1 ]]; then - sslv2_sockets "$exportrsa_ssl2_cipher_list_hex" "true" - if [[ $? -eq 3 ]] && [[ "$V2_HELLO_CIPHERSPEC_LENGTH" -ne 0 ]]; then - exportrsa_ssl2_cipher_list_hex="$(strip_spaces "${exportrsa_ssl2_cipher_list_hex//,/}")" - len=${#exportrsa_ssl2_cipher_list_hex} - detected_ssl2_ciphers="$(grep "Supported cipher: " "$TEMPDIR/$NODEIP.parse_sslv2_serverhello.txt")" - for (( i=0; i<len; i=i+6 )); do - [[ "$detected_ssl2_ciphers" =~ x${exportrsa_ssl2_cipher_list_hex:i:6} ]] && sclient_success=0 && break - done - fi - fi - else - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -cipher $exportrsa_cipher_list -connect $NODEIP:$PORT $PROXY $SNI -no_ssl2") >$TMPFILE 2>$ERRFILE </dev/null - sclient_connect_successful $? $TMPFILE - sclient_success=$? - debugme grep -Ea "error|failure" $ERRFILE | grep -Eav "unable to get local|verify error" - if [[ $sclient_success -ne 0 ]] && "$HAS_SSL2"; then - $OPENSSL s_client $STARTTLS $BUGS -cipher $exportrsa_cipher_list -connect $NODEIP:$PORT $PROXY -ssl2 >$TMPFILE 2>$ERRFILE </dev/null - sclient_connect_successful $? $TMPFILE - sclient_success=$? - fi - fi - if [[ $sclient_success -eq 0 ]]; then - pr_svrty_critical "VULNERABLE (NOT ok)"; out ", uses EXPORT RSA ciphers" - fileout "$jsonID" "CRITICAL" "VULNERABLE, uses EXPORT RSA ciphers" "$cve" "$cwe" "$hint" - else - pr_svrty_best "not vulnerable (OK)"; out "$addtl_warning" - fileout "$jsonID" "OK" "not vulnerable $addtl_warning" "$cve" "$cwe" - fi - outln - - if [[ $DEBUG -ge 2 ]]; then - if "$using_sockets"; then - for hexc in $(sed 's/, / /g' <<< "$exportrsa_tls_cipher_list_hex, $exportrsa_ssl2_cipher_list_hex"); do - if [[ ${#hexc} -eq 5 ]]; then - hexc="0x${hexc:0:2},0x${hexc:3:2}" - else - hexc="0x${hexc:0:2},0x${hexc:3:2},0x${hexc:6:2}" - fi - for (( i=0; i < TLS_NR_CIPHERS; i++ )); do - [[ "$hexc" == "${TLS_CIPHER_HEXCODE[i]}" ]] && break - done - [[ $i -eq $TLS_NR_CIPHERS ]] && tm_out "$hexc " || tm_out "${TLS_CIPHER_OSSL_NAME[i]} " - done - tmln_out - else - actually_supported_osslciphers $exportrsa_cipher_list - fi - fi - debugme echo $nr_supported_ciphers - - tmpfile_handle ${FUNCNAME[0]}.txt - return 0 -} - -# ARGs see below -# Sets the global DH_GROUP_OFFERED, start value: "", after this function: -# DH_GROUP_OFFERED="" -# DH_GROUP_OFFERED="<name of group>" -# return: 1: common primes file problem, 2: no pkey support, 0: went w/o error -get_common_prime() { - local jsonID2="$1" - local key_bitstring="$2" - local spaces="$3" - local pubkey dh_p="" - local -i subret=0 - local common_primes_file="$TESTSSL_INSTALL_DIR/etc/common-primes.txt" - local -i lineno_matched=0 - - "$HAS_PKEY" || return 2 - pubkey="$($OPENSSL pkey -pubin -text_pub -noout 2>>$ERRFILE <<< "$key_bitstring")" - if [[ "$pubkey" =~ GROUP: ]]; then - DH_GROUP_OFFERED="${pubkey#*GROUP: }" - case "$DH_GROUP_OFFERED" in - modp_1536) DH_GROUP_OFFERED="RFC3526/Oakley Group 5" ;; - modp_2048) DH_GROUP_OFFERED="RFC3526/Oakley Group 14" ;; - modp_3072) DH_GROUP_OFFERED="RFC3526/Oakley Group 15" ;; - modp_4096) DH_GROUP_OFFERED="RFC3526/Oakley Group 16" ;; - modp_6144) DH_GROUP_OFFERED="RFC3526/Oakley Group 17" ;; - modp_8192) DH_GROUP_OFFERED="RFC3526/Oakley Group 18" ;; - dh_1024_160) DH_GROUP_OFFERED="RFC5114/1024-bit DSA group with 160-bit prime order subgroup" ;; - dh_2048_224) DH_GROUP_OFFERED="RFC5114/2048-bit DSA group with 224-bit prime order subgroup" ;; - dh_2048_256) DH_GROUP_OFFERED="RFC5114/2048-bit DSA group with 256-bit prime order subgroup" ;; - esac - pubkey="$(awk -F'(' '/Public-Key/ { print $2 }' <<< "$pubkey")" - DH_GROUP_LEN_P="${pubkey%% bit*}" - return 0 - fi - dh_p="$(awk '/prime:|P:/,/generator:|G:/' <<< "$pubkey" | grep -Ev "prime|P:|generator|G:")" - dh_p="$(strip_spaces "$(colon_to_spaces "$(newline_to_spaces "$dh_p")")")" - [[ "${dh_p:0:2}" == "00" ]] && dh_p="${dh_p:2}" - DH_GROUP_LEN_P="$((4*${#dh_p}))" - debugme tmln_out "len(dh_p): $DH_GROUP_LEN_P | dh_p: $dh_p" - [[ "$DEBUG" -gt 1 ]] && echo "$dh_p" > $TEMPDIR/dh_p.txt - if [[ ! -s "$common_primes_file" ]]; then - prln_local_problem "couldn't read common primes file $common_primes_file" - out "${spaces}" - fileout "$jsonID2" "WARN" "couldn't read common primes file $common_primes_file" - return 1 - else - dh_p="$(toupper "$dh_p")" - # In the previous line of the match is basically the hint we want to echo - # the most elegant thing to get the previous line [ awk '/regex/ { print x }; { x=$0 }' ] doesn't work with gawk - lineno_matched=$(grep -n "$dh_p" "$common_primes_file" 2>/dev/null | awk -F':' '{ print $1 }') - if [[ "$lineno_matched" -ne 0 ]]; then - DH_GROUP_OFFERED="$(awk "NR == $lineno_matched-1" "$common_primes_file" | awk -F'"' '{ print $2 }')" - #subret=1 # vulnerable: common prime - else - DH_GROUP_OFFERED="Unknown DH group" - : - #subret=0 # not vulnerable: no known common prime - fi - return 0 - fi -} - - -# helper function for run_logjam see below -# -out_common_prime() { - local jsonID2="$1" - local cve="$2" - local cwe="$3" - - [[ "$DH_GROUP_OFFERED" == ffdhe* ]] && [[ ! "$DH_GROUP_OFFERED" =~ \ ]] && DH_GROUP_OFFERED="RFC7919/$DH_GROUP_OFFERED" - if [[ "$DH_GROUP_OFFERED" =~ ffdhe ]] && [[ "$DH_GROUP_OFFERED" =~ \ ]]; then - out "common primes detected: "; pr_italic "$DH_GROUP_OFFERED" - fileout "$jsonID2" "INFO" "$DH_GROUP_OFFERED" "$cve" "$cwe" - # Now (below) size matters -- i.e. the bit size. As this is about a known prime we label it more strict. - # This needs maybe needs another thought as it could appear inconsistent with run_pfs and elsewhere. - # for now we label the bit size similar in the screen, but distinguish the leading text for logjam before - elif [[ $DH_GROUP_LEN_P -le 800 ]]; then - pr_svrty_critical "VULNERABLE (NOT ok):"; out " common prime: " - fileout "$jsonID2" "CRITICAL" "$DH_GROUP_OFFERED" "$cve" "$cwe" - pr_dh "$DH_GROUP_OFFERED" $DH_GROUP_LEN_P - elif [[ $DH_GROUP_LEN_P -le 1024 ]]; then - # really? Here we assume that 1024-bit common prime for nation states are worth and possible to precompute (TBC) - # otherwise 1024 are just medium - pr_svrty_high "VULNERABLE (NOT ok):"; out " common prime: " - fileout "$jsonID2" "HIGH" "$DH_GROUP_OFFERED" "$cve" "$cwe" - pr_dh "$DH_GROUP_OFFERED" $DH_GROUP_LEN_P - elif [[ $DH_GROUP_LEN_P -le 1536 ]]; then - pr_svrty_low "common prime: " - fileout "$jsonID2" "LOW" "$DH_GROUP_OFFERED" "$cve" "$cwe" - pr_dh "$DH_GROUP_OFFERED" $DH_GROUP_LEN_P - else - out "common prime with $DH_GROUP_LEN_P bits detected: " - fileout "$jsonID2" "INFO" "$DH_GROUP_OFFERED" "$cve" "$cwe" - pr_dh "$DH_GROUP_OFFERED" $DH_GROUP_LEN_P - fi -} - - -# see https://weakdh.org/logjam.html -run_logjam() { - local -i sclient_success=0 - local exportdh_cipher_list="EXP1024-DHE-DSS-DES-CBC-SHA:EXP1024-DHE-DSS-RC4-SHA:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA" - local exportdh_cipher_list_hex="00,63, 00,65, 00,14, 00,11" - local all_dh_ciphers="cc,15, 00,b3, 00,91, c0,97, 00,a3, 00,9f, cc,aa, c0,a3, c0,9f, 00,6b, 00,6a, 00,39, 00,38, 00,c4, 00,c3, 00,88, 00,87, 00,a7, 00,6d, 00,3a, 00,c5, 00,89, 00,ab, cc,ad, c0,a7, c0,43, c0,45, c0,47, c0,53, c0,57, c0,5b, c0,67, c0,6d, c0,7d, c0,81, c0,85, c0,91, 00,a2, 00,9e, c0,a2, c0,9e, 00,aa, c0,a6, 00,67, 00,40, 00,33, 00,32, 00,be, 00,bd, 00,9a, 00,99, 00,45, 00,44, 00,a6, 00,6c, 00,34, 00,bf, 00,9b, 00,46, 00,b2, 00,90, c0,96, c0,42, c0,44, c0,46, c0,52, c0,56, c0,5a, c0,66, c0,6c, c0,7c, c0,80, c0,84, c0,90, 00,66, 00,18, 00,8e, 00,16, 00,13, 00,1b, 00,8f, 00,63, 00,15, 00,12, 00,1a, 00,65, 00,14, 00,11, 00,19, 00,17, 00,b5, 00,b4, 00,2d" # 93 ciphers - local -i i nr_supported_ciphers=0 server_key_exchange_len=0 ephemeral_pub_len=0 - local addtl_warning="" hexc - local -i ret=0 subret=0 - local server_key_exchange key_bitstring="" - local spaces=" " - local vuln_exportdh_ciphers=false - local openssl_no_expdhciphers=false - local str="" - local using_sockets=true - local cve="CVE-2015-4000" - local cwe="CWE-310" - local hint="" - local jsonID="LOGJAM" - local jsonID2="${jsonID}-common_primes" - - [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for LOGJAM vulnerability " && outln - pr_bold " LOGJAM"; out " ($cve), experimental " - - "$SSL_NATIVE" && using_sockets=false - # Also as the openssl binary distributed has everything we need measurements show that - # there's no impact whether we use sockets or TLS here, so the default is sockets here - if ! "$using_sockets"; then - nr_supported_ciphers=$(count_ciphers $(actually_supported_osslciphers $exportdh_cipher_list)) - debugme echo $nr_supported_ciphers - case $nr_supported_ciphers in - 0) prln_local_problem "$OPENSSL doesn't have any DH EXPORT ciphers configured" - fileout "$jsonID" "WARN" "Not tested. $OPENSSL doesn't support any DH EXPORT ciphers" "$cve" "$cwe" - out "$spaces" - openssl_no_expdhciphers=true - ;; - 1|2|3) addtl_warning=" ($magenta""tested w/ $nr_supported_ciphers/4 ciphers only!$off)" ;; - 4) ;; - esac - fi - - # test for DH export ciphers first - if "$using_sockets"; then - tls_sockets "03" "$exportdh_cipher_list_hex, 00,ff" - sclient_success=$? - [[ $sclient_success -eq 2 ]] && sclient_success=0 - [[ $sclient_success -eq 0 ]] && vuln_exportdh_ciphers=true - elif [[ $nr_supported_ciphers -ne 0 ]]; then - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -cipher $exportdh_cipher_list -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE </dev/null - sclient_connect_successful $? $TMPFILE - [[ $? -eq 0 ]] && vuln_exportdh_ciphers=true - debugme grep -Ea "error|failure" $ERRFILE | grep -Eav "unable to get local|verify error" - fi - - if [[ $DEBUG -ge 2 ]]; then - if "$using_sockets"; then - for hexc in $(sed 's/, / /g' <<< "$exportdh_cipher_list_hex"); do - hexc="0x${hexc:0:2},0x${hexc:3:2}" - for (( i=0; i < TLS_NR_CIPHERS; i++ )); do - [[ "$hexc" == "${TLS_CIPHER_HEXCODE[i]}" ]] && break - done - [[ $i -eq $TLS_NR_CIPHERS ]] && tm_out "$hexc " || tm_out "${TLS_CIPHER_OSSL_NAME[i]} " - done - tmln_out - else - echo $(actually_supported_osslciphers $exportdh_cipher_list) - fi - fi - - # Try all ciphers that use an ephemeral DH key. If successful, check whether the key uses a weak prime. - if [[ -n "$DH_GROUP_OFFERED" ]]; then - if [[ "$DH_GROUP_OFFERED" =~ Unknown ]]; then - subret=0 # no common DH key detected - else - subret=1 # known prime/DH key - fi - elif "$using_sockets"; then - tls_sockets "03" "$all_dh_ciphers, 00,ff" "ephemeralkey" - sclient_success=$? - if [[ $sclient_success -eq 0 ]] || [[ $sclient_success -eq 2 ]]; then - cp "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" $TMPFILE - key_bitstring="$(awk '/-----BEGIN PUBLIC KEY/,/-----END PUBLIC KEY/ { print $0 }' $TMPFILE)" - fi - else - # FIXME: determine # of ciphers supported, 48 only are the shipped binaries - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -cipher kEDH -msg -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE </dev/null - sclient_connect_successful $? $TMPFILE - if [[ $? -eq 0 ]] && grep -q ServerKeyExchange $TMPFILE; then - # Example: '<<< TLS 1.0 Handshake [length 010b], ServerKeyExchange' - # get line with ServerKeyExchange, cut from the beginning to "length ". cut from the end to ']' - str="$(awk '/<<< TLS 1.[0-2].*ServerKeyExchange$/' $TMPFILE)" - if [[ -z "$str" ]] ; then - str="$(awk '/<<< SSL [2-3].*ServerKeyExchange$/' $TMPFILE)" - fi - str="${str#<*length }" - str="${str%]*}" - server_key_exchange_len=$(hex2dec "$str") - server_key_exchange_len=2+$server_key_exchange_len/16 - server_key_exchange="$(grep -A $server_key_exchange_len ServerKeyExchange $TMPFILE | tail -n +2)" - server_key_exchange="$(toupper "$(strip_spaces "$(newline_to_spaces "$server_key_exchange")")")" - server_key_exchange="${server_key_exchange%%[!0-9A-F]*}" - server_key_exchange_len=${#server_key_exchange} - [[ $server_key_exchange_len -gt 8 ]] && [[ "${server_key_exchange:0:2}" == "0C" ]] && ephemeral_pub_len=$(hex2dec "${server_key_exchange:2:6}") - [[ $ephemeral_pub_len -ne 0 ]] && [[ $ephemeral_pub_len -le $server_key_exchange_len ]] && key_bitstring="$(get_dh_ephemeralkey "${server_key_exchange:8}")" - fi - fi - - if [[ -n "$key_bitstring" ]]; then - if [[ -z "$DH_GROUP_OFFERED" ]]; then - get_common_prime "$jsonID2" "$key_bitstring" "$spaces" - ret=$? # no common primes file would be ret=1 --> we should treat that some place else before - fi - if [[ "$DH_GROUP_OFFERED" =~ Unknown ]]; then - subret=0 # no common DH key detected - else - subret=1 # known prime/DH key - fi - elif [[ -z "$DH_GROUP_OFFERED" ]]; then - subret=3 - fi - - # Now if we have DH export ciphers we print them out first - if "$vuln_exportdh_ciphers"; then - pr_svrty_high "VULNERABLE (NOT ok):"; out " uses DH EXPORT ciphers" - fileout "$jsonID" "HIGH" "VULNERABLE, uses DH EXPORT ciphers" "$cve" "$cwe" "$hint" - if [[ $subret -eq 3 ]]; then - out ", no DH key detected with <= TLS 1.2" - fileout "$jsonID2" "OK" "no DH key detected with <= TLS 1.2" - elif [[ $subret -eq 1 ]]; then - out "\n${spaces}" - out_common_prime "$jsonID2" "$cve" "$cwe" - elif [[ $subret -eq 0 ]]; then - out " no common primes detected" - fileout "$jsonID2" "INFO" "--" "$cve" "$cwe" - elif [[ $ret -eq 1 ]]; then - out "FIXME 1" - fi - else - if [[ $subret -eq 1 ]]; then - out_common_prime "$jsonID2" "$cve" "$cwe" - if ! "$openssl_no_expdhciphers"; then - outln "," - out "${spaces}but no DH EXPORT ciphers${addtl_warning}" - fileout "$jsonID" "OK" "not vulnerable, no DH EXPORT ciphers,$addtl_warning" "$cve" "$cwe" - fi - elif [[ $subret -eq 3 ]]; then - pr_svrty_good "not vulnerable (OK):"; out " no DH EXPORT ciphers${addtl_warning}" - fileout "$jsonID" "OK" "not vulnerable, no DH EXPORT ciphers,$addtl_warning" "$cve" "$cwe" - out ", no DH key detected with <= TLS 1.2" - fileout "$jsonID2" "OK" "no DH key with <= TLS 1.2" "$cve" "$cwe" - elif [[ $subret -eq 0 ]]; then - pr_svrty_good "not vulnerable (OK):"; out " no DH EXPORT ciphers${addtl_warning}" - fileout "$jsonID" "OK" "not vulnerable, no DH EXPORT ciphers,$addtl_warning" "$cve" "$cwe" - # we issue a special warning if there's no common prime but the bit length is too low - if [[ $DH_GROUP_LEN_P -le 1024 ]]; then - out "\n${spaces}But: " - pr_dh "$DH_GROUP_OFFERED" $DH_GROUP_LEN_P - case $? in - 1) fileout "$jsonID" "CRITICAL" "no DH EXPORT ciphers, no common prime but $DH_GROUP_OFFERED has only $DH_GROUP_LEN_P bits, $addtl_warning" "$cve" "$cwe" ;; - 2) fileout "$jsonID" "HIGH" "no DH EXPORT ciphers, no common prime but $DH_GROUP_OFFERED has only $DH_GROUP_LEN_P bits, $addtl_warning" "$cve" "$cwe";; - 3) fileout "$jsonID" "MEDIUM" "no DH EXPORT ciphers, no common prime but $DH_GROUP_OFFERED has only $DH_GROUP_LEN_P bits, $addtl_warning" "$cve" "$cwe";; - esac - else - out ", no common prime detected" - fileout "$jsonID2" "OK" "--" "$cve" "$cwe" - fi - elif [[ $ret -eq 1 ]]; then - pr_svrty_good "partly not vulnerable:"; out " no DH EXPORT ciphers${addtl_warning}" - fileout "$jsonID" "OK" "not vulnerable, no DH EXPORT ciphers,$addtl_warning" "$cve" "$cwe" - fi - fi - - outln - tmpfile_handle ${FUNCNAME[0]}.txt - return $ret -} - -# Decrypting RSA with Obsolete and Weakened eNcryption, more @ https://drownattack.com/ -run_drown() { - local -i nr_ciphers_detected ret=0 - local spaces=" " - local cert_fingerprint_sha2="" - local cve="CVE-2016-0800 CVE-2016-0703" - local cwe="CWE-310" - local hint="" - local jsonID="DROWN" - - if [[ $VULN_COUNT -le $VULN_THRESHLD ]]; then - outln - pr_headlineln " Testing for DROWN vulnerability " - outln - fi -# if we want to use OPENSSL: check for < openssl 1.0.2g, openssl 1.0.1s if native openssl - pr_bold " DROWN"; out " (${cve// /, }) " - - # Any fingerprint that is placed in $RSA_CERT_FINGERPRINT_SHA2 is also added to - # to $CERT_FINGERPRINT_SHA2, so if $CERT_FINGERPRINT_SHA2 is not empty, but - # $RSA_CERT_FINGERPRINT_SHA2 is empty, then the server doesn't have an RSA certificate. - if [[ -z "$CERT_FINGERPRINT_SHA2" ]]; then - get_host_cert "-cipher aRSA" - [[ $? -eq 0 ]] && cert_fingerprint_sha2="$($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha256 2>>$ERRFILE | sed -e 's/^.*Fingerprint=//' -e 's/://g' )" - else - cert_fingerprint_sha2="$RSA_CERT_FINGERPRINT_SHA2" - cert_fingerprint_sha2=${cert_fingerprint_sha2/SHA256 /} - fi - - if ( [[ "$STARTTLS_PROTOCOL" =~ ldap ]] || [[ "$STARTTLS_PROTOCOL" =~ irc ]] ); then - prln_local_problem "STARTTLS/$STARTTLS_PROTOCOL and --ssl-native collide here" - return 1 - fi - - if [[ $(has_server_protocol ssl2) -ne 1 ]]; then - sslv2_sockets - else - [[ aaa == bbb ]] # provoke retrurn code=1 - fi - - case $? in - 7) # strange reply, couldn't convert the cipher spec length to a hex number - pr_fixme "strange v2 reply " - outln " (rerun with DEBUG >=2)" - [[ $DEBUG -ge 3 ]] && hexdump -C "$TEMPDIR/$NODEIP.sslv2_sockets.dd" | head -1 - fileout "$jsonID" "WARN" "received a strange SSLv2 reply (rerun with DEBUG>=2)" "$cve" "$cwe" - ret=1 - ;; - 3) # vulnerable, [[ -n "$cert_fingerprint_sha2" ]] test is not needed as we should have RSA certificate here - lines=$(count_lines "$(hexdump -C "$TEMPDIR/$NODEIP.sslv2_sockets.dd" 2>/dev/null)") - debugme tm_out " ($lines lines) " - add_tls_offered ssl2 yes - if [[ "$lines" -gt 1 ]]; then - nr_ciphers_detected=$((V2_HELLO_CIPHERSPEC_LENGTH / 3)) - if [[ 0 -eq "$nr_ciphers_detected" ]]; then - prln_svrty_high "CVE-2015-3197: SSLv2 supported but couldn't detect a cipher (NOT ok)"; - fileout "$jsonID" "HIGH" "SSLv2 offered, but could not detect a cipher. Make sure you don't use this certificate elsewhere, see https://censys.io/ipv4?q=$cert_fingerprint_sha2" "$cve CVE-2015-3197" "$cwe" "$hint" - else - prln_svrty_critical "VULNERABLE (NOT ok), SSLv2 offered with $nr_ciphers_detected ciphers"; - fileout "$jsonID" "CRITICAL" "VULNERABLE, SSLv2 offered with $nr_ciphers_detected ciphers. Make sure you don't use this certificate elsewhere, see https://censys.io/ipv4?q=$cert_fingerprint_sha2" "$cve" "$cwe" "$hint" - fi - outln "$spaces Make sure you don't use this certificate elsewhere, see:" - out "$spaces " - pr_url "https://censys.io/ipv4?q=$cert_fingerprint_sha2" - outln - fi - ;; - *) prln_svrty_best "not vulnerable on this host and port (OK)" - fileout "$jsonID" "OK" "not vulnerable on this host and port" "$cve" "$cwe" - if [[ -n "$cert_fingerprint_sha2" ]]; then - outln "$spaces make sure you don't use this certificate elsewhere with SSLv2 enabled services" - out "$spaces " - pr_url "https://censys.io/ipv4?q=$cert_fingerprint_sha2" - outln " could help you to find out" - fileout "${jsonID}_hint" "INFO" "Make sure you don't use this certificate elsewhere with SSLv2 enabled services, see https://censys.io/ipv4?q=$cert_fingerprint_sha2" "$cve" "$cwe" - else - outln "$spaces no RSA certificate, thus certificate can't be used with SSLv2 elsewhere" - fileout "${jsonID}_hint" "INFO" "no RSA certificate, can't be used with SSLv2 elsewhere" "$cve" "$cwe" - fi - ;; - esac - - return $ret -} - - - -# Browser Exploit Against SSL/TLS: don't use CBC Ciphers in SSLv3 TLSv1.0 -run_beast(){ - local hexc dash cbc_cipher sslvers auth mac export - local -a ciph hexcode normalized_hexcode kx enc export2 - local proto proto_hex - local -i i subret nr_ciphers=0 sclient_success=0 - local detected_cbc_ciphers="" ciphers_to_test - local higher_proto_supported="" - local vuln_beast=false - local spaces=" " - local cr=$'\n' - local first=true - local continued=false - local cbc_cipher_list="EXP-RC2-CBC-MD5:IDEA-CBC-SHA:EXP-DES-CBC-SHA:DES-CBC-SHA:DES-CBC3-SHA:EXP-DH-DSS-DES-CBC-SHA:DH-DSS-DES-CBC-SHA:DH-DSS-DES-CBC3-SHA:EXP-DH-RSA-DES-CBC-SHA:DH-RSA-DES-CBC-SHA:DH-RSA-DES-CBC3-SHA:EXP-EDH-DSS-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:EDH-DSS-DES-CBC3-SHA:EXP-EDH-RSA-DES-CBC-SHA:EDH-RSA-DES-CBC-SHA:EDH-RSA-DES-CBC3-SHA:EXP-ADH-DES-CBC-SHA:ADH-DES-CBC-SHA:ADH-DES-CBC3-SHA:KRB5-DES-CBC-SHA:KRB5-DES-CBC3-SHA:KRB5-IDEA-CBC-SHA:KRB5-DES-CBC-MD5:KRB5-DES-CBC3-MD5:KRB5-IDEA-CBC-MD5:EXP-KRB5-DES-CBC-SHA:EXP-KRB5-RC2-CBC-SHA:EXP-KRB5-DES-CBC-MD5:EXP-KRB5-RC2-CBC-MD5:AES128-SHA:DH-DSS-AES128-SHA:DH-RSA-AES128-SHA:DHE-DSS-AES128-SHA:DHE-RSA-AES128-SHA:ADH-AES128-SHA:AES256-SHA:DH-DSS-AES256-SHA:DH-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ADH-AES256-SHA:CAMELLIA128-SHA:DH-DSS-CAMELLIA128-SHA:DH-RSA-CAMELLIA128-SHA:DHE-DSS-CAMELLIA128-SHA:DHE-RSA-CAMELLIA128-SHA:ADH-CAMELLIA128-SHA:EXP1024-RC2-CBC-MD5:EXP1024-DES-CBC-SHA:EXP1024-DHE-DSS-DES-CBC-SHA:CAMELLIA256-SHA:DH-DSS-CAMELLIA256-SHA:DH-RSA-CAMELLIA256-SHA:DHE-DSS-CAMELLIA256-SHA:DHE-RSA-CAMELLIA256-SHA:ADH-CAMELLIA256-SHA:PSK-3DES-EDE-CBC-SHA:PSK-AES128-CBC-SHA:PSK-AES256-CBC-SHA:DHE-PSK-3DES-EDE-CBC-SHA:DHE-PSK-AES128-CBC-SHA:DHE-PSK-AES256-CBC-SHA:RSA-PSK-3DES-EDE-CBC-SHA:RSA-PSK-AES128-CBC-SHA:RSA-PSK-AES256-CBC-SHA:SEED-SHA:DH-DSS-SEED-SHA:DH-RSA-SEED-SHA:DHE-DSS-SEED-SHA:DHE-RSA-SEED-SHA:ADH-SEED-SHA:PSK-AES128-CBC-SHA256:PSK-AES256-CBC-SHA384:DHE-PSK-AES128-CBC-SHA256:DHE-PSK-AES256-CBC-SHA384:RSA-PSK-AES128-CBC-SHA256:RSA-PSK-AES256-CBC-SHA384:ECDH-ECDSA-DES-CBC3-SHA:ECDH-ECDSA-AES128-SHA:ECDH-ECDSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-RSA-AES128-SHA:ECDH-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:AECDH-DES-CBC3-SHA:AECDH-AES128-SHA:AECDH-AES256-SHA:SRP-3DES-EDE-CBC-SHA:SRP-RSA-3DES-EDE-CBC-SHA:SRP-DSS-3DES-EDE-CBC-SHA:SRP-AES-128-CBC-SHA:SRP-RSA-AES-128-CBC-SHA:SRP-DSS-AES-128-CBC-SHA:SRP-AES-256-CBC-SHA:SRP-RSA-AES-256-CBC-SHA:SRP-DSS-AES-256-CBC-SHA:ECDHE-PSK-3DES-EDE-CBC-SHA:ECDHE-PSK-AES128-CBC-SHA:ECDHE-PSK-AES256-CBC-SHA:ECDHE-PSK-AES128-CBC-SHA256:ECDHE-PSK-AES256-CBC-SHA384:PSK-CAMELLIA128-SHA256:PSK-CAMELLIA256-SHA384:DHE-PSK-CAMELLIA128-SHA256:DHE-PSK-CAMELLIA256-SHA384:RSA-PSK-CAMELLIA128-SHA256:RSA-PSK-CAMELLIA256-SHA384:ECDHE-PSK-CAMELLIA128-SHA256:ECDHE-PSK-CAMELLIA256-SHA384" - local cbc_ciphers_hex="00,06, 00,07, 00,08, 00,09, 00,0A, 00,0B, 00,0C, 00,0D, 00,0E, 00,0F, 00,10, 00,11, 00,12, 00,13, 00,14, 00,15, 00,16, 00,19, 00,1A, 00,1B, 00,1E, 00,1F, 00,21, 00,22, 00,23, 00,25, 00,26, 00,27, 00,29, 00,2A, 00,2F, 00,30, 00,31, 00,32, 00,33, 00,34, 00,35, 00,36, 00,37, 00,38, 00,39, 00,3A, 00,41, 00,42, 00,43, 00,44, 00,45, 00,46, 00,61, 00,62, 00,63, 00,84, 00,85, 00,86, 00,87, 00,88, 00,89, 00,8B, 00,8C, 00,8D, 00,8F, 00,90, 00,91, 00,93, 00,94, 00,95, 00,96, 00,97, 00,98, 00,99, 00,9A, 00,9B, 00,AE, 00,AF, 00,B2, 00,B3, 00,B6, 00,B7, C0,03, C0,04, C0,05, C0,08, C0,09, C0,0A, C0,0D, C0,0E, C0,0F, C0,12, C0,13, C0,14, C0,17, C0,18, C0,19, C0,1A, C0,1B, C0,1C, C0,1D, C0,1E, C0,1F, C0,21, C0,22, C0,34, C0,35, C0,36, C0,37, C0,38, C0,64, C0,65, C0,66, C0,67, C0,68, C0,69, C0,70, C0,71, C0,94, C0,95, C0,96, C0,97, C0,98, C0,99, C0,9A, C0,9B, FE,FE, FE,FF, FF,E0, FF,E1" - local has_dh_bits="$HAS_DH_BITS" - local using_sockets=true - local cve="CVE-2011-3389" - local cwe="CWE-20" - local hint="" - local jsonID="BEAST" - - if [[ $VULN_COUNT -le $VULN_THRESHLD ]]; then - outln - pr_headlineln " Testing for BEAST vulnerability " - outln - fi - pr_bold " BEAST"; out " ($cve) " - - if "$TLS13_ONLY" || ( [[ $(has_server_protocol ssl3) -eq 1 ]] && [[ $(has_server_protocol tls1) -eq 1 ]] ); then - pr_svrty_good "not vulnerable (OK)" - outln ", no SSL3 or TLS1" - fileout "$jsonID" "OK" "not vulnerable, no SSL3 or TLS1" "$cve" "$cwe" - return 0 - fi - - "$SSL_NATIVE" && using_sockets=false - # $cbc_ciphers_hex has 126 ciphers, we omitted SRP-AES-256-CBC-SHA bc the trailing 00,ff below will pose - # a problem for ACE loadbalancers otherwise. So in case we know this is not true, we'll re-add it - ! "$SERVER_SIZE_LIMIT_BUG" && "$using_sockets" && cbc_ciphers_hex="$cbc_ciphers_hex, C0,20" - - [[ $TLS_NR_CIPHERS == 0 ]] && using_sockets=false - if "$using_sockets" || [[ $OSSL_VER_MAJOR -lt 1 ]]; then - for (( i=0; i < TLS_NR_CIPHERS; i++ )); do - hexc="${TLS_CIPHER_HEXCODE[i]}" - if [[ ${#hexc} -eq 9 ]] && [[ "${TLS_CIPHER_RFC_NAME[i]}" =~ CBC ]] && \ - [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SHA256 ]] && [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SHA384 ]]; then - ciph[nr_ciphers]="${TLS_CIPHER_OSSL_NAME[i]}" - hexcode[nr_ciphers]="${hexc:2:2},${hexc:7:2}" - rfc_ciph[nr_ciphers]="${TLS_CIPHER_RFC_NAME[i]}" - kx[nr_ciphers]="${TLS_CIPHER_KX[i]}" - enc[nr_ciphers]="${TLS_CIPHER_ENC[i]}" - export2[nr_ciphers]="${TLS_CIPHER_EXPORT[i]}" - ossl_supported[nr_ciphers]=${TLS_CIPHER_OSSL_SUPPORTED[i]} - if "$using_sockets" && "$WIDE" && ! "$has_dh_bits" && \ - ( [[ ${kx[nr_ciphers]} == Kx=ECDH ]] || [[ ${kx[nr_ciphers]} == Kx=DH ]] || [[ ${kx[nr_ciphers]} == Kx=EDH ]] ); then - ossl_supported[nr_ciphers]=false - fi - if [[ "${hexc:2:2}" == 00 ]]; then - normalized_hexcode[nr_ciphers]="x${hexc:7:2}" - else - normalized_hexcode[nr_ciphers]="x${hexc:2:2}${hexc:7:2}" - fi - nr_ciphers+=1 - fi - done - else - # no sockets, openssl - while read hexc dash ciph[nr_ciphers] sslvers kx[nr_ciphers] auth enc[nr_ciphers] mac export2[nr_ciphers]; do - if [[ ":${cbc_cipher_list}:" =~ :${ciph[nr_ciphers]}: ]]; then - ossl_supported[nr_ciphers]=true - if [[ "${hexc:2:2}" == "00" ]]; then - normalized_hexcode[nr_ciphers]="x${hexc:7:2}" - else - normalized_hexcode[nr_ciphers]="x${hexc:2:2}${hexc:7:2}" - fi - nr_ciphers+=1 - fi - done < <(actually_supported_osslciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 'ALL' "-tls1 -V") - fi - - # first determine whether it's mitigated by higher protocols - for proto in tls1_1 tls1_2; do - subret=$(has_server_protocol "$proto") - if [[ $subret -eq 0 ]]; then - case $proto in - tls1_1) higher_proto_supported+=" TLSv1.1" ;; - tls1_2) higher_proto_supported+=" TLSv1.2" ;; - esac - elif [[ $subret -eq 2 ]]; then - $OPENSSL s_client $(s_client_options "-state -"${proto}" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") 2>>$ERRFILE >$TMPFILE </dev/null - if sclient_connect_successful $? $TMPFILE; then - higher_proto_supported+=" $(get_protocol $TMPFILE)" - add_tls_offered "$proto" yes - fi - fi - done - - for proto in ssl3 tls1; do - if [[ "$proto" == ssl3 ]] && ! "$using_sockets" && ! locally_supported "-${proto}"; then - continued=true - out " " - continue - fi - subret=$(has_server_protocol "$proto") - if [[ $subret -eq 0 ]]; then - sclient_success=0 - elif [[ $subret -eq 1 ]]; then - sclient_success=1 - elif [[ "$proto" != "ssl3" ]] || "$HAS_SSL3"; then - $OPENSSL s_client $(s_client_options "-"$proto" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>>$ERRFILE </dev/null - sclient_connect_successful $? $TMPFILE - sclient_success=$? - else - tls_sockets "00" "$TLS_CIPHER" - sclient_success=$? - fi - if [[ $sclient_success -ne 0 ]]; then # protocol supported? - if "$continued"; then # second round: we hit TLS1 - if "$HAS_SSL3" || "$using_sockets"; then - pr_svrty_good "not vulnerable (OK)" ; outln ", no SSL3 or TLS1" - fileout "$jsonID" "OK" "not vulnerable, no SSL3 or TLS1" "$cve" "$cwe" - else - prln_svrty_good "no TLS1 (OK)" - fileout "$jsonID" "OK" "not vulnerable, no TLS1" "$cve" "$cwe" - fi - return 0 - else # protocol not succeeded but it's the first time - continued=true - continue # protocol not supported, so we do not need to check each cipher with that protocol - fi - fi # protocol succeeded - add_tls_offered "$proto" yes - - # now we test in one shot with the precompiled ciphers - if "$using_sockets"; then - case "$proto" in - "ssl3") proto_hex="00" ;; - "tls1") proto_hex="01" ;; - esac - tls_sockets "$proto_hex" "$cbc_ciphers_hex, 00,ff" - [[ $? -eq 0 ]] || continue - else - $OPENSSL s_client $(s_client_options "-"$proto" -cipher "$cbc_cipher_list" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>>$ERRFILE </dev/null - sclient_connect_successful $? $TMPFILE || continue - fi - - detected_cbc_ciphers="" - for ((i=0; i<nr_ciphers; i++)); do - ciphers_found[i]=false - sigalg[nr_ciphers]="" - done - while true; do - [[ "$proto" == ssl3 ]] && ! "$HAS_SSL3" && break - ciphers_to_test="" - for (( i=0; i < nr_ciphers; i++ )); do - ! "${ciphers_found[i]}" && "${ossl_supported[i]}" && ciphers_to_test+=":${ciph[i]}" - done - [[ -z "$ciphers_to_test" ]] && break - $OPENSSL s_client $(s_client_options "-cipher "${ciphers_to_test:1}" -"${proto}" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>>$ERRFILE </dev/null - sclient_connect_successful $? $TMPFILE || break - cbc_cipher=$(get_cipher $TMPFILE) - [[ -z "$cbc_cipher" ]] && break - for (( i=0; i < nr_ciphers; i++ )); do - [[ "$cbc_cipher" == "${ciph[i]}" ]] && break - done - ciphers_found[i]=true - if [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]] || [[ "${rfc_ciph[i]}" == - ]]; then - detected_cbc_ciphers+="${ciph[i]} " - else - detected_cbc_ciphers+="${rfc_ciph[i]} " - fi - vuln_beast=true - if "$WIDE" && ( [[ ${kx[i]} == Kx=ECDH ]] || [[ ${kx[i]} == Kx=DH ]] || [[ ${kx[i]} == Kx=EDH ]] ); then - dhlen=$(read_dhbits_from_file "$TMPFILE" quiet) - kx[i]="${kx[i]} $dhlen" - fi - "$WIDE" && "$SHOW_SIGALGO" && grep -q "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TMPFILE && \ - sigalg[i]="$(read_sigalg_from_file "$TMPFILE")" - done - if "$using_sockets"; then - while true; do - ciphers_to_test="" - for (( i=0; i < nr_ciphers; i++ )); do - ! "${ciphers_found[i]}" && ciphers_to_test+=", ${hexcode[i]}" - done - [[ -z "$ciphers_to_test" ]] && break - if "$SHOW_SIGALGO"; then - tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "all" - else - tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "ephemeralkey" - fi - [[ $? -ne 0 ]] && break - cbc_cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") - for (( i=0; i < nr_ciphers; i++ )); do - [[ "$cbc_cipher" == "${rfc_ciph[i]}" ]] && break - done - [[ $i -eq $nr_ciphers ]] && break - ciphers_found[i]=true - if ( [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]] && [[ "${ciph[i]}" != - ]] ) || [[ "${rfc_ciph[i]}" == - ]]; then - detected_cbc_ciphers+="${ciph[i]} " - else - detected_cbc_ciphers+="${rfc_ciph[i]} " - fi - vuln_beast=true - if "$WIDE" && ( [[ ${kx[i]} == Kx=ECDH ]] || [[ ${kx[i]} == Kx=DH ]] || [[ ${kx[i]} == Kx=EDH ]] ); then - dhlen=$(read_dhbits_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" quiet) - kx[i]="${kx[i]} $dhlen" - fi - "$WIDE" && "$SHOW_SIGALGO" && [[ -r "$HOSTCERT" ]] && \ - sigalg[i]="$(read_sigalg_from_file "$HOSTCERT")" - done - fi - - if "$WIDE" && [[ -n "$detected_cbc_ciphers" ]]; then - out "\n "; pr_underline "$(toupper $proto):\n"; - if "$first"; then - neat_header - fi - first=false - for (( i=0; i < nr_ciphers; i++ )); do - if "${ciphers_found[i]}" || "$SHOW_EACH_C"; then - export="${export2[i]}" - neat_list "$(tolower "${normalized_hexcode[i]}")" "${ciph[i]}" "${kx[i]}" "${enc[i]}" "${ciphers_found[i]}" - if "$SHOW_EACH_C"; then - if "${ciphers_found[i]}"; then - if [[ -n "$higher_proto_supported" ]]; then - pr_svrty_low "available" - else - pr_svrty_medium "available" - fi - else - pr_deemphasize "not a/v" - fi - fi - outln "${sigalg[i]}" - fi - done - fi - - if ! "$WIDE"; then - if [[ -n "$detected_cbc_ciphers" ]]; then - fileout "${jsonID}_CBC_$(toupper $proto)" "MEDIUM" "$detected_cbc_ciphers" "$cve" "$cwe" "$hint" - ! "$first" && out "$spaces" - out "$(toupper $proto): " - [[ -n "$higher_proto_supported" ]] && \ - pr_svrty_low "$(out_row_aligned_max_width "$detected_cbc_ciphers" " " $TERM_WIDTH)" || \ - pr_svrty_medium "$(out_row_aligned_max_width "$detected_cbc_ciphers" " " $TERM_WIDTH)" - outln - detected_cbc_ciphers="" # empty for next round - first=false - else - [[ $proto == tls1 ]] && ! $first && echo -n "$spaces " - prln_svrty_good "no CBC ciphers for $(toupper $proto) (OK)" - first=false - fi - else - if ! "$vuln_beast" ; then - prln_svrty_good "no CBC ciphers for $(toupper $proto) (OK)" - fileout "${jsonID}_CBC_$(toupper $proto)" "OK" "No CBC ciphers for $(toupper $proto)" "$cve" "$cwe" - fi - fi - done # for proto in ssl3 tls1 - - if "$vuln_beast"; then - if [[ -n "$higher_proto_supported" ]]; then - if "$WIDE"; then - outln; out " " - # NOT ok seems too harsh for me if we have TLS >1.0 - pr_svrty_low "VULNERABLE" - outln " -- but also supports higher protocols (possible mitigation) $higher_proto_supported" - outln - else - out "$spaces" - pr_svrty_low "VULNERABLE" - outln " -- but also supports higher protocols $higher_proto_supported (likely mitigated)" - fi - fileout "$jsonID" "LOW" "VULNERABLE -- but also supports higher protocols $higher_proto_supported (likely mitigated)" "$cve" "$cwe" "$hint" - else - if "$WIDE"; then - outln - else - out "$spaces" - fi - pr_svrty_medium "VULNERABLE" - outln " -- and no higher protocols as mitigation supported" - fileout "$jsonID" "MEDIUM" "VULNERABLE -- and no higher protocols as mitigation supported" "$cve" "$cwe" "$hint" - fi - fi - "$first" && ! "$vuln_beast" && prln_svrty_good "no CBC ciphers found for any protocol (OK)" - - "$using_sockets" && HAS_DH_BITS="$has_dh_bits" - tmpfile_handle ${FUNCNAME[0]}.txt - return 0 -} - - -# https://web.archive.org/web/20200324101422/http://www.isg.rhul.ac.uk/tls/Lucky13.html -# in a nutshell: don't offer CBC suites (again). MAC as a fix for padding oracles is not enough. Best: TLS v1.2+ AES GCM -run_lucky13() { - local spaces=" " - local cbc_ciphers="ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:SRP-DSS-AES-256-CBC-SHA:SRP-RSA-AES-256-CBC-SHA:SRP-AES-256-CBC-SHA:RSA-PSK-AES256-CBC-SHA384:DHE-PSK-AES256-CBC-SHA384:DHE-PSK-AES256-CBC-SHA:ECDHE-PSK-CAMELLIA256-SHA384:RSA-PSK-CAMELLIA256-SHA384:DHE-PSK-CAMELLIA256-SHA384:PSK-AES256-CBC-SHA384:PSK-CAMELLIA256-SHA384:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DH-RSA-AES256-SHA256:DH-DSS-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DH-RSA-AES256-SHA:DH-DSS-AES256-SHA:ECDHE-RSA-CAMELLIA256-SHA384:ECDHE-ECDSA-CAMELLIA256-SHA384:DHE-RSA-CAMELLIA256-SHA256:DHE-DSS-CAMELLIA256-SHA256:DH-RSA-CAMELLIA256-SHA256:DH-DSS-CAMELLIA256-SHA256:DHE-RSA-CAMELLIA256-SHA:DHE-DSS-CAMELLIA256-SHA:DH-RSA-CAMELLIA256-SHA:DH-DSS-CAMELLIA256-SHA:AECDH-AES256-SHA:ADH-AES256-SHA256:ADH-AES256-SHA:ADH-CAMELLIA256-SHA256:ADH-CAMELLIA256-SHA:ECDH-RSA-AES256-SHA384:ECDH-ECDSA-AES256-SHA384:ECDH-RSA-AES256-SHA:ECDH-ECDSA-AES256-SHA:ECDH-RSA-CAMELLIA256-SHA384:ECDH-ECDSA-CAMELLIA256-SHA384:AES256-SHA256:AES256-SHA:CAMELLIA256-SHA256:ECDHE-PSK-AES256-CBC-SHA384:ECDHE-PSK-AES256-CBC-SHA:CAMELLIA256-SHA:RSA-PSK-AES256-CBC-SHA:PSK-AES256-CBC-SHA:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:SRP-DSS-AES-128-CBC-SHA:SRP-RSA-AES-128-CBC-SHA:SRP-AES-128-CBC-SHA:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DH-RSA-AES128-SHA256:DH-DSS-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:DH-RSA-AES128-SHA:DH-DSS-AES128-SHA:ECDHE-RSA-CAMELLIA128-SHA256:ECDHE-ECDSA-CAMELLIA128-SHA256:DHE-RSA-CAMELLIA128-SHA256:DHE-DSS-CAMELLIA128-SHA256:DH-RSA-CAMELLIA128-SHA256:DH-DSS-CAMELLIA128-SHA256:DHE-RSA-SEED-SHA:DHE-DSS-SEED-SHA:DH-RSA-SEED-SHA:DH-DSS-SEED-SHA:DHE-RSA-CAMELLIA128-SHA:DHE-DSS-CAMELLIA128-SHA:DH-RSA-CAMELLIA128-SHA:DH-DSS-CAMELLIA128-SHA:AECDH-AES128-SHA:ADH-AES128-SHA256:ADH-AES128-SHA:ADH-CAMELLIA128-SHA256:ADH-SEED-SHA:ADH-CAMELLIA128-SHA:ECDH-RSA-AES128-SHA256:ECDH-ECDSA-AES128-SHA256:ECDH-RSA-AES128-SHA:ECDH-ECDSA-AES128-SHA:ECDH-RSA-CAMELLIA128-SHA256:ECDH-ECDSA-CAMELLIA128-SHA256:AES128-SHA256:AES128-SHA:CAMELLIA128-SHA256:ECDHE-PSK-AES128-CBC-SHA256:ECDHE-PSK-AES128-CBC-SHA:RSA-PSK-AES128-CBC-SHA256:DHE-PSK-AES128-CBC-SHA256:DHE-PSK-AES128-CBC-SHA:SEED-SHA:CAMELLIA128-SHA:ECDHE-PSK-CAMELLIA128-SHA256:RSA-PSK-CAMELLIA128-SHA256:DHE-PSK-CAMELLIA128-SHA256:PSK-AES128-CBC-SHA256:PSK-CAMELLIA128-SHA256:IDEA-CBC-SHA:RSA-PSK-AES128-CBC-SHA:PSK-AES128-CBC-SHA:KRB5-IDEA-CBC-SHA:KRB5-IDEA-CBC-MD5:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:SRP-DSS-3DES-EDE-CBC-SHA:SRP-RSA-3DES-EDE-CBC-SHA:SRP-3DES-EDE-CBC-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:DH-RSA-DES-CBC3-SHA:DH-DSS-DES-CBC3-SHA:AECDH-DES-CBC3-SHA:ADH-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:DES-CBC3-SHA:RSA-PSK-3DES-EDE-CBC-SHA:PSK-3DES-EDE-CBC-SHA:KRB5-DES-CBC3-SHA:KRB5-DES-CBC3-MD5:ECDHE-PSK-3DES-EDE-CBC-SHA:DHE-PSK-3DES-EDE-CBC-SHA:EXP1024-DHE-DSS-DES-CBC-SHA:EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:DH-RSA-DES-CBC-SHA:DH-DSS-DES-CBC-SHA:ADH-DES-CBC-SHA:EXP1024-DES-CBC-SHA:DES-CBC-SHA:KRB5-DES-CBC-SHA:KRB5-DES-CBC-MD5:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA:EXP-ADH-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-KRB5-RC2-CBC-SHA:EXP-KRB5-DES-CBC-SHA:EXP-KRB5-RC2-CBC-MD5:EXP-KRB5-DES-CBC-MD5:EXP-DH-DSS-DES-CBC-SHA:EXP-DH-RSA-DES-CBC-SHA" - cbc_ciphers_hex1="c0,28, c0,24, c0,14, c0,0a, c0,22, c0,21, c0,20, 00,b7, 00,b3, 00,91, c0,9b, c0,99, c0,97, 00,af, c0,95, 00,6b, 00,6a, 00,69, 00,68, 00,39, 00,38, 00,37, 00,36, c0,77, c0,73, 00,c4, 00,c3, 00,c2, 00,c1, 00,88, 00,87, 00,86, 00,85, c0,19, 00,6d, 00,3a, 00,c5, 00,89, c0,2a, c0,26, c0,0f, c0,05, c0,79, c0,75, 00,3d, 00,35, 00,c0, c0,38, c0,36, 00,84, 00,95, 00,8d, c0,3d, c0,3f, c0,41, c0,43, c0,45, c0,47, c0,49, c0,4b, c0,4d, c0,4f, c0,65, c0,67, c0,69, c0,71, c0,27, c0,23, c0,13, c0,09, c0,1f, c0,1e, c0,1d, 00,67, 00,40, 00,3f, 00,3e, 00,33, 00,32, 00,31, 00,30, c0,76, c0,72, 00,be, 00,bd, 00,bc, 00,bb, 00,9a, 00,99, 00,98, 00,97, 00,45, 00,44, 00,43, 00,42, c0,18, 00,6c, 00,34, 00,bf, 00,9b, 00,46, c0,29, c0,25, c0,0e, c0,04, c0,78, c0,74, 00,3c, 00,2f, 00,ba" - cbc_ciphers_hex2="c0,37, c0,35, 00,b6, 00,b2, 00,90, 00,96, 00,41, c0,9a, c0,98, c0,96, 00,ae, c0,94, 00,07, 00,94, 00,8c, 00,21, 00,25, c0,3c, c0,3e, c0,40, c0,42, c0,44, c0,46, c0,48, c0,4a, c0,4c, c0,4e, c0,64, c0,66, c0,68, c0,70, c0,12, c0,08, c0,1c, c0,1b, c0,1a, 00,16, 00,13, 00,10, 00,0d, c0,17, 00,1b, c0,0d, c0,03, 00,0a, 00,93, 00,8b, 00,1f, 00,23, c0,34, 00,8f, fe,ff, ff,e0, 00,63, 00,15, 00,12, 00,0f, 00,0c, 00,1a, 00,62, 00,09, 00,61, 00,1e, 00,22, fe,fe, ff,e1, 00,14, 00,11, 00,19, 00,08, 00,06, 00,27, 00,26, 00,2a, 00,29, 00,0b, 00,0e" - local has_dh_bits="$HAS_DH_BITS" - local -i nr_supported_ciphers=0 sclient_success - local using_sockets=true - local cve="CVE-2013-0169" - local cwe="CWE-310" - local hint="" - local jsonID="LUCKY13" - - if [[ $VULN_COUNT -le $VULN_THRESHLD ]]; then - outln - pr_headlineln " Testing for LUCKY13 vulnerability " - outln - fi - pr_bold " LUCKY13"; out " ($cve), experimental " - - if "$TLS13_ONLY"; then - pr_svrty_best "not vulnerable (OK)" - [[ $DEBUG -ge 1 ]] && out ", no CBC ciphers in TLS 1.3 only servers" - outln - fileout "$jsonID" "OK" "not vulnerable, TLS 1.3 only" "$cve" "$cwe" - return 0 - fi - - "$SSL_NATIVE" && using_sockets=false - # The openssl binary distributed has almost everything we need (PSK, KRB5 ciphers and feff, ffe0 are typically missing). - # Measurements show that there's little impact whether we use sockets or TLS here, so the default is sockets here - - if "$using_sockets"; then - tls_sockets "03" "${cbc_ciphers_hex1}, 00,ff" - sclient_success=$? - [[ "$sclient_success" -eq 2 ]] && sclient_success=0 - if [[ $sclient_success -ne 0 ]]; then - tls_sockets "03" "${cbc_ciphers_hex2}, 00,ff" - sclient_success=$? - [[ $sclient_success -eq 2 ]] && sclient_success=0 - fi - else - nr_cbc_ciphers=$(count_ciphers $cbc_ciphers) - nr_supported_ciphers=$(count_ciphers $(actually_supported_osslciphers $cbc_ciphers)) - $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -no_ssl2 -cipher $cbc_ciphers -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE </dev/null - sclient_connect_successful $? $TMPFILE - sclient_success=$? - [[ "$DEBUG" -eq 2 ]] && grep -Eq "error|failure" $ERRFILE | grep -Eav "unable to get local|verify error" - fi - if [[ $sclient_success -eq 0 ]]; then - out "potentially " - pr_svrty_low "VULNERABLE"; out ", uses cipher block chaining (CBC) ciphers with TLS. Check patches" - fileout "$jsonID" "LOW" "potentially vulnerable, uses TLS CBC ciphers" "$cve" "$cwe" "$hint" - # the CBC padding which led to timing differences during MAC processing has been solved in openssl (https://www.openssl.org/news/secadv/20130205.txt) - # and other software. However we can't tell with reasonable effort from the outside. Thus we still issue a warning and label it experimental - else - pr_svrty_best "not vulnerable (OK)"; - if "$using_sockets"; then - fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" - else - if [[ "$nr_supported_ciphers" -ge 133 ]]; then - # Likely only PSK/KRB5 ciphers are missing: display discrepancy but no warning - out ", $nr_supported_ciphers/$nr_cbc_ciphers local ciphers" - else - pr_warning ", $nr_supported_ciphers/$nr_cbc_ciphers local ciphers" - fi - fileout "$jsonID" "OK" "not vulnerable ($nr_supported_ciphers of $nr_cbc_ciphers local ciphers" "$cve" "$cwe" - fi - fi - outln - tmpfile_handle ${FUNCNAME[0]}.txt - [[ $sclient_success -ge 6 ]] && return 1 - return 0 -} - - -# https://tools.ietf.org/html/rfc7465 REQUIRES that TLS clients and servers NEVER negotiate the use of RC4 cipher suites! -# https://en.wikipedia.org/wiki/Transport_Layer_Security#RC4_attacks -# https://blog.cryptographyengineering.com/2013/03/attack-of-week-rc4-is-kind-of-broken-in.html -# -run_rc4() { - local -i rc4_offered=0 - local -i nr_ciphers=0 nr_ossl_ciphers=0 nr_nonossl_ciphers=0 sclient_success=0 - local n auth mac export hexc sslv2_ciphers_hex="" sslv2_ciphers_ossl="" s - local -a normalized_hexcode hexcode ciph sslvers kx enc export2 sigalg ossl_supported - local -i i - local -a ciphers_found ciphers_found2 hexcode2 ciph2 rfc_ciph2 - local -i -a index - local dhlen available="" ciphers_to_test supported_sslv2_ciphers proto - local has_dh_bits="$HAS_DH_BITS" rc4_detected="" - local using_sockets=true - local cve="CVE-2013-2566 CVE-2015-2808" - local cwe="CWE-310" - local hint="" - local jsonID="RC4" - - "$SSL_NATIVE" && using_sockets=false - "$FAST" && using_sockets=false - [[ $TLS_NR_CIPHERS == 0 ]] && using_sockets=false - - if [[ $VULN_COUNT -le $VULN_THRESHLD ]]; then - outln - pr_headlineln " Checking for vulnerable RC4 Ciphers " - outln - fi - pr_bold " RC4"; out " (${cve// /, }) " - - if "$TLS13_ONLY"; then - pr_svrty_best "not vulnerable (OK)" - [[ $DEBUG -ge 1 ]] && out ", no RC4 support in TLS 1.3 only servers" - outln - fileout "$jsonID" "OK" "not vulnerable, TLS 1.3 only" "$cve" "$cwe" - return 0 - fi - - # Get a list of all the cipher suites to test. #FIXME: This is rather ineffective as RC4 ciphers won't change. - # We should instead build a fixed list here like @ other functions - if "$using_sockets" || [[ $OSSL_VER_MAJOR -lt 1 ]]; then - for (( i=0; i < TLS_NR_CIPHERS; i++ )); do - if [[ "${TLS_CIPHER_RFC_NAME[i]}" =~ RC4 ]] && ( "$using_sockets" || "${TLS_CIPHER_OSSL_SUPPORTED[i]}" ); then - hexc="$(tolower "${TLS_CIPHER_HEXCODE[i]}")" - ciph[nr_ciphers]="${TLS_CIPHER_OSSL_NAME[i]}" - rfc_ciph[nr_ciphers]="${TLS_CIPHER_RFC_NAME[i]}" - sslvers[nr_ciphers]="${TLS_CIPHER_SSLVERS[i]}" - kx[nr_ciphers]="${TLS_CIPHER_KX[i]}" - enc[nr_ciphers]="${TLS_CIPHER_ENC[i]}" - export2[nr_ciphers]="${TLS_CIPHER_EXPORT[i]}" - ciphers_found[nr_ciphers]=false - sigalg[nr_ciphers]="" - ossl_supported[nr_ciphers]="${TLS_CIPHER_OSSL_SUPPORTED[i]}" - if "$using_sockets" && "$WIDE" && ! "$HAS_DH_BITS" && - ( [[ ${kx[nr_ciphers]} == "Kx=ECDH" ]] || [[ ${kx[nr_ciphers]} == "Kx=DH" ]] || [[ ${kx[nr_ciphers]} == "Kx=EDH" ]] ); then - ossl_supported[nr_ciphers]=false - fi - if [[ ${#hexc} -eq 9 ]]; then - hexcode[nr_ciphers]="${hexc:2:2},${hexc:7:2}" - if [[ "${hexc:2:2}" == 00 ]]; then - normalized_hexcode[nr_ciphers]="x${hexc:7:2}" - else - normalized_hexcode[nr_ciphers]="x${hexc:2:2}${hexc:7:2}" - fi - else - hexcode[nr_ciphers]="${hexc:2:2},${hexc:7:2},${hexc:12:2}" - normalized_hexcode[nr_ciphers]="x${hexc:2:2}${hexc:7:2}${hexc:12:2}" - sslv2_ciphers_hex+=", ${hexcode[nr_ciphers]}" - sslv2_ciphers_ossl+=":${ciph[nr_ciphers]}" - fi - nr_ciphers+=1 - fi - done - else - while read hexc n ciph[nr_ciphers] sslvers[nr_ciphers] kx[nr_ciphers] auth enc[nr_ciphers] mac export2[nr_ciphers]; do - if [[ "${ciph[nr_ciphers]}" =~ RC4 ]]; then - ciphers_found[nr_ciphers]=false - if [[ ${#hexc} -eq 9 ]]; then - if [[ "${hexc:2:2}" == 00 ]]; then - normalized_hexcode[nr_ciphers]="$(tolower "x${hexc:7:2}")" - else - normalized_hexcode[nr_ciphers]="$(tolower "x${hexc:2:2}${hexc:7:2}")" - fi - else - normalized_hexcode[nr_ciphers]="$(tolower "x${hexc:2:2}${hexc:7:2}${hexc:12:2}")" - sslv2_ciphers_ossl+=":${ciph[nr_ciphers]}" - fi - sigalg[nr_ciphers]="" - ossl_supported[nr_ciphers]=true - nr_ciphers+=1 - fi - done < <($OPENSSL ciphers $OSSL_CIPHERS_S -V 'ALL:COMPLEMENTOFALL:@STRENGTH' 2>>$ERRFILE) - fi - - if "$using_sockets" && [[ -n "$sslv2_ciphers_hex" ]] && [[ $(has_server_protocol ssl2) -ne 1 ]]; then - sslv2_sockets "${sslv2_ciphers_hex:2}" "true" - if [[ $? -eq 3 ]] && [[ "$V2_HELLO_CIPHERSPEC_LENGTH" -ne 0 ]]; then - supported_sslv2_ciphers="$(grep "Supported cipher: " "$TEMPDIR/$NODEIP.parse_sslv2_serverhello.txt")" - "$WIDE" && "$SHOW_SIGALGO" && s="$(read_sigalg_from_file "$HOSTCERT")" - for (( i=0 ; i<nr_ciphers; i++ )); do - if [[ "${sslvers[i]}" == SSLv2 ]] && [[ "$supported_sslv2_ciphers" =~ ${normalized_hexcode[i]} ]]; then - ciphers_found[i]=true - "$WIDE" && "$SHOW_SIGALGO" && sigalg[i]="$s" - rc4_offered=1 - fi - done - fi - elif "$HAS_SSL2" && [[ -n "$sslv2_ciphers_ossl" ]] && [[ $(has_server_protocol ssl2) -ne 1 ]]; then - $OPENSSL s_client -cipher "${sslv2_ciphers_ossl:1}" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY -ssl2 >$TMPFILE 2>$ERRFILE </dev/null - sclient_connect_successful $? "$TMPFILE" - if [[ $? -eq 0 ]]; then - supported_sslv2_ciphers="$(grep -A 4 "Ciphers common between both SSL endpoints:" $TMPFILE)" - "$WIDE" && "$SHOW_SIGALGO" && s="$(read_sigalg_from_file "$TMPFILE")" - for (( i=0 ; i<nr_ciphers; i++ )); do - if [[ "${sslvers[i]}" == SSLv2 ]] && [[ "$supported_sslv2_ciphers" =~ ${ciph[i]} ]]; then - ciphers_found[i]=true - "$WIDE" && "$SHOW_SIGALGO" && sigalg[i]="$s" - rc4_offered=1 - fi - done - fi - fi - - for (( i=0; i < nr_ciphers; i++ )); do - if "${ossl_supported[i]}" && [[ "${sslvers[i]}" != SSLv2 ]]; then - ciphers_found2[nr_ossl_ciphers]=false - ciph2[nr_ossl_ciphers]="${ciph[i]}" - index[nr_ossl_ciphers]=$i - nr_ossl_ciphers+=1 - fi - done - - for proto in -no_ssl2 -tls1_1 -tls1 -ssl3; do - [[ "$proto" != -no_ssl2 ]] && [[ $(has_server_protocol "${proto:1}") -eq 1 ]] && continue - ! "$HAS_SSL3" && [[ "$proto" == -ssl3 ]] && continue - while true; do - ciphers_to_test="" - for (( i=0; i < nr_ossl_ciphers; i++ )); do - ! "${ciphers_found2[i]}" && ciphers_to_test+=":${ciph2[i]}" - done - [[ -z "$ciphers_to_test" ]] && break - $OPENSSL s_client $(s_client_options "$proto -cipher "${ciphers_to_test:1}" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE </dev/null - sclient_connect_successful $? "$TMPFILE" || break - cipher=$(get_cipher $TMPFILE) - [[ -z "$cipher" ]] && break - for (( i=0; i < nr_ossl_ciphers; i++ )); do - [[ "$cipher" == "${ciph2[i]}" ]] && ciphers_found2[i]=true && break - done - [[ $i -eq $nr_ossl_ciphers ]] && break - rc4_offered=1 - i=${index[i]} - ciphers_found[i]=true - if "$WIDE" && ( [[ ${kx[i]} == "Kx=ECDH" ]] || [[ ${kx[i]} == "Kx=DH" ]] || [[ ${kx[i]} == "Kx=EDH" ]] ); then - dhlen=$(read_dhbits_from_file "$TMPFILE" quiet) - kx[i]="${kx[i]} $dhlen" - fi - "$WIDE" && "$SHOW_SIGALGO" && grep -q "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TMPFILE && \ - sigalg[i]="$(read_sigalg_from_file "$TMPFILE")" - done - done - - if "$using_sockets"; then - for (( i=0; i < nr_ciphers; i++ )); do - if ! "${ciphers_found[i]}" && [[ "${sslvers[i]}" != SSLv2 ]]; then - ciphers_found2[nr_nonossl_ciphers]=false - hexcode2[nr_nonossl_ciphers]="${hexcode[i]}" - rfc_ciph2[nr_nonossl_ciphers]="${rfc_ciph[i]}" - index[nr_nonossl_ciphers]=$i - nr_nonossl_ciphers+=1 - fi - done - fi - - for proto in 03 02 01 00; do - [[ $(has_server_protocol "$proto") -eq 1 ]] && continue - while true; do - ciphers_to_test="" - for (( i=0; i < nr_nonossl_ciphers; i++ )); do - ! "${ciphers_found2[i]}" && ciphers_to_test+=", ${hexcode2[i]}" - done - [[ -z "$ciphers_to_test" ]] && break - if "$WIDE" && "$SHOW_SIGALGO"; then - tls_sockets "$proto" "${ciphers_to_test:2}, 00,ff" "all" - else - tls_sockets "$proto" "${ciphers_to_test:2}, 00,ff" "ephemeralkey" - fi - sclient_success=$? - [[ $sclient_success -ne 0 ]] && [[ $sclient_success -ne 2 ]] && break - cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") - for (( i=0; i < nr_nonossl_ciphers; i++ )); do - [[ "$cipher" == "${rfc_ciph2[i]}" ]] && ciphers_found2[i]=true && break - done - [[ $i -eq $nr_nonossl_ciphers ]] && break - rc4_offered=1 - i=${index[i]} - ciphers_found[i]=true - if "$WIDE" && ( [[ ${kx[i]} == "Kx=ECDH" ]] || [[ ${kx[i]} == "Kx=DH" ]] || [[ ${kx[i]} == "Kx=EDH" ]] ); then - dhlen=$(read_dhbits_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" quiet) - kx[i]="${kx[i]} $dhlen" - fi - "$WIDE" && "$SHOW_SIGALGO" && [[ -r "$HOSTCERT" ]] && \ - sigalg[i]="$(read_sigalg_from_file "$HOSTCERT")" - done - done - - if [[ $rc4_offered -eq 1 ]]; then - "$WIDE" || pr_svrty_high "VULNERABLE (NOT ok): " - if "$WIDE"; then - outln "\n" - neat_header - fi - for (( i=0 ; i<nr_ciphers; i++ )); do - if ! "${ciphers_found[i]}" && ! "$SHOW_EACH_C"; then - continue # no successful connect AND not verbose displaying each cipher - fi - if "$WIDE"; then - #FIXME: JSON+CSV in wide mode is missing - export="${export2[i]}" - neat_list "${normalized_hexcode[i]}" "${ciph[i]}" "${kx[i]}" "${enc[i]}" "${ciphers_found[i]}" - if "$SHOW_EACH_C"; then - if "${ciphers_found[i]}"; then - pr_svrty_high "available" - else - pr_deemphasize "not a/v" - fi - fi - outln "${sigalg[i]}" - fi - if "${ciphers_found[i]}"; then - if ( [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]] && [[ "${ciph[i]}" != "-" ]] ) || [[ "${rfc_ciph[i]}" == "-" ]]; then - rc4_detected+="${ciph[i]} " - else - rc4_detected+="${rfc_ciph[i]} " - fi - fi - done - ! "$WIDE" && pr_svrty_high "$(out_row_aligned_max_width "$rc4_detected" " " $TERM_WIDTH)" - outln - "$WIDE" && out " " && prln_svrty_high "VULNERABLE (NOT ok)" - fileout "$jsonID" "HIGH" "VULNERABLE, Detected ciphers: $rc4_detected" "$cve" "$cwe" "$hint" - elif [[ $nr_ciphers -eq 0 ]]; then - prln_local_problem "No RC4 Ciphers configured in $OPENSSL" - fileout "$jsonID" "WARN" "RC4 ciphers not supported by local OpenSSL ($OPENSSL)" - else - prln_svrty_good "no RC4 ciphers detected (OK)" - fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" - fi - outln - - "$using_sockets" && HAS_DH_BITS="$has_dh_bits" - tmpfile_handle ${FUNCNAME[0]}.txt - [[ $sclient_success -ge 6 ]] && return 1 - return 0 -} - - -run_youknowwho() { - local cve="CVE-2013-2566" - local cwe="CWE-310" - # NOT FIXME as there's no code: https://web.archive.org/web/20191008002003/http://www.isg.rhul.ac.uk/tls/index.html - # https://blog.cryptographyengineering.com/2013/03/attack-of-week-rc4-is-kind-of-broken-in.html - return 0 - # in a nutshell: don't use RC4, really not! -} - - # https://www.usenix.org/conference/woot13/workshop-program/presentation/smyth - # https://secure-resumption.com/tlsauth.pdf -run_tls_truncation() { - #FIXME: difficult to test, is there any test available: pls let me know - : -} - -# Test for various server implementation errors that aren't tested for elsewhere. -# Inspired by https://datatracker.ietf.org/doc/draft-ietf-tls-grease. -run_grease() { - local -i success - local bug_found=false - local normal_hello_ok=false - local cipher_list proto selected_cipher selected_cipher_hex="" extn rnd_bytes - local alpn_proto alpn alpn_list_len_hex extn_len_hex - local selected_alpn_protocol grease_selected_alpn_protocol - local ciph list temp curve_found - local -i i j rnd alpn_list_len extn_len debug_level="" - local -i ret=0 - # Note: The following values were taken from https://datatracker.ietf.org/doc/draft-ietf-tls-grease. - # These arrays may need to be updated if the values change in the final version of this document. - local -a -r grease_cipher_suites=( "0a,0a" "1a,1a" "2a,2a" "3a,3a" "4a,4a" "5a,5a" "6a,6a" "7a,7a" "8a,8a" "9a,9a" "aa,aa" "ba,ba" "ca,ca" "da,da" "ea,ea" "fa,fa" ) - local -a -r grease_supported_groups=( "0a,0a" "1a,1a" "2a,2a" "3a,3a" "4a,4a" "5a,5a" "6a,6a" "7a,7a" "8a,8a" "9a,9a" "aa,aa" "ba,ba" "ca,ca" "da,da" "ea,ea" "fa,fa" ) - local -a -r grease_extn_values=( "0a,0a" "1a,1a" "2a,2a" "3a,3a" "4a,4a" "5a,5a" "6a,6a" "7a,7a" "8a,8a" "9a,9a" "aa,aa" "ba,ba" "ca,ca" "da,da" "ea,ea" "fa,fa" ) - local -r ecdhe_ciphers="cc,14, cc,13, c0,30, c0,2c, c0,28, c0,24, c0,14, c0,0a, c0,9b, cc,a9, cc,a8, c0,af, c0,ad, c0,77, c0,73, c0,19, cc,ac, c0,38, c0,36, c0,49, c0,4d, c0,5d, c0,61, c0,71, c0,87, c0,8b, c0,2f, c0,2b, c0,27, c0,23, c0,13, c0,09, c0,ae, c0,ac, c0,76, c0,72, c0,18, c0,37, c0,35, c0,9a, c0,48, c0,4c, c0,5c, c0,60, c0,70, c0,86, c0,8a, c0,11, c0,07, c0,16, c0,33, c0,12, c0,08, c0,17, c0,34, c0,10, c0,06, c0,15, c0,3b, c0,3a, c0,39" - local jsonID="GREASE" - - outln; pr_headline " Testing for server implementation bugs "; outln "\n" - - # Many of the following checks work by modifying the "basic" call to - # tls_sockets() and assuming the tested-for bug is present if the - # connection fails. However, this only works if the connection succeeds - # with the "basic" call. So, keep trying different "basic" calls until - # one is found that succeeds. - for (( i=0; i < 5; i++ )); do - case $i in - 0) proto="03" ; cipher_list="$TLS12_CIPHER" ;; - 2) proto="02" ; cipher_list="$TLS_CIPHER" ;; - 3) proto="01" ; cipher_list="$TLS_CIPHER" ;; - 4) proto="00" ; cipher_list="$TLS_CIPHER" ;; - esac - tls_sockets "$proto" "$cipher_list" - success=$? - if [[ $success -eq 0 ]] || [[ $success -eq 2 ]]; then - break - fi - done - if [[ $success -eq 0 ]] || [[ $success -eq 2 ]]; then - selected_cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") - if [[ $TLS_NR_CIPHERS -ne 0 ]]; then - for (( i=0; i < TLS_NR_CIPHERS; i++ )); do - [[ "$selected_cipher" == "${TLS_CIPHER_RFC_NAME[i]}" ]] && selected_cipher_hex="${TLS_CIPHER_HEXCODE[i]}" && break - done - elif "$HAS_SSL2"; then - selected_cipher_hex="$($OPENSSL ciphers -V -tls1 'ALL:COMPLEMENTOFALL' | awk '/'" $selected_cipher "'/ { print $1 }')" - elif "$HAS_CIPHERSUITES"; then - selected_cipher_hex="$($OPENSSL ciphers -V -ciphersuites "$TLS13_OSSL_CIPHERS" 'ALL:COMPLEMENTOFALL'| awk '/'" $selected_cipher "'/ { print $1 }')" - else - selected_cipher_hex="$($OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL'| awk '/'" $selected_cipher "'/ { print $1 }')" - fi - if [[ -n "$selected_cipher_hex" ]]; then - normal_hello_ok=true - selected_cipher_hex="${selected_cipher_hex:2:2},${selected_cipher_hex:7:2}" - fi - else - proto="03" - fi - - # Test for yaSSL bug - server only looks at second byte of each cipher - # suite listed in ClientHello (see issue #793). First check to see if - # server ignores the ciphers in the ClientHello entirely, then check to - # see if server only looks at second byte of each offered cipher. - - # Send a list of non-existent ciphers where the second byte does not match - # any existing cipher. - - # Need to ensure that $TEMPDIR/$NODEIP.parse_tls_serverhello.txt contains the results of the - # most recent calls to tls_sockets even if tls_sockets is not successful. Setting $DEBUG to - # a non-zero value ensures this. Setting it to 1 prevents any extra information from being - # displayed. - debug_level="$DEBUG" - [[ $DEBUG -eq 0 ]] && DEBUG=1 - debugme echo -e "\nSending ClientHello with non-existent ciphers." - tls_sockets "$proto" "de,d0, de,d1, d3,d2, de,d3, 00,ff" - success=$? - if [[ $success -eq 0 ]] || [[ $success -eq 2 ]]; then - prln_svrty_medium " Server claims to support non-existent cipher suite." - fileout "$jsonID" "MEDIUM" "Server claims to support non-existent cipher suite." - bug_found=true - elif grep -q "The ServerHello specifies a cipher suite that wasn't included in the ClientHello" "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" ; then - prln_svrty_medium " Server responded with a ServerHello rather than an alert even though it doesn't support any of the client-offered cipher suites." - fileout "$jsonID" "MEDIUM" "Server responded with a ServerHello rather than an alert even though it doesn't support any of the client-offered cipher suites." - bug_found=true - else - # Send a list of non-existent ciphers such that for each cipher that - # is defined, there is one in the list that matches in the second byte - # (but make sure list contains at more 127 ciphers). - debugme echo -e "\nSending ClientHello with non-existent ciphers, but that match existing ciphers in second byte." - tls_sockets "$proto" "de,01, de,02, de,03, de,04, de,05, de,06, de,07, de,08, de,09, de,0a, de,0b, de,0c, de,0d, de,0e, de,0f, de,10, de,11, de,12, de,13, de,14, de,15, de,16, de,17, de,18, de,19, de,1a, de,1b, de,23, de,24, de,25, de,26, de,27, de,28, de,29, de,2a, de,2b, de,2c, de,2d, de,2e, de,2f, de,30, de,31, de,32, de,33, de,34, de,35, de,36, de,37, de,38, de,39, de,3a, de,3b, de,3c, de,3d, de,3e, de,3f, de,40, de,41, de,42, de,43, de,44, de,45, de,46, de,60, de,61, de,62, de,63, de,64, de,65, de,66, de,67, de,68, de,69, de,6a, de,6b, de,6c, de,6d, de,72, de,73, de,74, de,75, de,76, de,77, de,78, de,79, de,84, de,85, de,86, de,87, de,88, de,89, de,96, de,97, de,98, de,99, de,9a, de,9b, de,9c, de,9d, de,9e, de,9f, de,a0, de,a1, de,a2, de,a3, de,a4, de,a5, de,a6, de,a7, de,ba, de,bb, de,bc, de,bd, de,be, de,bf, de,c0, de,c1, de,c2, de,c3, de,c4, de,c5, 00,ff" - success=$? - if [[ $success -eq 0 ]] || [[ $success -eq 2 ]]; then - prln_svrty_medium " Server claims to support non-existent cipher suite." - fileout "$jsonID" "MEDIUM" "Server claims to support non-existent cipher suite." - bug_found=true - elif grep -q " The ServerHello specifies a cipher suite that wasn't included in the ClientHello" "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" ; then - prln_svrty_medium " Server only compares against second byte in each cipher suite in ClientHello." - fileout "$jsonID" "MEDIUM" "Server only compares against second byte in each cipher suite in ClientHello." - bug_found=true - fi - fi - DEBUG="$debug_level" - - # Check that server ignores unrecognized extensions - # see https://datatracker.ietf.org/doc/draft-ietf-tls-grease - if "$normal_hello_ok" && [[ "$proto" != "00" ]]; then - # Try multiple different randomly-generated GREASE extensions, - # but make final test use zero-length extension value, just to - # be sure that works before testing server with a zero-length - # extension as the final extension. - for (( i=1; i <= 5; i++ )); do - # Create a random extension using one of the GREASE values. - rnd=$RANDOM%${#grease_extn_values[@]} - extn="${grease_extn_values[rnd]}" - if [[ $i -eq 5 ]]; then - extn_len=0 - else - # Not sure what a good upper bound is here, but a key_share - # extension with an ffdhe8192 would be over 1024 bytes. - extn_len=$RANDOM%1024 - fi - extn_len_hex=$(printf "%04x" $extn_len) - extn+=",${extn_len_hex:0:2},${extn_len_hex:2:2}" - for (( j=0; j <= extn_len-2; j=j+2 )); do - rnd_bytes="$(printf "%04x" $RANDOM)" - extn+=",${rnd_bytes:0:2},${rnd_bytes:2:2}" - done - if [[ $j -lt $extn_len ]]; then - rnd_bytes="$(printf "%04x" $RANDOM)" - extn+=",${rnd_bytes:0:2}" - fi - if [[ $DEBUG -ge 2 ]]; then - echo -en "\nSending ClientHello with unrecognized extension" - [[ $DEBUG -ge 3 ]] && echo -n ": $extn" - echo "" - fi - tls_sockets "$proto" "$cipher_list" "" "$extn" - success=$? - if [[ $success -ne 0 ]] && [[ $success -ne 2 ]]; then - break - fi - done - if [[ $success -ne 0 ]] && [[ $success -ne 2 ]]; then - prln_svrty_medium " Server fails if ClientHello contains an unrecognized extension." - outln " extension used in failed test: $extn" - fileout "$jsonID" "MEDIUM" "Server fails if ClientHello contains an unrecognized extension: $extn" - bug_found=true - else - # Check for inability to handle empty last extension (see PR #792 and - # https://www.ietf.org/mail-archive/web/tls/current/msg19720.html). - # (Since this test also uses an unrecognized extension, only run this - # test if the previous test passed, and use the final extension value - # from that test to ensure that the only difference is the location - # of the extension.) - - # The "extra extensions" parameter needs to include the padding and - # heartbeat extensions, since otherwise prepare_tls_clienthello() - # will add these extensions to the end of the ClientHello. - debugme echo -e "\nSending ClientHello with empty last extension." - tls_sockets "$proto" "$cipher_list" "" " - 00,0f, 00,01, 01, - 00,15, 00,56, - 00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00, - 00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00, - 00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00, - 00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00, - $extn" - success=$? - if [[ $success -ne 0 ]] && [[ $success -ne 2 ]]; then - prln_svrty_medium " Server fails if last extension in ClientHello is empty." - fileout "$jsonID" "MEDIUM" "Server fails if last extension in ClientHello is empty." - bug_found=true - fi - fi - fi - - # Check for SERVER_SIZE_LIMIT_BUG. - # Send a ClientHello with 129 cipher suites (including 0x00,0xff) to see - # if adding a 129th cipher to the list causes a failure. -#TODO: we need to clarify whether the mit is hit at 128 or 129 ciphers. - if "$normal_hello_ok" && [[ "$proto" == 03 ]]; then - debugme echo -e "\nSending ClientHello with 129 cipher suites." - tls_sockets "$proto" "00,27, $cipher_list" - success=$? - if [[ $success -ne 0 ]] && [[ $success -ne 2 ]]; then - prln_svrty_medium " Server fails if ClientHello includes more than 128 cipher suites." - fileout "$jsonID" "MEDIUM" "Server fails if ClientHello includes more than 128 cipher suites." - SERVER_SIZE_LIMIT_BUG=true - bug_found=true - fi - fi - - # Check for ClientHello size bug. According to RFC 7586 "at least one TLS - # implementation is known to hang the connection when [a] ClientHello - # record [with a length between 256 and 511 bytes] is received." - # If the length of the host name is more than 75 bytes (which would make - # $SNI more than 87 bytes), then the ClientHello would be more than 511 - # bytes if the server_name extension were included. Removing the SNI - # extension, however, may not be an option, since the server may reject the - # connection attempt for that reason. - if "$normal_hello_ok" && [[ "$proto" != 00 ]] && [[ ${#SNI} -le 87 ]]; then - # Normally prepare_tls_clienthello() will add a padding extension with a length - # that will make the ClientHello be 512 bytes in length. Providing an "extra - # extensions" parameter with a short padding extension prevents that. - debugme echo -e "\nSending ClientHello with length between 256 and 511 bytes." - tls_sockets "$proto" "$cipher_list" "" "00,15,00,01,00" - success=$? - if [[ $success -ne 0 ]] && [[ $success -ne 2 ]]; then - prln_svrty_medium " Server fails if ClientHello is between 256 and 511 bytes in length." - fileout "$jsonID" "MEDIUM" "Server fails if ClientHello is between 256 and 511 bytes in length." - bug_found=true - fi - fi - - # Check that server ignores unrecognized cipher suite values - # see https://datatracker.ietf.org/doc/draft-ietf-tls-grease - if "$normal_hello_ok"; then - list="" - for ciph in "${grease_cipher_suites[@]}"; do - list+=", $ciph" - done - debugme echo -e "\nSending ClientHello with unrecognized cipher suite values." - tls_sockets "$proto" "${list:2}, $selected_cipher_hex, 00,ff" - success=$? - if [[ $success -ne 0 ]] && [[ $success -ne 2 ]]; then - prln_svrty_medium " Server fails if ClientHello contains unrecognized cipher suite values." - fileout "$jsonID" "MEDIUM" "Server fails if ClientHello contains unrecognized cipher suite values." - bug_found=true - fi - fi - - # Check that servers that support ECDHE cipher suites ignore - # unrecognized named group values. - # see https://datatracker.ietf.org/doc/draft-ietf-tls-grease - if [[ "$proto" != "00" ]]; then - # Send a ClientHello that lists all of the ECDHE cipher suites - tls_sockets "$proto" "$ecdhe_ciphers, 00,ff" "ephemeralkey" - success=$? - if [[ $success -eq 0 ]] || [[ $success -eq 2 ]]; then - # Send the same ClientHello as before but with an unrecognized - # named group value added. Make the unrecognized value the first - # one in the list replacing one of the values in the original list, - # but don't replace the value that was selected by the server. - rnd=$RANDOM%${#grease_supported_groups[@]} - temp=$(awk -F': ' '/^Server Temp Key/ { print $2 }' "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") - curve_found="${temp%%,*}" - if [[ "$curve_found" == "ECDH" ]]; then - curve_found="${temp#*, }" - curve_found="${curve_found%%,*}" - fi - if [[ "$curve_found" == "B-571" ]]; then - extn=" - 00, 0a, # Type: Supported Elliptic Curves , see RFC 4492 - 00, 3e, 00, 3c, # lengths - ${grease_supported_groups[rnd]}, 00, 0e, 00, 19, 00, 1c, 00, 1e, 00, 0b, 00, 0c, 00, 1b, - 00, 18, 00, 09, 00, 0a, 00, 1a, 00, 16, 00, 17, 00, 1d, 00, 08, - 00, 06, 00, 07, 00, 14, 00, 15, 00, 04, 00, 05, 00, 12, 00, 13, - 00, 01, 00, 02, 00, 03, 00, 0f, 00, 10, 00, 11" - else - extn=" - 00, 0a, # Type: Supported Elliptic Curves , see RFC 4492 - 00, 3e, 00, 3c, # lengths - ${grease_supported_groups[rnd]}, 00, 0d, 00, 19, 00, 1c, 00, 1e, 00, 0b, 00, 0c, 00, 1b, - 00, 18, 00, 09, 00, 0a, 00, 1a, 00, 16, 00, 17, 00, 1d, 00, 08, - 00, 06, 00, 07, 00, 14, 00, 15, 00, 04, 00, 05, 00, 12, 00, 13, - 00, 01, 00, 02, 00, 03, 00, 0f, 00, 10, 00, 11" - fi - debugme echo -e "\nSending ClientHello with unrecognized named group value in supported_groups extension." - tls_sockets "$proto" "$ecdhe_ciphers, 00,ff" "" "$extn" - success=$? - if [[ $success -ne 0 ]] && [[ $success -ne 2 ]]; then - prln_svrty_medium " Server fails if ClientHello contains a supported_groups extension with an unrecognized named group value (${grease_supported_groups[rnd]})." - fileout "$jsonID" "MEDIUM" "Server fails if ClientHello contains a supported_groups extension with an unrecognized named group value (${grease_supported_groups[rnd]})." - bug_found=true - fi - fi - fi - - # Check that servers that support the ALPN extension ignore - # unrecognized ALPN values. - # see https://datatracker.ietf.org/doc/draft-ietf-tls-grease - if "$normal_hello_ok" && [[ -z $STARTTLS ]] && [[ "$proto" != "00" ]]; then - for alpn_proto in $ALPN_PROTOs; do - alpn+=",$(printf "%02x" ${#alpn_proto}),$(string_to_asciihex "$alpn_proto")" - done - alpn_list_len=${#alpn}/3 - alpn_list_len_hex=$(printf "%04x" $alpn_list_len) - extn_len=$alpn_list_len+2 - extn_len_hex=$(printf "%04x" $extn_len) - tls_sockets "$proto" "$cipher_list" "all" "00,10,${extn_len_hex:0:2},${extn_len_hex:2:2},${alpn_list_len_hex:0:2},${alpn_list_len_hex:2:2}$alpn" - success=$? - if [[ $success -ne 0 ]] && [[ $success -ne 2 ]]; then - prln_svrty_medium " Server fails if ClientHello contains an application_layer_protocol_negotiation extension." - fileout "$jsonID" "MEDIUM" "Server fails if ClientHello contains an application_layer_protocol_negotiation extension." - bug_found=true - else - selected_alpn_protocol="$(grep "ALPN protocol:" "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" | sed 's/ALPN protocol: //')" - # If using a "normal" ALPN extension worked, then add an unrecognized - # ALPN value to the beginning of the extension and try again. - alpn_proto="ignore/$selected_alpn_protocol" - alpn=",$(printf "%02x" ${#alpn_proto}),$(string_to_asciihex "$alpn_proto")$alpn" - alpn_list_len=${#alpn}/3 - alpn_list_len_hex=$(printf "%04x" $alpn_list_len) - extn_len=$alpn_list_len+2 - extn_len_hex=$(printf "%04x" $extn_len) - debugme echo -e "\nSending ClientHello with unrecognized ALPN value in application_layer_protocol_negotiation extension." - tls_sockets "$proto" "$cipher_list" "all" "00,10,${extn_len_hex:0:2},${extn_len_hex:2:2},${alpn_list_len_hex:0:2},${alpn_list_len_hex:2:2}$alpn" - success=$? - if [[ $success -ne 0 ]] && [[ $success -ne 2 ]]; then - prln_svrty_medium " Server fails if ClientHello contains an application_layer_protocol_negotiation extension with an unrecognized ALPN value." - fileout "$jsonID" "MEDIUM" "erver fails if ClientHello contains an application_layer_protocol_negotiation extension with an unrecognized ALPN value." - bug_found=true - else - grease_selected_alpn_protocol="$(grep "ALPN protocol:" "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" | sed 's/ALPN protocol: //')" - if [[ -z "$grease_selected_alpn_protocol" ]] && [[ -n "$selected_alpn_protocol" ]]; then - prln_svrty_medium " Server did not ignore unrecognized ALPN value in the application_layer_protocol_negotiation extension." - fileout "$jsonID" "MEDIUM" "Server did not ignore unrecognized ALPN value in the application_layer_protocol_negotiation extension." - bug_found=true - elif [[ "$grease_selected_alpn_protocol" =~ ignore/ ]]; then - prln_svrty_medium " Server selected \"ignore/\" ALPN value in the application_layer_protocol_negotiation extension." - fileout "$jsonID" "MEDIUM" "Server selected \"ignore/\" ALPN value in the application_layer_protocol_negotiation extension." - bug_found=true - fi - fi - fi - fi - - # TODO: For servers that support TLSv1.3, check that servers ignore - # an unrecognized named group value along with a corresponding - # unrecognized key share - # see https://www.ietf.org/mail-archive/web/tls/current/msg22322.html - # and https://www.ietf.org/mail-archive/web/tls/current/msg22319.html - - # TODO: For servers that support TLSv1.3, check that servers ignore unrecognized - # values in the supported_versions extension. - # see https://www.ietf.org/mail-archive/web/tls/current/msg22319.html - - # TODO: For servers that support TLSv1.3, check that servers don't require the - # psk_key_exchange_modes extension to be present in the ClientHello. - - if ! "$bug_found"; then - outln " No bugs found." - fileout "$jsonID" "OK" "No bugs found." - #return 0 - else - #return 1 - : - fi - return $ret - #FIXME: No client side error cases where we want to return 1? -} - -# If the server supports any non-PSK cipher suites that use RSA key transport, -# check if the server is vulnerable to Bleichenbacher's Oracle Threat (ROBOT) attacks. -# See "Return Of Bleichenbacher's Oracle Threat (ROBOT)" by Hanno Böck, -# Juraj Somorovsky, and Craig Young (https://robotattack.org). -# -run_robot() { - local tls_hexcode="03" - # A list of all non-PSK cipher suites that use RSA key transport - local cipherlist="00,9d, c0,a1, c0,9d, 00,3d, 00,35, 00,c0, 00,84, c0,3d, c0,51, c0,7b, ff,00, ff,01, ff,02, ff,03, c0,a0, c0,9c, 00,9c, 00,3c, 00,2f, 00,ba, 00,96, 00,41, 00,07, c0,3c, c0,50, c0,7a, 00,05, 00,04, 00,0a, fe,ff, ff,e0, 00,62, 00,09, 00,61, fe,fe, ff,e1, 00,64, 00,60, 00,08, 00,06, 00,03, 00,3b, 00,02, 00,01" - # A list of all non-PSK cipher suites that use RSA key transport and that use AES in either GCM or CBC mode. - local aes_gcm_cbc_cipherlist="00,9d, 00,9c, 00,3d, 00,35, 00,3c, 00,2f" - local padded_pms encrypted_pms cke_prefix client_key_exchange rnd_pad - local rnd_pms="aa112233445566778899112233445566778899112233445566778899112233445566778899112233445566778899" - local change_cipher_spec finished resp - local -a response - local -i i subret len iteration testnum pubkeybytes - local pubkeybits - local vulnerable=false send_ccs_finished=true - local -i start_time end_time robottimeout=$MAX_WAITSOCK - local cve="CVE-2017-17382 CVE-2017-17427 CVE-2017-17428 CVE-2017-13098 CVE-2017-1000385 CVE-2017-13099 CVE-2016-6883 CVE-2012-5081 CVE-2017-6168" - local cwe="CWE-203" - local jsonID="ROBOT" - - [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for Return of Bleichenbacher's Oracle Threat (ROBOT) vulnerability " && outln - pr_bold " ROBOT " - - if ( [[ "$STARTTLS_PROTOCOL" =~ ldap ]] || [[ "$STARTTLS_PROTOCOL" =~ irc ]] ); then - prln_local_problem "STARTTLS/$STARTTLS_PROTOCOL and --ssl-native collide here" - return 1 - fi - - if [[ ! "$HAS_PKUTIL" ]]; then - prln_local_problem "Your $OPENSSL does not support the pkeyutl utility." - fileout "$jsonID" "WARN" "$OPENSSL does not support the pkeyutl utility." "$cve" "$cwe" - return 1 - elif ! "$HAS_PKEY"; then - prln_local_problem "Your $OPENSSL does not support the pkey utility." - fileout "$jsonID" "WARN" "$OPENSSL does not support the pkey utility." "$cve" "$cwe" - return 1 - fi - - if [[ 0 -eq $(has_server_protocol tls1_2) ]]; then - tls_hexcode="03" - elif [[ 0 -eq $(has_server_protocol tls1_1) ]]; then - tls_hexcode="02" - elif [[ 0 -eq $(has_server_protocol tls1) ]]; then - tls_hexcode="01" - elif [[ 0 -eq $(has_server_protocol ssl3) ]]; then - tls_hexcode="00" - fi - - # Some hosts are only vulnerable with GCM. First send a list of - # ciphers that use AES in GCM or CBC mode, with the GCM ciphers - # listed first, and then try all ciphers that use RSA key transport - # if there is no connection on the first try. - tls_sockets "$tls_hexcode" "$aes_gcm_cbc_cipherlist, 00,ff" - subret=$? - if [[ $subret -eq 0 ]] || [[ $subret -eq 2 ]]; then - cipherlist="$aes_gcm_cbc_cipherlist" - tls_hexcode="${DETECTED_TLS_VERSION:2:2}" - else - if [[ "$tls_hexcode" != "03" ]]; then - cipherlist="$(strip_inconsistent_ciphers "$tls_hexcode" ", $cipherlist")" - cipherlist="${cipherlist:2}" - fi - tls_sockets "$tls_hexcode" "$cipherlist, 00,ff" - subret=$? - if [[ $subret -eq 2 ]]; then - tls_hexcode="${DETECTED_TLS_VERSION:2:2}" - cipherlist="$(strip_inconsistent_ciphers "$tls_hexcode" ", $cipherlist")" - cipherlist="${cipherlist:2}" - elif [[ $subret -ne 0 ]]; then - prln_svrty_best "Server does not support any cipher suites that use RSA key transport" - fileout "$jsonID" "OK" "not vulnerable, no RSA key transport cipher" "$cve" "$cwe" - return 0 - fi - fi - - # Run the tests in two iterations. In iteration 0, send 5 different client - # key exchange (CKE) messages followed by change cipher spec (CCS) and - # Finished messages, and check whether the server provided the same - # response in each case. If the server didn't provide the same response - # for all five messages in iteration 0, then it is vulnerable. Otherwise - # try a second time (iteration 1) with the same CKE messages, but without - # sending the CCS or Finished messages. - # Iterations 0 and 1 are run with a short timeout waiting for the server - # to respond to the CKE message. If the server was found to be potentially - # vulnerable in iteration 0 or 1 and testssl.sh timed out waiting for a - # response in some cases, then retry the test using a longer timeout value. - for (( iteration=0; iteration < 3; iteration++ )); do - if [[ $iteration -eq 1 ]]; then - # If the server was found to be vulnerable in iteration 0, then - # there's no need to try the alternative message flow. - "$vulnerable" && continue - send_ccs_finished=false - elif [[ $iteration -eq 2 ]]; then - # The tests are being rerun, so reset the vulnerable flag. - vulnerable=false - fi - for (( testnum=0; testnum < 5; testnum++ )); do - response[testnum]="untested" - done - for (( testnum=0; testnum < 5; testnum++ )); do - tls_sockets "$tls_hexcode" "$cipherlist, 00,ff" "all" "" "" "false" - - # Create the padded premaster secret to encrypt. The padding should be - # of the form "00 02 <random> 00 <TLS version> <premaster secret>." - # However, for each test except testnum=0 the padding will be - # made incorrect in some way, as specified below. - - # Determine the length of the public key and create the <random> bytes. - # <random> should be a length that makes total length of $padded_pms - # the same as the length of the public key. <random> should contain no 00 bytes. - pubkeybits="$($OPENSSL x509 -noout -pubkey -in $HOSTCERT 2>>$ERRFILE | \ - $OPENSSL pkey -pubin -text_pub 2>>$ERRFILE | awk -F'(' '/Public-Key/ { print $2 }')" - pubkeybits="${pubkeybits%%bit*}" - pubkeybytes=$pubkeybits/8 - [[ $((pubkeybits%8)) -ne 0 ]] && pubkeybytes+=1 - rnd_pad="" - for (( len=0; len < pubkeybytes-52; len=len+2 )); do - rnd_pad+="abcd" - done - [[ $len -eq $pubkeybytes-52 ]] && rnd_pad+="ab" - - case "$testnum" in - # correct padding - 0) padded_pms="0002${rnd_pad}00${DETECTED_TLS_VERSION}${rnd_pms}" ;; - # wrong first two bytes - 1) padded_pms="4117${rnd_pad}00${DETECTED_TLS_VERSION}${rnd_pms}" ;; - # 0x00 on a wrong position - 2) padded_pms="0002${rnd_pad}11${rnd_pms}0011" ;; - # no 0x00 in the middle - 3) padded_pms="0002${rnd_pad}111111${rnd_pms}" ;; - # wrong version number (according to Klima / Pokorny / Rosa paper) - 4) padded_pms="0002${rnd_pad}000202${rnd_pms}" ;; - esac - - # Encrypt the padded premaster secret using the server's public key. - encrypted_pms="$(asciihex_to_binary "$padded_pms" | \ - $OPENSSL pkeyutl -encrypt -certin -inkey $HOSTCERT -pkeyopt rsa_padding_mode:none 2>/dev/null | \ - hexdump -v -e '16/1 "%02x"')" - if [[ -z "$encrypted_pms" ]]; then - if [[ "$DETECTED_TLS_VERSION" == "0300" ]]; then - socksend ",x15, x03, x00, x00, x02, x02, x00" 0 - else - socksend ",x15, x03, x01, x00, x02, x02, x00" 0 - fi - close_socket - prln_fixme "Conversion of public key failed around line $((LINENO - 9))" - fileout "$jsonID" "WARN" "Conversion of public key failed around line $((LINENO - 10)) " - return 1 - fi - - # Create the client key exchange message. - len=${#encrypted_pms}/2 - cke_prefix="16${DETECTED_TLS_VERSION}$(printf "%04x" $((len+6)))10$(printf "%06x" $((len+2)))$(printf "%04x" $len)" - encrypted_pms="$cke_prefix$encrypted_pms" - len=${#encrypted_pms} - client_key_exchange="" - for (( i=0; i<len; i=i+2 )); do - client_key_exchange+=", x${encrypted_pms:i:2}" - done - - # The contents of change cipher spec are fixed. - change_cipher_spec=", x14, x${DETECTED_TLS_VERSION:0:2}, x${DETECTED_TLS_VERSION:2:2}, x00, x01, x01" - - # Send an arbitrary Finished message. - finished=", x16, x${DETECTED_TLS_VERSION:0:2}, x${DETECTED_TLS_VERSION:2:2} - , x00, x40, x6e, x49, x65, x68, x00, x46, x79, xfd, x5a, x57, xdc - , x3e, xef, xb2, xd2, xac, xe0, x8c, x54, x2d, x5f, x00, x87, xdb - , xb6, xe3, x77, x2c, x9d, x88, x27, x38, x98, x7d, xcd, x7e, xac - , xdd, x5d, x72, xbe, x24, x0d, x20, x36, x14, x0e, x94, x51, xde - , xa0, xb6, xc7, x56, x28, xd8, xa1, xcb, x24, xb9, x03, xd0, x7c, x50" - - if "$send_ccs_finished"; then - debugme echo -en "\nsending client key exchange, change cipher spec, finished... " - socksend "$client_key_exchange$change_cipher_spec$finished" $USLEEP_SND - else - debugme echo -en "\nsending client key exchange... " - socksend "$client_key_exchange" $USLEEP_SND - fi - debugme echo "reading server error response..." - start_time=$(LC_ALL=C date "+%s") - sockread_serverhello 32768 $robottimeout - subret=$? - if [[ $subret -eq 0 ]]; then - end_time=$(LC_ALL=C date "+%s") - resp=$(hexdump -v -e '16/1 "%02x"' "$SOCK_REPLY_FILE") - response[testnum]="${resp%%[!0-9A-F]*}" - # The first time a response is received to a client key - # exchange message, measure the amount of time it took to - # receive a response and set the timeout value for future - # tests to 2 seconds longer than it took to receive a response. - [[ $iteration -ne 2 ]] && [[ $robottimeout -eq $MAX_WAITSOCK ]] && \ - [[ $((end_time-start_time)) -lt $((MAX_WAITSOCK-2)) ]] && \ - robottimeout=$((end_time-start_time+2)) - else - response[testnum]="Timeout waiting for alert" - fi - debugme echo -e "\nresponse[$testnum] = ${response[testnum]}" - [[ $DEBUG -ge 3 ]] && [[ $subret -eq 0 ]] && parse_tls_serverhello "${response[testnum]}" - close_socket - - # Don't continue testing if it has already been determined that - # tests need to be rerun with a longer timeout. - if [[ $iteration -ne 2 ]]; then - for (( i=1; i <= testnum; i++ )); do - if [[ "${response[i]}" != "${response[$((i-1))]}" ]] && \ - ( [[ "${response[i]}" == "Timeout waiting for alert" ]] || \ - [[ "${response[$((i-1))]}" == "Timeout waiting for alert" ]] ); then - vulnerable=true - break - fi - done - "$vulnerable" && break - fi - # Don't continue testing if it has already been determined that the server is - # strongly vulnerable. - if [[ $testnum -eq 2 ]]; then - [[ "${response[1]}" != "${response[2]}" ]] && break - elif [[ $testnum -eq 3 ]]; then - [[ "${response[2]}" != "${response[3]}" ]] && break - [[ "${response[0]}" != "${response[1]}" ]] && break - fi - done - # If the server provided the same error message for all tests, then this - # is an indication that the server is not vulnerable. - if [[ "${response[0]}" != "${response[1]}" ]] || [[ "${response[1]}" != "${response[2]}" ]] || \ - [[ "${response[2]}" != "${response[3]}" ]] || [[ "${response[3]}" != "${response[4]}" ]]; then - vulnerable=true - - # If the test was run with a short timeout and was found to be - # potentially vulnerable due to some tests timing out, then - # verify the results by rerunning with a longer timeout. - if [[ $robottimeout -eq $MAX_WAITSOCK ]]; then - break - elif [[ "${response[0]}" == "Timeout waiting for alert" ]] || \ - [[ "${response[1]}" == "Timeout waiting for alert" ]] || \ - [[ "${response[2]}" == "Timeout waiting for alert" ]] || \ - [[ "${response[3]}" == "Timeout waiting for alert" ]] || \ - [[ "${response[4]}" == "Timeout waiting for alert" ]]; then - robottimeout=10 - else - break - fi - fi - ! "$vulnerable" && [[ $iteration -eq 1 ]] && break - done - - if "$vulnerable"; then - if [[ "${response[1]}" == "${response[2]}" ]] && [[ "${response[2]}" == "${response[3]}" ]]; then - pr_svrty_medium "VULNERABLE (NOT ok)"; outln " - weakly vulnerable as the attack would take too long" - fileout "$jsonID" "MEDIUM" "VULNERABLE, but the attack would take too long" "$cve" "$cwe" - else - prln_svrty_critical "VULNERABLE (NOT ok)" - fileout "$jsonID" "CRITICAL" "VULNERABLE" "$cve" "$cwe" - fi - else - prln_svrty_best "not vulnerable (OK)" - fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" - fi - return 0 -} - -old_fart() { - out "Get precompiled bins or compile " - pr_url "https://github.com/PeterMosmans/openssl" - outln "." - fileout_insert_warning "old_fart" "WARN" "Your $OPENSSL $OSSL_VER version is an old fart... . It doesn\'t make much sense to proceed. Get precompiled bins or compile https://github.com/PeterMosmans/openssl ." - fatal "Your $OPENSSL $OSSL_VER version is an old fart... . It doesn't make much sense to proceed." $ERR_OSSLBIN -} - -# try very hard to determine the install path to get ahold of the mapping file and the CA bundles -# TESTSSL_INSTALL_DIR can be supplied via environment so that the cipher mapping and CA bundles can be found -# www.carbonwind.net/TLS_Cipher_Suites_Project/tls_ssl_cipher_suites_simple_table_all.htm -get_install_dir() { - [[ -z "$TESTSSL_INSTALL_DIR" ]] && TESTSSL_INSTALL_DIR="$(dirname "${BASH_SOURCE[0]}")" - - if [[ -r "$RUN_DIR/etc/cipher-mapping.txt" ]]; then - CIPHERS_BY_STRENGTH_FILE="$RUN_DIR/etc/cipher-mapping.txt" - [[ -z "$TESTSSL_INSTALL_DIR" ]] && TESTSSL_INSTALL_DIR="$RUN_DIR" # probably TESTSSL_INSTALL_DIR - fi - - [[ -r "$TESTSSL_INSTALL_DIR/etc/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/etc/cipher-mapping.txt" - if [[ ! -r "$CIPHERS_BY_STRENGTH_FILE" ]]; then - [[ -r "$RUN_DIR/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$RUN_DIR/cipher-mapping.txt" - [[ -r "$TESTSSL_INSTALL_DIR/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/cipher-mapping.txt" - fi - - # we haven't found the cipher file yet... - if [[ ! -r "$CIPHERS_BY_STRENGTH_FILE" ]] && type -p readlink &>/dev/null ; then - readlink -f ls &>/dev/null && \ - TESTSSL_INSTALL_DIR="$(readlink -f "$(basename "${BASH_SOURCE[0]}")")" || \ - TESTSSL_INSTALL_DIR="$(readlink "$(basename "${BASH_SOURCE[0]}")")" - # not sure whether Darwin has -f - TESTSSL_INSTALL_DIR="$(dirname "$TESTSSL_INSTALL_DIR" 2>/dev/null)" - [[ -r "$TESTSSL_INSTALL_DIR/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/cipher-mapping.txt" - [[ -r "$TESTSSL_INSTALL_DIR/etc/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/etc/cipher-mapping.txt" - fi - - # still no cipher mapping file: - if [[ ! -r "$CIPHERS_BY_STRENGTH_FILE" ]] && type -p realpath &>/dev/null ; then - TESTSSL_INSTALL_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")" - CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/etc/cipher-mapping.txt" - [[ -r "$TESTSSL_INSTALL_DIR/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/cipher-mapping.txt" - fi - - # still no cipher mapping file (and realpath is not present): - if [[ ! -r "$CIPHERS_BY_STRENGTH_FILE" ]] && type -p readlink &>/dev/null ; then - readlink -f ls &>/dev/null && \ - TESTSSL_INSTALL_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" || \ - TESTSSL_INSTALL_DIR="$(dirname "$(readlink "${BASH_SOURCE[0]}")")" - # not sure whether Darwin has -f - CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/etc/cipher-mapping.txt" - [[ -r "$TESTSSL_INSTALL_DIR/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/cipher-mapping.txt" - fi - - if [[ ! -r "$CIPHERS_BY_STRENGTH_FILE" ]]; then - DISPLAY_CIPHERNAMES="openssl-only" - debugme echo "$CIPHERS_BY_STRENGTH_FILE" - prln_warning "\nATTENTION: No cipher mapping file found!" - outln "Please note from 2.9 on $PROG_NAME needs files in \"\$TESTSSL_INSTALL_DIR/etc/\" to function correctly." - outln - ignore_no_or_lame "Type \"yes\" to ignore this warning and proceed at your own risk" "yes" - [[ $? -ne 0 ]] && exit $ERR_RESOURCE - fi - - TLS_DATA_FILE="$TESTSSL_INSTALL_DIR/etc/tls_data.txt" - if [[ ! -r "$TLS_DATA_FILE" ]]; then - prln_warning "\nATTENTION: No TLS data file found -- needed for socket-based handshakes" - outln "Please note from 2.9 on $PROG_NAME needs files in \"\$TESTSSL_INSTALL_DIR/etc/\" to function correctly." - outln - ignore_no_or_lame "Type \"yes\" to ignore this warning and proceed at your own risk" "yes" - [[ $? -ne 0 ]] && exit $ERR_RESOURCE - else - : # see #705, in a nutshell: not portable to initialize a global array inside a function. Thus it'll be done in main part below - fi -} - - -test_openssl_suffix() { - local naming_ext="$(uname).$(uname -m)" - local uname_arch="$(uname -m)" - local myarch_suffix="" - - [[ $uname_arch =~ 64 ]] && myarch_suffix=64 || myarch_suffix=32 - if [[ -f "$1/openssl" ]] && [[ -x "$1/openssl" ]]; then - OPENSSL="$1/openssl" - return 0 - elif [[ -f "$1/openssl.$naming_ext" ]] && [[ -x "$1/openssl.$naming_ext" ]]; then - OPENSSL="$1/openssl.$naming_ext" - return 0 - elif [[ -f "$1/openssl.$uname_arch" ]] && [[ -x "$1/openssl.$uname_arch" ]]; then - OPENSSL="$1/openssl.$uname_arch" - return 0 - elif [[ -f "$1/openssl$myarch_suffix" ]] && [[ -x "$1/openssl$myarch_suffix" ]]; then - OPENSSL="$1/openssl$myarch_suffix" - return 0 - fi - return 1 -} - - -find_openssl_binary() { - local s_client_has=$TEMPDIR/s_client_has.txt - local s_client_starttls_has=$TEMPDIR/s_client_starttls_has.txt - local openssl_location cwd="" - local ossl_wo_dev_info - local curve - local -a curves_ossl=("sect163k1" "sect163r1" "sect163r2" "sect193r1" "sect193r2" "sect233k1" "sect233r1" "sect239k1" "sect283k1" "sect283r1" "sect409k1" "sect409r1" "sect571k1" "sect571r1" "secp160k1" "secp160r1" "secp160r2" "secp192k1" "prime192v1" "secp224k1" "secp224r1" "secp256k1" "prime256v1" "secp384r1" "secp521r1" "brainpoolP256r1" "brainpoolP384r1" "brainpoolP512r1" "X25519" "X448") - - # 0. check environment variable whether it's executable - if [[ -n "$OPENSSL" ]] && [[ ! -x "$OPENSSL" ]]; then - prln_warning "\ncannot find specified (\$OPENSSL=$OPENSSL) binary." - tmln_out " Looking some place else ..." - elif [[ -x "$OPENSSL" ]]; then - : # 1. all ok supplied $OPENSSL was found and has executable bit set -- testrun comes below - elif [[ -e "/mnt/c/Windows/System32/bash.exe" ]] && test_openssl_suffix "$(dirname "$(type -p openssl)")"; then - # 2. otherwise, only if on Bash on Windows, use system binaries only. - SYSTEM2="WSL" - elif test_openssl_suffix "$TESTSSL_INSTALL_DIR"; then - : # 3. otherwise try openssl in path of testssl.sh - elif test_openssl_suffix "$TESTSSL_INSTALL_DIR/bin"; then - : # 4. otherwise here, this is supposed to be the standard --platform independent path in the future!!! - elif test_openssl_suffix "$(dirname "$(type -p openssl)")"; then - : # 5. we tried hard and failed, so now we use the system binaries - fi - - [[ ! -x "$OPENSSL" ]] && fatal "cannot exec or find any openssl binary" $ERR_OSSLBIN - - # The former detection only was flawed, because when the system supplied openssl.cnf file - # couldn't be parsed by our openssl it bailed out here with a misleading error, see #1982. - # Now we try with another version of the config file and if it still fails we bail out. - if ! $OPENSSL version -d >/dev/null 2>&1 ; then - export OPENSSL_CONF="$TESTSSL_INSTALL_DIR/etc/openssl.cnf" - if ! $OPENSSL version -d >/dev/null 2>&1 ; then - fatal "cannot exec or find any openssl binary" $ERR_OSSLBIN - else - [[ "$DEBUG" -ge 1 ]] && echo "We provide our own openssl.cnf file as the one from your system cannot be used" - fi - fi - - # https://www.openssl.org/news/openssl-notes.html - OSSL_NAME=$($OPENSSL version 2>/dev/null | awk '{ print $1 }') - OSSL_VER=$($OPENSSL version 2>/dev/null | awk -F' ' '{ print $2 }') - OSSL_VER_MAJOR="${OSSL_VER%%\.*}" - ossl_wo_dev_info="${OSSL_VER%%-*}" - OSSL_VER_MINOR="${ossl_wo_dev_info#$OSSL_VER_MAJOR\.}" - OSSL_VER_MINOR="${OSSL_VER_MINOR%%[a-zA-Z]*}" - OSSL_VER_APPENDIX="${OSSL_VER#$OSSL_VER_MAJOR\.$OSSL_VER_MINOR}" - OSSL_VER_PLATFORM=$($OPENSSL version -p 2>/dev/null | sed 's/^platform: //') - OSSL_BUILD_DATE=$($OPENSSL version -a 2>/dev/null | grep '^built' | sed -e 's/built on//' -e 's/: ... //' -e 's/: //' -e 's/ UTC//' -e 's/ +0000//' -e 's/.000000000//') - - # see #190, reverting logic: unless otherwise proved openssl has no dh bits - case "$OSSL_VER_MAJOR.$OSSL_VER_MINOR" in - 1.0.2|1.1.0|1.1.1|3.0.0) HAS_DH_BITS=true ;; - esac - if [[ "$OSSL_NAME" =~ LibreSSL ]]; then - [[ ${OSSL_VER//./} -ge 210 ]] && HAS_DH_BITS=true - if "$SSL_NATIVE"; then - outln - pr_warning "LibreSSL in native ssl mode is not a good choice for testing INSECURE features!" - fi - fi - - initialize_engine - - openssl_location="$(type -p $OPENSSL)" - [[ -n "$GIT_REL" ]] && \ - cwd="$PWD" || \ - cwd="$RUN_DIR" - if [[ "$openssl_location" == ${PWD}/bin ]]; then - OPENSSL_LOCATION="\$PWD/bin/$(basename "$openssl_location")" - elif [[ "$openssl_location" =~ $cwd ]] && [[ "$cwd" != '.' ]]; then - OPENSSL_LOCATION="${openssl_location%%$cwd}" - else - OPENSSL_LOCATION="$openssl_location" - fi - - OSSL_CIPHERS_S="" - HAS_SSL2=false - HAS_SSL3=false - HAS_TLS13=false - HAS_X448=false - HAS_X25519=false - HAS_NO_SSL2=false - HAS_NOSERVERNAME=false - HAS_CIPHERSUITES=false - HAS_COMP=false - HAS_NO_COMP=false - HAS_CURVES=false - OSSL_SUPPORTED_CURVES="" - HAS_PKEY=false - HAS_PKUTIL=false - HAS_ALPN=false - HAS_NPN=false - HAS_FALLBACK_SCSV=false - HAS_PROXY=false - HAS_XMPP=false - HAS_POSTGRES=false - HAS_MYSQL=false - HAS_LMTP=false - HAS_NNTP=false - HAS_IRC=false - HAS_CHACHA20=false - HAS_AES128_GCM=false - HAS_AES256_GCM=false - HAS_ZLIB=false - - $OPENSSL ciphers -s 2>&1 | grep -aiq "unknown option" || \ - OSSL_CIPHERS_S="-s" - - # This and all other occurrences we do a little trick using "invalid." to avoid plain and - # link level DNS lookups. See issue #1418 and https://tools.ietf.org/html/rfc6761#section-6.4 - $OPENSSL s_client -ssl2 -connect invalid. 2>&1 | grep -aiq "unknown option" || \ - HAS_SSL2=true - - $OPENSSL s_client -ssl3 -connect invalid. 2>&1 | grep -aiq "unknown option" || \ - HAS_SSL3=true - - $OPENSSL s_client -tls1_3 -connect invalid. 2>&1 | grep -aiq "unknown option" || \ - HAS_TLS13=true - - $OPENSSL genpkey -algorithm X448 2>&1 | grep -aq "not found" || \ - HAS_X448=true - - $OPENSSL genpkey -algorithm X25519 2>&1 | grep -aq "not found" || \ - HAS_X25519=true - - $OPENSSL s_client -no_ssl2 -connect invalid. 2>&1 | grep -aiq "unknown option" || \ - HAS_NO_SSL2=true - - $OPENSSL s_client -noservername -connect invalid. 2>&1 | grep -aiq "unknown option" || \ - HAS_NOSERVERNAME=true - - $OPENSSL s_client -ciphersuites -connect invalid. 2>&1 | grep -aiq "unknown option" || \ - HAS_CIPHERSUITES=true - - $OPENSSL s_client -comp -connect invalid. 2>&1 | grep -aiq "unknown option" || \ - HAS_COMP=true - - $OPENSSL s_client -no_comp -connect invalid. 2>&1 | grep -aiq "unknown option" || \ - HAS_NO_COMP=true - - OPENSSL_NR_CIPHERS=$(count_ciphers "$(actually_supported_osslciphers 'ALL:COMPLEMENTOFALL' 'ALL')") - - if $OPENSSL s_client -curves "${curves_ossl[0]}" -connect invalid. 2>&1 | grep -aiq "unknown option"; then - for curve in "${curves_ossl[@]}"; do - $OPENSSL s_client -groups $curve -connect invalid.:8443 2>&1 | grep -Eiaq "Error with command|unknown option|Failed to set groups" - [[ $? -ne 0 ]] && OSSL_SUPPORTED_CURVES+=" $curve " - done - else - HAS_CURVES=true - for curve in "${curves_ossl[@]}"; do - $OPENSSL s_client -curves $curve -connect invalid. 2>&1 | grep -Eiaq "Error with command|unknown option" - [[ $? -ne 0 ]] && OSSL_SUPPORTED_CURVES+=" $curve " - done - fi - - $OPENSSL pkey -help 2>&1 | grep -q Error || \ - HAS_PKEY=true - - $OPENSSL pkeyutl 2>&1 | grep -q Error || \ - HAS_PKUTIL=true - - # For the following we feel safe enough to query the s_client help functions. - # That was not good enough for the previous lookups - $OPENSSL s_client -help 2>$s_client_has - - $OPENSSL s_client -starttls foo 2>$s_client_starttls_has - - grep -qw '\-alpn' $s_client_has && \ - HAS_ALPN=true - - grep -qw '\-nextprotoneg' $s_client_has && \ - HAS_NPN=true - - grep -qw '\-fallback_scsv' $s_client_has && \ - HAS_FALLBACK_SCSV=true - - grep -q '\-proxy' $s_client_has && \ - HAS_PROXY=true - - grep -q '\-xmpp' $s_client_has && \ - HAS_XMPP=true - - grep -q 'postgres' $s_client_starttls_has && \ - HAS_POSTGRES=true - - grep -q 'mysql' $s_client_starttls_has && \ - HAS_MYSQL=true - - grep -q 'lmtp' $s_client_starttls_has && \ - HAS_LMTP=true - - grep -q 'nntp' $s_client_starttls_has && \ - HAS_NNTP=true - - grep -q 'irc' $s_client_starttls_has && \ - HAS_IRC=true - - $OPENSSL enc -chacha20 -K 12345678901234567890123456789012 -iv 01000000123456789012345678901234 > /dev/null 2> /dev/null <<< "test" - [[ $? -eq 0 ]] && HAS_CHACHA20=true - - $OPENSSL enc -aes-128-gcm -K 0123456789abcdef0123456789abcdef -iv 0123456789abcdef01234567 > /dev/null 2> /dev/null <<< "test" - [[ $? -eq 0 ]] && HAS_AES128_GCM=true - - $OPENSSL enc -aes-256-gcm -K 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef -iv 0123456789abcdef01234567 > /dev/null 2> /dev/null <<< "test" - [[ $? -eq 0 ]] && HAS_AES256_GCM=true - - [[ "$(echo -e "\x78\x9C\xAB\xCA\xC9\x4C\xE2\x02\x00\x06\x20\x01\xBC" | $OPENSSL zlib -d 2>/dev/null)" == zlib ]] && HAS_ZLIB=true - - if [[ -n "$CONNECT_TIMEOUT" ]] || [[ -n "$OPENSSL_TIMEOUT" ]]; then - # We don't set a general timeout as we might not have "timeout" installed and we only - # do what is instructed. Thus we check first what the command line params were, - # then we proceed - if type -p timeout >/dev/null 2>&1; then - # There are different versions of "timeout". Check whether --preserve-status is supported - if timeout --help 2>/dev/null | grep -q 'preserve-status'; then - TIMEOUT_CMD="timeout --preserve-status" - else - TIMEOUT_CMD="timeout" - fi - else - TIMEOUT_CMD="" - outln - fatal "You specified a connect or openssl timeout but the binary \"timeout\" couldn't be found " $ERR_RESOURCE - fi - fi - - if ! "$do_mass_testing"; then - if [[ -n $OPENSSL_TIMEOUT ]]; then - OPENSSL="$TIMEOUT_CMD $OPENSSL_TIMEOUT $OPENSSL" - fi - fi - - return 0 -} - - -check4openssl_oldfarts() { - case "$OSSL_VER" in - 0.9.7*|0.9.6*|0.9.5*) - # 0.9.5a was latest in 0.9.5 an released 2000/4/1, that'll NOT suffice for this test - old_fart ;; - 0.9.8) - case $OSSL_VER_APPENDIX in - a|b|c|d|e) old_fart;; # no SNI! - # other than that we leave this for MacOSX and FreeBSD but it's a pain and likely gives false negatives/positives - esac - ;; - esac - if [[ $OSSL_VER_MAJOR -lt 1 ]]; then ## mm: Patch for libressl - prln_warning " Your \"$OPENSSL\" is way too old (<version 1.0) !" - case $SYSTEM in - *BSD|Darwin) - out " Please use binary provided in \$INSTALLDIR/bin/ or from ports/brew or compile from " - pr_url "github.com/PeterMosmans/openssl"; outln "." - fileout_insert_warning "too_old_openssl" "WARN" "Your $OPENSSL $OSSL_VER version is way too old. Please use binary provided in \$INSTALLDIR/bin/ or from ports/brew or compile from github.com/PeterMosmans/openssl ." ;; - *) out " Update openssl binaries or compile from " - pr_url "https://github.com/PeterMosmans/openssl"; outln "." - fileout_insert_warning "too_old_openssl" "WARN" "Update openssl binaries or compile from https://github.com/PeterMosmans/openssl .";; - esac - ignore_no_or_lame " Type \"yes\" to accept false negatives or positives" "yes" - [[ $? -ne 0 ]] && exit $ERR_CLUELESS - fi - outln -} - -# FreeBSD needs to have /dev/fd mounted. This is a friendly hint, see #258 -check_bsd_mount() { - if [[ "$(uname)" == FreeBSD ]]; then - if ! mount | grep -q "^devfs"; then - outln "you seem to run $PROG_NAME= in a jail. Hopefully you're did \"mount -t fdescfs fdesc /dev/fd\"" - elif mount | grep '/dev/fd' | grep -q fdescfs; then - : - else - fatal "You need to mount fdescfs on FreeBSD: \"mount -t fdescfs fdesc /dev/fd\"" $ERR_OTHERCLIENT - fi - fi -} - -# This sets the PRINTF command for writing into TCP sockets. It is needed because -# The shell builtin printf flushes the write buffer at every \n, ("\x0a") which -# in turn means a new TCP fragment. That causes a slight performance penalty and -# and some F5s to hiccup, see #1113. Unfortunately this can be used only with GNU's -# and OpenBSD's /usr/bin/printf -- FreeBSD + OS X can't do this. Thus here we need -# to pipe through dd or cat, see socksend() and socksend_clienthello(). An empty -# $PRINTF signals the bash internal printf which then uses cat as a stdout buffer. -# A better solution needs to follow. -# -choose_printf() { - local p ptf - - ptf="$(type -aP printf)" - if [[ -n "$ptf" ]]; then - for p in $ptf; do - if $p "\xc0\x14\xc0\xff\xee" | hexdump -C | grep -q 'c0 14 c0 ff ee'; then - PRINTF=$p - return 0 - fi - done - fi - if type -t printf >/dev/null; then - PRINTF="" - return 0 - fi - fatal "Neither external printf nor shell internal found. " $ERR_CLUELESS -} - - -help() { - cat << EOF - - "$PROG_NAME [options] <URI>" or "$PROG_NAME <options>" - - -"$PROG_NAME <options>", where <options> is: - - --help what you're looking at - -b, --banner displays banner + version of $PROG_NAME - -v, --version same as previous - -V, --local pretty print all local ciphers - -V, --local <pattern> which local ciphers with <pattern> are available? If pattern is not a number: word match - - <pattern> is always an ignore case word pattern of cipher hexcode or any other string in the name, kx or bits - -"$PROG_NAME <URI>", where <URI> is: - - <URI> host|host:port|URL|URL:port port 443 is default, URL can only contain HTTPS protocol) - -"$PROG_NAME [options] <URI>", where [options] is: - - -t, --starttls <protocol> Does a default run against a STARTTLS enabled <protocol, - protocol is <ftp|smtp|lmtp|pop3|imap|xmpp|telnet|ldap|nntp|postgres|mysql> - --xmpphost <to_domain> For STARTTLS enabled XMPP it supplies the XML stream to-'' domain -- sometimes needed - --mx <domain/host> Tests MX records from high to low priority (STARTTLS, port 25) - --file/-iL <fname> Mass testing option: Reads one testssl.sh command line per line from <fname>. - Can be combined with --serial or --parallel. Implicitly turns on "--warnings batch". - Text format 1: Comments via # allowed, EOF signals end of <fname> - Text format 2: nmap output in greppable format (-oG), 1 port per line allowed - --mode <serial|parallel> Mass testing to be done serial (default) or parallel (--parallel is shortcut for the latter) - --warnings <batch|off> "batch" doesn't continue when a testing error is encountered, off continues and skips warnings - --connect-timeout <seconds> useful to avoid hangers. Max <seconds> to wait for the TCP socket connect to return - --openssl-timeout <seconds> useful to avoid hangers. Max <seconds> to wait before openssl connect will be terminated - -single check as <options> ("$PROG_NAME URI" does everything except -E and -g): - -e, --each-cipher checks each local cipher remotely - -E, --cipher-per-proto checks those per protocol - -s, --std, --standard tests standard cipher categories by strength - -p, --protocols checks TLS/SSL protocols (including SPDY/HTTP2) - -g, --grease tests several server implementation bugs like GREASE and size limitations - -S, --server-defaults displays the server's default picks and certificate info - -P, --server-preference displays the server's picks: protocol+cipher - -x, --single-cipher <pattern> tests matched <pattern> of ciphers - (if <pattern> not a number: word match) - -c, --client-simulation test client simulations, see which client negotiates with cipher and protocol - -h, --header, --headers tests HSTS, HPKP, server/app banner, security headers, cookie, reverse proxy, IPv4 address - - -U, --vulnerable tests all (of the following) vulnerabilities (if applicable) - -H, --heartbleed tests for Heartbleed vulnerability - -I, --ccs, --ccs-injection tests for CCS injection vulnerability - -T, --ticketbleed tests for Ticketbleed vulnerability in BigIP loadbalancers - -BB, --robot tests for Return of Bleichenbacher's Oracle Threat (ROBOT) vulnerability - -R, --renegotiation tests for renegotiation vulnerabilities - -C, --compression, --crime tests for CRIME vulnerability (TLS compression issue) - -B, --breach tests for BREACH vulnerability (HTTP compression issue) - -O, --poodle tests for POODLE (SSL) vulnerability - -Z, --tls-fallback checks TLS_FALLBACK_SCSV mitigation - -W, --sweet32 tests 64 bit block ciphers (3DES, RC2 and IDEA): SWEET32 vulnerability - -A, --beast tests for BEAST vulnerability - -L, --lucky13 tests for LUCKY13 - -F, --freak tests for FREAK vulnerability - -J, --logjam tests for LOGJAM vulnerability - -D, --drown tests for DROWN vulnerability - -f, --pfs, --fs, --nsa checks (perfect) forward secrecy settings - -4, --rc4, --appelbaum which RC4 ciphers are being offered? - -tuning / connect options (most also can be preset via environment variables): - --fast omits some checks: using openssl for all ciphers (-e), show only first preferred cipher. - -9, --full includes tests for implementation bugs and cipher per protocol (could disappear) - --bugs enables the "-bugs" option of s_client, needed e.g. for some buggy F5s - --assume-http if protocol check fails it assumes HTTP protocol and enforces HTTP checks - --ssl-native fallback to checks with OpenSSL where sockets are normally used - --openssl <PATH> use this openssl binary (default: look in \$PATH, \$RUN_DIR of $PROG_NAME) - --proxy <host:port|auto> (experimental) proxy connects via <host:port>, auto: values from \$env (\$http(s)_proxy) - -6 also use IPv6. Works only with supporting OpenSSL version and IPv6 connectivity - --ip <ip> a) tests the supplied <ip> v4 or v6 address instead of resolving host(s) in URI - b) arg "one" means: just test the first DNS returns (useful for multiple IPs) - -n, --nodns <min|none> if "none": do not try any DNS lookups, "min" queries A, AAAA and MX records - --sneaky leave less traces in target logs: user agent, referer - --ids-friendly skips a few vulnerability checks which may cause IDSs to block the scanning IP - --phone-out allow to contact external servers for CRL download and querying OCSP responder - --add-ca <cafile> path to <cafile> or a comma separated list of CA files enables test against additional CAs. - --basicauth <user:pass> provide HTTP basic auth information. - -output options (can also be preset via environment variables): - --quiet don't output the banner. By doing this you acknowledge usage terms normally appearing in the banner - --wide wide output for tests like RC4, BEAST. PFS also with hexcode, kx, strength, RFC name - --show-each for wide outputs: display all ciphers tested -- not only succeeded ones - --mapping <openssl| openssl: use the OpenSSL cipher suite name as the primary name cipher suite name form (default) - iana|rfc -> use the IANA/(RFC) cipher suite name as the primary name cipher suite name form - no-openssl| -> don't display the OpenSSL cipher suite name, display IANA/(RFC) names only - no-iana|no-rfc> -> don't display the IANA/(RFC) cipher suite name, display OpenSSL names only - --color <0|1|2|3> 0: no escape or other codes, 1: b/w escape codes, 2: color (default), 3: extra color (color all ciphers) - --colorblind swap green and blue in the output - --debug <0-6> 1: screen output normal but keeps debug output in /tmp/. 2-6: see "grep -A 5 '^DEBUG=' testssl.sh" - -file output options (can also be preset via environment variables) - --log, --logging logs stdout to '\${NODE}-p\${port}\${YYYYMMDD-HHMM}.log' in current working directory (cwd) - --logfile|-oL <logfile> logs stdout to 'dir/\${NODE}-p\${port}\${YYYYMMDD-HHMM}.log'. If 'logfile' is a dir or to a specified 'logfile' - --json additional output of findings to flat JSON file '\${NODE}-p\${port}\${YYYYMMDD-HHMM}.json' in cwd - --jsonfile|-oj <jsonfile> additional output to the specified flat JSON file or directory, similar to --logfile - --json-pretty additional JSON structured output of findings to a file '\${NODE}-p\${port}\${YYYYMMDD-HHMM}.json' in cwd - --jsonfile-pretty|-oJ <jsonfile> additional JSON structured output to the specified file or directory, similar to --logfile - --csv additional output of findings to CSV file '\${NODE}-p\${port}\${YYYYMMDD-HHMM}.csv' in cwd or directory - --csvfile|-oC <csvfile> additional output as CSV to the specified file or directory, similar to --logfile - --html additional output as HTML to file '\${NODE}-p\${port}\${YYYYMMDD-HHMM}.html' - --htmlfile|-oH <htmlfile> additional output as HTML to the specified file or directory, similar to --logfile - --out(f,F)ile|-oa/-oA <fname> log to a LOG,JSON,CSV,HTML file (see nmap). -oA/-oa: pretty/flat JSON. - "auto" uses '\${NODE}-p\${port}\${YYYYMMDD-HHMM}'. If fname if a dir uses 'dir/\${NODE}-p\${port}\${YYYYMMDD-HHMM}' - --hints additional hints to findings - --severity <severity> severities with lower level will be filtered for CSV+JSON, possible values <LOW|MEDIUM|HIGH|CRITICAL> - --append if (non-empty) <logfile>, <csvfile>, <jsonfile> or <htmlfile> exists, append to file. Omits any header - --outprefix <fname_prefix> before '\${NODE}.' above prepend <fname_prefix> - - -Options requiring a value can also be called with '=' e.g. testssl.sh -t=smtp --wide --openssl=/usr/bin/openssl <URI>. -<URI> always needs to be the last parameter. - -EOF - # Set HTMLHEADER and JSONHEADER to false so that the cleanup() function won't - # try to write footers to the HTML and JSON files. - HTMLHEADER=false - JSONHEADER=false - #' Fix syntax highlight on sublime - "$CHILD_MASS_TESTING" && kill -s USR1 $PPID - exit $1 -} - -maketempf() { - TEMPDIR=$(mktemp -d /tmp/testssl.XXXXXX) - if [[ $? -ne 0 ]]; then - # For e.g. devices where we can't write to /tmp we chose $PWD but we can't - # allow every char as we haven't quoted all strings depending on it, see #1445 - if [[ $PWD =~ [^A-Za-z0-9\.,/_-] ]]; then - fatal "\$PWD contains illegal chars: \"$BASH_REMATCH\"" $ERR_FCREATE - fi - TEMPDIR=$(mktemp -d "$PWD/testssl.XXXXXX") || exit $ERR_FCREATE - fi - TMPFILE=$TEMPDIR/tempfile.txt || exit $ERR_FCREATE - if [[ "$DEBUG" -eq 0 ]]; then - ERRFILE="/dev/null" - else - ERRFILE=$TEMPDIR/errorfile.txt || exit $ERR_FCREATE - fi - HOSTCERT=$TEMPDIR/host_certificate.pem -} - -prepare_debug() { - if [[ $DEBUG -ne 0 ]]; then - cat >$TEMPDIR/environment.txt << EOF - - -GIT_REL: $GIT_REL - -PID: $$ -commandline: "${CMDLINE[@]}" -bash version: ${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}.${BASH_VERSINFO[2]} -status: ${BASH_VERSINFO[4]} -machine: ${BASH_VERSINFO[5]} -operating system: $SYSTEM $SYSTEMREV -os constraint: $SYSTEM2 -shellopts: $SHELLOPTS -printf: $PRINTF -NO_ITALICS: $NO_ITALICS - -$($OPENSSL version -a 2>/dev/null) -OSSL_VER_MAJOR: $OSSL_VER_MAJOR -OSSL_VER_MINOR: $OSSL_VER_MINOR -OSSL_VER_APPENDIX: $OSSL_VER_APPENDIX -OSSL_BUILD_DATE: $OSSL_BUILD_DATE -OSSL_VER_PLATFORM: $OSSL_VER_PLATFORM - -OPENSSL_NR_CIPHERS: $OPENSSL_NR_CIPHERS -OPENSSL_CONF: $OPENSSL_CONF -HAS_CURVES: $HAS_CURVES -OSSL_SUPPORTED_CURVES: $OSSL_SUPPORTED_CURVES - -HAS_IPv6: $HAS_IPv6 -HAS_SSL2: $HAS_SSL2 -HAS_SSL3: $HAS_SSL3 -HAS_TLS13: $HAS_TLS13 -HAS_X448: $HAS_X448 -HAS_X25519: $HAS_X25519 -HAS_NO_SSL2: $HAS_NO_SSL2 -HAS_SPDY: $HAS_SPDY -HAS_ALPN: $HAS_ALPN -HAS_FALLBACK_SCSV: $HAS_FALLBACK_SCSV -HAS_COMP: $HAS_COMP -HAS_NO_COMP: $HAS_NO_COMP -HAS_CIPHERSUITES: $HAS_CIPHERSUITES -HAS_PKEY: $HAS_PKEY -HAS_PKUTIL: $HAS_PKUTIL -HAS_PROXY: $HAS_PROXY -HAS_XMPP: $HAS_XMPP -HAS_POSTGRES: $HAS_POSTGRES -HAS_MYSQL: $HAS_MYSQL -HAS_LMTP: $HAS_LMTP -HAS_NNTP: $HAS_NNTP -HAS_IRC: $HAS_IRC - -HAS_DIG: $HAS_DIG -HAS_HOST: $HAS_HOST -HAS_DRILL: $HAS_DRILL -HAS_NSLOOKUP: $HAS_NSLOOKUP -HAS_IDN: $HAS_IDN -HAS_IDN2: $HAS_IDN2 -HAS_AVAHIRESOLVE: $HAS_AVAHIRESOLVE -HAS_DIG_NOIDNOUT: $HAS_DIG_NOIDNOUT -HAS_DIG_R: $HAS_DIG_R - -PATH: $PATH -PROG_NAME: $PROG_NAME -TESTSSL_INSTALL_DIR: $TESTSSL_INSTALL_DIR -RUN_DIR: $RUN_DIR -CIPHERS_BY_STRENGTH_FILE: $CIPHERS_BY_STRENGTH_FILE - -CAPATH: $CAPATH -COLOR: $COLOR -COLORBLIND: $COLORBLIND -TERM_WIDTH: $TERM_WIDTH -INTERACTIVE: $INTERACTIVE -HAS_GNUDATE: $HAS_GNUDATE -HAS_FREEBSDDATE: $HAS_FREEBSDDATE -HAS_OPENBSDDATE: $HAS_OPENBSDDATE -HAS_SED_E: $HAS_SED_E - -SHOW_EACH_C: $SHOW_EACH_C -SSL_NATIVE: $SSL_NATIVE -ASSUME_HTTP $ASSUME_HTTP -BASICAUTH: $BASICAUTH -SNEAKY: $SNEAKY -OFFENSIVE: $OFFENSIVE -PHONE_OUT: $PHONE_OUT - -DEBUG: $DEBUG - -HSTS_MIN: $HSTS_MIN -HPKP_MIN: $HPKP_MIN -CLIENT_MIN_PFS: $CLIENT_MIN_PFS -DAYS2WARN1: $DAYS2WARN1 -DAYS2WARN2: $DAYS2WARN2 - -HEADER_MAXSLEEP: $HEADER_MAXSLEEP -MAX_WAITSOCK: $MAX_WAITSOCK -HEARTBLEED_MAX_WAITSOCK: $HEARTBLEED_MAX_WAITSOCK -CCS_MAX_WAITSOCK: $CCS_MAX_WAITSOCK -USLEEP_SND $USLEEP_SND -USLEEP_REC $USLEEP_REC - -EOF - type -p locale &>/dev/null && locale >>$TEMPDIR/environment.txt || echo "locale doesn't exist" >>$TEMPDIR/environment.txt - actually_supported_osslciphers 'ALL:COMPLEMENTOFALL' 'ALL' "-V" &>$TEMPDIR/all_local_ciphers.txt - fi - # see also $TEMPDIR/s_client_has.txt from find_openssl_binary -} - - -prepare_arrays() { - local hexc mac ossl_ciph - local ossl_supported_tls="" ossl_supported_sslv2="" - local -i i=0 - - if [[ -e "$CIPHERS_BY_STRENGTH_FILE" ]]; then - "$HAS_SSL2" && ossl_supported_sslv2="$($OPENSSL ciphers -ssl2 -V 'ALL:COMPLEMENTOFALL:@STRENGTH' 2>$ERRFILE)" - if "$HAS_SSL2"; then - ossl_supported_tls="$(actually_supported_osslciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 'ALL' "-tls1 -V")" - else - ossl_supported_tls="$(actually_supported_osslciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 'ALL' "-V")" - fi - TLS13_OSSL_CIPHERS="" - while read hexc n TLS_CIPHER_OSSL_NAME[i] TLS_CIPHER_RFC_NAME[i] TLS_CIPHER_SSLVERS[i] TLS_CIPHER_KX[i] TLS_CIPHER_AUTH[i] TLS_CIPHER_ENC[i] mac TLS_CIPHER_EXPORT[i]; do - TLS_CIPHER_HEXCODE[i]="$hexc" - TLS_CIPHER_OSSL_SUPPORTED[i]=false - if [[ ${#hexc} -eq 9 ]]; then - # >= SSLv3 ciphers - if [[ $OSSL_VER_MAJOR -lt 1 ]]; then - [[ ":${ossl_supported_tls}:" =~ ":${TLS_CIPHER_OSSL_NAME[i]}:" ]] && TLS_CIPHER_OSSL_SUPPORTED[i]=true - else - ossl_ciph="$(awk '/'"$hexc"'/ { print $3 }' <<< "$ossl_supported_tls")" - if [[ -n "$ossl_ciph" ]]; then - TLS_CIPHER_OSSL_SUPPORTED[i]=true - [[ "$ossl_ciph" != ${TLS_CIPHER_OSSL_NAME[i]} ]] && TLS_CIPHER_OSSL_NAME[i]="$ossl_ciph" - [[ "${hexc:2:2}" == 13 ]] && TLS13_OSSL_CIPHERS+=":$ossl_ciph" - fi - fi - elif [[ $OSSL_VER_MAJOR -lt 1 ]]; then - [[ ":${ossl_supported_sslv2}:" =~ ":${TLS_CIPHER_OSSL_NAME[i]}:" ]] && TLS_CIPHER_OSSL_SUPPORTED[i]=true - else - [[ "$ossl_supported_sslv2" =~ $hexc ]] && TLS_CIPHER_OSSL_SUPPORTED[i]=true - fi - i+=1 - done < "$CIPHERS_BY_STRENGTH_FILE" - fi - TLS_NR_CIPHERS=i - TLS13_OSSL_CIPHERS="${TLS13_OSSL_CIPHERS:1}" -} - - -mybanner() { - local idtag - local bb1 bb2 bb3 - - "$QUIET" && return - "$CHILD_MASS_TESTING" && return - OPENSSL_NR_CIPHERS=$(count_ciphers "$(actually_supported_osslciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 'ALL')") - [[ -n "$GIT_REL" ]] && idtag="$GIT_REL" - bb1=$(cat <<EOF - -########################################################### - $PROG_NAME $VERSION from -EOF -) - bb2=$(cat <<EOF - - This program is free software. Distribution and - modification under GPLv2 permitted. - USAGE w/o ANY WARRANTY. USE IT AT YOUR OWN RISK! - - Please file bugs @ -EOF -) - bb3=$(cat <<EOF - -########################################################### -EOF -) - pr_bold "$bb1 " - pr_boldurl "$SWURL"; outln - if [[ -n "$idtag" ]]; then - #FIXME: if we run it not off the git dir we miss the version tag. - # at least we don't want to display empty brackets here... - pr_bold " (" - pr_grey "$idtag" - prln_bold ")" - fi - pr_bold "$bb2 " - pr_boldurl "https://testssl.sh/bugs/"; outln - pr_bold "$bb3" - outln "\n" - outln " Using \"$($OPENSSL version 2>/dev/null)\" [~$OPENSSL_NR_CIPHERS ciphers]" - out " on $HNAME:" - outln "$OPENSSL_LOCATION" - outln " (built: \"$OSSL_BUILD_DATE\", platform: \"$OSSL_VER_PLATFORM\")\n" -} - -calc_scantime() { - END_TIME=$(date +%s) - SCAN_TIME=$(( END_TIME - START_TIME )) -} - -cleanup() { - # If parallel mass testing is being performed, then the child tests need - # to be killed before $TEMPDIR is deleted. Otherwise, error messages - # will be created if testssl.sh is stopped before all testing is complete. - "$INTERACTIVE" && [[ $NR_PARALLEL_TESTS -gt 0 ]] && echo -en "\r \r" 1>&2 - while [[ $NEXT_PARALLEL_TEST_TO_FINISH -lt $NR_PARALLEL_TESTS ]]; do - if [[ ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} -ne 0 ]] && \ - ps ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} >/dev/null ; then - kill ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} >&2 2>/dev/null - wait ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} 2>/dev/null # make sure pid terminated, see wait(1p) - get_next_message_testing_parallel_result "stopped" - else - # If a test had already completed, but its output wasn't yet processed, - # then process it now. - get_next_message_testing_parallel_result "completed" - fi - NEXT_PARALLEL_TEST_TO_FINISH+=1 - done - if [[ "$DEBUG" -ge 1 ]]; then - tmln_out - tm_underline "DEBUG (level $DEBUG): see files in $TEMPDIR" - tmln_out - else - [[ -d "$TEMPDIR" ]] && rm -rf "$TEMPDIR"; - fi - outln - # No shorthand expression to avoid errors when $CMDLINE_PARSED haven't been filled yet. - if [[ $CMDLINE_PARSED == true ]]; then - "$SECTION_FOOTER_NEEDED" && fileout_section_footer true - html_footer - fileout_footer - fi - # debugging off, see above - grep -q xtrace <<< "$SHELLOPTS" && ! "$DEBUG_ALLINONE" && exec 2>&42 42>&- -} - -child_error() { - cleanup - exit $ERR_CHILD -} - - -# Program terminates prematurely, with error code -# arg1: string to print / to write to file -# arg2: global error code, see ERR_* above -# arg3: an optional hint (string) -# -fatal() { - outln - prln_magenta "Fatal error: $1" >&2 - [[ -n "$LOGFILE" ]] && prln_magenta "Fatal error: $1" >>$LOGFILE - if [[ -n "$3" ]]; then - outln "$3" >&2 - [[ -n "$LOGFILE" ]] && outln "$3" >>$LOGFILE - fi - # Make sure we don't try to write into files when not created yet. - # No shorthand expression to avoid errors when $CMDLINE_PARSED haven't been filled yet. - [[ $CMDLINE_PARSED == true ]] && fileout "scanProblem" "FATAL" "$1" - exit $2 -} - -# This OTOH doesn't exit but puts a fatal error to the screen but continues with the next -# IP/hostname. It should only be used if a single IP/Hostname in a scan is not reachable. -# arg1: string to print / to write to file -# -ip_fatal() { - outln - prln_magenta "Fatal error: $1, proceeding with next IP (if any)" >&2 - [[ -n "$LOGFILE" ]] && prln_magenta "Fatal error: $1, proceeding with next IP (if any)" >>$LOGFILE - outln - fileout "scanProblem" "FATAL" "$1, proceeding with next IP (if any)" - return 0 -} - -# This generic function outputs an error onto the screen and handles logging. -# arg1: string to print / to write to file, arg2 (optional): additional hint to write -# -generic_nonfatal() { - prln_magenta "$1" >&2 - [[ -n $2 ]] && outln "$2" - [[ -n "$LOGFILE" ]] && prln_magenta "$1" >>$LOGFILE && [[ -n $2 ]] && outln "$2" >>$LOGFILE - outln - fileout "scanProblem" "WARN" "$1" - return 0 -} - -initialize_engine(){ - # for now only GOST engine - grep -q '^# testssl config file' "$OPENSSL_CONF" 2>/dev/null && \ - return 0 # We have been here already - if "$NO_ENGINE"; then - # Avoid potential conflicts also -- manual hook, see #1117 - export OPENSSL_CONF='' - return 1 - elif $OPENSSL engine gost -v 2>&1 | grep -Eq 'invalid command|no such engine'; then - outln - pr_warning "No engine or GOST support via engine with your $OPENSSL"; outln - fileout_insert_warning "engine_problem" "WARN" "No engine or GOST support via engine with your $OPENSSL" - export OPENSSL_CONF='' - return 1 - elif ! $OPENSSL engine gost -vvvv -t -c 2>/dev/null >/dev/null; then - # check for openssl 1.1.1 config -- not this may not be reliable. We only use this - # to suppress the warning (confuses users), see #1119 - # https://github.com/openssl/openssl/commit/b524b808a1d1ba204dbdcbb42de4e3bddb3472ac - if ! grep -q 'using the .include directive' /etc/ssl/openssl.cnf; then - outln - pr_warning "No engine or GOST support via engine with your $OPENSSL"; outln - fi - fileout_insert_warning "engine_problem" "WARN" "No engine or GOST support via engine with your $OPENSSL" - # Avoid clashes of OpenSSL 1.1.1 config file with our openssl 1.0.2. This is for Debian 10 - export OPENSSL_CONF='' - return 1 - else - # we have engine support. But we want to check whether an external OPENSSL_CONF was supplied. - # $TESTSSL_INSTALL_DIR/etc/openssl.cnf is an internal presetting, see #1982 - if [[ -n "$OPENSSL_CONF" ]] && [[ "$OPENSSL_CONF" != "$TESTSSL_INSTALL_DIR/etc/openssl.cnf" ]]; then - prln_warning "For now I am providing the config file to have GOST support" - else - OPENSSL_CONF=$TEMPDIR/gost.conf - # see https://www.mail-archive.com/openssl-users@openssl.org/msg65395.html - cat >$OPENSSL_CONF << EOF -# testssl config file for openssl - -openssl_conf = openssl_def - -[ openssl_def ] -engines = engine_section - -[ engine_section ] -gost = gost_section - -[ gost_section ] -engine_id = gost -default_algorithms = ALL -CRYPT_PARAMS = id-Gost28147-89-CryptoPro-A-ParamSet - -EOF - [[ $? -ne 0 ]] && exit $ERR_OSSLBIN - export OPENSSL_CONF - fi - fi - return 0 -} - -# arg1: text to display before "-->" -# arg2: arg needed to accept to continue -ignore_no_or_lame() { - local a - - [[ "$WARNINGS" == off ]] && return 0 - [[ "$WARNINGS" == batch ]] && return 1 - tm_warning "$1 --> " - read a - if [[ "$2" == "$(toupper "$2")" ]]; then - # all uppercase requested - if [[ "$a" == "$2" ]]; then - return 0 - else - return 1 - fi - elif [[ "$2" == "$(tolower "$a")" ]]; then - # we normalize the word to continue - return 0 - else - return 1 - fi -} - -# arg1: URI -parse_hn_port() { - local tmp_port - local node_tmp="" - - NODE="$1" - NODE="${NODE/https\:\/\//}" # strip "https" - NODE="${NODE%%/*}" # strip trailing urlpath - if grep -q ':$' <<< "$NODE"; then - if grep -wq http <<< "$NODE"; then - fatal "\"http\" is not what you meant probably" $ERR_CMDLINE - else - fatal "\"$1\" is not a valid URI" $ERR_CMDLINE - fi - fi - # Was an IPv6 address supplied like [AA:BB:CC::]:port ? - if grep -q ']' <<< "$NODE"; then - tmp_port=$(printf "$NODE" | sed 's/\[.*\]//' | sed 's/://') - # determine v6 port, supposed it was supplied additionally - if [[ -n "$tmp_port" ]]; then - PORT=$tmp_port - NODE=$(sed "s/:$PORT//" <<< "$NODE") - fi - NODE=$(sed -e 's/\[//' -e 's/\]//' <<< "$NODE") - else - # determine v4 port, supposed it was supplied additionally - grep -q ':' <<< "$NODE" && \ - PORT=$(sed 's/^.*\://' <<< "$NODE") && NODE=$(sed 's/\:.*$//' <<< "$NODE") - fi - NODE="${NODE%%.}" # strip trailing "." if supplied - - # We check for non-ASCII chars now. If there are some we'll try to convert it if IDN/IDN2 is installed - # If not, we'll continue. Hoping later that dig can use it. If not the error handler will tell - # Honestly we don't care whether it's IDN2008 or IDN2003 or Emoji domains as long as it works. - # So we try to resolve anything supplied. If it can't our resolver error handler takes care - if [[ "$NODE" == *[![:ascii:]]* ]]; then - if ! "$HAS_IDN2" && ! "$HAS_IDN"; then - prln_warning " URI contains non-ASCII characters and libidn/libidn2 not available." - outln " Trying to feed the resolver without converted \"$NODE\" ...\n" - #ToDo: fileout is missing - node_tmp="$NODE" - elif "$HAS_IDN2"; then - node_tmp="$(idn2 "$NODE" 2>/dev/null)" - fi - if "$HAS_IDN" && [[ -z "$node_tmp" ]]; then - node_tmp="$(idn "$NODE" 2>/dev/null)" - fi - if [[ -z "$node_tmp" ]]; then - prln_warning " URI contains non-ASCII characters and IDN conversion failed." - outln " Trying to feed the resolver without converted \"$NODE\" ...\n" - #ToDo: fileout is missing - node_tmp="$NODE" - fi - NODE="$node_tmp" - fi - - debugme echo $NODE:$PORT - SNI="-servername $NODE" - URL_PATH=$(sed 's/https:\/\///' <<< "$1" | sed 's/'"${NODE}"'//' | sed 's/.*'"${PORT}"'//') # remove protocol and node part and port - URL_PATH=$(sed 's/\/\//\//g' <<< "$URL_PATH") # we rather want // -> / - URL_PATH=${URL_PATH%%.} # strip trailing "." so that it is not interpreted as URL - [[ -z "$URL_PATH" ]] && URL_PATH="/" - debugme echo "URL_PATH: $URL_PATH" - return 0 # NODE, URL_PATH, PORT is set now -} - - -# args: string containing ip addresses -filter_ip6_address() { - local a - - for a in "$@"; do - if ! is_ipv6addr "$a"; then - continue - fi - if "$HAS_SED_E"; then - sed -E 's/^abcdeABCDEFf0123456789:]//g' <<< "$a" | sed -e '/^$/d' -e '/^;;/d' - else - sed -r 's/[^abcdefABCDEF0123456789:]//g' <<< "$a" | sed -e '/^$/d' -e '/^;;/d' - fi - done -} - -filter_ip4_address() { - local a - - for a in "$@"; do - if ! is_ipv4addr "$a"; then - continue - fi - if "$HAS_SED_E"; then - sed -E 's/[^[:digit:].]//g' <<< "$a" | sed -e '/^$/d' - else - sed -r 's/[^[:digit:].]//g' <<< "$a" | sed -e '/^$/d' - fi - done -} - -# For security testing sometimes we have local entries. Getent is BS under Linux for localhost: No network, no resolution -# arg1 is the entry we want to look up in the host file -get_local_aaaa() { - local ip6="" - local etchosts="/etc/hosts /c/Windows/System32/drivers/etc/hosts" - - [[ -z "$1" ]] && echo "" && return 1 - # Also multiple records should work fine - ip6=$(grep -wih "$1" $etchosts 2>/dev/null | grep ':' | grep -Ev '^#|\.local' | grep -Ei "[[:space:]]$1" | awk '{ print $1 }') - if is_ipv6addr "$ip6"; then - echo "$ip6" - else - echo "" - fi -} -get_local_a() { - local ip4="" - local etchosts="/etc/hosts /c/Windows/System32/drivers/etc/hosts" - - ip4=$(grep -wih "$1" $etchosts 2>/dev/null | grep -Ev ':|^#|\.local' | grep -Ei "[[:space:]]$1" | awk '{ print $1 }') - if is_ipv4addr "$ip4"; then - echo "$ip4" - else - echo "" - fi -} - -# Does a hard exit if no lookup binary is provided -# Checks for IDN capabilities also -# -check_resolver_bins() { - local saved_openssl_conf="$OPENSSL_CONF" - - OPENSSL_CONF="" # see https://github.com/drwetter/testssl.sh/issues/134 - type -p dig &> /dev/null && HAS_DIG=true - type -p host &> /dev/null && HAS_HOST=true - type -p drill &> /dev/null && HAS_DRILL=true - type -p nslookup &> /dev/null && HAS_NSLOOKUP=true - type -p avahi-resolve &>/dev/null && HAS_AVAHIRESOLVE=true - type -p idn &>/dev/null && HAS_IDN=true - type -p idn2 &>/dev/null && HAS_IDN2=true - - if ! "$HAS_DIG" && ! "$HAS_HOST" && ! "$HAS_DRILL" && ! "$HAS_NSLOOKUP"; then - fatal "Neither \"dig\", \"host\", \"drill\" or \"nslookup\" is present" $ERR_DNSBIN - fi - if "$HAS_DIG"; then - # Old dig versions don't have an option to ignore $HOME/.digrc - if ! dig -h | grep -qE '\-r.*~/.digrc'; then - HAS_DIG_R=false - DIG_R="" - fi - if dig -h | grep -Eq idnout; then - HAS_DIG_NOIDNOUT=true - fi - fi - OPENSSL_CONF="$saved_openssl_conf" # see https://github.com/drwetter/testssl.sh/issues/134 - return 0 -} - -# arg1: a host name. Returned will be 0-n IPv4 addresses -# watch out: $1 can also be a cname! --> all checked -get_a_record() { - local ip4="" - local saved_openssl_conf="$OPENSSL_CONF" - local noidnout="" - - "$HAS_DIG_NOIDNOUT" && noidnout="+noidnout" - [[ "$NODNS" == none ]] && return 0 # if no DNS lookup was instructed, leave here - if [[ "$1" == localhost ]]; then - # This is a bit ugly but prevents from doing DNS lookups which could fail - echo 127.0.0.1 - return 0 - fi - if is_ipv4addr "$1"; then - # This saves walking through this. Also it avoids hangs e.g. if you run docker locally without reachabale DNS - echo $1 - return 0 - fi - OPENSSL_CONF="" # see https://github.com/drwetter/testssl.sh/issues/134 - if [[ "$NODE" == *.local ]]; then - if "$HAS_AVAHIRESOLVE"; then - ip4=$(filter_ip4_address $(avahi-resolve -4 -n "$1" 2>/dev/null | awk '{ print $2 }')) - elif "$HAS_DIG"; then - ip4=$(filter_ip4_address $(dig $DIG_R @224.0.0.251 -p 5353 +short -t a +notcp "$1" 2>/dev/null | sed '/^;;/d')) - else - fatal "Local hostname given but no 'avahi-resolve' or 'dig' available." $ERR_DNSBIN - fi - fi - if [[ -z "$ip4" ]] && "$HAS_HOST"; then - ip4=$(filter_ip4_address $(host -t a "$1" 2>/dev/null | awk '/address/ { print $NF }')) - fi - if [[ -z "$ip4" ]] && "$HAS_DRILL"; then - ip4=$(filter_ip4_address $(drill a "$1" | awk '/ANSWER SECTION/,/AUTHORITY SECTION/ { print $NF }' | awk '/^[0-9]/')) - fi - if [[ -z "$ip4" ]] && "$HAS_DIG"; then - ip4=$(filter_ip4_address $(dig $DIG_R +short +timeout=2 +tries=2 $noidnout -t a "$1" 2>/dev/null | awk '/^[0-9]/ { print $1 }')) - fi - if [[ -z "$ip4" ]] && "$HAS_NSLOOKUP"; then - ip4=$(filter_ip4_address $(strip_lf "$(nslookup -querytype=a "$1" 2>/dev/null | awk '/^Name/ { getline; print $NF }')")) - fi - OPENSSL_CONF="$saved_openssl_conf" # see https://github.com/drwetter/testssl.sh/issues/134 - safe_echo "$ip4" -} - -# arg1: a host name. Returned will be 0-n IPv6 addresses -# watch out: $1 can also be a cname! --> all checked -get_aaaa_record() { - local ip6="" - local saved_openssl_conf="$OPENSSL_CONF" - local noidnout="" - - "$HAS_DIG_NOIDNOUT" && noidnout="+noidnout" - [[ "$NODNS" == none ]] && return 0 # if no DNS lookup was instructed, leave here - OPENSSL_CONF="" # see https://github.com/drwetter/testssl.sh/issues/134 - if is_ipv6addr "$1"; then - # This saves walking through this. Also it avoids hangs e.g. if you run docker locally without reachabale DNS - echo "$1" - return 0 - elif is_ipv4addr "$1"; then - # we need also this here as get_aaaa_record is always called after get_a_record and we want to handle this at a low level - return 0 - fi - if [[ -z "$ip6" ]]; then - if [[ "$NODE" == *.local ]]; then - if "$HAS_AVAHIRESOLVE"; then - ip6=$(filter_ip6_address $(avahi-resolve -6 -n "$1" 2>/dev/null | awk '{ print $2 }')) - elif "$HAS_DIG"; then - ip6=$(filter_ip6_address $(dig $DIG_R @ff02::fb -p 5353 -t aaaa +short +notcp "$NODE")) - else - fatal "Local hostname given but no 'avahi-resolve' or 'dig' available." $ERR_DNSBIN - fi - elif "$HAS_HOST"; then - ip6=$(filter_ip6_address $(host -t aaaa "$1" | awk '/address/ { print $NF }')) - elif "$HAS_DRILL"; then - ip6=$(filter_ip6_address $(drill aaaa "$1" | awk '/ANSWER SECTION/,/AUTHORITY SECTION/ { print $NF }' | awk '/^[0-9]/')) - elif "$HAS_DIG"; then - ip6=$(filter_ip6_address $(dig $DIG_R +short +timeout=2 +tries=2 $noidnout -t aaaa "$1" 2>/dev/null | awk '/^[0-9]/ { print $1 }')) - elif "$HAS_NSLOOKUP"; then - ip6=$(filter_ip6_address $(strip_lf "$(nslookup -type=aaaa "$1" 2>/dev/null | awk '/'"^${a}"'.*AAAA/ { print $NF }')")) - fi - fi - OPENSSL_CONF="$saved_openssl_conf" # see https://github.com/drwetter/testssl.sh/issues/134 - safe_echo "$ip6" -} - -# RFC6844: DNS Certification Authority Authorization (CAA) Resource Record -# arg1: domain to check for -get_caa_rr_record() { - local raw_caa="" - local -i len_caa_property - local caa_property_name - local caa_property_value - local saved_openssl_conf="$OPENSSL_CONF" - local all_caa="" - local noidnout="" - - "$HAS_DIG_NOIDNOUT" && noidnout="+noidnout" - - [[ -n "$NODNS" ]] && return 0 # if minimum DNS lookup was instructed, leave here - # if there's a type257 record there are two output formats here, mostly depending on age of distribution - # roughly that's the difference between text and binary format - # 1) 'google.com has CAA record 0 issue "symantec.com"' - # 2) 'google.com has TYPE257 record \# 19 0005697373756573796D616E7465632E636F6D' - # for dig +short the output always starts with '0 issue [..]' or '\# 19 [..]' so we normalize thereto to keep caa_flag, caa_property - # caa_property then has key/value pairs, see https://tools.ietf.org/html/rfc6844#section-3 - OPENSSL_CONF="" - if "$HAS_DRILL"; then - raw_caa="$(drill $1 type257 | awk '/'"^${1}"'.*CAA/ { print $5,$6,$7 }')" - elif "$HAS_HOST"; then - raw_caa="$(host -t type257 $1)" - if grep -Ewvq "has no CAA|has no TYPE257" <<< "$raw_caa"; then - raw_caa="$(sed -e 's/^.*has CAA record //' -e 's/^.*has TYPE257 record //' <<< "$raw_caa")" - fi - elif "$HAS_DIG"; then - raw_caa="$(dig $DIG_R +short +timeout=3 +tries=3 $noidnout type257 "$1" 2>/dev/null | awk '{ print $1" "$2" "$3 }')" - # empty if no CAA record - elif "$HAS_NSLOOKUP"; then - raw_caa="$(strip_lf "$(nslookup -type=type257 $1 | grep -w rdata_257)")" - if [[ -n "$raw_caa" ]]; then - raw_caa="$(sed 's/^.*rdata_257 = //' <<< "$raw_caa")" - fi - else - return 1 - # No dig, drill, host, or nslookup --> complaint was elsewhere already - fi - OPENSSL_CONF="$saved_openssl_conf" # see https://github.com/drwetter/testssl.sh/issues/134 - debugme echo $raw_caa - - if [[ "$raw_caa" =~ \#\ [0-9][0-9] ]]; then - # for posteo we get this binary format returned e.g. for old dig versions: - # \# 19 0005697373756567656F74727573742E636F6D - # \# 23 0009697373756577696C6467656F74727573742E636F6D - # \# 34 0005696F6465666D61696C746F3A686F73746D617374657240706F73 74656F2E6465 - # # len caaflag <more_see_below> @ p o s t e o . d e - while read hash len line ;do - if [[ "${line:0:2}" == "00" ]]; then # probably the caa flag, always 00, so we don't keep this - len_caa_property=$(printf "%0d" "$((10#${line:2:2}))") # get len and do type casting, for posteo we have 05 or 09 here as a string - len_caa_property=$((len_caa_property*2)) # =>word! Now get name from 4th and value from 4th+len position... - line="${line/ /}" # especially with iodefs there's a blank in the string which we just skip - caa_property_name="$(hex2ascii ${line:4:$len_caa_property})" - caa_property_value="$(hex2ascii "${line:$((4+len_caa_property)):100}")" - # echo "${caa_property_name}=${caa_property_value}" - all_caa+="${caa_property_name}=${caa_property_value}\n" - else - outln "please report unknown CAA RR $line with flag @ $NODE" - return 7 - fi - done <<< "$raw_caa" - sort <<< "$(safe_echo "$all_caa")" - return 0 - elif grep -q '"' <<< "$raw_caa"; then - raw_caa=${raw_caa//\"/} # strip all ". Now we should have flag, name, value - #caa_property_name="$(awk '{ print $2 }' <<< "$raw_caa")" - #caa_property_value="$(awk '{ print $3 }' <<< "$raw_caa")" - safe_echo "$(sort <<< "$(awk '{ print $2"="$3 }' <<< "$raw_caa")")" - return 0 - else - # no caa record - return 1 - fi - -# to do: -# 4: check whether $1 is a CNAME and take this - return 0 -} - -# arg1: domain -get_mx_record() { - local mxs"" - local saved_openssl_conf="$OPENSSL_CONF" - local noidnout="" - - "$HAS_DIG_NOIDNOUT" && noidnout="+noidnout" - OPENSSL_CONF="" # see https://github.com/drwetter/testssl.sh/issues/134 - # we need the last two columns here - if "$HAS_HOST"; then - mxs="$(host -t MX "$1" 2>/dev/null | awk '/is handled by/ { print $(NF-1), $NF }')" - elif "$HAS_DRILL"; then - mxs="$(drill mx $1 | awk '/IN[ \t]MX[ \t]+/ { print $(NF-1), $NF }')" - elif "$HAS_DIG"; then - mxs="$(dig $DIG_R +short $noidnout -t MX "$1" 2>/dev/null | awk '/^[0-9]/ { print $1" "$2 }')" - elif "$HAS_NSLOOKUP"; then - mxs="$(strip_lf "$(nslookup -type=MX "$1" 2>/dev/null | awk '/mail exchanger/ { print $(NF-1), $NF }')")" - else - # shouldn't reach this, as we checked in the top - fatal "No dig, host, drill or nslookup" $ERR_DNSBIN - fi - OPENSSL_CONF="$saved_openssl_conf" - echo "$mxs" -} - - -# set IPADDRs and IP46ADDRs -# -determine_ip_addresses() { - local ip4="" - local ip6="" - - ip4="$(get_a_record "$NODE")" - ip6="$(get_aaaa_record "$NODE")" - IP46ADDRs=$(newline_to_spaces "$ip4 $ip6") - - if [[ -n "$CMDLINE_IP" ]]; then - # command line has supplied an IP address or "one" - if [[ "$CMDLINE_IP" == one ]]; then - # use first IPv6 or IPv4 address - if "$HAS_IPv6" && [[ -n "$ip6" ]]; then - CMDLINE_IP="$(head -1 <<< "$ip6")" - else - CMDLINE_IP="$(head -1 <<< "$ip4")" - fi - fi - NODEIP="$CMDLINE_IP" - if is_ipv4addr "$NODEIP"; then - ip4="$NODEIP" - elif is_ipv6addr "$NODEIP"; then - ip6="$NODEIP" - else - fatal "couldn't identify supplied \"CMDLINE_IP\"" $ERR_DNSLOOKUP - fi - elif is_ipv4addr "$NODE"; then - ip4="$NODE" # only an IPv4 address was supplied as an argument, no hostname - SNI="" # override Server Name Indication as we test the IP only - else - ip4=$(get_local_a "$NODE") # is there a local host entry? - if [[ -z "$ip4" ]]; then # empty: no (LOCAL_A is predefined as false) - ip4=$(get_a_record "$NODE") - else - LOCAL_A=true # we have the ip4 from local host entry and need to signal this to testssl - fi - # same now for ipv6 - ip6=$(get_local_aaaa "$NODE") - if [[ -z "$ip6" ]]; then - ip6=$(get_aaaa_record "$NODE") - else - LOCAL_AAAA=true # we have a local ipv6 entry and need to signal this to testssl - fi - fi - - # IPv6 only address - if [[ -z "$ip4" ]]; then - if "$HAS_IPv6"; then - IPADDRs=$(newline_to_spaces "$ip6") - IP46ADDRs="$IPADDRs" # IP46ADDRs are the ones to display, IPADDRs the ones to test - fi - else - if "$HAS_IPv6" && [[ -n "$ip6" ]]; then - if is_ipv6addr "$CMDLINE_IP"; then - IPADDRs=$(newline_to_spaces "$ip6") - else - IPADDRs=$(newline_to_spaces "$ip4 $ip6") - fi - else - IPADDRs=$(newline_to_spaces "$ip4") - fi - fi - if [[ -z "$IPADDRs" ]]; then - if [[ -n "$ip6" ]]; then - fatal "Only IPv6 address(es) for \"$NODE\" available, maybe add \"-6\" to $0" $ERR_DNSLOOKUP - else - fatal "No IPv4/IPv6 address(es) for \"$NODE\" available" $ERR_DNSLOOKUP - fi - fi - return 0 # IPADDR and IP46ADDR is set now -} - -determine_rdns() { - local saved_openssl_conf="$OPENSSL_CONF" - local nodeip="" rdns="" line="" - - [[ -n "$NODNS" ]] && rDNS="(instructed to minimize DNS queries)" && return 0 # PTR records were not asked for - local nodeip="$(tr -d '[]' <<< $NODEIP)" # for DNS we do not need the square brackets of IPv6 addresses - OPENSSL_CONF="" # see https://github.com/drwetter/testssl.sh/issues/134 - if [[ "$NODE" == *.local ]]; then - if "$HAS_AVAHIRESOLVE"; then - rDNS=$(avahi-resolve -a $nodeip 2>/dev/null | awk '{ print $2 }') - elif "$HAS_DIG"; then - rDNS=$(dig $DIG_R -x $nodeip @224.0.0.251 -p 5353 +notcp +noall +answer +short | awk '{ print $1 }') - fi - elif "$HAS_HOST"; then - rDNS=$(host -t PTR $nodeip 2>/dev/null | awk '/pointer/ { print $NF }') - elif "$HAS_DRILL"; then - rDNS=$(drill -x ptr $nodeip 2>/dev/null | awk '/ANSWER SECTION/ { getline; print $NF }') - elif "$HAS_DIG"; then - # 1+2 should suffice. It's a compromise for if e.g. network is down but we have a docker/localhost server - rDNS=$(dig $DIG_R -x $nodeip +timeout=1 +tries=2 +noall +answer +short | awk '{ print $1 }') # +short returns also CNAME, e.g. openssl.org - elif "$HAS_NSLOOKUP"; then - rDNS=$(strip_lf "$(nslookup -type=PTR $nodeip 2>/dev/null | grep -v 'canonical name =' | grep 'name = ' | awk '{ print $NF }' | sed 's/\.$//')") - fi - OPENSSL_CONF="$saved_openssl_conf" # see https://github.com/drwetter/testssl.sh/issues/134 - # First, rDNS can contain > 1 line due to multiple PTR DNS records, though this is not recommended. - # So we use a loop to check for each FQDN returned. There we remove chars which under weird - # circumstances (see #1506) can show up here. The blacklist is taken from RFC 1912 ("Allowable characters in a - # label for a host name are only ASCII, letters, digits, and the `-' character") - while read -r line; do - line="$(tr -dc '[a-zA-Z0-9-_.]' <<< "$line")" - [[ -z "$rdns" ]] && rdns="$line" || rdns="$rdns $line" - done <<< "$rDNS" - rDNS="$rdns" - [[ -z "$rDNS" ]] && rDNS="--" - return 0 -} - -# We need to get the IP address of the proxy so we can use it in fd_socket -# -check_proxy() { - if [[ -n "$PROXY" ]]; then - if ! "$HAS_PROXY"; then - fatal "Your $OPENSSL is too old to support the \"-proxy\" option" $ERR_OSSLBIN - fi - if [[ "$PROXY" == auto ]]; then - # Get $ENV https_proxy is the one we care about for connects - PROXY="${https_proxy#*\/\/}" - # Fallback: - [[ -z "$PROXY" ]] && PROXY="${http_proxy#*\/\/}" - [[ -z "$PROXY" ]] && fatal "you specified \"--proxy=auto\" but \"\$http(s)_proxy\" is empty" $ERR_CMDLINE - fi - # strip off http/https part if supplied: - PROXY="${PROXY/http\:\/\//}" - PROXY="${PROXY/https\:\/\//}" # this shouldn't be needed - PROXYNODE="${PROXY%:*}" - PROXYPORT="${PROXY#*:}" - is_number "$PROXYPORT" || fatal "Proxy port cannot be determined from \"$PROXY\"" $ERR_CMDLINE - - #if is_ipv4addr "$PROXYNODE" || is_ipv6addr "$PROXYNODE" ; then - # IPv6 via openssl -proxy: that doesn't work. Sockets does -#FIXME: finish this with LibreSSL which supports an IPv6 proxy - if is_ipv4addr "$PROXYNODE"; then - PROXYIP="$PROXYNODE" - else - PROXYIP="$(get_a_record "$PROXYNODE" 2>/dev/null | grep -v alias | sed 's/^.*address //')" - [[ -z "$PROXYIP" ]] && fatal "Proxy IP cannot be determined from \"$PROXYNODE\"" $ERR_CMDLINE - fi - PROXY="-proxy $PROXYIP:$PROXYPORT" - fi -} - - -# this is only being called from determine_optimal_proto in order to check whether we have a server -# with client authentication, a server with no SSL session ID switched off -# -sclient_auth() { - [[ $1 -eq 0 ]] && return 0 # no client auth (CLIENT_AUTH=false is preset globally) - if [[ -n $(awk '/Master-Key: / { print $2 }' "$2") ]]; then # connect succeeded - if grep -q '^<<< .*CertificateRequest' "$2"; then # CertificateRequest message in -msg - CLIENT_AUTH=true - return 0 - fi - if [[ -z $(awk '/Session-ID: / { print $2 }' "$2") ]]; then # probably no SSL session - if [[ 2 -eq $(grep -c CERTIFICATE "$2") ]]; then # do another sanity check to be sure - CLIENT_AUTH=false - NO_SSL_SESSIONID=true # NO_SSL_SESSIONID is preset globally to false for all other cases - return 0 - fi - fi - fi - # what's left now is: master key empty, handshake returned not successful, session ID empty --> not successful - return 1 -} - -# Determine the best parameters to use with tls_sockets(): -# For TLSv1.3, determine what extension number to use for the key_share extension. -# For TLSv1.2, determine what cipher list to send, since there are more than 128 -# TLSv1.2 ciphers and some servers fail if the ClientHello contains too many ciphers. -# If both TLSv1.3 and TLSv1.2 ClientHello messages result in failed connection attempts, -# then try to determine whether: -# (1) This is an SSLv2-only server -# (2) This server supports some protocol in SSLv3 - TLSv1.1, but cannot handle version negotiation. -# (3) This is not a TLS/SSL enabled server. -# This information can be used by determine_optimal_proto() to help distinguish between a server -# that is not TLS/SSL enabled and one that is not compatible with the version of OpenSSL being used. -determine_optimal_sockets_params() { - local -i ret1=1 ret2=1 - local i proto cipher_offered - local all_failed=true - - # If a STARTTLS protocol is specified and $SSL_NATIVE is true, then skip this test, since - # $SSL_NATIVE may have been set to true as a result of tls_sockets() not supporting the STARTTLS - # protocol. - [[ -n "$STARTTLS_PROTOCOL" ]] && "$SSL_NATIVE" && return 0 - - # NOTE: The following code is only needed as long as draft versions of TLSv1.3 prior to draft 23 - # are supported. It is used to determine whether a draft 23 or pre-draft 23 ClientHello should be - # sent. - KEY_SHARE_EXTN_NR="33" - tls_sockets "04" "$TLS13_CIPHER" "" "00, 2b, 00, 0f, 0e, 03,04, 7f,1c, 7f,1b, 7f,1a, 7f,19, 7f,18, 7f,17" - if [[ $? -eq 0 ]]; then - add_tls_offered tls1_3 yes - all_failed=false - else - KEY_SHARE_EXTN_NR="28" - tls_sockets "04" "$TLS13_CIPHER" "" "00, 2b, 00, 0b, 0a, 7f,16, 7f,15, 7f,14, 7f,13, 7f,12" - if [[ $? -eq 0 ]]; then - add_tls_offered tls1_3 yes - all_failed=false - else - add_tls_offered tls1_3 no - KEY_SHARE_EXTN_NR="33" - fi - fi - if ! "$all_failed"; then - # Determine which version of TLS 1.3 was offered. For drafts 18-21 the - # version appears in the ProtocolVersion field of the ServerHello. For - # drafts 22-28 and the final TLS 1.3 the ProtocolVersion field contains - # 0303 and the actual version appears in the supported_versions extension. - if [[ "${TLS_SERVER_HELLO:8:3}" == 7F1 ]]; then - add_tls_offered tls1_3_draft$(hex2dec "${TLS_SERVER_HELLO:10:2}") yes - elif [[ "$TLS_SERVER_HELLO" =~ 002B00020304 ]]; then - add_tls_offered tls1_3_rfc8446 yes - elif [[ "$TLS_SERVER_HELLO" =~ 002B00027F1[2-9A-C] ]]; then - add_tls_offered tls1_3_draft$(hex2dec "${BASH_REMATCH:10:2}") yes - fi - fi - - # Need to determine which set of ciphers is best to use with - # a TLSv1.2 ClientHello since there are far more than 128 ciphers - # that can be used. - tls_sockets "03" "$TLS12_CIPHER" - ret1=$? - if [[ $ret1 -eq 0 ]] || [[ $ret1 -eq 2 ]]; then - case $DETECTED_TLS_VERSION in - 0303) add_tls_offered tls1_2 yes ;; - 0302) add_tls_offered tls1_1 yes ;; - 0301) add_tls_offered tls1 yes ;; - 0300) add_tls_offered ssl3 yes ;; - esac - all_failed=false - fi - - # Try again with a different, less common, set of cipher suites - # see #807 and #806. If using these cipher suites results in a - # successful connection, then change $TLS12_CIPHER to these - # cipher suites so that later tests will use this list of cipher - # suites. - if [[ $ret1 -ne 0 ]]; then - tls_sockets "03" "$TLS12_CIPHER_2ND_TRY" - ret2=$? - if [[ $ret2 -eq 0 ]]; then - add_tls_offered tls1_2 yes - TLS12_CIPHER="$TLS12_CIPHER_2ND_TRY" - all_failed=false - else - add_tls_offered tls1_2 no - fi - if [[ $ret2 -eq 2 ]]; then - case $DETECTED_TLS_VERSION in - 0302) add_tls_offered tls1_1 yes ;; - 0301) add_tls_offered tls1 yes ;; - 0300) add_tls_offered ssl3 yes ;; - esac - [[ $ret1 -ne 2 ]] && TLS12_CIPHER="$TLS12_CIPHER_2ND_TRY" - all_failed=false - fi - fi - if [[ $ret1 -eq 0 ]] || [[ $ret2 -eq 0 ]]; then - cipher_offered="$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")" - if [[ "$cipher_offered" == TLS_* ]] || [[ "$cipher_offered" == SSL_* ]]; then - cipher_offered="$(rfc2hexcode "$cipher_offered")" - else - cipher_offered="$(openssl2hexcode "$cipher_offered")" - fi - [[ ${#cipher_offered} -eq 9 ]] && TLS12_CIPHER_OFFERED="${cipher_offered:2:2},${cipher_offered:7:2}" - fi - - if "$all_failed"; then - # One of the following must be true: - # * This is not a TLS/SSL enabled server. - # * The server only supports SSLv2 - # * The server does not handle version negotiation correctly. - for proto in 01 00 02; do - tls_sockets "$proto" "$TLS_CIPHER" "" "" "true" - ret1=$? - if [[ $ret1 -ne 0 ]]; then - case $proto in - 02) add_tls_offered tls1_1 no ;; - 01) add_tls_offered tls1 no ;; - 00) add_tls_offered ssl3 no ;; - esac - fi - if [[ $ret1 -eq 0 ]] || [[ $ret1 -eq 2 ]]; then - case $DETECTED_TLS_VERSION in - 0302) add_tls_offered tls1_1 yes ;; - 0301) add_tls_offered tls1 yes ;; - 0300) add_tls_offered ssl3 yes ;; - esac - OPTIMAL_SOCKETS_PROTO="$proto" - all_failed=false - break - fi - done - fi - if "$all_failed"; then - sslv2_sockets - [[ $? -eq 3 ]] && all_failed=false && add_tls_offered ssl2 yes - fi - ALL_FAILED_SOCKETS="$all_failed" - return 0 -} - - -# This function determines (STARTTLS_)OPTIMAL_PROTO. It is basically a workaround function as under certain -# circumstances a ClientHello without specifying a protocol will fail. -# Circumstances observed so far: 1.) IIS 6 and openssl 1.0.2 as opposed to 1.0.1 2.) starttls + dovecot imap. -# Independent on the server side it seems reasonable to to know upfront which protocol always works -# -# arg1: if empty: no STARTTLS, else: STARTTLS protocol -# The first try in the loop is empty as we prefer not to specify always a protocol if we can get along w/o it -# -determine_optimal_proto() { - local all_failed=true - local tmp="" - local proto optimal_proto - - "$do_tls_sockets" && return 0 - - >$ERRFILE - if [[ -n "$1" ]]; then - # STARTTLS workaround needed see https://github.com/drwetter/testssl.sh/issues/188 -- kind of odd - for STARTTLS_OPTIMAL_PROTO in -tls1_2 -tls1 -ssl3 -tls1_1 -tls1_3 -ssl2; do - case $STARTTLS_OPTIMAL_PROTO in - -tls1_3) "$HAS_TLS13" || continue ;; - -ssl3) "$HAS_SSL3" || continue ;; - -ssl2) "$HAS_SSL2" || continue ;; - *) ;; - esac - $OPENSSL s_client $(s_client_options "$STARTTLS_OPTIMAL_PROTO $BUGS -connect "$NODEIP:$PORT" $PROXY -msg $STARTTLS $SNI") </dev/null >$TMPFILE 2>>$ERRFILE - if sclient_auth $? $TMPFILE; then - all_failed=false - add_tls_offered "${STARTTLS_OPTIMAL_PROTO/-/}" yes - break - fi - done - "$all_failed" && STARTTLS_OPTIMAL_PROTO="" - optimal_proto="$STARTTLS_OPTIMAL_PROTO" - debugme echo "STARTTLS_OPTIMAL_PROTO: $STARTTLS_OPTIMAL_PROTO" - else - # No STARTTLS - for proto in '' -tls1_2 -tls1 -tls1_3 -ssl3 -tls1_1 -ssl2; do - case $proto in - -tls1_3) "$HAS_TLS13" || continue ;; - -ssl3) "$HAS_SSL3" || continue ;; - -ssl2) "$HAS_SSL2" || continue ;; - *) ;; - esac - $OPENSSL s_client $(s_client_options "$proto $BUGS -connect "$NODEIP:$PORT" -msg $PROXY $SNI") </dev/null >$TMPFILE 2>>$ERRFILE - if sclient_auth $? $TMPFILE; then - # we use the successful handshake at least to get one valid protocol supported -- it saves us time later - if [[ -z "$proto" ]]; then - # convert to openssl terminology - tmp=$(get_protocol $TMPFILE) - tmp=${tmp/\./_} - tmp=${tmp/v/} - tmp="$(tolower $tmp)" - add_tls_offered "${tmp}" yes - debugme echo "one proto determined: $tmp" - OPTIMAL_PROTO="" - else - add_tls_offered "${proto/-/}" yes - OPTIMAL_PROTO="$proto" - fi - all_failed=false - break - fi - done - "$all_failed" && OPTIMAL_PROTO="" - optimal_proto="$OPTIMAL_PROTO" - - debugme echo "OPTIMAL_PROTO: $OPTIMAL_PROTO" - fi - [[ "$optimal_proto" != -ssl2 ]] && ! "$all_failed" && grep -q '^Server Temp Key' $TMPFILE && HAS_DH_BITS=true # FIX #190 - if [[ "$(has_server_protocol "tls1_3")" -eq 0 ]] && [[ "$(has_server_protocol "tls1_2")" -ne 0 ]] && - [[ "$(has_server_protocol "tls1_1")" -ne 0 ]] && [[ "$(has_server_protocol "tls1")" -ne 0 ]] && - [[ "$(has_server_protocol "ssl3")" -ne 0 ]]; then - TLS13_ONLY=true - fi - - if [[ "$optimal_proto" == -ssl2 ]]; then - prln_magenta "$NODEIP:$PORT appears to only support SSLv2." - ignore_no_or_lame " Type \"yes\" to proceed and accept false negatives or positives" "yes" - [[ $? -ne 0 ]] && exit $ERR_CLUELESS - elif "$all_failed" && ! "$ALL_FAILED_SOCKETS"; then - if ! "$HAS_TLS13" && "$TLS13_ONLY"; then - pr_magenta " $NODE:$PORT appears to support TLS 1.3 ONLY. You better use --openssl=<path_to_openssl_supporting_TLS_1.3>" - if ! "$OSSL_SHORTCUT" || [[ ! -x /usr/bin/openssl ]] || /usr/bin/openssl s_client -tls1_3 -connect invalid. 2>&1 | grep -aiq "unknown option"; then - outln - ignore_no_or_lame " Type \"yes\" to proceed and accept all scan problems" "yes" - [[ $? -ne 0 ]] && exit $ERR_CLUELESS - MAX_OSSL_FAIL=10 - else - # dirty hack but an idea for the future to be implemented upfront: Now we know, we'll better off - # with the OS supplied openssl binary. We need to inittialize variables / arrays again though. - # And the service detection can't be made up for now - outln ", \n proceeding with /usr/bin/openssl" - OPENSSL=/usr/bin/openssl - find_openssl_binary - prepare_arrays - fi - elif ! "$HAS_SSL3" && [[ "$(has_server_protocol "ssl3")" -eq 0 ]] && [[ "$(has_server_protocol "tls1_3")" -ne 0 ]] && \ - [[ "$(has_server_protocol "tls1_2")" -ne 0 ]] && [[ "$(has_server_protocol "tls1_1")" -ne 0 ]] && - [[ "$(has_server_protocol "tls1")" -ne 0 ]]; then - prln_magenta " $NODE:$PORT appears to support SSLv3 ONLY. You better use --openssl=<path_to_openssl_supporting_SSL_3>" - ignore_no_or_lame " Type \"yes\" to proceed and accept all scan problems" "yes" - [[ $? -ne 0 ]] && exit $ERR_CLUELESS - MAX_OSSL_FAIL=10 - else - prln_bold " Your OpenSSL cannot connect to $NODEIP:$PORT" - ignore_no_or_lame " The results might look ok but they could be nonsense. Really proceed ? (\"yes\" to continue)" "yes" - [[ $? -ne 0 ]] && exit $ERR_CLUELESS - fi - elif "$all_failed"; then - outln - if "$HAS_IPv6"; then - pr_bold " Your $OPENSSL is not IPv6 aware, or $NODEIP:$PORT " - else - pr_bold " $NODEIP:$PORT " - fi - tmpfile_handle ${FUNCNAME[0]}.txt - prln_bold "doesn't seem to be a TLS/SSL enabled server"; - ignore_no_or_lame " The results might look ok but they could be nonsense. Really proceed ? (\"yes\" to continue)" "yes" - [[ $? -ne 0 ]] && exit $ERR_CLUELESS - elif ! "$all_failed" && "$ALL_FAILED_SOCKETS" && ! "$SSL_NATIVE"; then - # For some reason connecting with tls_sockets/sslv2_sockets didn't work, but connecting - # with $OPENSSL s_client did. - # FIXME: Should we include some sort of "please report" note here? - prln_magenta " Testing with $NODE:$PORT only worked using $OPENSSL." - prln_magenta " Test results may be somewhat better if the --ssl-native option is used." - ignore_no_or_lame " Type \"yes\" to proceed and accept false negatives or positives" "yes" - [[ $? -ne 0 ]] && exit $ERR_CLUELESS - fi - - tmpfile_handle ${FUNCNAME[0]}.txt - return 0 -} - - -# arg1 (optional): ftp smtp, lmtp, pop3, imap, xmpp, telnet, ldap, postgres, mysql, irc, nntp (maybe with trailing s) -# -determine_service() { - local ua - local protocol - local basicauth_header="" - - # Check if we can connect to $NODEIP:$PORT. Attention: This ALWAYS uses sockets. Thus timeouts for --ssl-=native do not apply - if ! fd_socket 5; then - if [[ -n "$PROXY" ]]; then - fatal "You're sure $PROXYNODE:$PROXYPORT allows tunneling here? Can't connect to \"$NODEIP:$PORT\"" $ERR_CONNECT - else - if "$MULTIPLE_CHECKS"; then - ip_fatal "Couldn't connect to $NODEIP:$PORT" - return 1 - else - fatal "Can't connect to \"$NODEIP:$PORT\"\nMake sure a firewall is not between you and your scanning target!" $ERR_CONNECT - fi - fi - fi - close_socket - - outln - if [[ -z "$1" ]]; then - # no STARTTLS. - determine_optimal_sockets_params - determine_optimal_proto - $SNEAKY && \ - ua="$UA_SNEAKY" || \ - ua="$UA_STD" - if [[ -n "$BASICAUTH" ]]; then - basicauth_header="Authorization: Basic $(safe_echo "$BASICAUTH" | $OPENSSL base64 2>/dev/null)\r\n" - fi - GET_REQ11="GET $URL_PATH HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $ua\r\n${basicauth_header}Accept-Encoding: identity\r\nAccept: text/*\r\nConnection: Close\r\n\r\n" - # returns always 0: - service_detection $OPTIMAL_PROTO - else # STARTTLS - if [[ "$1" == postgres ]]; then - protocol="postgres" - else - protocol=${1%s} # strip trailing 's' in ftp(s), smtp(s), pop3(s), etc - fi - - case "$protocol" in - ftp|smtp|lmtp|pop3|imap|xmpp|telnet|ldap|postgres|mysql|nntp) - STARTTLS="-starttls $protocol" - if [[ "$protocol" == xmpp ]]; then - if [[ -n "$XMPP_HOST" ]]; then - if ! "$HAS_XMPP"; then - fatal "Your $OPENSSL does not support the \"-xmpphost\" option" $ERR_OSSLBIN - fi - STARTTLS="$STARTTLS -xmpphost $XMPP_HOST" # small hack -- instead of changing calls all over the place - # see https://xmpp.org/rfcs/rfc3920.html - else - if is_ipv4addr "$NODE"; then - # XMPP needs a jabber domainname - if [[ -n "$rDNS" ]]; then - prln_warning " IP address doesn't work for XMPP, trying PTR record $rDNS" - # remove trailing . - NODE=${rDNS%%.} - else - fatal "No DNS supplied and no PTR record available which I can try for XMPP" $ERR_DNSLOOKUP - fi - fi - if "$HAS_XMPP"; then - # small hack -- instead of changing calls all over the place - STARTTLS="$STARTTLS -xmpphost $NODE" - else - # If the XMPP name cannot be provided using -xmpphost, - # then it needs to be provided to the -connect option - NODEIP="$NODE" - fi - fi - elif [[ "$protocol" == postgres ]]; then - # Check if openssl version supports postgres. - if ! "$HAS_POSTGRES"; then - fatal "Your $OPENSSL does not support the \"-starttls postgres\" option" $ERR_OSSLBIN - fi - elif [[ "$protocol" == mysql ]]; then - # Check if openssl version supports mysql. - if ! "$HAS_MYSQL"; then - fatal "Your $OPENSSL does not support the \"-starttls mysql\" option" $ERR_OSSLBIN - fi - elif [[ "$protocol" == lmtp ]]; then - # Check if openssl version supports lmtp. - if ! "$HAS_LMTP"; then - fatal "Your $OPENSSL does not support the \"-starttls lmtp\" option" $ERR_OSSLBIN - fi - elif [[ "$protocol" == nntp ]]; then - # Check if openssl version supports lmtp. - if ! "$HAS_NNTP"; then - fatal "Your $OPENSSL does not support the \"-starttls nntp\" option" $ERR_OSSLBIN - fi - fi - determine_optimal_sockets_params - determine_optimal_proto "$1" - - out " Service set:$CORRECT_SPACES STARTTLS via " - out "$(toupper "$protocol")" - [[ "$protocol" == mysql ]] && out " (experimental)" - fileout "service" "INFO" "$protocol" - [[ -n "$XMPP_HOST" ]] && out " (XMPP domain=\'$XMPP_HOST\')" - outln - ;; - *) outln - fatal "momentarily only ftp, smtp, lmtp, pop3, imap, xmpp, telnet, ldap, nntp, postgres and mysql allowed" $ERR_CMDLINE - ;; - esac - fi - tmpfile_handle ${FUNCNAME[0]}.txt - return 0 # OPTIMAL_PROTO, GET_REQ*/HEAD_REQ* is set now -} - - -# Sets SERVER_SIZE_LIMIT_BUG to true or false, depending on whether we hit the 128 cipher limit. -# Return value is 0 unless we have a problem executing -# -determine_sizelimitbug() { - # overflow_cipher must be some cipher that does not appear in TLS12_CIPHER. - local overflow_cipher='C0,86' - local -i nr_ciphers - - # For STARTTLS protocols not being implemented yet via sockets this is a bypass otherwise it won't be usable at all (e.g. LDAP) - # Fixme: find out whether we can't skip this in general for STARTTLS - [[ "$STARTTLS" =~ ldap ]] && return 0 - [[ "$STARTTLS" =~ irc ]] && return 0 - - # Only with TLS 1.2 offered at the server side it is possible to hit this bug, in practise. Thus - # we assume if TLS 1.2 is not supported, the server has no cipher size limit bug. It still may, - # theoretically, but in a regular check with testssl.sh we won't hit this limit with lower protocols. - # Upon calling this function we already know whether TLS 1.2 is supported. If TLS 1.2 is supported, we - # send 129 ciphers (including 00FF) and check whether it works. - - if [[ 1 -eq $(has_server_protocol 03) ]]; then - SERVER_SIZE_LIMIT_BUG=false - else - if [[ "$DEBUG" -ge 1 ]]; then - nr_ciphers="$(tr ' ' '\n' <<< "${overflow_cipher}, $TLS12_CIPHER" | sed -e '/^$/d' | wc -l)" - if [[ $nr_ciphers -ne 129 ]]; then - prln_warning "FIXME line $LINENO, ${FUNCNAME[0]} sending $nr_ciphers ciphers rather than 129." - else - debugme echo "${FUNCNAME[0]} sending $nr_ciphers ciphers" - fi - fi - tls_sockets 03 "${overflow_cipher}, ${TLS12_CIPHER}" - if [[ $? -eq 0 ]]; then - SERVER_SIZE_LIMIT_BUG=false - else - SERVER_SIZE_LIMIT_BUG=true - fi - debugme echo -e "\nSERVER_SIZE_LIMIT_BUG: $SERVER_SIZE_LIMIT_BUG" - fi - if "$SERVER_SIZE_LIMIT_BUG"; then - out " Pre-test: " - prln_svrty_medium "128 cipher limit bug" - fileout "pre_128cipher" "MEDIUM" "128 cipher limit bug" - else - [[ "$DEBUG" -ge 1 ]] && outln " Pre-test: No 128 cipher limit bug" - fileout "pre_128cipher" "INFO" "No 128 cipher limit bug" - fi - return 0 -} - - -display_rdns_etc() { - local ip further_ip_addrs="" - local nodeip="$(tr -d '[]' <<< $NODEIP)" # for displaying IPv6 addresses we don't need [] - - if [[ -n "$PROXY" ]]; then - out " Via Proxy: $CORRECT_SPACES" - outln "$PROXYIP:$PROXYPORT " - fi - if [[ $(count_words "$IP46ADDRs") -gt 1 ]]; then - out " Further IP addresses: $CORRECT_SPACES" - for ip in $IP46ADDRs; do - if [[ "$ip" == "$NODEIP" ]] || [[ "[$ip]" == "$NODEIP" ]]; then - continue - else - further_ip_addrs+="$ip " - fi - done - outln "$(out_row_aligned_max_width "$further_ip_addrs" " $CORRECT_SPACES" $TERM_WIDTH)" - fi - if "$LOCAL_A"; then - outln " A record via: $CORRECT_SPACES /etc/hosts " - elif "$LOCAL_AAAA"; then - outln " AAAA record via: $CORRECT_SPACES /etc/hosts " - elif [[ -n "$CMDLINE_IP" ]]; then - if is_ipv6addr $"$CMDLINE_IP"; then - outln " AAAA record via: $CORRECT_SPACES supplied IP \"$CMDLINE_IP\"" - else - outln " A record via: $CORRECT_SPACES supplied IP \"$CMDLINE_IP\"" - fi - fi - if [[ "$rDNS" =~ instructed ]]; then - out "$(printf " %-23s " "rDNS ($nodeip):")" - out "$rDNS" - elif [[ -n "$rDNS" ]]; then - out "$(printf " %-23s " "rDNS ($nodeip):")" - out "$(out_row_aligned_max_width "$rDNS" " $CORRECT_SPACES" $TERM_WIDTH)" - fi -} - -datebanner() { - local scan_time_f="" - - if [[ "$1" =~ Done ]] ; then - scan_time_f="$(printf "%04ss" "$SCAN_TIME")" # 4 digits because of windows - pr_reverse "$1 $(date +%F) $(date +%T) [$scan_time_f] -->> $NODEIP:$PORT ($NODE) <<--" - else - pr_reverse "$1 $(date +%F) $(date +%T) -->> $NODEIP:$PORT ($NODE) <<--" - fi - outln "\n" - [[ "$1" =~ Start ]] && display_rdns_etc -} - -# one line with char $1 over screen width $2 -draw_line() { - out "$(printf -- "$1"'%.s' $(eval "echo {1.."$(($2))"}"))" -} - - -run_mx_all_ips() { - local mxs mx - local mxport - local -i ret=0 - local word="" - - STARTTLS_PROTOCOL="smtp" - # test first higher priority servers - mxs=$(get_mx_record "$1" | sort -n | sed -e 's/^.* //' -e 's/\.$//' | tr '\n' ' ') - if [[ $CMDLINE_IP == one ]]; then - word="as instructed one" # with highest priority - mxs=${mxs%% *} - else - word="the only" - fi - mxport=${2:-25} - if [[ -n "$LOGFILE" ]]; then - prepare_logging - else - prepare_logging "${FNAME_PREFIX}mx-$1" - fi - if [[ -n "$mxs" ]] && [[ "$mxs" != ' ' ]]; then - [[ $(count_words "$mxs") -gt 1 ]] && MULTIPLE_CHECKS=true - if "$MULTIPLE_CHECKS"; then - pr_bold "Testing all MX records (on port $mxport): " - else - pr_bold "Testing $word MX record (on port $mxport): " - fi - outln "$mxs" - [[ $mxport == 465 ]] && STARTTLS_PROTOCOL="" # no starttls for tcp 465, all other ports are starttls - for mx in $mxs; do - draw_line "-" $((TERM_WIDTH * 2 / 3)) - outln - parse_hn_port "$mx:$mxport" - determine_ip_addresses || continue - if [[ $(count_words "$IPADDRs") -gt 1 ]]; then # we have more than one ipv4 address to check - MULTIPLE_CHECKS=true - pr_bold "Testing all IPv4 addresses (port $PORT): "; outln "$IPADDRs" - for ip in $IPADDRs; do - NODEIP="$ip" - lets_roll "${STARTTLS_PROTOCOL}" - done - else - NODEIP="$IPADDRs" - lets_roll "${STARTTLS_PROTOCOL}" - fi - ret=$(($? + ret)) - done - draw_line "-" $((TERM_WIDTH * 2 / 3)) - outln - pr_bold "Done testing all MX records (on port $mxport): "; outln "$mxs" - else - prln_bold " $1 has no MX records(s)" - fi - return $ret -} - -# If run_mass_testing() is being used, then create the command line -# for the test based on the global command line (all elements of the -# command line provided to the parent, except the --file/-iL option) and the -# specific command line options for the test to be run. Each argument -# in the command line needs to be a separate element in an array in order -# to deal with word splitting within file names (see #702). -# -# If run_mass_testing_parallel() is being used, then in addition to the above, -# modify global command line for child tests so that if all (JSON, CSV, HTML) -# output is to go into a single file, each child will have its output placed in -# a separate, named file, so that the separate files can be concatenated -# together once they are complete to create the single file. -# -# If run_mass_testing() is being used, then "$1" is "serial". If -# run_mass_testing_parallel() is being used, then "$1" is "parallel XXXXXXXX" -# where XXXXXXXX is the number of the test being run. -# -create_mass_testing_cmdline() { - local testing_type="$1" - local cmd test_number - local outfile_arg - local -i nr_cmds=0 index=0 - local skip_next=false - - MASS_TESTING_CMDLINE=() - [[ "$testing_type" =~ parallel ]] && read -r testing_type test_number <<< "$testing_type" - - # Start by adding the elements from the global command line to the command line for the - # test. If run_mass_testing_parallel(), then modify the command line so that, when - # required, each child process sends its test results to a separate file. If a cmd - # uses '=' for supplying a value we just skip next parameter (we don't use 'parse_opt_equal_sign' here) - debugme echo "${CMDLINE_ARRAY[@]}" - for cmd in "${CMDLINE_ARRAY[@]}"; do - "$skip_next" && skip_next=false && index+=1 && continue - if [[ "$cmd" =~ --file ]] || [[ "$cmd" =~ -iL ]]; then - # Don't include the "--file[=...] or -iL argument in the child's command - # line, but do include "--warnings=batch". - MASS_TESTING_CMDLINE[nr_cmds]="--warnings=batch" - nr_cmds+=1 - # next is the file itself, as no '=' was supplied - [[ "$cmd" == --file ]] && skip_next=true - [[ "$cmd" == -iL ]] && skip_next=true - elif [[ "$testing_type" == serial ]]; then - if "$JSONHEADER" && ( [[ "$cmd" =~ --jsonfile-pretty ]] || [[ "$cmd" =~ -oJ ]] ); then - >"$TEMPDIR/jsonfile_child.json" - MASS_TESTING_CMDLINE[nr_cmds]="--jsonfile-pretty=$TEMPDIR/jsonfile_child.json" - # next is the jsonfile itself, as no '=' was supplied - [[ "$cmd" == --jsonfile-pretty ]] && skip_next=true - [[ "$cmd" == -oJ ]] && skip_next=true - elif "$JSONHEADER" && ( [[ "$cmd" =~ --jsonfile ]] || [[ "$cmd" =~ -oj ]] ); then - >"$TEMPDIR/jsonfile_child.json" - MASS_TESTING_CMDLINE[nr_cmds]="--jsonfile=$TEMPDIR/jsonfile_child.json" - # next is the jsonfile itself, as no '=' was supplied - [[ "$cmd" == --jsonfile ]] && skip_next=true - [[ "$cmd" == -oj ]] && skip_next=true - elif "$JSONHEADER" && ( [[ "$cmd" =~ --outFile ]] || [[ "$cmd" =~ -oA ]] ); then - outfile_arg="$(parse_opt_equal_sign "$cmd" "${CMDLINE_ARRAY[index+1]}")" - >"$TEMPDIR/jsonfile_child.json" - MASS_TESTING_CMDLINE[nr_cmds]="-oJ=$TEMPDIR/jsonfile_child.json" - nr_cmds+=1 - MASS_TESTING_CMDLINE[nr_cmds]="-oC=$outfile_arg.csv" - nr_cmds+=1 - MASS_TESTING_CMDLINE[nr_cmds]="-oH=$outfile_arg.html" - # next is the filename itself, as no '=' was supplied - [[ "$cmd" == --outFile ]] && skip_next=true - [[ "$cmd" == -oA ]] && skip_next=true - elif "$JSONHEADER" && ( [[ "$cmd" =~ --outfile ]] || [[ "$cmd" =~ -oa ]] ); then - outfile_arg="$(parse_opt_equal_sign "$cmd" "${CMDLINE_ARRAY[index+1]}")" - >"$TEMPDIR/jsonfile_child.json" - MASS_TESTING_CMDLINE[nr_cmds]="-oj=$TEMPDIR/jsonfile_child.json" - nr_cmds+=1 - MASS_TESTING_CMDLINE[nr_cmds]="-oC=$outfile_arg.csv" - nr_cmds+=1 - MASS_TESTING_CMDLINE[nr_cmds]="-oH=$outfile_arg.html" - # next is the filename itself, as no '=' was supplied - [[ "$cmd" == --outfile ]] && skip_next=true - [[ "$cmd" == -oa ]] && skip_next=true - else - MASS_TESTING_CMDLINE[nr_cmds]="$cmd" - fi - nr_cmds+=1 - else - case "$cmd" in - --jsonfile|--jsonfile=*|-oj|-oj=*) - # If <jsonfile> is a file, then have provide a different - # file name to each child process. If <jsonfile> is a - # directory, then just pass it on to the child processes. - if "$JSONHEADER"; then - MASS_TESTING_CMDLINE[nr_cmds]="--jsonfile=$TEMPDIR/jsonfile_${test_number}.json" - # next is the jsonfile itself, as no '=' was supplied - [[ "$cmd" == --jsonfile ]] && skip_next=true - [[ "$cmd" == -oj ]] && skip_next=true - else - MASS_TESTING_CMDLINE[nr_cmds]="$cmd" - fi - ;; - --jsonfile-pretty|--jsonfile-pretty=*|-oJ|-oJ=*) - if "$JSONHEADER"; then - MASS_TESTING_CMDLINE[nr_cmds]="--jsonfile-pretty=$TEMPDIR/jsonfile_${test_number}.json" - [[ "$cmd" == --jsonfile-pretty ]] && skip_next=true - [[ "$cmd" == -oJ ]] && skip_next=true - else - MASS_TESTING_CMDLINE[nr_cmds]="$cmd" - fi - ;; - --csvfile|--csvfile=*|-oC|-oC=*) - if "$CSVHEADER"; then - MASS_TESTING_CMDLINE[nr_cmds]="--csvfile=$TEMPDIR/csvfile_${test_number}.csv" - [[ "$cmd" == --csvfile ]] && skip_next=true - [[ "$cmd" == -oC ]] && skip_next=true - else - MASS_TESTING_CMDLINE[nr_cmds]="$cmd" - fi - ;; - --htmlfile|--htmlfile=*|-oH|-oH=*) - if "$HTMLHEADER"; then - MASS_TESTING_CMDLINE[nr_cmds]="--htmlfile=$TEMPDIR/htmlfile_${test_number}.html" - [[ "$cmd" == --htmlfile ]] && skip_next=true - [[ "$cmd" == -oH ]] && skip_next=true - else - MASS_TESTING_CMDLINE[nr_cmds]="$cmd" - fi - ;; - --outfile|--outfile=*|-oa|-oa=*) - if "$JSONHEADER"; then - MASS_TESTING_CMDLINE[nr_cmds]="-oj=$TEMPDIR/jsonfile_${test_number}.json" - nr_cmds+=1 - MASS_TESTING_CMDLINE[nr_cmds]="-oC=$TEMPDIR/csvfile_${test_number}.csv" - nr_cmds+=1 - MASS_TESTING_CMDLINE[nr_cmds]="-oH=$TEMPDIR/htmlfile_${test_number}.html" - # next is the filename itself, as no '=' was supplied - [[ "$cmd" == --outfile ]] && skip_next=true - [[ "$cmd" == -oa ]] && skip_next=true - else - MASS_TESTING_CMDLINE[nr_cmds]="$cmd" - fi - ;; - --outFile|--outFile=*|-oA|-oA=*) - if "$JSONHEADER"; then - MASS_TESTING_CMDLINE[nr_cmds]="-oJ=$TEMPDIR/jsonfile_${test_number}.json" - nr_cmds+=1 - MASS_TESTING_CMDLINE[nr_cmds]="-oC=$TEMPDIR/csvfile_${test_number}.csv" - nr_cmds+=1 - MASS_TESTING_CMDLINE[nr_cmds]="-oH=$TEMPDIR/htmlfile_${test_number}.html" - # next is the filename itself, as no '=' was supplied - [[ "$cmd" == --outFile ]] && skip_next=true - [[ "$cmd" == -oA ]] && skip_next=true - else - MASS_TESTING_CMDLINE[nr_cmds]="$cmd" - fi - ;; - *) - MASS_TESTING_CMDLINE[nr_cmds]="$cmd" - ;; - esac - nr_cmds+=1 - fi - index+=1 - done - - # Now add the command line arguments for the specific test to the command line. - # Skip the first argument sent to this function, since it specifies the type of testing being performed. - shift - while [[ $# -gt 0 ]]; do - MASS_TESTING_CMDLINE[nr_cmds]="$1" - nr_cmds+=1 - shift - done - - return 0 -} - - -ports2starttls() { - local tcp_port=$1 - local ret=0 - - # https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers - case $tcp_port in - 21) echo "-t ftp " ;; - 23) echo "-t telnet " ;; - 119|433) echo "-t nntp " ;; # to come - 25|587) echo "-t smtp " ;; - 110) echo "-t pop3 " ;; - 143) echo "-t imap " ;; - 389) echo "-t ldap ";; - 3306) echo "-t mysql " ;; - 5222) echo "-t xmpp " ;; # domain of jabber server maybe needed - 5432) echo "-t postgres " ;; - 563) ;; # NNTPS - 636) ;; # LDAP - 1443|8443|443|981) ;; # HTTPS - 465) ;; # HTTPS | SMTP - 631) ;; # CUPS - 853) ;; # DNS over TLS - 995|993) ;; # POP3|IMAP - 3389) ;; # RDP - *) ret=1 ;; # we don't know this ports so we rather do not scan it - esac - return $ret -} - -nmap_to_plain_file() { - local target_fname="" - local oneline="" - local ip hostdontcare round_brackets ports_specs starttls - local tmp port host_spec protocol ssl_hint dontcare dontcare1 - #FIXME: IPv6 is missing here - - # Ok, since we are here we are sure to have an nmap file. To avoid questions we make sure it's the right format too - if [[ "$(head -1 "$FNAME")" =~ ( -oG )(.*) ]] || [[ "$(head -1 "$FNAME")" =~ ( -oA )(.*) ]] ; then - # yes, greppable - if [[ $(grep -c Status "$FNAME") -ge 1 ]]; then - [[ $(grep -c '\/open\/' "$FNAME") -eq 0 ]] && \ - fatal "Nmap file $FNAME should contain at least one open port" $ERR_FNAMEPARSE - else - fatal "strange, nmap grepable misses \"Status\"" -1 - fi - else - fatal "Nmap file $FNAME is not in grep(p)able format (-oG filename.g(n)map)" $ERR_FNAMEPARSE - fi - # create ${FNAME%.*}.txt in $TEMPDIR - target_fname="${FNAME%.*}.txt" - target_fname="${target_fname##*\/}" # strip path (Unix) - target_fname="${target_fname##*\\}" # strip path (Dos) - target_fname="$TEMPDIR/$target_fname" - > "${target_fname}" || fatal "Cannot create \"${target_fname}\"" $ERR_FCREATE - - # Line x: "Host: AAA.BBB.CCC.DDD (<FQDN>) Status: Up" - # Line x+1: "Host: AAA.BBB.CCC.DDD (<FQDN>) Ports: 443/open/tcp//https///" - # (or): Host: AAA.BBB.CCC.DDD (<FQDN>) Ports: 22/open/tcp//ssh//<banner>/, 25/open/tcp//smtp//<banner>/, 443/open/tcp//ssl|http//<banner> - while read -r hostdontcare ip round_brackets tmp ports_specs; do - [[ "$ports_specs" =~ "Status: " ]] && continue # we don't need this - [[ "$ports_specs" =~ '/open/tcp/' ]] || continue # no open tcp at all for this IP --> move - host_spec="$ip" - fqdn="${round_brackets/\(/}" - fqdn="${fqdn/\)/}" - if [[ -n "$fqdn" ]]; then - tmp="$(get_a_record "$fqdn")" - debugme echo "$tmp \?= $ip" - if [[ "$tmp" == "$ip" ]]; then - host_spec="$fqdn" - fi - fi - while read -r oneline; do - # 25/open/tcp//smtp//<banner>/, - [[ "$oneline" =~ '/open/tcp/' ]] || continue # no open tcp for this port on this IP --> move on - IFS=/ read -r port dontcare protocol ssl_hint dontcare1 <<< "$oneline" - if [[ "$ssl_hint" =~ ^(ssl|https) ]] || [[ "$dontcare1" =~ ^(ssl|https) ]]; then - echo "${host_spec}:${port}" >>"$target_fname" - else - starttls="$(ports2starttls $port)" - [[ $? -eq 1 ]] && continue # nmap got a port but we don't know how to speak to - [[ "$DEBUG" -ge 1 ]] && echo "${starttls}$host_spec:$port" - echo "${starttls}${host_spec}:${port}" >>"$target_fname" - fi - done < <(tr ',' '\n' <<< "$ports_specs") - done < "$FNAME" - [[ "$DEBUG" -ge 1 ]] && echo - - [[ -s "$target_fname" ]] || \ - fatal "Couldn't find any open port in $FNAME" $ERR_FNAMEPARSE - export FNAME=$target_fname -} - -run_mass_testing() { - local cmdline="" - local first=true - local gnmapadd="" - local saved_fname="$FNAME" - - if [[ ! -r "$FNAME" ]] && "$IKNOW_FNAME"; then - fatal "Can't read file \"$FNAME\"" $ERR_FNAMEPARSE - fi - - if [[ "$(head -1 "$FNAME")" =~ (Nmap [4-8])(.*)( scan initiated )(.*) ]]; then - gnmapadd="grep(p)able nmap " - nmap_to_plain_file - fi - - pr_reverse "====== Running in file batch mode with ${gnmapadd}file=\"$saved_fname\" ======"; outln "\n" - while read -r cmdline; do - cmdline="$(filter_input "$cmdline")" - [[ -z "$cmdline" ]] && continue - [[ "$cmdline" == EOF ]] && break - # Create the command line for the child in the form of an array (see #702) - create_mass_testing_cmdline "serial" $cmdline - draw_line "=" $((TERM_WIDTH / 2)); outln; - outln "$(create_cmd_line_string "$0" "${MASS_TESTING_CMDLINE[@]}")" - # we call ourselves here. $do_mass_testing is the parent, $CHILD_MASS_TESTING... you figured - if [[ -z "$(type -p "$0")" ]]; then - CHILD_MASS_TESTING=true "$RUN_DIR/$PROG_NAME" "${MASS_TESTING_CMDLINE[@]}" - else - CHILD_MASS_TESTING=true "$0" "${MASS_TESTING_CMDLINE[@]}" - fi - if "$JSONHEADER" && [[ -s "$TEMPDIR/jsonfile_child.json" ]]; then - # Need to ensure that a separator is only added if the test - # produced some JSON output. - "$first" || fileout_separator # this is needed for appended output, see #687 - first=false - cat "$TEMPDIR/jsonfile_child.json" >> "$JSONFILE" - FIRST_FINDING=false - fi - done < "${FNAME}" - return $? -} - -# This function is called when it has been determined that the next child -# process has completed or it has been stopped. If the child process completed, -# then this process prints the child process's output to the terminal and, if -# appropriate, adds any JSON, CSV, and HTML output it has created to the -# appropriate file. If the child process was stopped, then a message indicating -# that is printed, but the incomplete results are not used. -# -get_next_message_testing_parallel_result() { - draw_line "=" $((TERM_WIDTH / 2)); outln; - outln "${PARALLEL_TESTING_CMDLINE[NEXT_PARALLEL_TEST_TO_FINISH]}" - if [[ "$1" == completed ]]; then - cat "$TEMPDIR/term_output_$(printf "%08d" $NEXT_PARALLEL_TEST_TO_FINISH).log" - if "$JSONHEADER" && [[ -s "$TEMPDIR/jsonfile_$(printf "%08d" $NEXT_PARALLEL_TEST_TO_FINISH).json" ]]; then - # Need to ensure that a separator is only added if the test - # produced some JSON output. - "$FIRST_JSON_OUTPUT" || fileout_separator # this is needed for appended output, see #687 - FIRST_JSON_OUTPUT=false - FIRST_FINDING=false - cat "$TEMPDIR/jsonfile_$(printf "%08d" $NEXT_PARALLEL_TEST_TO_FINISH).json" >> "$JSONFILE" - fi - "$CSVHEADER" && cat "$TEMPDIR/csvfile_$(printf "%08d" $NEXT_PARALLEL_TEST_TO_FINISH).csv" >> "$CSVFILE" - "$HTMLHEADER" && cat "$TEMPDIR/htmlfile_$(printf "%08d" $NEXT_PARALLEL_TEST_TO_FINISH).html" >> "$HTMLFILE" - elif [[ "$1" == "stopped" ]]; then - outln "\nTest was stopped before it completed.\n" - else - outln "\nTest timed out before it completed.\n" - fi -} - -#FIXME: not called/tested yet -run_mass_testing_parallel() { - local cmdline="" - local -i i nr_active_tests=0 - local -a -i start_time=() - local -i curr_time wait_time - local gnmapadd="" - local saved_fname="$FNAME" - - if [[ ! -r "$FNAME" ]] && $IKNOW_FNAME; then - fatal "Can't read file \"$FNAME\"" $ERR_FNAMEPARSE - fi - - if [[ "$(head -1 "$FNAME")" =~ (Nmap [4-8])(.*)( scan initiated )(.*) ]]; then - gnmapadd="grep(p)able nmap " - nmap_to_plain_file - fi - - pr_reverse "====== Running in file batch mode with ${gnmapadd}file=\"$saved_fname\" ======"; outln "\n" - while read -r cmdline; do - cmdline="$(filter_input "$cmdline")" - [[ -z "$cmdline" ]] && continue - [[ "$cmdline" == "EOF" ]] && break - # Create the command line for the child in the form of an array (see #702) - create_mass_testing_cmdline "parallel $(printf "%08d" $NR_PARALLEL_TESTS)" $cmdline - - # fileout() won't include the "service" information in the JSON file for the child process - # if the JSON file doesn't already exist. - "$JSONHEADER" && >"$TEMPDIR/jsonfile_$(printf "%08d" $NR_PARALLEL_TESTS).json" - PARALLEL_TESTING_CMDLINE[NR_PARALLEL_TESTS]="$(create_cmd_line_string "$0" "${MASS_TESTING_CMDLINE[@]}")" - if [[ -z "$(type -p "$0")" ]]; then - CHILD_MASS_TESTING=true "$RUN_DIR/$PROG_NAME" "${MASS_TESTING_CMDLINE[@]}" > "$TEMPDIR/term_output_$(printf "%08d" $NR_PARALLEL_TESTS).log" 2>&1 & - else - CHILD_MASS_TESTING=true "$0" "${MASS_TESTING_CMDLINE[@]}" > "$TEMPDIR/term_output_$(printf "%08d" $NR_PARALLEL_TESTS).log" 2>&1 & - fi - PARALLEL_TESTING_PID[NR_PARALLEL_TESTS]=$! - start_time[NR_PARALLEL_TESTS]=$(date +%s) - if "$INTERACTIVE"; then - echo -en "\r \r" 1>&2 - echo -n "Started test #$NR_PARALLEL_TESTS" 1>&2 - [[ $NEXT_PARALLEL_TEST_TO_FINISH -lt $NR_PARALLEL_TESTS ]] && \ - echo -n " (waiting for test #$NEXT_PARALLEL_TEST_TO_FINISH to finish)" 1>&2 - fi - NR_PARALLEL_TESTS+=1 - nr_active_tests+=1 - sleep $PARALLEL_SLEEP - # Get the results of any completed tests - while [[ $NEXT_PARALLEL_TEST_TO_FINISH -lt $NR_PARALLEL_TESTS ]]; do - if [[ ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} -eq 0 ]]; then - "$INTERACTIVE" && echo -en "\r \r" 1>&2 - get_next_message_testing_parallel_result "completed" - NEXT_PARALLEL_TEST_TO_FINISH+=1 - elif ! ps ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} >/dev/null ; then - "$INTERACTIVE" && echo -en "\r \r" 1>&2 - get_next_message_testing_parallel_result "completed" - NEXT_PARALLEL_TEST_TO_FINISH+=1 - nr_active_tests=$nr_active_tests-1 - else - break - fi - done - if [[ $nr_active_tests -ge $MAX_PARALLEL ]]; then - curr_time=$(date +%s) - while true; do - # Check to see if any test completed - for (( i=NEXT_PARALLEL_TEST_TO_FINISH; i < NR_PARALLEL_TESTS; i++ )); do - if [[ ${PARALLEL_TESTING_PID[i]} -ne 0 ]] && \ - ! ps ${PARALLEL_TESTING_PID[i]} >/dev/null ; then - PARALLEL_TESTING_PID[i]=0 - nr_active_tests=$nr_active_tests-1 - break - fi - done - [[ $nr_active_tests -lt $MAX_PARALLEL ]] && break - if [[ $curr_time-${start_time[NEXT_PARALLEL_TEST_TO_FINISH]} -ge $MAX_WAIT_TEST ]]; then - # No test completed in the allocated time, so the first one to - # start will be killed. - kill ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} >&2 2>/dev/null - wait ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} 2>/dev/null # make sure pid terminated, see wait(1p) - "$INTERACTIVE" && echo -en "\r \r" 1>&2 - get_next_message_testing_parallel_result "timeout" - NEXT_PARALLEL_TEST_TO_FINISH+=1 - nr_active_tests=$nr_active_tests-1 - break - fi - # Wake up to increment the counter every second (so that the counter - # appears to users as if it is operating smoothly), but check the - # status of the $MAX_PARALLEL active processes less often, since the - # ps command is expensive. - for (( i=0; i <= $((MAX_PARALLEL/5)); i++ )); do - wait_time=$((curr_time-start_time[NEXT_PARALLEL_TEST_TO_FINISH])) - [[ $wait_time -gt $MAX_WAIT_TEST ]] && wait_time=$MAX_WAIT_TEST - if "$INTERACTIVE"; then - echo -en "\r \r" 1>&2 - echo -n "Waiting for test #$NEXT_PARALLEL_TEST_TO_FINISH to finish" 1>&2 - if [[ $((MAX_WAIT_TEST-wait_time)) -le 60 ]]; then - echo -n " ($((MAX_WAIT_TEST-wait_time)) seconds to timeout)" 1>&2 - else - echo -n " ($wait_time seconds)" 1>&2 - fi - fi - [[ $wait_time -ge $MAX_WAIT_TEST ]] && break - sleep 1 - curr_time=$(date +%s) - done - done - fi - done < "$FNAME" - - # Wait for remaining tests to finish - curr_time=$(date +%s) - while [[ $NEXT_PARALLEL_TEST_TO_FINISH -lt $NR_PARALLEL_TESTS ]]; do - if [[ ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} -eq 0 ]] || \ - ! ps ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} >/dev/null ; then - "$INTERACTIVE" && echo -en "\r \r" 1>&2 - get_next_message_testing_parallel_result "completed" - NEXT_PARALLEL_TEST_TO_FINISH+=1 - elif [[ $curr_time-${start_time[NEXT_PARALLEL_TEST_TO_FINISH]} -ge $MAX_WAIT_TEST ]]; then - kill ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} >&2 2>/dev/null - wait ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} 2>/dev/null # make sure pid terminated, see wait(1p) - "$INTERACTIVE" && echo -en "\r \r" 1>&2 - get_next_message_testing_parallel_result "timeout" - NEXT_PARALLEL_TEST_TO_FINISH+=1 - else - # Here it is okay to check process status every second, since the - # status of only one process is being checked. - if "$INTERACTIVE"; then - echo -en "\r \r" 1>&2 - wait_time=$((curr_time-start_time[NEXT_PARALLEL_TEST_TO_FINISH])) - [[ $wait_time -gt $MAX_WAIT_TEST ]] && wait_time=$MAX_WAIT_TEST - echo -n "Waiting for test #$NEXT_PARALLEL_TEST_TO_FINISH to finish" 1>&2 - if [[ $((MAX_WAIT_TEST-wait_time)) -le 60 ]]; then - echo -n " ($((MAX_WAIT_TEST-wait_time)) seconds to timeout)" 1>&2 - else - echo -n " ($wait_time seconds)" 1>&2 - fi - fi - sleep 1 - curr_time=$(date +%s) - fi - done - return $? -} - - - -# This initializes boolean global do_* variables. They keep track of what to do -# -- as the name insinuates -initialize_globals() { - do_allciphers=false - do_vulnerabilities=false - do_beast=false - do_lucky13=false - do_breach=false - do_ccs_injection=false - do_ticketbleed=false - do_robot=false - do_cipher_per_proto=false - do_crime=false - do_freak=false - do_logjam=false - do_drown=false - do_header=false - do_heartbleed=false - do_mx_all_ips=false - do_mass_testing=false - do_logging=false - do_json=false - do_pretty_json=false - do_csv=false - do_html=false - do_pfs=false - do_protocols=false - do_rc4=false - do_grease=false - do_renego=false - do_cipherlists=false - do_server_defaults=false - do_server_preference=false - do_ssl_poodle=false - do_sweet32=false - do_tls_fallback_scsv=false - do_cipher_match=false - do_tls_sockets=false - do_client_simulation=false - do_display_only=false - do_starttls=false -} - - -# Set default scanning options for the boolean global do_* variables. -set_scanning_defaults() { - do_allciphers=true - do_vulnerabilities=true - do_beast=true - do_lucky13=true - do_breach=true - do_heartbleed="$OFFENSIVE" - do_ccs_injection="$OFFENSIVE" - do_ticketbleed="$OFFENSIVE" - do_robot="$OFFENSIVE" - do_crime=true - do_freak=true - do_logjam=true - do_drown=true - do_ssl_poodle=true - do_sweet32=true - do_header=true - do_pfs=true - do_rc4=true - do_protocols=true - do_renego=true - do_cipherlists=true - do_server_defaults=true - do_server_preference=true - do_tls_fallback_scsv=true - do_client_simulation=true - if "$OFFENSIVE"; then - VULN_COUNT=16 - else - VULN_COUNT=12 - fi -} - -# returns number of $do variables set = number of run_funcs() to perform -count_do_variables() { - local gbl - local true_nr=0 - - for gbl in do_allciphers do_vulnerabilities do_beast do_lucky13 do_breach do_ccs_injection do_ticketbleed do_cipher_per_proto do_crime \ - do_freak do_logjam do_drown do_header do_heartbleed do_mx_all_ips do_pfs do_protocols do_rc4 do_grease do_robot do_renego \ - do_cipherlists do_server_defaults do_server_preference do_ssl_poodle do_tls_fallback_scsv \ - do_sweet32 do_client_simulation do_cipher_match do_tls_sockets do_mass_testing do_display_only; do - [[ "${!gbl}" == true ]] && let true_nr++ - done - return $true_nr -} - - -debug_globals() { - local gbl - - for gbl in do_allciphers do_vulnerabilities do_beast do_lucky13 do_breach do_ccs_injection do_ticketbleed do_cipher_per_proto do_crime \ - do_freak do_logjam do_drown do_header do_heartbleed do_mx_all_ips do_pfs do_protocols do_rc4 do_grease do_robot do_renego \ - do_cipherlists do_server_defaults do_server_preference do_ssl_poodle do_tls_fallback_scsv \ - do_sweet32 do_client_simulation do_cipher_match do_tls_sockets do_mass_testing do_display_only; do - printf "%-22s = %s\n" $gbl "${!gbl}" - done - printf "%-22s : %s\n" URI: "$URI" -} - - -# arg1: either switch+value (=) or switch -# arg2: value (if no = provided) -parse_opt_equal_sign() { - if [[ "$1" == *=* ]]; then - echo ${1#*=} - return 1 # = means we don't need to shift args! - else - echo "$2" - return 0 # we need to shift - fi -} - -# Create the command line string for printing purposes -# See https://stackoverflow.com/questions/10835933/preserve-quotes-in-bash-arguments -create_cmd_line_string() { - local arg - local -a allargs=() - local chars='[ !"#$&()*,;<>?\^`{|}]' - - while [[ $# -gt 0 ]]; do - if [[ $1 == *\'* ]]; then - arg=\""$1"\" - elif [[ $1 == *$chars* ]]; then - arg="'$1'" - else - arg="$1" - fi - allargs+=("$arg") # ${allargs[@]} is to be used only for printing - shift - done - printf '%s\n' "${allargs[*]}" -} - -check_base_requirements() { - local binary='' - local whitelist=' hexdump grep awk sed ' - - for binary in 'hexdump' 'dd' 'grep' 'awk' 'tr' 'sed' 'wc' 'date' 'cat' 'ps' 'kill' 'head' 'tail' 'dirname'; do - if ! type -p "${binary}" &> /dev/null; then - fatal "You need to install ${binary} for this program to work" $ERR_RESOURCE - fi - [[ ${whitelist} =~ \ ${binary}\ ]] && continue - "${binary}" --help 2>&1 | grep -iq busybox - if [[ $? -eq 0 ]]; then - fatal "${binary} is from busybox. Please install a regular binary" $ERR_RESOURCE - fi - done -} - -parse_cmd_line() { - local outfile_arg="" - local cipher_mapping - local -i subret=0 - - CMDLINE="$(create_cmd_line_string "${CMDLINE_ARRAY[@]}")" - CMDLINE_PARSED=false - - case $1 in - --help|"") - help 0 - ;; - -b|--banner|-v|--version) - maketempf - get_install_dir - find_openssl_binary - prepare_debug - mybanner - exit $ALLOK - ;; - esac - - # initializing - initialize_globals - - while [[ $# -gt 0 ]]; do - case $1 in - --mx) - do_mx_all_ips=true - PORT=25 - ;; - --mx465) # doesn't work with major ISPs - do_mx_all_ips=true - PORT=465 - ;; - --mx587) # doesn't work with major ISPs - do_mx_all_ips=true - PORT=587 - ;; - --ip|--ip=*) - CMDLINE_IP="$(parse_opt_equal_sign "$1" "$2")" - [[ $? -eq 0 ]] && shift - if [[ "$CMDLINE_IP" == proxy ]]; then - DNS_VIA_PROXY=true - unset CMDLINE_IP - fi - # normalize any IPv6 address - CMDLINE_IP="${CMDLINE_IP//[/}" # fix vim syntax highlighting "] - CMDLINE_IP="${CMDLINE_IP//]/}" - ;; - -n|--nodns|-n=*|--nodns=*) - NODNS="$(parse_opt_equal_sign "$1" "$2")" - [[ $? -eq 0 ]] && shift - if [[ "$NODNS" != none ]] && [[ "$NODNS" != min ]]; then - fatal "Value for nodns switch can be either \"min\" or \"none\"" $ERR_CMDLINE - fi - ;; - -V|-V=*|--local|--local=*) # attention, this could have a value or not! - do_display_only=true - PATTERN2SHOW="$(parse_opt_equal_sign "$1" "$2")" - subret=$? - if [[ "$PATTERN2SHOW" == -* ]]; then - unset PATTERN2SHOW # we hit the next command ==> not our value - else # it was ours, point to next arg - [[ $subret -eq 0 ]] && shift - fi - ;; - -x|-x=*|--single[-_]cipher|--single[-_]cipher=*) - do_cipher_match=true - single_cipher=$(parse_opt_equal_sign "$1" "$2") - [[ $? -eq 0 ]] && shift - ;; - -t|-t=*|--starttls|--starttls=*) - do_starttls=true - STARTTLS_PROTOCOL="$(parse_opt_equal_sign "$1" "$2")" - [[ $? -eq 0 ]] && shift - case $STARTTLS_PROTOCOL in - ftp|smtp|lmtp|pop3|imap|xmpp|telnet|ldap|irc|nntp|postgres|mysql) ;; - ftps|smtps|lmtps|pop3s|imaps|xmpps|telnets|ldaps|ircs|nntps|mysqls) ;; - *) tmln_magenta "\nunrecognized STARTTLS protocol \"$1\", see help" 1>&2 - help 1 ;; - esac - ;; - --xmpphost|--xmpphost=*) - XMPP_HOST=$(parse_opt_equal_sign "$1" "$2") - [[ $? -eq 0 ]] && shift - ;; - -e|--each-cipher) - do_allciphers=true - ;; - -E|--cipher-per-proto|--cipher_per_proto) - do_cipher_per_proto=true - ;; - -p|--protocols) - do_protocols=true - ;; - -s|--std|--standard) - do_cipherlists=true - ;; - -S|--server[-_]defaults) - do_server_defaults=true - ;; - -P|--server[_-]preference|--preference) - do_server_preference=true - ;; - -h|--header|--headers) - do_header=true - ;; - -c|--client-simulation) - do_client_simulation=true - ;; - -U|--vulnerable|--vulnerabilities) - # Lookahead function: If the order of the cmdline is '-U --ids-friendly' - # then we need to make sure we catch --ids-friendly. Normally we do not, - # see #1717. The following statement makes sure. In the do-while + case-esac - # loop it will be execute again, but it does not hurt - if [[ "${CMDLINE_ARRAY[@]}" =~ --ids-friendly ]]; then - OFFENSIVE=false - fi - do_vulnerabilities=true - do_heartbleed="$OFFENSIVE" - do_ccs_injection="$OFFENSIVE" - do_ticketbleed="$OFFENSIVE" - do_robot="$OFFENSIVE" - do_renego=true - do_crime=true - do_breach=true - do_ssl_poodle=true - do_tls_fallback_scsv=true - do_sweet32=true - do_freak=true - do_drown=true - do_logjam=true - do_beast=true - do_lucky13=true - do_rc4=true - if "$OFFENSIVE"; then - VULN_COUNT=16 - else - VULN_COUNT=12 - fi - ;; - --ids-friendly) - OFFENSIVE=false - ;; - -H|--heartbleed) - do_heartbleed=true - let "VULN_COUNT++" - ;; - -I|--ccs|--ccs[-_]injection) - do_ccs_injection=true - let "VULN_COUNT++" - ;; - -T|--ticketbleed) - do_ticketbleed=true - let "VULN_COUNT++" - ;; - -BB|--robot) - do_robot=true - ;; - -R|--renegotiation) - do_renego=true - let "VULN_COUNT++" - ;; - -C|--compression|--crime) - do_crime=true - let "VULN_COUNT++" - ;; - -B|--breach) - do_breach=true - let "VULN_COUNT++" - ;; - -O|--poodle) - do_ssl_poodle=true - do_tls_fallback_scsv=true - let "VULN_COUNT++" - ;; - -Z|--tls[_-]fallback|tls[_-]fallback[_-]scs) - do_tls_fallback_scsv=true - let "VULN_COUNT++" - ;; - -W|--sweet32) - do_sweet32=true - let "VULN_COUNT++" - ;; - -F|--freak) - do_freak=true - let "VULN_COUNT++" - ;; - -D|--drown) - do_drown=true - let "VULN_COUNT++" - ;; - -J|--logjam) - do_logjam=true - let "VULN_COUNT++" - ;; - -A|--beast) - do_beast=true - let "VULN_COUNT++" - ;; - -L|--lucky13) - do_lucky13=true - let "VULN_COUNT++" - ;; - -4|--rc4|--appelbaum) - do_rc4=true - let "VULN_COUNT++" - ;; - -f|--pfs|--fs|--nsa) - do_pfs=true - ;; - -g|--grease) - do_grease=true - ;; - -9|--full) - set_scanning_defaults - do_allciphers=false - do_cipher_per_proto=true - do_grease=true - ;; - --add-ca|--add-CA|--add-ca=*|--add-CA=*) - ADDITIONAL_CA_FILES="$(parse_opt_equal_sign "$1" "$2")" - [[ $? -eq 0 ]] && shift - ;; - --devel) ### this development feature will soon disappear - # arg1: SSL/TLS protocol (SSLv2=22) - # arg2: list of cipher suites / hostname/ip - # arg3: hostname/ip - HEX_CIPHER="$TLS12_CIPHER" - # DEBUG=3 ./testssl.sh --devel 04 "13,02, 13,01" google.com --> TLS 1.3 - # DEBUG=3 ./testssl.sh --devel 03 "cc, 13, c0, 13" google.de --> TLS 1.2, old CHACHA/POLY - # DEBUG=3 ./testssl.sh --devel 03 "cc,a8, cc,a9, cc,aa, cc,ab, cc,ac" blog.cloudflare.com --> new CHACHA/POLY - # DEBUG=3 ./testssl.sh --devel 01 yandex.ru --> TLS 1.0 - # DEBUG=3 ./testssl.sh --devel 00 <host which supports SSLv3> - # DEBUG=3 ./testssl.sh --devel 22 <host which still supports SSLv2> - TLS_LOW_BYTE="$2"; - if [[ $# -eq 4 ]]; then # protocol AND ciphers specified - HEX_CIPHER="$3" - shift - fi - shift - do_tls_sockets=true - outln "\nTLS_LOW_BYTE, HEX_CIPHER: \"${TLS_LOW_BYTE}\", \"${HEX_CIPHER}\"" - ;; - --wide) - WIDE=true - ;; - --assuming[_-]http|--assume[-_]http) - ASSUME_HTTP=true - ;; - --sneaky) - SNEAKY=true - ;; - -q|--quiet) - QUIET=true - ;; - --file|--file=*|-iL|-iL=*) - # no shift here as otherwise URI is empty and it bails out - FNAME="$(parse_opt_equal_sign "$1" "$2")" - [[ $? -eq 0 ]] && shift - IKNOW_FNAME=true - WARNINGS=batch # set this implicitly! - do_mass_testing=true - ;; - --mode|--mode=*) - MASS_TESTING_MODE="$(parse_opt_equal_sign "$1" "$2")" - [[ $? -eq 0 ]] && shift - case "$MASS_TESTING_MODE" in - serial|parallel) ;; - *) tmln_magenta "\nmass testing mode can be either \"serial\" or \"parallel\"" 1>&2 - help 1 - esac - ;; - --serial) - MASS_TESTING_MODE=serial - ;; - --parallel) - MASS_TESTING_MODE=parallel - ;; - --warnings|--warnings=*) - WARNINGS=$(parse_opt_equal_sign "$1" "$2") - [[ $? -eq 0 ]] && shift - case "$WARNINGS" in - batch|off) ;; - *) tmln_magenta "\nwarnings can be either \"batch\", or \"off\"" 1>&2 - help 1 - esac - ;; - --show[-_]each) - SHOW_EACH_C=true - ;; - --fast) - FAST=true - ;; - --bugs) - BUGS="-bugs" - ;; - --debug|--debug=*) - DEBUG=$(parse_opt_equal_sign "$1" "$2") - [[ $? -eq 0 ]] && shift - case $DEBUG in - [0-6]) ;; - *) tmln_magenta "\nunrecognized debug value \"$1\", must be between 0..6" 1>&2 - help 1 - esac - ;; - --color|--color=*) - COLOR="$(parse_opt_equal_sign "$1" "$2")" - [[ $? -eq 0 ]] && shift - case $COLOR in - [0-3]) ;; - *) COLOR=2 - tmln_magenta "\nunrecognized color: \"$1\", must be between 0..3" 1>&2 - help 1 - esac - ;; - --colorblind) - COLORBLIND=true - ;; - --log|--logging) - "$do_logging" && fatal "two --log* arguments" $ERR_CMDLINE - do_logging=true - ;; # DEFINITION of LOGFILE if no arg specified: automagically in parse_hn_port() - # following does the same but additionally we can specify a log location - --logfile|--logfile=*|-oL|-oL=*) - "$do_logging" && fatal "two --log* arguments" $ERR_CMDLINE - LOGFILE="$(parse_opt_equal_sign "$1" "$2")" - [[ $? -eq 0 ]] && shift - do_logging=true - ;; - --json) - "$do_pretty_json" && fatal "flat and pretty JSON output are mutually exclusive" $ERR_CMDLINE - "$do_json" && fatal "--json and --jsonfile are mutually exclusive" $ERR_CMDLINE - if [[ "$2" =~ \.(json|JSON)$ ]]; then - fatal "No file name allowed after \"--json\" (use \"--jsonfile\" instead)" $ERR_CMDLINE - fi - do_json=true - ;; # DEFINITION of JSONFILE is not arg specified: automagically in parse_hn_port() - # following does the same but additionally we can specify a log location - --jsonfile|--jsonfile=*|-oj|-oj=*) - "$do_pretty_json" && fatal "flat and pretty JSON output are mutually exclusive" $ERR_CMDLINE - "$do_json" && fatal "--json and --jsonfile are mutually exclusive" $ERR_CMDLINE - JSONFILE="$(parse_opt_equal_sign "$1" "$2")" - [[ $? -eq 0 ]] && shift - do_json=true - ;; - --json-pretty) - "$do_json" && fatal "flat and pretty JSON output are mutually exclusive" $ERR_CMDLINE - "$do_pretty_json" && fatal "--json-pretty and --jsonfile-pretty are mutually exclusive" $ERR_CMDLINE - if [[ "$2" =~ \.(json|JSON)$ ]]; then - fatal "No file name allowed after \"--json\" (use \"--jsonfile-pretty\" instead)" $ERR_CMDLINE - fi - do_pretty_json=true - ;; - --jsonfile-pretty|--jsonfile-pretty=*|-oJ|-oJ=*) - "$do_json" && fatal "flat and pretty JSON output are mutually exclusive" $ERR_CMDLINE - "$do_pretty_json" && fatal "--json-pretty and --jsonfile-pretty are mutually exclusive" $ERR_CMDLINE - JSONFILE="$(parse_opt_equal_sign "$1" "$2")" - [[ $? -eq 0 ]] && shift - do_pretty_json=true - ;; - --severity|--severity=*) - set_severity_level "$(parse_opt_equal_sign "$1" "$2")" - [[ $? -eq 0 ]] && shift - ;; - --hints) - GIVE_HINTS=true - ;; - --csv) - "$do_csv" && fatal "two --csv* arguments" $ERR_CMDLINE - if [[ "$2" =~ \.(csv|CSV)$ ]]; then - fatal "No file name allowed after \"--csv\" (use \"--csvfile\" instead)" $ERR_CMDLINE - fi - do_csv=true - ;; # DEFINITION of CSVFILE is not arg specified: automagically in parse_hn_port() - # following does the same but additionally we can specify a log location - --csvfile|--csvfile=*|-oC|-oC=*) - "$do_csv" && fatal "two --csv* arguments" $ERR_CMDLINE - CSVFILE="$(parse_opt_equal_sign "$1" "$2")" - [[ $? -eq 0 ]] && shift - do_csv=true - ;; - --html) - "$do_html" && fatal "two --html* arguments" $ERR_CMDLINE - if [[ "$2" =~ \.(htm|html|HTM|HTML)$ ]]; then - fatal "No file name allowed after \"--html\" (use \"--htmlfile\" instead)" $ERR_CMDLINE - fi - do_html=true - ;; # DEFINITION of HTMLFILE is not arg specified: automagically in parse_hn_port() - # following does the same but additionally we can specify a file location - --htmlfile|--htmlfile=*|-oH|-oH=*) - "$do_html" && fatal "two --html* arguments" $ERR_CMDLINE - HTMLFILE="$(parse_opt_equal_sign "$1" "$2")" - [[ $? -eq 0 ]] && shift - do_html=true - ;; - --outfile|--outfile=*|-oa|-oa=*) - ( "$do_html" || "$do_json" || "$do_pretty_json" || "$do_csv" || "$do_logging" ) && fatal "check your arguments four multiple file output options" $ERR_CMDLINE - outfile_arg="$(parse_opt_equal_sign "$1" "$2")" - if [[ "$outfile_arg" != "auto" ]]; then - if [[ -d "$outfile_arg" ]]; then - HTMLFILE="$outfile_arg" - CSVFILE="$outfile_arg" - JSONFILE="$outfile_arg" - LOGFILE="$outfile_arg" - else - HTMLFILE="$outfile_arg.html" - CSVFILE="$outfile_arg.csv" - JSONFILE="$outfile_arg.json" - LOGFILE="$outfile_arg.log" - fi - fi - [[ $? -eq 0 ]] && shift - do_html=true - do_json=true - do_csv=true - do_logging=true - ;; - --outFile|--outFile=*|-oA|-oA=*) - ( "$do_html" || "$do_json" || "$do_pretty_json" || "$do_csv" || "$do_logging" ) && fatal "check your arguments four multiple file output options" $ERR_CMDLINE - outfile_arg="$(parse_opt_equal_sign "$1" "$2")" - if [[ "$outfile_arg" != "auto" ]]; then - if [[ -d "$outfile_arg" ]]; then - HTMLFILE="$outfile_arg" - CSVFILE="$outfile_arg" - JSONFILE="$outfile_arg" - LOGFILE="$outfile_arg" - else - HTMLFILE="$outfile_arg.html" - CSVFILE="$outfile_arg.csv" - JSONFILE="$outfile_arg.json" - LOGFILE="$outfile_arg.log" - fi - fi - [[ $? -eq 0 ]] && shift - do_html=true - do_pretty_json=true - do_csv=true - do_logging=true - ;; - --append) - APPEND=true - ;; - --outprefix) - FNAME_PREFIX="$(parse_opt_equal_sign "$1" "$2")" - if [[ $? -eq 0 ]]; then - shift - case "$(get_last_char "$FNAME_PREFIX")" in - '.') ;; - '-') ;; - '_') ;; - *) FNAME_PREFIX="${FNAME_PREFIX}-" ;; - esac - fi - ;; - --openssl|--openssl=*) - OPENSSL="$(parse_opt_equal_sign "$1" "$2")" - [[ $? -eq 0 ]] && shift - ;; - --openssl-timeout|--openssl-timeout=*) - OPENSSL_TIMEOUT="$(parse_opt_equal_sign "$1" "$2")" - [[ $? -eq 0 ]] && shift - ;; - --connect-timeout|--connect-timeout=*) - CONNECT_TIMEOUT="$(parse_opt_equal_sign "$1" "$2")" - [[ $? -eq 0 ]] && shift - ;; - --mapping|--mapping=*) - cipher_mapping="$(parse_opt_equal_sign "$1" "$2")" - [[ $? -eq 0 ]] && shift - case "$cipher_mapping" in - no-openssl) DISPLAY_CIPHERNAMES="rfc-only" ;; - no-rfc|no-iana) DISPLAY_CIPHERNAMES="openssl-only" ;; - openssl) DISPLAY_CIPHERNAMES="openssl" ;; - rfc|iana) DISPLAY_CIPHERNAMES="rfc" ;; - *) tmln_warning "\nmapping can only be \"no-openssl\", \"no-iana\"(\"no-rfc\"), \"openssl\" or \"iana\"(\"rfc\")" 1>&2 - help 1 ;; - esac - ;; - --proxy|--proxy=*) - PROXY="$(parse_opt_equal_sign "$1" "$2")" - [[ $? -eq 0 ]] && shift - ;; - --phone-out) - PHONE_OUT=true - ;; - -6) # doesn't work automagically. My versions have -DOPENSSL_USE_IPV6, CentOS/RHEL/FC do not - HAS_IPv6=true - ;; - --has[-_]dhbits|--has[_-]dh[-_]bits) - # Should work automagically. Helper switch for CentOS,RHEL+FC w openssl server temp key backport (version 1.0.1), see #190 - HAS_DH_BITS=true - ;; - --ssl_native|--ssl-native) - SSL_NATIVE=true - ;; - --basicauth|--basicauth=*) - BASICAUTH="$(parse_opt_equal_sign "$1" "$2")" - [[ $? -eq 0 ]] && shift - ;; - (--) shift - break - ;; - (-*) tmln_warning "$0: unrecognized option \"$1\"" 1>&2; - help 1 - ;; - (*) break - ;; - esac - shift - done - - # Show usage if no further options were specified - if [[ -z "$1" ]] && [[ -z "$FNAME" ]] && ! "$do_display_only"; then - fatal "URI missing" $ERR_CMDLINE - else - # What is left here should be the URI. - URI="$1" - [[ -n "$2" ]] && fatal "URI comes last" $ERR_CMDLINE - fi - - # Now spot some incompatibilities in cmdlines - [[ $CMDLINE_IP == one ]] && [[ "$NODNS" == none ]] && fatal "\"--ip=one\" and \"--nodns=none\" don't work together" $ERR_CMDLINE - [[ $CMDLINE_IP == one ]] && ( is_ipv4addr "$URI" || is_ipv6addr "$URI" ) && fatal "\"--ip=one\" plus supplying an IP address doesn't work" $ERR_CMDLINE - "$do_mx_all_ips" && [[ "$NODNS" == none ]] && fatal "\"--mx\" and \"--nodns=none\" don't work together" $ERR_CMDLINE - [[ -n "$CONNECT_TIMEOUT" ]] && [[ "$MASS_TESTING_MODE" == parallel ]] && fatal "Parallel mass scanning and specifying connect timeouts currently don't work together" $ERR_CMDLINE - - ADDITIONAL_CA_FILES="${ADDITIONAL_CA_FILES//,/ }" - for fname in $ADDITIONAL_CA_FILES; do - [[ -s "$fname" ]] || fatal "CA file \"$fname\" does not exist" $ERR_RESOURCE - grep -q "BEGIN CERTIFICATE" "$fname" || fatal "\"$fname\" is not CA file in PEM format" $ERR_RESOURCE - done - - [[ "$DEBUG" -ge 5 ]] && debug_globals - - count_do_variables - [[ $? -eq 0 ]] && set_scanning_defaults - CMDLINE_PARSED=true -} - - -# connect call from openssl needs ipv6 in square brackets -nodeip_to_proper_ip6() { - local len_nodeip=0 - - if is_ipv6addr $NODEIP; then - ${UNBRACKTD_IPV6} || NODEIP="[$NODEIP]" - len_nodeip=${#NODEIP} - CORRECT_SPACES="$(printf -- " "'%.s' $(eval "echo {1.."$((len_nodeip - 17))"}"))" - # IPv6 addresses are longer, this variable takes care that "further IP" and "Service" is properly aligned - fi -} - - -reset_hostdepended_vars() { - TLS_EXTENSIONS="" - PROTOS_OFFERED="" - CURVES_OFFERED="" - OPTIMAL_PROTO="" - ALL_FAILED_SOCKETS=true - SERVER_SIZE_LIMIT_BUG=false -} - -# Rough estimate, in the future we maybe want to make use of nano secs (%N). Note this -# is for performance debugging purposes (MEASURE_TIME=yes), eye candy is not important. -# -stopwatch() { - local new_delta - local column=$((COLUMNS - 0)) # for future adjustments - - "$MEASURE_TIME" || return - new_delta=$(( $(date +%s) - LAST_TIME )) - printf "%${column}s" "$1: $new_delta" - [[ -e "$MEASURE_TIME_FILE" ]] && echo "$1 : $new_delta " >> "$MEASURE_TIME_FILE" - LAST_TIME=$(( new_delta + LAST_TIME )) -} - - -# arg1(optional): "init" --> just initializing. Or: STARTTLS protocol -lets_roll() { - local -i ret=0 - local section_number=0 - - if [[ "$1" == init ]]; then - # called once upfront to be able to measure preparation time b4 everything starts - START_TIME=$(date +%s) - LAST_TIME=$START_TIME - [[ -n "$MEASURE_TIME_FILE" ]] && >"$MEASURE_TIME_FILE" - return 0 - fi - stopwatch initialized - - [[ -z "$NODEIP" ]] && fatal "$NODE doesn't resolve to an IP address" $ERR_DNSLOOKUP - nodeip_to_proper_ip6 - reset_hostdepended_vars - determine_rdns # Returns always zero or has already exited if fatal error occurred - stopwatch determine_rdns - - ((SERVER_COUNTER++)) - datebanner " Start" - determine_service "$1" # STARTTLS service? Other will be determined here too. Returns 0 if test connect was ok or has already exited if fatal error occurred - # determine_service() can return 1, it indicates that this IP cannot be reached but there are more IPs to check - if [[ $? -eq 0 ]] ; then - # "secret" devel options --devel: - if "$do_tls_sockets"; then - if [[ "$TLS_LOW_BYTE" == 22 ]]; then - sslv2_sockets "" "true" - else - if [[ "$TLS_LOW_BYTE" == 04 ]]; then - if "$CERT_COMPRESSION"; then - # See PR #1279 - [[ $DEBUG -eq 3 ]] && tmln_out "including TLS extension certificate compression" - tls_sockets "$TLS_LOW_BYTE" "$HEX_CIPHER" "all+" "00,1b, 00,03, 02, 00,01" - else - tls_sockets "$TLS_LOW_BYTE" "$HEX_CIPHER" "ephemeralkey" - fi - else - tls_sockets "$TLS_LOW_BYTE" "$HEX_CIPHER" "all" - fi - fi - echo $? - exit $ALLOK; - fi - if "$do_cipher_match"; then - # we will have an invalid JSON with no if statement - fileout_section_header $section_number false - run_cipher_match ${single_cipher} - stopwatch run_cipher_match - else - fileout_section_header $section_number false && ((section_number++)) - determine_sizelimitbug - fileout_section_footer false - - ((section_number++)) - # all top level functions now following have the prefix "run_" - fileout_section_header $section_number false && ((section_number++)) - "$do_protocols" && { - run_protocols; ret=$(($? + ret)); stopwatch run_protocols; - run_npn; ret=$(($? + ret)); stopwatch run_npn; - run_alpn; ret=$(($? + ret)); stopwatch run_alpn; - } - fileout_section_header $section_number true && ((section_number++)) - "$do_grease" && { run_grease; ret=$(($? + ret)); stopwatch run_grease; } - - fileout_section_header $section_number true && ((section_number++)) - "$do_cipherlists" && { run_cipherlists; ret=$(($? + ret)); stopwatch run_cipherlists; } - - fileout_section_header $section_number true && ((section_number++)) - "$do_pfs" && { run_pfs; ret=$(($? + ret)); stopwatch run_pfs; } - - fileout_section_header $section_number true && ((section_number++)) - "$do_server_preference" && { run_server_preference; ret=$(($? + ret)); stopwatch run_server_preference; } - - fileout_section_header $section_number true && ((section_number++)) - "$do_server_defaults" && { run_server_defaults; ret=$(($? + ret)); stopwatch run_server_defaults; } - - if "$do_header"; then - #TODO: refactor this into functions - fileout_section_header $section_number true && ((section_number++)) - if [[ $SERVICE == HTTP ]]; then - run_http_header "$URL_PATH"; ret=$(($? + ret)) - run_http_date "$URL_PATH"; ret=$(($? + ret)) - run_hsts "$URL_PATH"; ret=$(($? + ret)) - run_hpkp "$URL_PATH"; ret=$(($? + ret)) - run_server_banner "$URL_PATH"; ret=$(($? + ret)) - run_appl_banner "$URL_PATH"; ret=$(($? + ret)) - run_cookie_flags "$URL_PATH"; ret=$(($? + ret)) - run_security_headers "$URL_PATH"; ret=$(($? + ret)) - run_rp_banner "$URL_PATH"; ret=$(($? + ret)) - stopwatch do_header - fi - else - ((section_number++)) - fi - - # vulnerabilities - if [[ $VULN_COUNT -gt $VULN_THRESHLD ]] || "$do_vulnerabilities"; then - outln; pr_headlineln " Testing vulnerabilities " - outln - fi - - fileout_section_header $section_number true && ((section_number++)) - "$do_heartbleed" && { run_heartbleed; ret=$(($? + ret)); stopwatch run_heartbleed; } - "$do_ccs_injection" && { run_ccs_injection; ret=$(($? + ret)); stopwatch run_ccs_injection; } - "$do_ticketbleed" && { run_ticketbleed; ret=$(($? + ret)); stopwatch run_ticketbleed; } - "$do_robot" && { run_robot; ret=$(($? + ret)); stopwatch run_robot; } - "$do_renego" && { run_renego; ret=$(($? + ret)); stopwatch run_renego; } - "$do_crime" && { run_crime; ret=$(($? + ret)); stopwatch run_crime; } - "$do_breach" && { run_breach "$URL_PATH" ; ret=$(($? + ret)); stopwatch run_breach; } - "$do_ssl_poodle" && { run_ssl_poodle; ret=$(($? + ret)); stopwatch run_ssl_poodle; } - "$do_tls_fallback_scsv" && { run_tls_fallback_scsv; ret=$(($? + ret)); stopwatch run_tls_fallback_scsv; } - "$do_sweet32" && { run_sweet32; ret=$(($? + ret)); stopwatch run_sweet32; } - "$do_freak" && { run_freak; ret=$(($? + ret)); stopwatch run_freak; } - "$do_drown" && { run_drown ret=$(($? + ret)); stopwatch run_drown; } - "$do_logjam" && { run_logjam; ret=$(($? + ret)); stopwatch run_logjam; } - "$do_beast" && { run_beast; ret=$(($? + ret)); stopwatch run_beast; } - "$do_lucky13" && { run_lucky13; ret=$(($? + ret)); stopwatch run_lucky13; } - "$do_rc4" && { run_rc4; ret=$(($? + ret)); stopwatch run_rc4; } - - fileout_section_header $section_number true && ((section_number++)) - "$do_allciphers" && { run_allciphers; ret=$(($? + ret)); stopwatch run_allciphers; } - "$do_cipher_per_proto" && { run_cipher_per_proto; ret=$(($? + ret)); stopwatch run_cipher_per_proto; } - - fileout_section_header $section_number true && ((section_number++)) - "$do_client_simulation" && { run_client_simulation; ret=$(($? + ret)); stopwatch run_client_simulation; } - fi - fileout_section_footer true - fi - - outln - calc_scantime - datebanner " Done" - - # reset the failed connect counter as we are finished - NR_SOCKET_FAIL=0 - NR_OSSL_FAIL=0 - - "$MEASURE_TIME" && printf "$1: %${COLUMNS}s\n" "$SCAN_TIME" - [[ -e "$MEASURE_TIME_FILE" ]] && echo "Total : $SCAN_TIME " >> "$MEASURE_TIME_FILE" - - return $ret -} - - - -################# main ################# - - - RET=0 # this is a global as we can have a function main(), see #705. Should we toss then all local $ret? - ip="" - stopwatch start - - lets_roll init - initialize_globals - check_base_requirements # needs to come after $do_html is defined - parse_cmd_line "$@" - # CMDLINE_PARSED has been set now. Don't put a function immediately after this which calls fatal(). - # Rather put it after csv_header below. - # html_header() needs to be called early! Otherwise if html_out() is called before html_header() and the - # command line contains --htmlfile <htmlfile> or --html, it'll make problems with html output, see #692. - # json_header and csv_header could be called later but for context reasons we'll leave it here - html_header - json_header - csv_header - get_install_dir - # see #705, we need to source TLS_DATA_FILE here instead of in get_install_dir(), see #705 - [[ -r "$TLS_DATA_FILE" ]] && . "$TLS_DATA_FILE" - set_color_functions - maketempf - find_openssl_binary - choose_printf - check_resolver_bins - prepare_debug ; stopwatch parse - prepare_arrays ; stopwatch prepare_arrays - mybanner - check_proxy - check4openssl_oldfarts - check_bsd_mount - - - if "$do_display_only"; then - prettyprint_local "$PATTERN2SHOW" - exit $? - fi - fileout_banner - - if "$do_mass_testing"; then - prepare_logging - if [[ "$MASS_TESTING_MODE" == parallel ]]; then - run_mass_testing_parallel - else - run_mass_testing - fi - exit $? - fi - html_banner - - #TODO: there shouldn't be the need for a special case for --mx, only the ip addresses we would need upfront and the do-parser - if "$do_mx_all_ips"; then - #FIXME: do we need this really here? - count_do_variables # if we have just 1x "do_*" --> we do a standard run -- otherwise just the one specified - [[ $? -eq 1 ]] && set_scanning_defaults - run_mx_all_ips "${URI}" $PORT # we should reduce run_mx_all_ips to what's necessary as below we have similar code - exit $? - fi - - [[ -z "$NODE" ]] && parse_hn_port "${URI}" # NODE, URL_PATH, PORT, IPADDRs and IP46ADDR is set now - prepare_logging - - if ! determine_ip_addresses; then - fatal "No IP address could be determined" $ERR_DNSLOOKUP - fi - if [[ $(count_words "$IPADDRs") -gt 1 ]]; then # we have more than one ipv4 address to check - MULTIPLE_CHECKS=true - pr_bold "Testing all IPv4 addresses (port $PORT): "; outln "$IPADDRs" - for ip in $IPADDRs; do - draw_line "-" $((TERM_WIDTH * 2 / 3)) - outln - NODEIP="$ip" - lets_roll "${STARTTLS_PROTOCOL}" - RET=$((RET + $?)) # RET value per IP address - done - draw_line "-" $((TERM_WIDTH * 2 / 3)) - outln - pr_bold "Done testing now all IP addresses (on port $PORT): "; outln "$IPADDRs" - else # Just 1x ip4v to check, applies also if CMDLINE_IP was supplied - NODEIP="$IPADDRs" - lets_roll "${STARTTLS_PROTOCOL}" - RET=$? - fi - -exit $RET - |