#!/usr/bin/env bash # # bash is needed for some distros which use dash as /bin/sh and for tcp sockets which # this program uses a couple of times. Also some expressions are bashisms as I expect # them to be faster. Idea is to not overdo it though. # testssl.sh is a program for spotting weak SSL encryption, ciphers, version and some # vulnerablities or features # # Devel version is availabe from https://github.com/drwetter/testssl.sh # Stable version from https://testssl.sh # Please file bugs at github! VERSION="2.5dev" SWURL="https://testssl.sh" SWCONTACT="dirk aet testssl dot sh" # Main author: Dirk Wetter, copyleft: 2007-2015, contributions so far see CREDIT.md # # License: GPLv2, see http://www.fsf.org/licensing/licenses/info/GPLv2.html # and accompanying license "LICENSE.txt". Redistribution + modification under this # license permitted. # If you enclose this script or parts of it in your software, it has to # be accompanied by the same license (see link) and the place where to get # the recent version of this program. Don't violate the license! # # USAGE WITHOUT ANY WARRANTY, THE SOFTWARE IS PROVIDED "AS IS". USE IT AT # your OWN RISK! # HISTORY: I know this shell script is still on its way to be nice and readable. ;-) It # all started with a few openssl commands around 2006. That's because openssl is a such a good # swiss army knife (see e.g. wiki.openssl.org/index.php/Command_Line_Utilities) that it was # difficult to resist wrapping # with some shell commandos around it. This is how everything started. # Now it has grown up, it has bash socket support for some features which basically replacing # more and more functions of OpenSSL and will serve as some kind of library in the future. # 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 intgeractive shell: zsh (zmodload zsh/net/socket # -- checkout zsh/net/tcp too!) But bash is way more often used within Linux and it's perfect # for cross plattform support, see MacOS X and Windows MSYS2 extenstion. # # Q: So what's the difference to www.ssllabs.com/ssltest or sslcheck.globalsign.com/? # A: As of now ssllabs only check webservers on standard ports, reachable from # the internet. And the examples above are 3rd parties. If those restrictions are fine # with you, and you need a management compatible rating -- go ahead and use those. # Also testssl.sh is meant as a tool in your hand and it's way more flexible. # # Note that for "standard" openssl binaries a lot of features (ciphers, protocols, vulnerabilities) # are disabled as they'll impact security otherwise. For security testing though we need # all b0rken features. testssl.sh will over time replace those checks with bash sockets -- # however it's still recommended to use the supplied binaries or cook your own, see # https://github.com/drwetter/testssl.sh/blob/master/openssl-bins/openssl-1.0.2-chacha.pm/Readme.md # Don't worry if feature X is not available you'll get a warning about this missing feature! readonly PROG_NAME=$(basename "$0") PROG_DIR=$(readlink "$BASH_SOURCE") 2>/dev/null readonly RUN_DIR=$(dirname $0) # following variables make use of $ENV, e.g. OPENSSL= ./testssl.sh # 0 means (normally) true here. Some of the variables are also accessible with a command line switch COLOR=${COLOR:-2} # 2: Full color, 1: b/w+positioning, 0: no ESC at all SHOW_LOC_CIPH=${SHOW_LOC_CIPH:-1} # will client side ciphers displayed before an individual test (makes no sense normally) SHOW_EACH_C=${SHOW_EACH_C:-0} # where individual ciphers are tested show just the positively ones tested #FIXME: wrong value SNEAKY=${SNEAKY:-1} # if zero: the referer and useragent we leave while checking the http header is just usual SSL_NATIVE=${SSL_NATIVE:-1} # we do per default bash sockets where possible 0: switch back to native openssl ASSUMING_HTTP=${ASSUMING_HTTP:-1} # in seldom cases (WAF, old servers/grumpy SSL) the service detection fails. Set to 0 for forcing HTTP DEBUG=${DEBUG:-0} # if 1 the temp files won't be erased. 2: list more what's going on (formerly: eq VERBOSE=1), # 3: slight hexdumps + other info, 4: send bytes via sockets, 5: received, 6: whole 9 yards #FIXME: still to be filled with (more) sense or following to be included: VERBERR=${VERBERR:-1} # 0 means to be more verbose (handshake errors to be displayed so that one can tell better # whether handshake succeeded or not. While testing individual ciphers you also need to have SHOW_EACH_C=1 LONG=${LONG:-1} # whether to display for some options the cipher or the table with hexcode/KX,Enc,strength etc. HEADER_MAXSLEEP=${HEADER_MAXSLEEP:-5} # we wait this long before killing the process to retrieve a service banner / http header readonly MAX_WAITSOCK=10 # waiting at max 10 seconds for socket reply readonly CCS_MAX_WAITSOCK=5 # for the two CCS payload (each) readonly HEARTBLEED_MAX_WAITSOCK=8 # for the heartbleed payload USLEEP_SND=${USLEEP_SND:-0.1} # sleep time for general socket send USLEEP_REC=${USLEEP_REC:-0.2} # sleep time for general socket receive CAPATH="${CAPATH:-/etc/ssl/certs/}" # Does nothing yet (FC has only a CA bundle per default, ==> openssl version -d) readonly HSTS_MIN=179 # >179 days is ok for HSTS readonly HPKP_MIN=30 # >=30 days should be ok for HPKP_MIN, practical hints? readonly CLIENT_MIN_PFS=5 # number of ciphers needed to run a test for PFS readonly DAYS2WARN1=60 # days to warn before cert expires, threshold 1 readonly DAYS2WARN2=30 # days to warn before cert expires, threshold 2 # more global vars, here just declared readonly ECHO="/usr/bin/printf --" # works under Linux, BSD, MacOS. TERM_DWITH=${COLUMNS:-$(tput cols)} # for future costum line wrapping TERM_CURRPOS=0 # ^^^ we also need to find out the length or current pos in the line readonly SYSTEM=$(uname -s) # OS readonly NPN_PROTOs="spdy/4a2,spdy/3,spdy/3.1,spdy/2,spdy/1,http/1.1" TEMPDIR="" TMPFILE="" HOSTCERT="" HEADERFILE="" HEADERFILE_BREACH="" LOGFILE="" TLS_PROTO_OFFERED="" DETECTED_TLS_VERSION="" SOCKREPLY="" SOCK_REPLY_FILE="" HEXC="" NW_STR="" LEN_STR="" SNI="" IP4="" IP6="" OSSL_VER="" # openssl version, will be autodetermined OSSL_VER_MAJOR=0 OSSL_VER_MINOR=0 OSSL_VER_APPENDIX="none" NODEIP="" VULN_COUNT=0 readonly VULN_THRESHLD=1 # if bigger than this no we show a separate header in blue IPS="" SERVICE="" # is the server running an HTTP server, SMTP, POP or IMAP? URI="" STARTTLS_PROTOCOL="" OPTIMAL_PROTO="" # we need this for IIS6 (sigh) and OpenSSL 1.02, otherwise some handshakes will fail, see https://github.com/PeterMosmans/openssl/issues/19#issuecomment-100897892 TLS_TIME="" TLS_NOW="" HTTP_TIME="" GET_REQ11="" HEAD_REQ10="" readonly UA_SNEAKY="Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)" readonly UA_STD="Mozilla/5.0 (X11; Linux x86_64; rv:42.0) Gecko/19700101 Firefox/42.0" # Devel stuff, see -q below TLS_LOW_BYTE="" HEX_CIPHER="" # debugging help: #PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' readonly PS4='${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' # make sure that temporary files are cleaned up after use trap "cleanup" QUIT EXIT # The various hexdump commands we need to replace xxd (BSD compatability)) HEXDUMPVIEW=(hexdump -C) # This is used in verbose mode to see what's going on HEXDUMP=(hexdump -ve '16/1 "%02x " " \n"') # This is used to analyse the reply HEXDUMPPLAIN=(hexdump -ve '1/1 "%.2x"') # Replaces both xxd -p and tr -cd '[:print:]' ###### some hexbytes for bash network sockets ###### # 133 standard cipher + 4x GOST for TLS 1.2 and SPDY/NPN readonly TLS12_CIPHER=" cc,14, cc,13, cc,15, c0,30, c0,2c, c0,28, c0,24, c0,14, c0,0a, c0,22, c0,21, c0,20, 00,a5, 00,a3, 00,a1, 00,9f, 00,6b, 00,6a, 00,69, 00,68, 00,39, 00,38, 00,37, 00,36, 00,80, 00,81, 00,82, 00,83, c0,77, c0,73, 00,c4, 00,c3, 00,c2, 00,c1, 00,88, 00,87, 00,86, 00,85, c0,32, c0,2e, c0,2a, c0,26, c0,0f, c0,05, c0,79, c0,75, 00,9d, 00,3d, 00,35, 00,c0, 00,84, c0,2f, c0,2b, c0,27, c0,23, c0,13, c0,09, c0,1f, c0,1e, c0,1d, 00,a4, 00,a2, 00,a0, 00,9e, 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,31, c0,2d, c0,29, c0,25, c0,0e, c0,04, c0,78, c0,74, 00,9c, 00,3c, 00,2f, 00,ba, 00,96, 00,41, 00,07, c0,11, c0,07, 00,66, c0,0c, c0,02, 00,05, 00,04, c0,12, c0,08, c0,1c, c0,1b, c0,1a, 00,16, 00,13, 00,10, 00,0d, c0,0d, c0,03, 00,0a, 00,63, 00,15, 00,12, 00,0f, 00,0c, 00,62, 00,09, 00,65, 00,64, 00,14, 00,11, 00,0e, 00,0b, 00,08, 00,06, 00,03, 00,ff" # 76 standard cipher +4x GOST for SSLv3, TLS 1, TLS 1.1 readonly TLS_CIPHER=" c0,14, c0,0a, c0,22, c0,21, c0,20, 00,39, 00,38, 00,37, 00,36, 00,88, 00,87, 00,86, 00,85, c0,0f, c0,05, 00,35, 00,84, c0,13, c0,09, c0,1f, c0,1e, c0,1d, 00,33, 00,32, 00,80, 00,81, 00,82, 00,83, 00,31, 00,30, 00,9a, 00,99, 00,98, 00,97, 00,45, 00,44, 00,43, 00,42, c0,0e, c0,04, 00,2f, 00,96, 00,41, 00,07, c0,11, c0,07, 00,66, c0,0c, c0,02, 00,05, 00,04, c0,12, c0,08, c0,1c, c0,1b, c0,1a, 00,16, 00,13, 00,10, 00,0d, c0,0d, c0,03, 00,0a, 00,63, 00,15, 00,12, 00,0f, 00,0c, 00,62, 00,09, 00,65, 00,64, 00,14, 00,11, 00,0e, 00,0b, 00,08, 00,06, 00,03, 00,ff" readonly SSLv2_CLIENT_HELLO=" ,80,34 # length (here: 52) ,01 # Client Hello ,00,02 # SSLv2 ,00,1b # cipher spec length (here: 27 ) ,00,00 # session ID length ,00,10 # challenge length ,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 ,00,00,00 # 9th ,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) # FIXME: http://max.euston.net/d/tip_sslciphers.html ###### output functions ###### out() { $ECHO "$1" } outln() { [[ -z "$1" ]] || $ECHO "$1" $ECHO "\n" } # color print functions, see also http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html pr_off() { [[ "$COLOR" -ne 0 ]] && out "\033[m\c" } pr_liteblueln() { pr_liteblue "$1"; outln; } pr_liteblue() { [[ "$COLOR" -eq 2 ]] && out "\033[0;34m$1" || out "$1" pr_off } pr_blueln() { pr_blue "$1"; outln; } pr_blue() { [[ "$COLOR" -eq 2 ]] && out "\033[1;34m$1" || out "$1" pr_off } pr_literedln() { pr_litered "$1"; outln; } pr_litered() { [[ "$COLOR" -eq 2 ]] && out "\033[0;31m$1" || pr_bold "$1" pr_off } pr_redln() { pr_red "$1"; outln; } pr_red() { [[ "$COLOR" -eq 2 ]] && out "\033[1;31m$1" || pr_bold "$1" pr_off } pr_litemagentaln() { pr_litemagenta "$1"; outln; } pr_litemagenta() { [[ "$COLOR" -eq 2 ]] && out "\033[0;35m$1" || pr_underline "$1" pr_off } pr_magentaln() { pr_magenta "$1"; outln; } pr_magenta() { [[ "$COLOR" -eq 2 ]] && out "\033[1;35m$1" || pr_underline "$1" pr_off } pr_litecyanln() { pr_litecyan "$1"; outln; } pr_litecyan() { [[ "$COLOR" -eq 2 ]] && out "\033[0;36m$1" || out "$1" pr_off } pr_cyanln() { pr_cyan "$1"; outln; } pr_cyan() { [[ "$COLOR" -eq 2 ]] && out "\033[1;36m$1" || out "$1" pr_off } pr_greyln() { pr_grey "$1"; outln; } pr_grey() { [[ "$COLOR" -eq 2 ]] && out "\033[1;30m$1" || out "$1" pr_off } pr_litegreyln() { pr_litegrey "$1"; outln; } pr_litegrey() { [[ "$COLOR" -eq 2 ]] && out "\033[0;37m$1" || out "$1" pr_off } pr_litegreenln() { pr_litegreen "$1"; outln; } pr_litegreen() { [[ "$COLOR" -eq 2 ]] && out "\033[0;32m$1" || out "$1" pr_off } pr_greenln() { pr_green "$1"; outln; } pr_green() { [[ "$COLOR" -eq 2 ]] && out "\033[1;32m$1" || out "$1" pr_off } pr_brownln() { pr_brown "$1"; outln; } pr_brown() { [[ "$COLOR" -eq 2 ]] && out "\033[0;33m$1" || out "$1" pr_off } pr_yellowln() { pr_yellow "$1"; outln; } pr_yellow() { [[ "$COLOR" -eq 2 ]] && out "\033[1;33m$1" || out "$1" pr_off } pr_boldln() { pr_bold "$1" ; outln; } pr_bold() { [[ "$COLOR" -ne 0 ]] && out "\033[1m$1" || out "$1" ; pr_off; } pr_underline() { [[ "$COLOR" -ne 0 ]] && out "\033[4m$1" || out "$1" ; pr_off; } pr_boldandunder() { [[ "$COLOR" -ne 0 ]] && out "\033[1m\033[4m$1" || out "$1" ; pr_off; } pr_reverse() { [[ "$COLOR" -ne 0 ]] && out "\033[7m$1" || out "$1"; pr_off; } ### colorswitcher (see e.g. https://linuxtidbits.wordpress.com/2008/08/11/output-color-on-bash-scripts/ ### http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html # empty vars if we have no color: red="" green="" brown="" blue="" cyan="" off="" bold="" underline="" if [[ "$COLOR" -eq 2 ]]; then red=$(tput setaf 1) green=$(tput setaf 2) brown=$(tput setaf 3) blue=$(tput setaf 4) yellow=$(tput setaf 3; tput bold) off=$(tput sgr0) fi if [[ "$COLOR" -ge 1 ]]; then bold=$(tput bold) underline=$(tput sgr 0 1) fi ###### helper function definitions ###### debugme() { [[ $DEBUG -ge 2 ]] && "$@" } tmpfile_handle() { if [[ "$DEBUG" -eq 0 ]] ; then rm $TMPFILE else mv $TMPFILE "$TEMPDIR/$1" fi } # whether it is ok to offer/not to offer enc/cipher/version ok(){ if [ "$2" -eq 1 ] ; then case $1 in 1) pr_redln "offered (NOT ok)" ;; # 1 1 0) pr_greenln "not offered (OK)" ;; # 0 1 esac else case $1 in 7) pr_brownln "not offered" ;; # 7 0 6) pr_literedln "offered (NOT ok)" ;; # 6 0 5) pr_litered "supported but couldn't detect a cipher"; outln "(may need debugging)" ;; # 5 5 4) pr_litegreenln "offered (OK)" ;; # 4 0 3) pr_brownln "offered" ;; # 3 0 2) outln "offered" ;; # 2 0 1) pr_greenln "offered (OK)" ;; # 1 0 0) pr_boldln "not offered" ;; # 0 0 esac fi return $2 } # ARG1= pid which is in the backgnd and we wait for ($2 seconds) wait_kill(){ pid=$1 maxsleep=$2 while true; do if ! ps $pid >/dev/null ; then return 0 # didn't reach maxsleep yet fi sleep 1 maxsleep=$((maxsleep - 1)) test $maxsleep -eq 0 && break done # needs to be killed: kill $pid >&2 2>/dev/null wait $pid 2>/dev/null #FIXME: do we need wait here???? normally it's good to report the exit status?! return 3 # killed } ###### 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 runs_HTTP() { # SNI is nonsense for !HTTPS but fortunately other protocols don't seem to care printf "$GET_REQ11" | $OPENSSL s_client $1 -quiet -connect $NODEIP:$PORT $SNI &>$TMPFILE & wait_kill $! $HEADER_MAXSLEEP head $TMPFILE | grep -aq ^HTTP && SERVICE=HTTP head $TMPFILE | grep -aq SMTP && SERVICE=SMTP head $TMPFILE | grep -aq POP && SERVICE=POP head $TMPFILE | grep -aq IMAP && SERVICE=IMAP debugme head $TMPFILE # $TMPFILE contains also a banner which we could use if there's a need for it out " Service detected: " case $SERVICE in HTTP) out " $SERVICE" ret=0 ;; IMAP|POP|SMTP) out " $SERVICE, thus skipping HTTP specific checks" ret=0 ;; *) out " Couldn't determine what's running on port $PORT" if [[ $ASSUMING_HTTP -eq 0 ]]; then SERVICE=HTTP out " -- ASSUMING_HTTP set though" ret=0 else out ", assuming not HTTP, skipping HTTP checks" ret=1 fi ;; esac outln tmpfile_handle $FUNCNAME.txt return $ret } #problems not handled: chunked http_header() { outln; pr_blue "--> Testing HTTP header response"; outln "\n" [ -z "$1" ] && url="/" || url="$1" if [ $SNEAKY -eq 0 ] ; then referer="http://google.com/" useragent="$UA_SNEAKY" else referer="TLS/SSL-Tester from $SWURL" useragent="$UA_STD" fi ( $OPENSSL s_client $OPTIMAL_PROTO -quiet -connect $NODEIP:$PORT $SNI << EOF GET $url HTTP/1.1 Host: $NODE Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.7,de-de;q=0.3 User-Agent: $useragent Referer: $referer Connection: close EOF ) &>$HEADERFILE & pid=$! if wait_kill $pid $HEADER_MAXSLEEP; then if ! egrep -iaq "XML|HTML|DOCTYPE|HTTP|Connection" $HEADERFILE; then pr_litemagenta " likely HTTP header requests failed (#lines: $(wc -l < $HEADERFILE | sed 's/ //g'))." outln "Rerun with DEBUG=1 and inspect \"http_header.txt\"\n" debugme cat $HEADERFILE ret=7 fi sed -e '/^$HEADERFILE.2 #### ^^^ Attention: the filtering for the html body only as of now, doesn't work for other content yet mv $HEADERFILE.2 $HEADERFILE # sed'ing in place doesn't work with BSD and Linux simultaneously ret=0 else pr_litemagentaln " failed (HTTP header request stalled)" ret=3 fi if egrep -aq "^HTTP.1.. 301|^HTTP.1.. 302|^Location" $HEADERFILE; then redir2=$(grep -a '^Location' $HEADERFILE | sed 's/Location: //' | tr -d '\r\n') outln " (got 30x to $redir2 - may be better try this URL?)\n" fi if egrep -aq "^HTTP.1.. 401|^WWW-Authenticate" $HEADERFILE; then outln " (got 401 / WWW-Authenticate, can't look beyond it)\n" fi [[ $DEBUG -eq 0 ]] && rm $HEADERFILE.2 2>/dev/null return $ret } includeSubDomains() { if grep -aiqw includeSubDomains "$1"; then pr_litegreen ", includeSubDomains" else pr_litecyan ", just this domain" fi } preload() { grep -aiqw preload "$1" && pr_litegreen ", preload" } hsts() { local hsts_age_sec local hsts_age_days if [ ! -s $HEADERFILE ] ; then http_header "$1" || return 3 fi pr_bold " HSTS " grep -iaw '^Strict-Transport-Security' $HEADERFILE >$TMPFILE if [ $? -eq 0 ]; then grep -aciw '^Strict-Transport-Security' $HEADERFILE | egrep -waq "1" || out "(two HSTS header, using 1st one) " hsts_age_sec=$(sed -e 's/[^0-9]*//g' $TMPFILE | head -1) #FIXME: test for number! hsts_age_days=$(( hsts_age_sec / 86400)) if [ $hsts_age_days -gt $HSTS_MIN ]; then pr_litegreen "$hsts_age_days days" ; out "=$hsts_age_sec s" else out "$hsts_age_sec s = " pr_brown "$hsts_age_days days (<$HSTS_MIN is not good enough)" fi includeSubDomains "$TMPFILE" preload "$TMPFILE" #FIXME: To be checked against e.g. https://dxr.mozilla.org/mozilla-central/source/security/manager/boot/src/nsSTSPreloadList.inc # and https://chromium.googlesource.com/chromium/src/+/master/net/http/transport_security_state_static.json else out "--" fi outln tmpfile_handle $FUNCNAME.txt return $? } hpkp() { local -i hpkp_age_sec local -i hpkp_age_days local -i hpkp_nr_keys local hpkp_key hpkp_key_hostcert local spaces=" " local -i key_found=1 if [ ! -s $HEADERFILE ] ; then http_header "$1" || return 3 fi pr_bold " HPKP " egrep -aiw '^Public-Key-Pins|Public-Key-Pins-Report-Only' $HEADERFILE >$TMPFILE if [ $? -eq 0 ]; then egrep -aciw '^Public-Key-Pins|Public-Key-Pins-Report-Only' $HEADERFILE | egrep -waq "1" || out "(two HPKP headers, using 1st one) " # dirty trick so that grep -c really counts occurances and not lines w/ occurances: hpkp_nr_keys=$(sed 's/pin-sha/pin-sha\n/g' < $TMPFILE | grep -ac pin-sha) if [ $hpkp_nr_keys -eq 1 ]; then pr_litered "One key is not sufficent, " fi hpkp_age_sec=$(sed -e 's/\r//g' -e 's/^.*max-age=//' -e 's/;.*//' $TMPFILE) #FIXME: test for bumber! hpkp_age_days=$((hpkp_age_sec / 86400)) if [ $hpkp_age_days -ge $HPKP_MIN ]; then pr_litegreen "$hpkp_age_days days" ; out "=$hpkp_age_sec s" else out "$hpkp_age_sec s = " pr_brown "$hpkp_age_days days (<$HPKP_MIN is not good enough)" fi includeSubDomains "$TMPFILE" preload "$TMPFILE" # get the key fingerprints sed -i -e 's/Public-Key-Pins://g' -e s'/Public-Key-Pins-Report-Only://' $TMPFILE [ -s "$HOSTCERT" ] || get_host_cert hpkp_key_hostcert="$($OPENSSL x509 -in $HOSTCERT -pubkey -noout | grep -v PUBLIC | \ $OPENSSL base64 -d | $OPENSSL dgst -sha256 -binary | $OPENSSL base64)" while read hpkp_key; do if [ "$hpkp_key_hostcert" = "$hpkp_key" ] || [ "$hpkp_key_hostcert" = "$hpkp_key=" ]; then out "\n$spaces matching key: " pr_litegreen "$hpkp_key" key_found=0 fi debugme echo "$hpkp_key | $hpkp_key_hostcert" done < <(sed -e 's/;/\n/g' -e 's/ //g' -e 's/\"//g' $TMPFILE | awk -F'=' '/pin.*=/ { print $2 }') [ $key_found -ne 0 ] && out "\n$spaces " && pr_litered "No matching key for pin found" else out "--" fi outln tmpfile_handle $FUNCNAME.txt return $? } emphasize_stuff_in_headers(){ # see http://www.grymoire.com/Unix/Sed.html#uh-3 # outln "$1" | sed "s/[0-9]*/$brown&$off/g" outln "$1" | sed -e "s/\([0-9]\)/$brown\1$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/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/X-Powered-By: ASP.NET/"$yellow"X-Powered-By: ASP.NET$off/g" \ -e "s/X-Powered-By/"$yellow"X-Powered-By$off/g" \ -e "s/X-AspNet-Version/"$yellow"X-AspNet-Version$off/g" } serverbanner() { if [ ! -s $HEADERFILE ] ; then http_header "$1" || return 3 fi pr_bold " Server " grep -ai '^Server' $HEADERFILE >$TMPFILE if [ $? -eq 0 ]; then serverbanner=$(sed -e 's/^Server: //' -e 's/^server: //' $TMPFILE) if [ x"$serverbanner" == "x\n" -o x"$serverbanner" == "x\n\r" -o x"$serverbanner" == "x" ]; then outln "banner exists but empty string" else emphasize_stuff_in_headers "$serverbanner" [[ "$serverbanner" = *Microsoft-IIS/6.* ]] && [[ $OSSL_VER == 1.0.2* ]] && \ pr_litemagentaln " It's recommended to run another test w/ OpenSSL 1.01 !" # see https://github.com/PeterMosmans/openssl/issues/19#issuecomment-100897892 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!)" fi tmpfile_handle $FUNCNAME.txt return $? } applicationbanner() { if [ ! -s $HEADERFILE ] ; then http_header "$1" || return 3 fi pr_bold " Application " # examples: dev.testssl.sh, php.net, asp.net , www.regonline.com egrep -ai '^X-Powered-By|^X-AspNet-Version|^X-Version' $HEADERFILE >$TMPFILE if [ $? -ne 0 ]; then outln " (no banner at \"$url\")" else #cat $TMPFILE | sed 's/^.*:/:/' | sed -e :a -e '$!N;s/\n:/ \n\ +/;ta' -e 'P;D' | sed 's/://g' #sed 's/^/ /g' $TMPFILE | tr -t '\n\r' ' ' | sed "s/\([0-9]\)/$pr_red\1$off/g" emphasize_stuff_in_headers "$(sed 's/^/ /g' $TMPFILE | tr '\n\r' ' ')" #i=0 #cat $TMPFILE | sed 's/^/ /' | while read line; do # out "$line" # if [[ $i -eq 0 ]] ; then # out " " # i=1 # fi #done fi tmpfile_handle $FUNCNAME.txt return $? } cookieflags() { # ARG1: Path, ARG2: path if [ ! -s $HEADERFILE ] ; then http_header "$1" || return 3 fi pr_bold " Cookie(s) " grep -ai '^Set-Cookie' $HEADERFILE >$TMPFILE if [ $? -eq 0 ]; then nr_cookies=$(wc -l < $TMPFILE | sed 's/ //g') out "$nr_cookies issued: " 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_brown "$negative_word" ;; [123456789]) pr_litegreen "$nr_secure/$nr_cookies";; esac out " secure, " nr_httponly=$(grep -cai httponly $TMPFILE) case $nr_httponly in 0) pr_brown "$negative_word" ;; [123456789]) pr_litegreen "$nr_httponly/$nr_cookies";; esac out " HttpOnly" else out "(none issued at \"$url\")" fi outln tmpfile_handle $FUNCNAME.txt return 0 } moreflags() { local good_flags2test="X-Frame-Options X-XSS-Protection X-Content-Type-Options Content-Security-Policy X-Content-Security-Policy X-WebKit-CSP" local other_flags2test="Access-Control-Allow-Origin Via Upgrade X-Served-By" local egrep_pattern="" local f2t result_str local blanks=" " if [ ! -s $HEADERFILE ] ; then http_header "$1" || return 3 fi pr_bold " Security headers " egrep_pattern=$(echo "$good_flags2test $other_flags2test"| sed -e 's/ /|\^/g' -e 's/^/\^/g') # space -> |^ egrep -ai $egrep_pattern $HEADERFILE >$TMPFILE if [ $? -ne 0 ]; then outln "(none at \"$url\")" ret=1 else ret=0 first=true for f2t in $good_flags2test; do result_str=$(grep -i "^$f2t" $TMPFILE) [ -z "$result_str" ] && continue if ! $first; then out "$blanks" # output leading spaces if the first header else first=false fi if [ $(echo "$result_str" | wc -l | sed 's/ //g') -eq 1 ]; then pr_litegreenln "$result_str" else # for the case we have two times the same header: # exchange the linefeeds between the two lines only: pr_litecyan "double -->" ; echo "$result_str" | tr '\n\r' ' | ' | sed 's/| $//g' pr_litecyanln "<-- double" #FIXME: https://report-uri.io has double here fi done # now the same with other flags for f2t in $other_flags2test; do result_str=$(grep -i "^$f2t" $TMPFILE) [ -z "$result_str" ] && continue if $first; then outln "$result_str" first=false else out "$blanks"; outln "$result_str" fi done fi #FIXME: I am not testing for the correctness or anything stupid yet, e.g. "X-Frame-Options: allowall" tmpfile_handle $FUNCNAME.txt return $ret } # #1: string with 2 opensssl codes, HEXC= same in NSS/ssllab terminology normalize_ciphercode() { part1=$(echo "$1" | awk -F',' '{ print $1 }') part2=$(echo "$1" | awk -F',' '{ print $2 }') part3=$(echo "$1" | awk -F',' '{ print $3 }') if [ "$part1" == "0x00" ] ; then # leading 0x00 HEXC=$part2 else part2=$(echo $part2 | sed 's/0x//g') if [ -n "$part3" ] ; then # a SSLv2 cipher has three parts part3=$(echo $part3 | sed 's/0x//g') fi HEXC="$part1$part2$part3" fi HEXC=$(echo $HEXC | tr 'A-Z' 'a-z' | sed 's/0x/x/') #tolower + strip leading 0 return 0 } prettyprint_local() { pr_blue "--> Displaying all local ciphers"; if [ ! -z "$1" ]; then pr_blue "matching word pattern "\"$1\"" (ignore case)"; fi outln "\n" neat_header if [ -z "$1" ]; then $OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL:@STRENGTH' | while read hexcode dash ciph sslvers kx auth enc mac export ; do # -V doesn't work with openssl < 1.0 normalize_ciphercode $hexcode neat_list $HEXC $ciph $kx $enc outln done else for arg in $(echo $@ | sed 's/,/ /g'); do $OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL:@STRENGTH' | while read hexcode dash ciph sslvers kx auth enc mac export ; do # -V doesn't work with openssl < 1.0 normalize_ciphercode $hexcode neat_list $HEXC $ciph $kx $enc | grep -wai "$arg" done done fi outln return 0 } # list ciphers (and makes sure you have them locally configured) # arg[1]: cipher list (or anything else) listciphers() { $OPENSSL ciphers $1 &>$TMPFILE ret=$? debugme cat $TMPFILE tmpfile_handle $FUNCNAME.txt return $ret } # argv[1]: cipher list to test # argv[2]: string on console # argv[3]: ok to offer? 0: yes, 1: no std_cipherlists() { out "$2 "; if listciphers $1; then # is that locally available?? [ $SHOW_LOC_CIPH -eq 0 ] && out "local ciphers are: " && sed 's/:/, /g' $TMPFILE $OPENSSL s_client -cipher "$1" $STARTTLS -connect $NODEIP:$PORT $SNI 2>$TMPFILE >/dev/null pr_red else #ok 0 0 # was not offered, that's ok ok 0 1 # was not offered --> pr_green fi ;; esac tmpfile_handle $FUNCNAME.txt else singlespaces=$(echo "$2" | sed -e 's/ \+/ /g' -e 's/^ //' -e 's/ $//g' -e 's/ //g') pr_magentaln "Local problem: No $singlespaces configured in $OPENSSL" fi # we need lf in those cases: [[ $DEBUG -ge 2 ]] && echo } # sockets inspired by http://blog.chris007.de/?p=238 # ARG1: hexbyte with a leading comma (!!), seperated by commas # ARG2: sleep socksend() { # the following works under BSD and Linux, which is quite tricky. So don't mess with it unless you're really sure what you do data=$(echo "$1" | sed -e 's/# .*$//g' -e 's/ //g' | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//; /^$/d' | sed 's/,/\\/g' | tr -d '\n') [[ $DEBUG -ge 4 ]] && echo "\"$data\"" printf -- "$data" >&5 2>/dev/null & sleep $2 } #FIXME: This is only for HB and CCS, others use sockread_serverhello() sockread() { [ "x$2" = "x" ] && maxsleep=$MAX_WAITSOCK || maxsleep=$2 ret=0 ddreply=$(mktemp $TEMPDIR/ddreply.XXXXXX) || return 7 dd bs=$1 of=$ddreply count=1 <&5 2>/dev/null & pid=$! wait_kill $pid $maxsleep ret=$? SOCKREPLY=$(cat $ddreply) rm $ddreply return $ret } show_rfc_style(){ [ ! -r "$MAP_RFC_FNAME" ] && return 1 RFCname=$(grep -iw $1 "$MAP_RFC_FNAME" | sed -e 's/^.*TLS/TLS/' -e 's/^.*SSL/SSL/') [[ -n "$RFCname" ]] && out "$RFCname" return 0 } neat_header(){ outln "Hexcode Cipher Suite Name (OpenSSL) KeyExch. Encryption Bits${MAP_RFC_FNAME:+ Cipher Suite Name (RFC)}" outln "%s-------------------------------------------------------------------------${MAP_RFC_FNAME:+----------------------------------------------}" } neat_list(){ kx=$(echo $3 | sed 's/Kx=//g') enc=$(echo $4 | sed 's/Enc=//g') strength=$(echo $enc | sed -e 's/.*(//' -e 's/)//') # strength = encryption bits strength=$(echo $strength | sed -e 's/ChaCha20-Poly1305/ly1305/g') # workaround for empty bits ChaCha20-Poly1305 enc=$(echo $enc | sed -e 's/(.*)//g' -e 's/ChaCha20-Poly1305/ChaCha20-Po/g') # workaround for empty bits ChaCha20-Poly1305 echo "$export" | grep -iq export && strength="$strength,export" if [ -r "$MAP_RFC_FNAME" ]; then printf -- " %-7s %-30s %-10s %-11s%-11s${MAP_RFC_FNAME:+ %-48s}${SHOW_EACH_C:+ }" "$1" "$2" "$kx" "$enc" "$strength" "$(show_rfc_style $HEXC)" else printf -- " %-7s %-30s %-10s %-11s%-11s${SHOW_EACH_C:+ }" "$1" "$2" "$kx" "$enc" "$strength" fi } test_just_one(){ pr_blue "--> Testing single cipher with word pattern "\"$1\"" (ignore case)"; outln "\n" neat_header for arg in $(echo $@ | sed 's/,/ /g'); do # 1st check whether openssl has cipher or not $OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL:@STRENGTH' | while read hexcode dash ciph sslvers kx auth enc mac export ; do normalize_ciphercode $hexcode neat_list $HEXC $ciph $kx $enc | grep -qwai "$arg" if [ $? -eq 0 ]; then $OPENSSL s_client -cipher $ciph $STARTTLS -connect $NODEIP:$PORT $SNI &>$TMPFILE Testing all locally available $nr_ciphers ciphers against the server"; outln "(ordered by encryption strength)\n" neat_header $OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL:@STRENGTH' | while read hexcode n ciph sslvers kx auth enc mac export; do # FIXME: e.g. OpenSSL < 1.0 doesn't understand "-V" --> we can't do anything about it! $OPENSSL s_client -cipher $ciph $STARTTLS -connect $NODEIP:$PORT $SNI &>$TMPFILE Testing all locally available ciphers per protocol against the server"; outln "(ordered by encryption strength)\n" neat_header outln " -ssl2 SSLv2\n -ssl3 SSLv3\n -tls1 TLS 1\n -tls1_1 TLS 1.1\n -tls1_2 TLS 1.2"| while read proto proto_text; do locally_supported "$proto" "$proto_text" || continue outln $OPENSSL ciphers $proto -V 'ALL:COMPLEMENTOFALL:@STRENGTH' | while read hexcode n ciph sslvers kx auth enc mac export; do # -V doesn't work with openssl < 1.0 $OPENSSL s_client -cipher $ciph $proto $STARTTLS -connect $NODEIP:$PORT $SNI &>$TMPFILE &1 | grep -aq "unknown option" if [ $? -eq 0 ]; then pr_magentaln "Local problem: $OPENSSL doesn't support \"s_client $1\"" ret=7 else ret=0 fi return $ret } testversion() { local sni=$SNI [ "x$1" = "x-ssl2" ] && sni="" # newer openssl throw an error if SNI with SSLv2 $OPENSSL s_client -state $1 $STARTTLS -connect $NODEIP:$PORT $sni &>$TMPFILE error lesen [ "$VERBERR" -eq 0 ] && egrep "error|failure" $TMPFILE | egrep -av "unable to get local|verify error" if grep -aq "no cipher list" $TMPFILE ; then ret=5 fi tmpfile_handle $FUNCNAME.txt return $ret } testprotohelper() { if locally_supported "$1" "$2" ; then testversion "$1" "$2" return $? # 0: offered # 1: not offered # 5: protocol ok, but no cipher else return 7 fi } runprotocols() { local using_sockets=0 pr_blue "--> Testing protocols"; if [ $SSL_NATIVE -eq 0 ] || [ -n "$STARTTLS" ]; then using_sockets=1 outln "(via native openssl)\n" else outln "(via sockets for SSLv2, SSLv3)\n" fi out " SSLv2 "; if [ $SSL_NATIVE -eq 0 ] || [ -n "$STARTTLS" ]; then testprotohelper "-ssl2" case $? in 0) pr_redln "offered (NOT ok)" ;; 1) pr_greenln "not offered (OK)" ;; 5) pr_litered "supported but couldn't detect a cipher"; outln "(may need debugging)" ;; # protocol ok, but no cipher 7) ;; # no local support esac else sslv2_sockets #FIXME: --> Umschreiben, Interpretation mit CASE wie native fi out " SSLv3 "; if [ $SSL_NATIVE -eq 0 ] || [ -n "$STARTTLS" ]; then testprotohelper "-ssl3" else tls_sockets "00" "$TLS_CIPHER" fi case $? in 0) pr_literedln "offered (NOT ok)" ;; 1) pr_greenln "not offered (OK)" ;; 2) ok 0 1 ;; #FIXME: downgraded. still missing a testcase here 5) pr_litered "supported but couldn't detect a cipher"; outln "(may need debugging)" ;; # protocol ok, but no cipher 7) ;; # no local support esac out " TLS 1 "; #if [ $SSL_NATIVE -eq 0 ] || [ -n "$STARTTLS" ]; then testprotohelper "-tls1" #else #tls_sockets "01" "$TLS_CIPHER" #fi case $? in 0) outln "offered" ;; # no GCM, thus only normal print 1) outln "not offered" ;; # neither good or bad # 2) ok 0 0 ;; downgraded 5) outln "supported but couldn't detect a cipher (may need debugging)" ;; # protocol ok, but no cipher 7) ;; # no local support esac out " TLS 1.1 "; testprotohelper "-tls1_1" case $? in 0) outln "offered" ;; # normal print 1) outln "not offered" ;; # neither good or bad 5) outln "supported but couldn't detect a cipher (may need debugging)" ;; # protocol ok, but no cipher 7) ;; # no local support esac out " TLS 1.2 "; testprotohelper "-tls1_2" case $? in 0) pr_greenln "offered (OK)" ;; # GCM cipher in TLS 1.2: very good! 1) pr_brownln "not offered" ;; # no GCM, penalty 5) outln "supported but couldn't detect a cipher (may need debugging)" ;; # protocol ok, but no cipher 7) ;; # no local support esac return 0 } run_std_cipherlists() { outln pr_blue "--> Testing standard cipher lists"; outln "\n" # see ciphers(1ssl) std_cipherlists NULL:eNULL " Null Cipher " 1 std_cipherlists aNULL " Anonymous NULL Cipher " 1 std_cipherlists ADH " Anonymous DH Cipher " 1 std_cipherlists EXPORT40 " 40 Bit encryption " 1 std_cipherlists EXPORT56 " 56 Bit encryption " 1 std_cipherlists EXPORT " Export Cipher (general) " 1 std_cipherlists LOW " Low (<=64 Bit) " 1 std_cipherlists DES " DES Cipher " 1 std_cipherlists 3DES " Triple DES Cipher " 2 std_cipherlists "MEDIUM:!NULL:!aNULL:!SSLv2" " Medium grade encryption " 2 std_cipherlists "HIGH:!NULL:!aNULL" " High grade encryption " 0 return 0 } # arg1: file with input for grepping the bit length for ECDH/DHE read_dhbits_from_file() { local bits local old_fart=" (openssl too old to show DH bits)" [ $OSSL_VER_MAJOR -ne 1 ] && pr_litemagenta "$old_fart" && return 0 [ "$OSSL_VER_MINOR" == "0.1" ] && pr_litemagenta "$old_fart" && return 0 bits=$(awk -F': ' '/^Server Temp Key/ { print $2 }' "$1") bits=$(echo $bits | sed -e 's/, P-...//' -e 's/,//g' -e 's/ / /g') # FIXME: for seldom cases it might be better to parse the bit length, the Kx and go from there [ -n "$bits" ] && out ", " case $bits in "DH 768"*) pr_red "$bits" ;; "DH 1024"*) pr_litered "$bits" ;; "DH 2048"*|"DH 4096"*) pr_green "$bits" ;; "ECDH 256"*|"ECDH 384"*|"ECDH 521"*) pr_green "$bits" ;; "") out " (no DH kx)" ;; #empty *) pr_bold "FIXME:"; out ">$bits<" ;; esac return 0 } server_preference() { local list1="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:DHE-DSS-AES128-SHA:DHE-DSS-AES128-SHA:DHE-DSS-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-AES256-SHA384:ECDHE-RSA-AES256-SHA:DHE-DSS-AES256-GCM-SHA384:AES256-SHA256" outln; pr_blue "--> Testing server preferences"; outln "\n" out " Has server cipher order? " $OPENSSL s_client $STARTTLS -cipher $list1 -connect $NODEIP:$PORT $SNI /dev/null >$TMPFILE if [ $? -ne 0 ]; then pr_magenta "no matching cipher in this list found (pls report this): " outln "$list1 . " ret=6 else cipher1=$(grep -wa Cipher $TMPFILE | egrep -avw "New|is" | sed -e 's/^ \+Cipher \+://' -e 's/ //g') list2=$(echo $list1 | tr ':' '\n' | sort -r | tr '\n' ':') # pr_reverse the list $OPENSSL s_client $STARTTLS -cipher $list2 -connect $NODEIP:$PORT $SNI /dev/null >$TMPFILE cipher2=$(grep -wa Cipher $TMPFILE | egrep -avw "New|is" | sed -e 's/^ \+Cipher \+://' -e 's/ //g') if [[ "$cipher1" != "$cipher2" ]]; then pr_litered "nope (NOT ok)" remark4default_cipher=" (limited sense as client will pick)" else pr_green "yes (OK)" remark4default_cipher="" fi [[ $DEBUG -ge 2 ]] && out " $cipher1 | $cipher2" outln $OPENSSL s_client $STARTTLS -connect $NODEIP:$PORT $SNI /dev/null >$TMPFILE out " Negotiated protocol " default_proto=$(grep -aw "Protocol" $TMPFILE | sed -e 's/^.*Protocol.*://' -e 's/ //g') case "$default_proto" in *TLSv1.2) pr_greenln $default_proto ;; *TLSv1.1) pr_litegreenln $default_proto ;; *TLSv1) outln $default_proto ;; *SSLv2) pr_redln $default_proto ;; *SSLv3) pr_redln $default_proto ;; "") pr_litemagenta "default proto empty"; [[ $OSSL_VER == 1.0.2* ]] && outln "(IIS6+OpenSSL 1.02?)" ;; # maybe you can try to use openssl 1.01 here *) outln "$default_proto" ;; esac out " Negotiated cipher " default_cipher=$(grep -aw "Cipher" $TMPFILE | egrep -avw "New|is" | sed -e 's/^.*Cipher.*://' -e 's/ //g') case "$default_cipher" in *NULL*|*EXP*) pr_red "$default_cipher" ;; *RC4*) pr_litered "$default_cipher" ;; *CBC*) pr_brown "$default_cipher" ;; #FIXME BEAST: We miss some CBC ciphers here, need to work w/ a list *GCM*) pr_green "$default_cipher" ;; # best ones *CHACHA20*) pr_green "$default_cipher" ;; # best ones ECDHE*AES*) pr_yellow "$default_cipher" ;; # it's CBC. --> lucky13 "") pr_litemagenta "default cipher empty" ; [[ $OSSL_VER == 1.0.2* ]] && out "(IIS6+OpenSSL 1.02?)" ;; # maybe you can try to use openssl 1.01 here *) out "$default_cipher" ;; esac read_dhbits_from_file "$TMPFILE" outln "$remark4default_cipher" if [ ! -z "$remark4default_cipher" ]; then out " Negotiated cipher per proto $remark4default_cipher" i=1 for p in ssl2 ssl3 tls1 tls1_1 tls1_2; do locally_supported -"$p" || continue $OPENSSL s_client $STARTTLS -"$p" -connect $NODEIP:$PORT $SNI /dev/null >$TMPFILE if [ $? -eq 0 ]; then proto[i]=$(grep -aw "Protocol" $TMPFILE | sed -e 's/^.*Protocol.*://' -e 's/ //g') cipher[i]=$(grep -aw "Cipher" $TMPFILE | egrep -avw "New|is" | sed -e 's/^.*Cipher.*://' -e 's/ //g') [[ ${cipher[i]} == "0000" ]] && cipher[i]="" # Hack! [[ $DEBUG -ge 2 ]] && outln "Default cipher for ${proto[i]}: ${cipher[i]}" else proto[i]="" cipher[i]="" fi i=$(($i + 1)) done if spdy_pre ; then # is NPN/SPDY supported and is this no STARTTLS? $OPENSSL s_client -host $NODE -port $PORT -nextprotoneg "$NPN_PROTOs" /dev/null >$TMPFILE if [ $? -eq 0 ]; then proto[i]=$(grep -aw "Next protocol" $TMPFILE | sed -e 's/^Next protocol://' -e 's/(.)//' -e 's/ //g') if [ -z "${proto[i]}" ]; then cipher[i]="" else cipher[i]=$(grep -aw "Cipher" $TMPFILE | egrep -avw "New|is" | sed -e 's/^.*Cipher.*://' -e 's/ //g') [[ $DEBUG -ge 2 ]] && outln "Default cipher for ${proto[i]}: ${cipher[i]}" fi fi fi for i in 1 2 3 4 5 6; do if [[ -n "${cipher[i]}" ]]; then # cipher not empty if [[ -z "${cipher[i-1]}" ]]; then # previous one empty outln printf -- " %-30s %s" "${cipher[i]}:" "${proto[i]}" # print out both else # previous NOT empty if [[ "${cipher[i-1]}" == "${cipher[i]}" ]]; then # and previous protocol same cipher out ", ${proto[i]}" # same cipher --> only print out protocol behind it else outln printf -- " %-30s %s" "${cipher[i]}:" "${proto[i]}" # print out both fi fi fi done fi fi tmpfile_handle $FUNCNAME.txt if [ -z "$remark4default_cipher" ]; then cipher_pref_check else outln "\n No further cipher order check as order is determined by the client" fi return 0 } cipher_pref_check() { local p proto protos local tested_cipher cipher out " Cipher order" for p in ssl2 ssl3 tls1 tls1_1 tls1_2; do $OPENSSL s_client $STARTTLS -"$p" -connect $NODEIP:$PORT $SNI /dev/null >$TMPFILE if [ $? -eq 0 ]; then tested_cipher="" proto=$(grep -aw "Protocol" $TMPFILE | sed -e 's/^.*Protocol.*://' -e 's/ //g') cipher=$(grep -aw "Cipher" $TMPFILE | egrep -avw "New|is" | sed -e 's/^.*Cipher.*://' -e 's/ //g') [ -z "$proto" ] && continue # for early openssl versions sometimes needed outln printf " %-10s %s " "$proto:" "$cipher" tested_cipher="-"$cipher while true; do $OPENSSL s_client $STARTTLS -"$p" -cipher "ALL:$tested_cipher" -connect $NODEIP:$PORT $SNI /dev/null >$TMPFILE [ $? -ne 0 ] && break cipher=$(grep -aw "Cipher" $TMPFILE | egrep -avw "New|is" | sed -e 's/^.*Cipher.*://' -e 's/ //g') out "$cipher " tested_cipher="$tested_cipher:-$cipher" done fi done outln if ! spdy_pre ; then # is NPN/SPDY supported and is this no STARTTLS? : else protos=$($OPENSSL s_client -host $NODE -port $PORT -nextprotoneg \"\" /dev/null | grep -a "^Protocols " | sed -e 's/^Protocols.*server: //' -e 's/,//g') for p in $protos; do $OPENSSL s_client -host $NODE -port $PORT -nextprotoneg "$p" /dev/null >$TMPFILE cipher=$(grep -aw "Cipher" $TMPFILE | egrep -avw "New|is" | sed -e 's/^.*Cipher.*://' -e 's/ //g') printf " %-10s %s " "$p:" "$cipher" tested_cipher="-"$cipher while true; do $OPENSSL s_client -cipher "ALL:$tested_cipher" -host $NODE -port $PORT -nextprotoneg "$p" /dev/null >$TMPFILE [ $? -ne 0 ] && break cipher=$(grep -aw "Cipher" $TMPFILE | egrep -avw "New|is" | sed -e 's/^.*Cipher.*://' -e 's/ //g') out "$cipher " tested_cipher="$tested_cipher:-$cipher" done outln done fi tmpfile_handle $FUNCNAME.txt return 0 } get_host_cert() { # arg1 is proto or empty $OPENSSL s_client $STARTTLS -connect $NODEIP:$PORT $SNI $1 2>/dev/null $HOSTCERT return $? } server_defaults() { local proto local gost_status_problem=false local now difftime local extensions local sessticket_str lifetime unit keysize algo local expire ocsp_uri crl savedir startdate enddate issuer_c issuer_o issuer sans san cn cn_nosni outln pr_blue "--> Testing server defaults (Server Hello)"; outln "\n" # first TLS time: if [ -n "$STARTTLS" ] ; then outln " TLS timestamp: (not yet implemented for STARTTLS) " else tls_sockets "03" "$TLS12_CIPHER" [ -z "$TLS_TIME" ] && tls_sockets "02" "$TLS_CIPHER" [ -z "$TLS_TIME" ] && tls_sockets "01" "$TLS_CIPHER" [ -z "$TLS_TIME" ] && tls_sockets "00" "$TLS_CIPHER" if [ -n "$TLS_TIME" ]; then difftime=$(($TLS_NOW - $TLS_TIME)) if [[ "${#difftime}" -gt 5 ]]; then # openssl >= 1.0.1f fills this field with random values out " TLS timestamp: random values, no fingerprinting possible " else [[ $difftime != "-"* ]] && [[ $difftime != "0" ]] && difftime="+$difftime" out " TLS clock skew: $difftime sec from localtime"; fi debugme out "$TLS_TIME" outln else out " TLS timestamp: "; pr_litemagentaln "SSLv3 through TLS 1.2 didn't return a timestamp" fi fi # HTTP date: out " HTTP clock skew: " if [[ $SERVICE != "HTTP" ]] ; then out "not tested as we're not tagetting HTTP" else printf "$GET_REQ11" | $OPENSSL s_client $OPTIMAL_PROTO -ign_eof -connect $NODEIP:$PORT $SNI &>$TMPFILE now=$(date "+%s") HTTP_TIME=$(awk -F': ' '/^date:/ { print $2 } /^Date:/ { print $2 }' $TMPFILE) if [ -n "$HTTP_TIME" ] ; then case $SYSTEM in *BSD|Darwin) HTTP_TIME=$(date -j -f "%a, %d %b %Y %T %Z" "$HTTP_TIME" "+%s" 2>/dev/null) ;; # the trailing \r confuses BSD flavors otherwise *) HTTP_TIME=$(date --date="$HTTP_TIME" "+%s") ;; esac difftime=$(($now - $HTTP_TIME)) [[ $difftime != "-"* ]] && [[ $difftime != "0" ]] && difftime="+$difftime" out "$difftime sec from localtime"; else out "Got no HTTP time, maybe try different URL?"; fi debugme out "$HTTP_TIME" fi outln #TLS extensions follow now # throwing 1st every cipher/protocol at the server to know what works for proto in tls1_2 tls1_1 tls1 ssl3; do $OPENSSL s_client $STARTTLS -connect $NODEIP:$PORT $SNI -$proto -tlsextdebug -status /dev/null >$TMPFILE ret=$? get_host_cert "-$proto" [ $? -eq 0 ] && [ $ret -eq 0 ] && break ret=7 done # this loop is needed for IIS/6 if [ $ret -eq 7 ]; then # "-status" kills GOST only servers, so we do another test without it and see whether that works then: if ! $OPENSSL s_client $STARTTLS -connect $NODEIP:$PORT $SNI -$proto -tlsextdebug /dev/null >$TMPFILE; then pr_magentaln "$OPENSSL returned an error around line $LINENO". tmpfile_handle tlsextdebug+status.txt return 7 # this is ugly, I know else gost_status_problem=true fi fi $OPENSSL s_client $STARTTLS -connect $NODEIP:$PORT -$proto 2>/dev/null $HOSTCERT.nosni out " TLS server extensions " extensions=$(grep -aw "^TLS server extension" $TMPFILE | sed -e 's/^TLS server extension \"//' -e 's/\".*$/,/g') if [ -z "$extensions" ]; then outln "(none)" else echo $extensions | sed 's/,$//' # remove last comma fi out " Session Tickets RFC 5077 " sessticket_str=$(grep -aw "session ticket" $TMPFILE | grep -a lifetime) if [ -z "$sessticket_str" ]; then outln "(none)" else lifetime=$(echo $sessticket_str | grep -a lifetime | sed 's/[A-Za-z:() ]//g') unit=$(echo $sessticket_str | grep -a lifetime | sed -e 's/^.*'"$lifetime"'//' -e 's/[ ()]//g') outln "$lifetime $unit" fi out " Server key size " keysize=$(grep -aw "^Server public key is" $TMPFILE | sed -e 's/^Server public key is //' -e 's/bit//' -e 's/ //') if [ -z "$keysize" ]; then outln "(couldn't determine)" else if [ "$keysize" -le 768 ]; then pr_red "$keysize" elif [ "$keysize" -le 1024 ]; then pr_brown "$keysize" elif [ "$keysize" -le 2048 ]; then out "$keysize" elif [ "$keysize" -le 4096 ]; then pr_litegreen "$keysize" else out "weird keysize: $keysize" fi fi outln " bit" #FIXME: google seems to have EC keys which displays as 256 Bit out " Signature Algorithm " algo=$($OPENSSL x509 -in $HOSTCERT -noout -text | grep "Signature Algorithm" | sed 's/^.*Signature Algorithm: //' | sort -u ) case $algo in sha1WithRSAEncryption) pr_brownln "SHA1 with RSA" ;; sha256WithRSAEncryption) pr_litegreenln "SHA256 with RSA" ;; sha512WithRSAEncryption) pr_litegreenln "SHA512 with RSA" ;; ecdsa-with-SHA256) pr_litegreenln "ECDSA with SHA256" ;; md5*) pr_redln "MD5" ;; *) outln "$algo" ;; esac # old, but interesting: https://blog.hboeck.de/archives/754-Playing-with-the-EFF-SSL-Observatory.html out " Fingerprint / Serial " outln "$($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha1 | sed 's/Fingerprint=//' | sed 's/://g' ) / $($OPENSSL x509 -noout -in $HOSTCERT -serial | sed 's/serial=//')" outln " $($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha256 | sed 's/Fingerprint=//' | sed 's/://g' )" out " Common Name (CN) " cn=$($OPENSSL x509 -in $HOSTCERT -noout -subject | sed 's/subject= //' | sed -e 's/^.*CN=//' -e 's/\/emailAdd.*//') pr_underline "$cn" cn_nosni=$($OPENSSL x509 -in $HOSTCERT.nosni -noout -subject | sed 's/subject= //' | sed -e 's/^.*CN=//' -e 's/\/emailAdd.*//') [[ $DEBUG -ge 2 ]] && out "\'$NODE\' | \'$cn\' | \'$cn_nosni\'" if [[ $NODE == $cn_nosni ]]; then if [[ $SERVICE != "HTTP" ]] ; then outln " (matches certificate directly)" else outln " (works w/o SNI)" fi else if [[ $SERVICE != "HTTP" ]] ; then pr_brownln " (CN doesn't match but for non-HTTP services it might be ok)" else out " (CN response to request w/o SNI: "; pr_underline "$cn_nosni"; outln ")" fi fi sans=$($OPENSSL x509 -in $HOSTCERT -noout -text | grep -A3 "Subject Alternative Name" | grep "DNS:" | \ sed -e 's/DNS://g' -e 's/ //g' -e 's/,/\n/g' -e 's/othername://g') # ^^^ CACert out " subjectAltName (SAN) " if [ -n "$sans" ]; then sans=$(echo "$sans" | sed -e ':a' -e 'N' -e '$!ba' -e 's/\n/ /g') # replace line feed by " " for san in $sans; do out "$underline$san$off " done else out "-- " fi outln out " Issuer " issuer=$($OPENSSL x509 -in $HOSTCERT -noout -issuer | sed -e 's/^.*CN=//g' -e 's/\/.*$//g') issuer_o=$($OPENSSL x509 -in $HOSTCERT -noout -issuer | sed 's/^.*O=//g' | sed 's/\/.*$//g') if $OPENSSL x509 -in $HOSTCERT -noout -issuer | grep -q 'C=' ; then issuer_c=$($OPENSSL x509 -in $HOSTCERT -noout -issuer | sed 's/^.*C=//g' | sed 's/\/.*$//g') else issuer_c="" # CACert would have 'issuer= ' here otherwise fi if [ "$issuer_o" == "issuer=" ] || [ "$issuer" == "$CN" ] ; then pr_redln "selfsigned (not OK)" else [ "$issuer_c" == "" ] && \ outln "$underline$issuer$off ($underline$issuer_o$off" || \ outln "$underline$issuer$off ($underline$issuer_o$off from $underline$issuer_c$off)" fi out " Certificate Expiration " expire=$($OPENSSL x509 -in $HOSTCERT -checkend 0) if ! echo $expire | grep -qw not; then pr_red "expired!" else SECS2WARN=$((24 * 60 * 60 * $DAYS2WARN2)) # low threshold first expire=$($OPENSSL x509 -in $HOSTCERT -checkend $SECS2WARN) if echo "$expire" | grep -qw not; then SECS2WARN=$((24 * 60 * 60 * $DAYS2WARN2)) expire=$($OPENSSL x509 -in $HOSTCERT -checkend $SECS2WARN) if echo "$expire" | grep -qw not; then pr_litegreen ">= $DAYS2WARN1 days" else pr_brown "expires < $DAYS2WARN1 days" fi else pr_litered "expires < $DAYS2WARN2 days!" fi fi case $SYSTEM in *BSD|Darwin*) enddate=$(date -j -f "%b %d %T %Y %Z" "$($OPENSSL x509 -in $HOSTCERT -noout -enddate | cut -d= -f 2)" +"%F %H:%M %z") startdate=$(date -j -f "%b %d %T %Y %Z" "$($OPENSSL x509 -in $HOSTCERT -noout -startdate | cut -d= -f 2)" +"%F %H:%M") ;; *) enddate=$(date --date="$($OPENSSL x509 -in $HOSTCERT -noout -enddate | cut -d= -f 2)" +"%F %H:%M %z") startdate=$(date --date="$($OPENSSL x509 -in $HOSTCERT -noout -startdate | cut -d= -f 2)" +"%F %H:%M") ;; esac outln " ($startdate --> $enddate)" savedir=$(pwd); cd $TEMPDIR $OPENSSL s_client -showcerts $STARTTLS -connect $NODEIP:$PORT $SNI 2>/dev/null ("level" c ".crt")} /---END CERTIFICATE-----/{inc=0}' nrsaved=$(ls $TEMPDIR/level?.crt 2>/dev/null | wc -w | sed 's/^ *//') outln " # of certificates provided $nrsaved" cd $savedir out " Certificate Revocation List " crl=$($OPENSSL x509 -in $HOSTCERT -noout -text | grep -A 4 "CRL Distribution" | grep URI | sed 's/^.*URI://') [ x"$crl" == "x" ] && pr_literedln "--" || echo "$crl" out " OCSP URI " ocsp_uri=$($OPENSSL x509 -in $HOSTCERT -noout -ocsp_uri) [ x"$ocsp_uri" == "x" ] && pr_literedln "--" || echo "$ocsp_uri" out " OCSP stapling " if grep "OCSP response" $TMPFILE | grep -q "no response sent" ; then out " not offered" else if grep "OCSP Response Status" $TMPFILE | grep -q successful; then pr_litegreen " OCSP stapling offered" else if [ $gost_status_problem = "true" ]; then outln " (GOST servers make problems here, sorry)" ret=0 else outln " not sure what's going on here, debug:" grep -A 20 "OCSP response" $TMPFILE ret=2 fi fi fi outln tmpfile_handle tlsextdebug+status.txt return $ret } # FIXME: revoked, see checkcert.sh # FIXME: Trust (only CN) # http://www.heise.de/security/artikel/Forward-Secrecy-testen-und-einrichten-1932806.html pfs() { local ret local none local number_pfs local hexcode n ciph sslvers kx auth enc mac # https://community.qualys.com/blogs/securitylabs/2013/08/05/configuring-apache-nginx-and-openssl-for-forward-secrecy -- but with RC4: #local pfs_ciphers='EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA256 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EDH+aRSA EECDH RC4 !RC4-SHA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS:@STRENGTH' local pfs_ciphers='EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA256 EECDH+aRSA+SHA256 EDH+aRSA EECDH !RC4-SHA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS:@STRENGTH' # ^^^ the exclusion via ! doesn't work with libressl and openssl 0.9.8 outln pr_blue "--> Testing (perfect) forward secrecy, (P)FS"; outln " -- omitting 3DES, RC4 and Null Encryption here" $OPENSSL ciphers -V "$pfs_ciphers" >$TMPFILE 2>/dev/null # -V doesn't work with openssl < 1.0 if [ $? -ne 0 ] ; then number_pfs=$(wc -l < $TMPFILE | sed 's/ //g') if [ "$number_pfs" -le "$CLIENT_MIN_PFS" ] ; then # this will be called also if the ! and @ syntax can't be understood outln pr_magentaln " Local problem: you only have $number_pfs PFS ciphers on the client side " [ $number_pfs -ne 0 ] && cat $TMPFILE return 1 fi fi savedciphers=$(cat $TMPFILE) [ $SHOW_LOC_CIPH -eq 0 ] && echo "local ciphers available for testing PFS:" && echo $(cat $TMPFILE) $OPENSSL s_client -cipher 'ECDH:DH' $STARTTLS -connect $NODEIP:$PORT $SNI &>$TMPFILE /dev/null &1 | grep -qw nextprotoneg if [ $? -ne 0 ]; then pr_magentaln "Local problem: $OPENSSL doesn't support SPDY/NPN"; return 7 fi return 0 } spdy() { out " SPDY/NPN " spdy_pre || return 0 $OPENSSL s_client -host $NODE -port $PORT -nextprotoneg $NPN_PROTOs /dev/null >$TMPFILE tmpstr=$(grep -a '^Protocols' $TMPFILE | sed 's/Protocols.*: //') if [ -z "$tmpstr" -o "$tmpstr" = " " ] ; then out "not offered" ret=1 else # now comes a strange thing: "Protocols advertised by server:" is empty but connection succeeded if echo $tmpstr | egrep -aq "spdy|http" ; then pr_bold "$tmpstr" ; out " (advertised)" ret=0 else pr_litemagenta "please check manually, server response was ambigious ..." ret=10 fi fi outln # btw: nmap can do that too http://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.txt return $ret } # arg for a fd doesn't work here fd_socket() { if ! exec 5<>/dev/tcp/$NODEIP/$PORT; then # 2>/dev/null removes an error message, but disables debugging outln pr_magenta "Unable to open a socket to $NODEIP:$PORT" # It can last ~2 minutes but for for those rare occasions we don't do a tiemout handler here, KISS return 6 fi return 0 } close_socket(){ exec 5<&- exec 5>&- return 0 } ## old network code ^^^^^^ ###### new funcs for network follow # first: helper function for protocol checks code2network() { # arg1: formatted string here in the code NW_STR=$(echo "$1" | sed -e 's/,/\\\x/g' | sed -e 's/# .*$//g' -e 's/ //g' -e '/^$/d' | tr -d '\n' | tr -d '\t') } len2twobytes() { len_arg1=$(echo ${#1}) [[ $len_arg1 -le 2 ]] && LEN_STR=$(printf "00, %02s \n" $1) [[ $len_arg1 -eq 3 ]] && LEN_STR=$(printf "%02s, %02s \n" ${1:0:1} ${1:1:2}) [[ $len_arg1 -eq 4 ]] && LEN_STR=$(printf "%02s, %02s \n" ${1:0:2} ${1:2:2}) } socksend_sslv2_clienthello() { code2network "$1" data=$(echo $NW_STR) [[ "$DEBUG" -ge 4 ]] && echo "\"$data\"" printf -- "$data" >&5 2>/dev/null & sleep $USLEEP_SND } # for SSLv2 to TLS 1.2: 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 & pid=$! wait_kill $pid $maxsleep return $? } # arg1: name of file with socket reply display_sslv2_serverhello() { # 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! local ret=3 v2_hello_ascii=$(hexdump -v -e '16/1 "%02X"' $1) [[ "$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_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_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 length: 0x$v2_hello_cert_length" echo "SSLv2 cipher spec length: 0x$v2_hello_cipherspec_length" fi fi return $ret } # arg1: name of file with socket reply display_tls_serverhello() { # server hello, handshake details see http://en.wikipedia.org/wiki/Transport_Layer_Security-SSL#TLS_record # byte 0: type: x16=TLS, 0x15=TLS alert, 0x14=CCS, 0x18=HB # byte 1+2: TLS version word, see below. 1st byte is always 03 # byte 3+4: length all # byte 5: handshake type (2=hello) TLS alert: level (2=fatal), descr (0x28=handshake failure) # byte 6+7+8: length server hello # byte 9+10: 03, TLS version byte (00=SSL3, 01=TLS1 02=TLS1.1 03=TLS 1.2 # byte 11-14: TLS timestamp # byte 15-42: random, 28 bytes # byte 43: session id length # byte 44+45+sid-len: cipher suite! # byte 46+sid-len: compression method: 00: none, 01: deflate # byte 47+48+sid-len: extension length tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' $1) [[ "$DEBUG" -eq 5 ]] && echo $tls_hello_ascii # one line without any blanks [[ -z $tls_hello_ascii ]] && debugme echo "server hello empty, TCP connection closed" && return 0 # no server hello received # now scrape two bytes out of the reply per byte tls_hello_initbyte="${tls_hello_ascii:0:2}" # normally this is x16 tls_hello_protocol="${tls_hello_ascii:2:4}" tls_len_all=${tls_hello_ascii:6:4} if [[ $tls_hello_initbyte != "16" ]] ; then if [[ $DEBUG -ge 2 ]]; then echo "tls_hello_initbyte: 0x$tls_hello_initbyte" echo "tls_hello_protocol: 0x$tls_hello_protocol" echo "tls_len_all: $tls_len_all" echo "tls_err_level: ${tls_hello_ascii:10:2}" echo "tls_err_descr: 0x${tls_hello_ascii:12:2}" fi return 1 fi DETECTED_TLS_VERSION=$tls_hello_protocol tls_hello="${tls_hello_ascii:10:2}" # normally this is x02 tls_hello_protocol2="${tls_hello_ascii:18:4}" tls_hello_time="${tls_hello_ascii:22:8}" TLS_TIME=$(printf "%d\n" 0x$tls_hello_time) case $SYSTEM in *BSD|Darwin) tls_time=$(date -j -f %s "$TLS_TIME" "+%Y-%m-%d %r") ;; *) tls_time=$(date --date="@$TLS_TIME" "+%Y-%m-%d %r") ;; esac tls_sid_len=$(printf "%d\n" 0x${tls_hello_ascii:86:2}) let sid_offset=88+$tls_sid_len*2 tls_cipher_suite="${tls_hello_ascii:$sid_offset:4}" let sid_offset=92+$tls_sid_len*2 tls_compression_method="${tls_hello_ascii:$sid_offset:2}" if [[ $DEBUG -ge 2 ]]; then echo "tls_hello: 0x$tls_hello" if [[ $DEBUG -ge 4 ]]; then echo "tls_hello_protocol2: 0x$tls_hello_protocol2" echo "tls_sid_len: $tls_sid_len" fi echo "tls_hello_time: 0x$tls_hello_time ($tls_time)" echo "tls_cipher_suite: 0x$tls_cipher_suite" echo "tls_compression_method: 0x$tls_compression_method" outln fi return 0 } sslv2_sockets() { local ciphers_detected fd_socket 5 || return 6 [[ "$DEBUG" -ge 2 ]] && outln "sending client hello... " socksend_sslv2_clienthello "$SSLv2_CLIENT_HELLO" sockread_serverhello 32768 [[ "$DEBUG" -ge 2 ]] && outln "reading server hello... " if [[ "$DEBUG" -ge 4 ]]; then hexdump -C $SOCK_REPLY_FILE | head -6 outln fi display_sslv2_serverhello "$SOCK_REPLY_FILE" case $? in 7) # strange reply, couldn't convert the cipher spec length to a hex number pr_litemagenta "strange v2 reply " outln " (rerun with DEBUG >=2)" [[ $DEBUG -ge 3 ]] && hexdump -C $SOCK_REPLY_FILE | head -1 ret=7 ;; 1) # no sslv2 server hello returned, like in openlitespeed which returns HTTP! pr_greenln "not offered (OK)" ret=0 ;; 0) # reset pr_greenln "not offered (OK)" ret=0 ;; 3) # everything else lines=$(hexdump -C "$SOCK_REPLY_FILE" 2>/dev/null | wc -l | sed 's/ //g') [[ "$DEBUG" -ge 2 ]] && out " ($lines lines) " if [[ "$lines" -gt 1 ]] ;then ciphers_detected=$(($V2_HELLO_CIPHERSPEC_LENGTH / 3 )) if [ 0 -eq "$ciphers_detected" ] ; then pr_litered "supported but couldn't detect a cipher"; outln "(may need further attention)" else pr_red "offered (NOT ok)"; outln " -- $ciphers_detected ciphers" fi ret=1 fi ;; esac pr_off debugme outln close_socket TMPFILE=$SOCK_REPLY_FILE tmpfile_handle $FUNCNAME.dd return $ret } # ARG1: TLS version low byte (00: SSLv3, 01: TLS 1.0, 02: TLS 1.1, 03: TLS 1.2) # ARG2: CIPHER_SUITES string socksend_tls_clienthello() { local tls_low_byte local servername_hexstr len_servername len_servername_hex local hexdump_format_str local len_sni_listlen len_sni_ext len_extension_hex local cipher_suites len_ciph_suites len_ciph_suites_word local len_client_hello_word len_all_word tls_low_byte="$1" len_servername=$(echo ${#NODE}) hexdump_format_str="$len_servername/1 \"%02x,\"" servername_hexstr=$(printf $NODE | hexdump -v -e "${hexdump_format_str}" | sed 's/,$//') code2network "$2" # CIPHER_SUITES cipher_suites="$NW_STR" # we don't have the leading \x here so string length is two byte less, see next #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 # 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))) len_extension_hex=$(printf "%02x\n" $((len_servername+9))) len_ciph_suites_byte=$(echo ${#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 # RFC 3546 doesn't specify SSLv3 to have SNI, openssl just ignores the switch if supplied if [ "$tls_low_byte" == "00" ]; then len2twobytes $(printf "%02x\n" $((0x$len_ciph_suites + 0x27))) else len2twobytes $(printf "%02x\n" $((0x$len_ciph_suites + 0x27 + 0x$len_extension_hex + 0x2))) fi len_client_hello_word="$LEN_STR" #[[ $DEBUG -ge 3 ]] && echo $len_client_hello_word if [ "$tls_low_byte" == "00" ]; then len2twobytes $(printf "%02x\n" $((0x$len_ciph_suites + 0x2b))) else len2twobytes $(printf "%02x\n" $((0x$len_ciph_suites + 0x2b + 0x$len_extension_hex + 0x2))) fi len_all_word="$LEN_STR" #[[ $DEBUG -ge 3 ]] && echo $len_all_word TLS_CLIENT_HELLO=" # TLS header ( 5 bytes) ,16, 03, $tls_low_byte # TLS Version ,$len_all_word # Length <--- # Handshake header: ,01 # Type (x01 for ClientHello) ,00, $len_client_hello_word # Length ClientHello ,03, $tls_low_byte # TLS Version (again) ,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, 0a, 85 ,03, 90, 9f, 77, 04, 33, d4, de ,00 # Session ID length ,$len_ciph_suites_word # Cipher suites length ,$cipher_suites ,01 # Compression methods length ,00" # Compression method (x00 for NULL) if [ "$tls_low_byte" == "00" ]; then EXTENSION_CONTAINING_SNI="" # RFC 3546 doesn't specify SSLv3 to have SNI, openssl just ignores the switch if supplied else EXTENSION_CONTAINING_SNI=" ,00, $len_extension_hex # first the len of all (here: 1) extentions. We assume len(hostname) < FF - 9 ,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 ,$servername_hexstr" # server_name target fi fd_socket 5 || return 6 code2network "$TLS_CLIENT_HELLO$EXTENSION_CONTAINING_SNI" data=$(echo $NW_STR) [[ "$DEBUG" -ge 4 ]] && echo "\"$data\"" printf -- "$data" >&5 2>/dev/null & sleep $USLEEP_SND return 0 } # ARG1: TLS version low byte (00: SSLv3, 01: TLS 1.0, 02: TLS 1.1, 03: TLS 1.2) tls_sockets() { local ret save local lines local tls_low_byte local cipher_list_2send tls_low_byte="$1" if [ -n "$2" ]; then # use supplied arg2 if there 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 [[ "$DEBUG" -ge 2 ]] && echo "sending client hello..." socksend_tls_clienthello "$tls_low_byte" "$cipher_list_2send" 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 sockread_serverhello 32768 TLS_NOW=$(date "+%s") [[ "$DEBUG" -ge 2 ]] && outln "reading server hello..." if [[ "$DEBUG" -ge 3 ]]; then hexdump -C $SOCK_REPLY_FILE | head -6 echo fi display_tls_serverhello "$SOCK_REPLY_FILE" save=$? # see https://secure.wand.net.nz/trac/libprotoident/wiki/SSL lines=$(hexdump -C "$SOCK_REPLY_FILE" 2>/dev/null | wc -l | sed 's/ //g') [[ "$DEBUG" -ge 2 ]] && out " (returned $lines lines) " # printf "Protokoll "; tput bold; printf "$tls_low_byte = $tls_str"; tput sgr0; printf ": " # 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 if [[ 03$tls_low_byte -eq $DETECTED_TLS_VERSION ]]; then ret=0 # available else [[ $DEBUG -ge 2 ]] && echo -n "send: 0x03$tls_low_byte, returned: 0x$DETECTED_TLS_VERSION" ret=2 # NOT available, server downgraded fi fi debugme outln else debugme "stuck on sending: $ret" fi close_socket TMPFILE=$SOCK_REPLY_FILE tmpfile_handle $FUNCNAME.dd return $ret } ####### vulnerabilities follow ####### # general overview which browser supports which vulnerability: # http://en.wikipedia.org/wiki/Transport_Layer_Security-SSL#Web_browsers # mainly adapted from https://gist.github.com/takeshixx/10107280 heartbleed(){ [ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_blue "--> Testing for heartbleed vulnerability" && outln "\n" pr_bold " Heartbleed\c"; out " (CVE-2014-0160) " if [ ! -z "$STARTTLS" ] ; then outln "(not yet implemented for STARTTLS)" return 0 fi # determine TLS versions available: $OPENSSL s_client $STARTTLS -connect $NODEIP:$PORT -tlsextdebug &>$TMPFILE Testing for CCS injection vulnerability" && outln "\n" pr_bold " CCS "; out " (CVE-2014-0224) " if [ ! -z "$STARTTLS" ] ; then outln "(not yet implemented for STARTTLS)" return 0 fi $OPENSSL s_client $STARTTLS -connect $NODEIP:$PORT &>$TMPFILE RST outln fi reply_sanitized=$(echo "$SOCKREPLY" | "${HEXDUMPPLAIN[@]}" | sed 's/^..........//') lines=$(echo "$SOCKREPLY" | "${HEXDUMP[@]}" | wc -l | sed 's/ //g') if [ "$reply_sanitized" == "0a" ] || [ "$lines" -gt 1 ] ; then pr_green "not vulnerable (OK)" ret=0 else pr_red "VULNERABLE (NOT ok)" ret=1 fi [ $retval -eq 3 ] && out " (timed out)" outln close_socket tmpfile_handle $FUNCNAME.txt return $ret } renego() { # no SNI here. Not needed as there won't be two different SSL stacks for one IP local legacycmd="" local insecure_renogo_str local sec_renego sec_client_renego [ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_blue "--> Testing for Renegotiation vulnerability" && outln "\n" pr_bold " Secure Renegotiation "; out "(CVE 2009-3555) " # and RFC5746, OSVDB 59968-59974 # community.qualys.com/blogs/securitylabs/2009/11/05/ssl-and-tls-authentication-gap-vulnerability-discovered insecure_renogo_str="Secure Renegotiation IS NOT" $OPENSSL s_client $STARTTLS -connect $NODEIP:$PORT 2>&1 $TMPFILE & # msg enables us to look deeper into it while debugging wait_kill $! $HEADER_MAXSLEEP if [ $? -eq 3 ]; then pr_litegreen "likely not vulnerable (OK)"; outln "(timed out)" # it hung sec_client_renego=1 else # second try in the foreground as we are sure now it won't hang echo R | $OPENSSL s_client $legacycmd $STARTTLS -msg -connect $NODEIP:$PORT &>$TMPFILE sec_client_renego=$? # 0=client is renegotiating & doesn't return an error --> vuln! case $sec_client_renego in 0) pr_litered "VULNERABLE (NOT ok)"; outln ", DoS threat" ;; 1) pr_litegreenln "not vulnerable (OK)" ;; *) "FIXME (bug): $sec_client_renego" ;; esac fi #FIXME Insecure Client-Initiated Renegotiation is missing tmpfile_handle $FUNCNAME.txt return $(($sec_renego + $sec_client_renego)) #FIXME: the return value is wrong, should be 0 if all ok. But as the caller doesn't care we don't care either ... yet ;-) } crime() { # in a nutshell: don't offer TLS/SPDY compression on the server side # This tests for CRIME Vulnerability (www.ekoparty.org/2012/juliano-rizzo.php) on HTTPS, not SPDY (yet) # Please note that it is an attack where you need client side control, so in regular situations this # means anyway "game over", w/wo CRIME # www.h-online.com/security/news/item/Vulnerability-in-SSL-encryption-is-barely-exploitable-1708604.html [ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_blue "--> Testing for CRIME vulnerability" && outln "\n" pr_bold " CRIME, TLS " ; out "(CVE-2012-4929) " case "$OSSL_VER" in 0.9.8*) ADDCMD="-no_ssl2" ;; 0.9.9*|1.0*) ADDCMD="" ;; esac # first we need to test whether OpenSSL binary has zlib support $OPENSSL zlib -e -a -in /dev/stdin &>/dev/stdout &1 $TMPFILE if grep -a Compression $TMPFILE | grep -aq NONE >/dev/null; then pr_green "not vulnerable (OK)" [[ $SERVICE == "HTTP" ]] || out " (not using HTTP anyway)" ret=0 else if [[ $SERVICE == "HTTP" ]]; then pr_red "VULNERABLE (NOT ok)" else pr_brown "VULNERABLE (NOT ok), but not using HTTP: probably no exploit known" fi ret=1 fi # not clear whether this is a protocol != HTTP as one needs to have the ability to modify the # compression input which is done via javascript in the context of HTTP 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 # $OPENSSL s_client help 2>&1 | grep -qw nextprotoneg # if [ $? -eq 0 ]; then # $OPENSSL s_client -host $NODE -port $PORT -nextprotoneg $NPN_PROTOs $SNI /dev/null >$TMPFILE # if [ $? -eq 0 ]; then # echo # pr_bold "CRIME Vulnerability, SPDY \c" ; outln "(CVE-2012-4929): \c" # STR=$(grep Compression $TMPFILE ) # if echo $STR | grep -q NONE >/dev/null; then # pr_green "not vulnerable (OK)" # ret=$(($ret + 0)) # else # pr_red "VULNERABLE (NOT ok)" # ret=$(($ret + 1)) # fi # fi # fi [ $VERBERR -eq 0 ] && outln "$STR" #echo tmpfile_handle $FUNCNAME.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 referers are the important thing here! breach() { [[ $SERVICE != "HTTP" ]] && return 7 [ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_blue "--> Testing for BREACH (HTTP compression) vulnerability" && outln "\n" pr_bold " BREACH"; out " (CVE-2013-3587) " url="$1" [ -z "$url" ] && url="/" if [ $SNEAKY -eq 0 ] ; then # see https://community.qualys.com/message/20360 if [[ "$NODE" =~ google ]]; then referer="http://yandex.ru/" # otherwise we have a false positive for google.com else referer="http://google.com/" fi useragent="$UA_SNEAKY" else referer="TLS/SSL-Tester from $SWURL" useragent="$UA_STD" fi ( $OPENSSL s_client $OPTIMAL_PROTO -quiet -connect $NODEIP:$PORT $SNI << EOF GET $url HTTP/1.1 Host: $NODE User-Agent: $useragent Accept: text/* Accept-Language: en-US,en Accept-encoding: gzip,deflate,compress Referer: $referer Connection: close EOF ) &>$HEADERFILE_BREACH & pid=$! if wait_kill $pid $HEADER_MAXSLEEP; then result=$(grep -a '^Content-Encoding' $HEADERFILE_BREACH | sed -e 's/^Content-Encoding//' -e 's/://' -e 's/ //g') result=$(echo $result | tr -cd '\40-\176') if [ -z $result ]; then pr_green "no HTTP compression (OK) " ret=0 else pr_litered "NOT ok: uses $result HTTP compression " ret=1 fi # Catch: any URL can be vulnerable. I am testing now only the root. URL! outln "(only \"$url\" tested)" else pr_litemagentaln "failed (HTTP header request stalled)" ret=3 fi return $ret } # Padding Oracle On Downgraded Legacy Encryption, in a nutshell: don't use CBC Ciphers in SSLv3 ssl_poodle() { local ret local cbc_ciphers [ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_blue "--> Testing for SSLv3 POODLE (Padding Oracle On Downgraded Legacy Encryption)" && outln "\n" pr_bold " POODLE, SSL"; out " (CVE-2014-3566) " cbc_ciphers=$($OPENSSL ciphers -v 'ALL:eNULL' | awk '/CBC/ { print $1 }' | tr '\n' ':') #FIXME: even with worst openssl client (FreeBSD9) we have 17 reasonable ciphers but is that enough to check?? debugme echo $cbc_ciphers $OPENSSL s_client -ssl3 $STARTTLS -cipher $cbc_ciphers -connect $NODEIP:$PORT $SNI &>$TMPFILE Testing for FREAK attack" && outln "\n" pr_bold " FREAK "; out " (CVE-2015-0204), experimental " no_exportrsa_ciphers=$($OPENSSL ciphers -v 'ALL:eNULL' | egrep -a "^EXP.*RSA" | wc -l | sed 's/ //g') exportrsa_ciphers=$($OPENSSL ciphers -v 'ALL:eNULL' | awk '/^EXP.*RSA/ {print $1}' | tr '\n' ':') debugme echo $exportrsa_ciphers # with correct build it should list these 7 ciphers (plus the two latter as SSLv2 ciphers): # EXP1024-DES-CBC-SHA:EXP1024-RC4-SHA:EXP-EDH-RSA-DES-CBC-SHA:EXP-DH-RSA-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-RC4-MD5 case $no_exportrsa_ciphers in 0) pr_magentaln "Local problem: your $OPENSSL doesn't have any EXPORT RSA ciphers configured" return 3 ;; 1,2,3) addtl_warning=" (tested only with $no_exportrsa_ciphers out of 9 ciphers)" ;; 7,8,9,10,11) addtl_warning="" ;; 4,5,6) addtl_warning=" (tested with $no_exportrsa_ciphers/9 ciphers)" ;; esac $OPENSSL s_client $STARTTLS -cipher $exportrsa_ciphers -connect $NODEIP:$PORT $SNI &>$TMPFILE Testing for BEAST vulnerability" && outln "\n" pr_bold " BEAST"; out " (CVE-2011-3389) " # 2) test handfull of common CBC ciphers for proto in ssl3 tls1; do $OPENSSL s_client -"$proto" $STARTTLS -connect $NODEIP:$PORT $SNI >$TMPFILE 2>/dev/null $TMPFILE 2>/dev/null /dev/null >$TMPFILE Checking for vulnerable RC4 Ciphers" ; outln "\n" fi pr_bold " RC4"; out " (CVE-2013-2566, CVE-2015-2808) " $OPENSSL ciphers -V 'RC4:@STRENGTH' >$TMPFILE # -V doesn't work with openssl < 1.0 [ $LONG -eq 0 ] && [ $SHOW_LOC_CIPH -eq 0 ] && echo "local ciphers available for testing RC4:" && echo $(cat $TMPFILE) $OPENSSL s_client -cipher $($OPENSSL ciphers RC4) $STARTTLS -connect $NODEIP:$PORT $SNI &>/dev/null /dev/null ret=$? # here we have a fp with openssl < 1.0 if [[ $ret -ne 0 ]] && [[ "$SHOW_EACH_C" -eq 0 ]] ; then continue # no successful connect AND not verbose displaying each cipher fi if [ $LONG -eq 0 ]; then normalize_ciphercode $hexcode neat_list $HEXC $ciph $kx $enc $strength if [[ "$SHOW_EACH_C" -ne 0 ]]; then if [[ $ret -eq 0 ]]; then pr_litered "available" else out "not a/v" fi else rc4_offered=1 out fi outln else pr_litered "$ciph " fi done < $TMPFILE # ^^^^^ posix redirect as shopt will either segfault or doesn't work with old bash versions outln else pr_litegreenln "no RC4 ciphers detected (OK)" rc4_offered=0 fi tmpfile_handle $FUNCNAME.txt return $rc4_offered } youknowwho() { # CVE-2013-2566, # NOT FIXME as there's no code: http://www.isg.rhul.ac.uk/tls/ # http://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 tls_truncation() { #FIXME: difficult to test, is there any test available, pls let me know : } old_fart() { pr_magentaln "Your $OPENSSL $OSSL_VER version is an old fart... . It doesn\'t make much sense to proceed." outln "Get precompiled bins or compile https://github.com/PeterMosmans/openssl ." exit 3 } find_openssl_binary() { # 0. check environment variable whether it's executable if [ ! -z "$OPENSSL" ] && [ ! -x "$OPENSSL" ]; then pr_redln "\ncannot find (\$OPENSSL=$OPENSSL) binary." outln "continuing ..." fi if [ -x "$OPENSSL" ]; then # 1. check environment variable : else # 2. otherwise try openssl in path of testssl.sh OPENSSL=$RUN_DIR/openssl if [ ! -x "$OPENSSL" ] ; then # 3. with arch suffix OPENSSL=$RUN_DIR/openssl.$(uname -m) if [ ! -x "$OPENSSL" ] ; then #4. finally: didn't find anything, so we take the one from the system: OPENSSL=$(which openssl 2>/dev/null) fi fi fi "$OPENSSL" version -a 2>&1 >/dev/null if [ $? -ne 0 ] || [ ! -x "$OPENSSL" ]; then outln pr_magentaln "FATAL: cannot exec or find any openssl binary " exit -1 fi # http://www.openssl.org/news/openssl-notes.html OSSL_VER=$($OPENSSL version | awk -F' ' '{ print $2 }') OSSL_VER_MAJOR=$(echo "$OSSL_VER" | sed 's/\..*$//') OSSL_VER_MINOR=$(echo "$OSSL_VER" | sed -e 's/^.\.//' | tr -d '[a-zA-Z]-') OSSL_VER_APPENDIX=$(echo "$OSSL_VER" | tr -d '[0-9.]') OSSL_VER_PLATFORM=$($OPENSSL version -p | sed 's/^platform: //') OSSL_BUILD_DATE=$($OPENSSL version -a | grep '^built' | sed -e 's/built on//' -e 's/: ... //' -e 's/: //' -e 's/ UTC//' -e 's/ +0000//' -e 's/.000000000//') echo $OSSL_BUILD_DATE | grep -q "not available" && OSSL_BUILD_DATE="" export OPENSSL OSSL_VER OSSL_BUILD_DATE OSSL_VER_PLATFORM return 0 } openssl_age() { 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 pr_magentaln " Your \"$OPENSSL\" is way too old ( <-h|--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> what local cipher with is a/v? $PROG_NAME URI ("$PROG_NAME URI" does everything except ciphers per proto/each cipher) <-e|--each-cipher> checks each local cipher remotely <-E|--cipher-per-proto> checks those per protocol <-f|--ciphers> checks common cipher suites <-p|--protocols> checks TLS/SSL protocols <-S|--server_defaults> displays the servers default picks and certificate info <-P|--preference> displays the servers picks: protocol+cipher <-y|--spdy|--npn> checks for SPDY/NPN <-x|--single-cipher-test> tests matched of cipher <-U|--vulnerable> tests all vulnerabilities <-B|--heartbleed> tests for heartbleed vulnerability <-I|--ccs|--ccs-injection> tests for CCS injection vulnerability <-R|--renegotiation> tests renegotiation vulnerabilities <-C|--compression|--crime> tests CRIME vulnerability <-T|--breach> tests BREACH vulnerability <-O|--poodle> tests for POODLE (SSL) vulnerability <-F|--freak> tests FREAK vulnerability <-A|--beast> tests BEAST vulnerability <-s|--pfs|--fs|--nsa> checks (perfect) forward secrecy settings <-4|--rc4|--appelbaum> which RC4 ciphers are being offered? <-H|--header|--headers> tests HSTS, HPKP, server/app banner, security headers, cookie special invocations: <-t|--starttls> protocol does a default run against a STARTTLS enabled service <--mx> domain/host tests MX records from high to low priority (STARTTLS, port 25) partly mandatory parameters: URI host|host:port|URL|URL:port (port 443 is assumed unless otherwise specified) pattern an ignore case word pattern of cipher hexcode or any other string in the name, kx or bits protocol is one of ftp,smtp,pop3,imap,xmpp,telnet,ldap (for the latter two you need e.g. the supplied openssl) tuning options: --assuming-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 --sneaky be less verbose wrt referer headers --long wide output for tests like RC4 also with hexcode, kx, strength --warnings "batch" doesn't wait for keypress, "off|false" skips connection warning --color 0: no escape or other codes 1: b/w escape codes 2: color (default) --debug 1: screen output normal but debug output in itemp files. 2-6: see line ~60 Need HTML output? Just pipe through "aha" (Ansi HTML Adapter: github.com/theZiz/aha) like "$PROG_NAME | aha >output.html" EOF exit $1 } mybanner() { me=$(basename "$0") osslver=$($OPENSSL version) osslpath=$(which $OPENSSL) nr_ciphers=$($OPENSSL ciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' | sed 's/:/ /g' | wc -w | sed 's/ //g') hn=$(hostname) #poor man's ident (nowadays ident not neccessarily installed) idtag=$(grep -a '\$Id' $0 | grep -aw "[E]xp" | sed -e 's/^# //' -e 's/\$ $/\$/') [ "$COLOR" -ne 0 ] && idtag="\033[1;30m$idtag\033[m\033[1m" bb=$(cat <$TEMPDIR/environment.txt << EOF PID: $$ bash version: ${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}.${BASH_VERSINFO[2]} status: ${BASH_VERSINFO[4]} machine: ${BASH_VERSINFO[5]} operating system: $SYSTEM shellopts: $SHELLOPTS "$osslver" [$nr_ciphers ciphers] on $hn:$osslpath built: "$OSSL_BUILD_DATE", platform: "$OSSL_VER_PLATFORM" $idtag PATH: $PATH PROG_NAME: $PROG_NAME PROG_DIR: $PROG_DIR RUN_DIR: $RUN_DIR CAPATH: $CAPATH ECHO: $ECHO COLOR: $COLOR TERM_DWITH: $TERM_DWITH SHOW_LOC_CIPH: $SHOW_LOC_CIPH SHOW_EACH_C: $SHOW_EACH_C SSL_NATIVE: $SSL_NATIVE ASSUMING_HTTP $ASSUMING_HTTP SNEAKY: $SNEAKY VERBERR: $VERBERR 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 which locale &>/dev/null && locale >>$TEMPDIR/environment.txt || echo "locale doesn't exist" >>$TEMPDIR/environment.txt $OPENSSL ciphers -V $1 &>$TEMPDIR/all_local_ciphers.txt fi } cleanup () { if [[ "$DEBUG" -ge 1 ]] ; then outln "\n" pr_underline "DEBUG (level $DEBUG): see files in $TEMPDIR" else [ -d "$TEMPDIR" ] && rm -rf ${TEMPDIR}; fi outln [ -n "$NODE" ] && datebanner "Done" # only if running against server outln } # for now only GOST engine initialize_engine(){ if ! $OPENSSL engine gost -vvvv -t -c >/dev/null 2>&1; then outln pr_litemagenta "No engine or GOST support via engine with your $OPENSSL"; outln return 1 elif $OPENSSL engine gost -vvvv -t -c 2>&1 | grep -iq "No such" ; then outln pr_litemagenta "No engine or GOST support via engine with your $OPENSSL"; outln return 1 elif echo $osslver | grep -q LibreSSL; then return 1 else if [ ! -z "$OPENSSL_CONF" ]; then pr_litemagenta "For now I am providing the config file in to have GOST support"; outln else [ -z "$TEMPDIR" ] && maketempf OPENSSL_CONF=$TEMPDIR/gost.conf || exit 6 # see https://www.mail-archive.com/openssl-users@openssl.org/msg65395.html cat >$OPENSSL_CONF << EOF 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 export OPENSSL_CONF fi fi return 0 } ignore_no_or_lame() { [ "$WARNINGS" = "off" -o "$WARNINGS" = "false" ] && return 0 [ "$WARNINGS" = "batch" ] && return 1 pr_magenta "$1 " read a case $a in Y|y|Yes|YES|yes) return 0;; default) ;; esac return 1 } # Parameters: 1 URI # [2] protocol parse_hn_port() { PORT=443 # unless otherwise auto-determined, see below NODE="$1" # strip "https" and trailing urlpath supposed it was supplied additionally echo $NODE | grep -q 'https://' && NODE=$(echo $NODE | sed -e 's/https\:\/\///') # strip trailing urlpath NODE=$(echo $NODE | sed -e 's/\/.*$//') # was the address supplied like [AA:BB:CC::]:port ? if echo $NODE | grep -q ']' ; then tmp_port=$(printf $NODE | sed 's/\[.*\]//' | sed 's/://') # determine v6 port, supposed it was supplied additionally if [ ! -z "$tmp_port" ] ; then PORT=$tmp_port NODE=$(printf $NODE | sed "s/:$PORT//") fi NODE=$(printf $NODE | sed -e 's/\[//' -e 's/\]//') else # determine v4 port, supposed it was supplied additionally echo $NODE | grep -q ':' && PORT=$(echo $NODE | sed 's/^.*\://') && NODE=$(echo $NODE | sed 's/\:.*$//') fi SNI="-servername $NODE" URL_PATH=$(echo $1 | sed 's/.*'"${NODE}"'//' | sed 's/.*'"${PORT}"'//') # remove protocol and node part and port URL_PATH=$(echo $URL_PATH | sed 's/\/\//\//g') # we rather want // -> / [ -z "$URL_PATH" ] && URL_PATH="/" # now get NODEIP if ! get_dns_entries ; then pr_magenta "Can't proceed: No IP address for \"$NODE\" available"; outln "\n" exit -1 fi # check if we can connect to port if ! fd_socket; then ignore_no_or_lame "Ignore? " [ $? -ne 0 ] && exit 3 fi close_socket datebanner "Testing" if [[ -z "$2" ]] ; then # for starttls we want another check # determine protocol which works (needed for IIS6). If we don't have IIS6, 1st try will succeed --> better because we use the variable # all over the place. Stupid thing that we need to do that stuff for IIS<=6 for OPTIMAL_PROTO in "" "-tls1_2" "-tls1" "-ssl3" "-tls1_1" "-ssl2" ""; do $OPENSSL s_client $OPTIMAL_PROTO -connect "$NODEIP:$PORT" $SNI /dev/null && all_failed=1 && break all_failed=0 done debugme echo "OPTIMAL_PROTO: $OPTIMAL_PROTO" if [ $all_failed -eq 0 ]; then outln pr_boldln " $NODEIP:$PORT doesn't seem a TLS/SSL enabled server or it requires a certificate"; ignore_no_or_lame " Note that the results might look ok but they are nonsense. Proceed ? " [ $? -ne 0 ] && exit 3 fi if [ $SNEAKY -eq 0 ] ; then GET_REQ11="GET $URL_PATH HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $UA_SNEAKY\r\nConnection: Close\r\nAccept: text/*\r\n\r\n" HEAD_REQ10="HEAD $URL_PATH HTTP/1.0\r\nUser-Agent: $UA_SNEAKY\r\nAccept: text/*\r\n\r\n" else GET_REQ11="GET $URL_PATH HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $UA_STD\r\nConnection: Close\r\nAccept: text/*\r\n\r\n" HEAD_REQ10="HEAD $URL_PATH HTTP/1.0\r\nUser-Agent: $UA_STD\r\nAccept: text/*\r\n\r\n" fi runs_HTTP $OPTIMAL_PROTO else protocol=$(echo "$2" | sed 's/s$//') # strip trailing s in ftp(s), smtp(s), pop3(s), imap(s), ldap(s), telnet(s) case "$protocol" in ftp|smtp|pop3|imap|xmpp|telnet|ldap) STARTTLS="-starttls $protocol"; export STARTTLS $OPENSSL s_client -connect $NODEIP:$PORT $STARTTLS 2>/dev/null >$TMPFILE &2 exit 1 ;; esac fi ${do_mx_allentries} || initialize_engine outln return 0 } get_dns_entries() { test4iponly=$(printf $NODE | sed -e 's/[0-9]//g' -e 's/\.//g') if [ "x$test4iponly" == "x" ]; then # only an IPv4 address was supplied IP4=$NODE SNI="" # override Server Name Indication as we test the IP only else # for security testing sometimes we have local entries. Getent is BS under Linux for localhost: No network, no resulution IP4=$(grep -w "$NODE" /etc/hosts | egrep -v ':|^#' | egrep "[[:space:]]$NODE" | awk '{ print $1 }') if which host &> /dev/null && [ -z "$IP4" ] ; then IP4=$(host -t a $NODE 2>/dev/null | grep -v alias | sed 's/^.*address //') if echo "$IP4" | grep -q NXDOMAIN || echo "$IP4" | grep -q "no A record"; then return 1 fi fi # MSYS2 has no host or getent, so we do this if [ -z "$IP4" ] ; then IP4=$(nslookup $NODE 2>/dev/null | grep -A10 Name | grep -v Name | sed 's/^Address.*: .//') [ -z "$IP4" ] && return 2 fi # for IPv6 we often get this :ffff:IPV4 address which isn't of any use #which getent 2>&1 >/dev/null && IP6=$(getent ahostsv6 $NODE | grep $NODE | awk '{ print $1}' | grep -v '::ffff' | uniq) if [ -z "$IP6" ] ; then if host -t aaaa $NODE &>/dev/null ; then IP6=$(host -t aaaa $NODE | grep -v alias | grep -v "no AAAA record" | sed 's/^.*address //') else IP6="" fi fi # MSYS2 has no host or getent, so we do this if [ -z "$IP6" ] ; then IP6=$(nslookup -type=aaaa $NODE 2>/dev/null | grep -A10 Name | grep -v Name | sed 's/^Address.*: .//') fi fi # test4iponly IPADDRs="$IP4" [ ! -z "$IP6" ] && IPADDRs="$IP4 $IP6" # FIXME: we could/should test more than one IPv4 addresses if available, same IPv6. For now we test the first IPv4: NODEIP=$(echo "$IP4" | head -1) [ -z "$NODEIP" ] && return 3 # we can't do this as some checks and even openssl are not yet IPv6 safe. BTW: bash sockets do IPv6 transparently! #NODEIP=$(echo "$IP6" | head -1) if which host &> /dev/null; then #rDNS=$(host -t PTR $NODEIP 2>/dev/null | grep -v "is an alias for" | sed -e 's/^.*pointer //' -e 's/\.$//') rDNS=$(host -t PTR $NODEIP 2>/dev/null | grep 'pointer' | sed -e 's/^.*pointer //' -e 's/\.$//') elif which nslookup &> /dev/null; then rDNS=$(nslookup -type=PTR $NODEIP 2> /dev/null | grep -v 'canonical name =' | grep 'name = ' | awk '{ print $NF }' | sed 's/\.$//') fi [ -z "$rDNS" ] && rDNS="--" return 0 } display_rdns_etc() { if [ $(printf "$IPADDRs" | wc -w) -gt 1 ]; then out " further IP addresses: " for i in $IPADDRs; do [ "$i" == "$NODEIP" ] && continue out " $i" done outln fi if [ -n "$rDNS" ] ; then printf " %-23s %s" "rDNS ($NODEIP):" "$rDNS" fi } datebanner() { tojour=$(date +%F)" "$(date +%R) outln pr_reverse "$1 now ($tojour) ---> $NODEIP:$PORT ($NODE) <---"; outln "\n" if [ "$1" = "Testing" ] ; then display_rdns_etc fi outln } mx_allentries() { local mxs mx local mxport if which host &> /dev/null; then mxs=$(host -t MX "$1" | grep 'handled by' | sed -e 's/^.*by //g' -e 's/\.$//') elif which dig &> /dev/null; then mxs=$(dig +short -t MX "$1") elif which nslookup &> /dev/null; then mxs=$(nslookup -type=MX "$1" 2> /dev/null | grep 'mail exchanger = ' | sed 's/^.*mail exchanger = //g') else pr_magentaln 'No dig, host or nslookup' exit 3 fi # test first higher priority servers mxs=$(echo "$mxs" | sort -n | sed -e 's/^.* //' -e 's/\.$//' | tr '\n' ' ') mxport=${2:-25} if [ -n "$mxs" ] && [ "$mxs" != ' ' ] ; then starttls_proto="smtp" [[ $mxport == "465" ]] && starttls_proto="" # no starttls for Port 465 pr_bold "Testing now all MX records (on port $mxport): "; outln "$mxs" for mx in $mxs; do parse_hn_port "$mx:$mxport" $starttls_proto && lets_roll done else pr_boldln " $1 has no MX records(s)" fi } # This intializes boolean global do_* variables, meant primarily to keep track of what to do initialize_globals() { do_allciphers=false do_vulnerabilities=false do_beast=false do_breach=false do_ccs_injection=false do_cipher_per_proto=false do_crime=false do_freak=false do_header=false do_heartbleed=false do_mx_allentries=false do_pfs=false do_protocols=false do_rc4=false do_renego=false do_run_std_cipherlists=false do_server_defaults=false do_server_preference=false do_spdy=false do_ssl_poodle=false do_test_just_one=false do_tls_sockets=false } # Set default scanning options set_scanning_defaults() { do_vulnerabilities=true do_beast=true do_breach=true do_ccs_injection=true do_crime=true do_freak=true do_header=true do_heartbleed=true do_pfs=true do_protocols=true do_rc4=true do_renego=true do_run_std_cipherlists=true do_server_defaults=true do_server_preference=true do_spdy=true do_ssl_poodle=true VULN_COUNT=10 } query_globals() { local gbl local true_nr=0 for gbl in do_allciphers do_vulnerabilities do_beast do_breach do_ccs_injection do_cipher_per_proto do_crime \ do_freak do_header do_heartbleed do_mx_allentries do_pfs do_protocols do_rc4 do_renego \ do_run_std_cipherlists do_server_defaults do_server_preference do_spdy do_ssl_poodle \ do_test_just_one do_tls_sockets; do [ "${!gbl}" == "true" ] && let true_nr++ done return $true_nr } debug_globals() { local gbl for gbl in do_allciphers do_vulnerabilities do_beast do_breach do_ccs_injection do_cipher_per_proto do_crime \ do_freak do_header do_heartbleed do_mx_allentries do_pfs do_protocols do_rc4 do_renego \ do_run_std_cipherlists do_server_defaults do_server_preference do_spdy do_ssl_poodle \ do_test_just_one do_tls_sockets; do printf "%-22s = %s\n" $gbl "${!gbl}" done printf "%-22s : %s\n" URI: "$URI" } # Parses options startup() { # Set defaults if only an URI was specified, maybe ToDo: use "="-option, then: ${i#*=} i.e. substring removal [[ "$#" -eq 1 ]] && set_scanning_defaults while [[ $# -gt 0 ]]; do case $1 in -b|--banner|-v|--version) exit 0;; --mx) do_mx_allentries=true;; --mx465) # doesn't work with major ISPs do_mx_allentries=true PORT=465 ;; --mx587) # doesn't work with major ISPs do_mx_allentries=true PORT=587 ;; -V|--local) initialize_engine # GOST support- prettyprint_local "$2" exit $? ;; -x|--single-ciphers-test|--single-cipher-test|--single_cipher_test|--single_ciphers_test) do_test_just_one=true single_cipher=$2 shift;; -t|--starttls) STARTTLS_PROTOCOL=$2 do_starttls=true shift;; -e|--each-cipher) do_allciphers=true;; -E|--cipher-per-proto|--cipher_per_proto) do_cipher_per_proto=true;; -h|--help) help 0 ;; -p|--protocols) do_protocols=true do_spdy=true;; -y|--spdy|--npn) do_spdy=true;; -f|--ciphers) do_run_std_cipherlists=true;; -S|--server_defaults|--server-defaults) do_server_defaults=true;; -P|--server_preference|--server-preference) do_server_preference=true;; -H|--header|--headers) do_header=true;; -U|--vulnerable) do_vulnerabilities=true do_heartbleed=true do_ccs_injection=true do_renego=true do_crime=true do_breach=true do_ssl_poodle=true do_freak=true do_beast=true do_rc4=true VULN_COUNT=10 ;; -B|--heartbleed) do_heartbleed=true let "VULN_COUNT++" ;; -I|--ccs|--ccs_injection|--ccs-injection) do_ccs_injection=true let "VULN_COUNT++" ;; -R|--renegotiation) do_renego=true let "VULN_COUNT++" ;; -C|--compression|--crime) do_crime=true let "VULN_COUNT++" ;; -T|--breach) do_breach=true let "VULN_COUNT++" ;; -O|--poodle) do_ssl_poodle=true let "VULN_COUNT++" ;; -F|--freak) do_freak=true let "VULN_COUNT++" ;; -A|--beast) do_beast=true let "VULN_COUNT++" ;; -4|--rc4|--appelbaum) do_rc4=true;; -s|--pfs|--fs|--nsa) do_pfs=true;; -q) ### this is a development feature and will disappear: # DEBUG=3 ./testssl.sh -q 03 "cc, 13, c0, 13" google.de # DEBUG=3 ./testssl.sh -q 01 yandex.ru TLS_LOW_BYTE="$2"; HEX_CIPHER="" if [ $# -eq 4 ]; then # protocol AND ciphers specified HEX_CIPHER="$3" shift fi shift do_tls_sockets=true outln "TLS_LOW_BYTE/HEX_CIPHER: ${TLS_LOW_BYTE}/${HEX_CIPHER}" ;; --long) LONG=0 ;; --assuming-http|--assuming_http|--assume_http|--assume-http) ASSUMING_HTTP=0 ;; --sneaky) SNEAKY=0 ;; --warnings) case "$2" in batch|off|false) WARNINGS="$2" ;; default) pr_magentaln "warnings can be either \"batch\", \"off\" or \"false\"" ;; esac shift ;; --show-each-cipher) SHOW_EACH_C=1 ;; #FIXME: sense is vice versa --debug) DEBUG="$2" shift ;; --color) COLOR=$2 if [ $COLOR -ne 0 ] && [ $COLOR -ne 1 ] && [ $COLOR -ne 2 ] ; then COLOR=2 pr_magentaln "$0: unrecognized color: $2" 1>&2 help 1 fi shift ;; --ssl_native|--ssl-native) SSL_NATIVE=0 ;; (--) shift break ;; (-*) pr_magentaln "$0: unrecognized option $1" 1>&2; help 1 ;; (*) break ;; esac shift done # Show usage if no options were specified [ -z $1 ] && help 0 # left off here is the URI URI=$1 [ "$DEBUG" -ge 4 ] && debug_globals # if we have no "do_*" set here --> query_globals: we do a standard run -- otherwise just the one specified query_globals && set_scanning_defaults } lets_roll() { local ret ${do_tls_sockets} && { tls_sockets "$TLS_LOW_BYTE" "$HEX_CIPHER"; exit $?; } ${do_test_just_one} && test_just_one ${single_cipher} ${do_allciphers} && { allciphers; ret=$(($? + ret)); } ${do_cipher_per_proto} && { cipher_per_proto; ret=$(($? + ret)); } ${do_protocols} && { runprotocols; ret=$(($? + ret)); } ${do_spdy} && { spdy; ret=$(($? + ret)); } ${do_run_std_cipherlists} && { run_std_cipherlists; ret=$(($? + ret)); } ${do_server_preference} && { server_preference; ret=$(($? + ret)); } ${do_server_defaults} && { server_defaults; ret=$(($? + ret)); } if ${do_header}; then #TODO: refactor this into functions if [[ $SERVICE == "HTTP" ]]; then hsts "$URL_PATH" hpkp "$URL_PATH" serverbanner "$URL_PATH" applicationbanner "$URL_PATH" cookieflags "$URL_PATH" moreflags "$URL_PATH" fi fi # vulnerabilities if [ $VULN_COUNT -gt $VULN_THRESHLD ] || ${do_vulnerabilities}; then outln; pr_blue "--> Testing vulnerabilities" outln "\n" fi ${do_heartbleed} && { heartbleed; ret=$(($? + ret)); } ${do_ccs_injection} && { ccs_injection; ret=$(($? + ret)); } ${do_renego} && { renego; ret=$(($? + ret)); } ${do_crime} && { crime; ret=$(($? + ret)); } ${do_breach} && { breach "$URL_PATH" ; ret=$(($? + ret)); } ${do_ssl_poodle} && { ssl_poodle; ret=$(($? + ret)); } ${do_freak} && { freak; ret=$(($? + ret)); } ${do_beast} && { beast; ret=$(($? + ret)); } ${do_rc4} && { rc4; ret=$(($? + ret)); } ${do_pfs} && { pfs; ret=$(($? + ret)); } return $ret } ################# main ################# find_openssl_binary mybanner [ -z "$PROG_DIR" ] && PROG_DIR="." # mapping file provides a pair "keycode/ RFC style name", see the RFCs, cipher(1) and # www.carbonwind.net/TLS_Cipher_Suites_Project/tls_ssl_cipher_suites_simple_table_all.htm [ -r "$(dirname $PROG_DIR)/mapping-rfc.txt" ] && MAP_RFC_FNAME=$(dirname $PROG_DIR)"/mapping-rfc.txt" initialize_globals startup "$@" openssl_age maketempf if ${do_mx_allentries} ; then query_globals # if we have just one "do_*" set here --> query_globals: we do a standard run -- otherwise just the one specified [ $? -eq 1 ] && set_scanning_defaults initialize_engine mx_allentries "${URI}" $PORT ret=$? else parse_hn_port "${URI}" "${STARTTLS_PROTOCOL}" lets_roll ret=$? fi exit $ret # $Id: testssl.sh,v 1.254 2015/05/25 19:14:57 dirkw Exp $ # vim:ts=5:sw=5 # ^^^ FYI: use vim and you will see everything beautifully indented with a 5 char tab