testssl.sh/testssl.sh

4198 lines
141 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# vim:ts=5:sw=5
# use vim and you will see everything beautifully indented with a 5 char tab
[ -z "$BASH_VERSINFO" ] && echo "\n$(tput setaf 5) Please make sure you're using bash! Bye...$(tput sgr0)\n" && exit 1
# testssl.sh is a program for spotting weak SSL encryption, ciphers, version and some
# vulnerabilities or features
#
# Devel version is available from https://github.com/drwetter/testssl.sh
# Stable version from https://testssl.sh
# Please file bugs at github! https://github.com/drwetter/testssl.sh/issues
# 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 for me as a pentester.
# 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 some shell commands around it, which I used for my pen tests. This is how everything started.
# Now it has grown up, it has bash socket support for 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 interactive shell: zsh (zmodload zsh/net/socket
# -- checkout zsh/net/tcp) too! /bin/bash though is way more often used within Linux and it's perfect
# for cross platform support, see MacOS X and also under Windows the MSYS2 extension.
# Cross-platform is one of the three ideas of this script. Second: Ease of installation.
# No compiling, install gems, go to CPAN, use pip etc. Third: Easy to use and to interpret
# the result.
#
# Did I mention it's open source?
#
# 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.
#
# Oh, and did I mention testssl.sh is open source?
#
# 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 broken 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!
# The idea is if this script can't tell something for sure it speaks up so that you have
# a better picture.
# debugging help:
readonly PS4='${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
# make sure that temporary files are cleaned up after use in ANY case
trap "cleanup" QUIT EXIT
readonly VERSION="2.5dev"
readonly SWCONTACT="dirk aet testssl dot sh"
echo $VERSION | grep -q dev && \
SWURL="http://dev.testssl.sh" ||
SWURL=" https://testssl.sh"
readonly PROG_NAME=$(basename "$0")
readonly RUN_DIR=$(dirname $0)
INSTALL_DIR=""
MAP_RFC_FNAME=""
which git &>/dev/null && readonly GIT_REL=$(git log --format='%h %ci' -1 2>/dev/null | awk '{ print $1" "$2" "$3 }')
readonly CVS_REL=$(tail -5 $0 | awk '/dirkw Exp/ { print $4" "$5" "$6}')
readonly CVS_REL_SHORT=$(tail -5 $0 | awk '/dirkw Exp/ { print $4 }')
readonly SYSTEM=$(uname -s)
date --help >/dev/null 2>&1 && \
readonly HAS_GNUDATE=true || \
readonly HAS_GNUDATE=false
echo A | sed -E 's/A//' >/dev/null 2>&1 && \
readonly HAS_SED_E=true || \
readonly HAS_SED_E=false
readonly ECHO="/usr/bin/printf --" # works under Linux, BSD, MacOS.
TERM_DWITH=${COLUMNS:-$(tput cols)} # for future custom line wrapping
TERM_CURRPOS=0 # ^^^ we also need to find out the length or current pos in the line
# following variables make use of $ENV, e.g. OPENSSL=<myprivate_path_to_openssl> ./testssl.sh <host>
# 0 means (normally) true here. Some of the variables are also accessible with a command line switch
# we have tab indentation with 5 virtual chars!
OPENSSL=${OPENSSL:-/usr/bin/openssl}
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
WIDE=${WIDE:-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
# furher vars needed to follow
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=""
OSSL_VER="" # openssl version, will be auto-determined
OSSL_VER_MAJOR=0
OSSL_VER_MINOR=0
OSSL_VER_APPENDIX="none"
HAS_DH_BITS=true
NODE=""
NODEIP=""
IPADDRs=""
IP46ADDRs=""
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=""
# The various hexdump commands we need to replace xxd (BSD compatibility))
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 analyze the reply
HEXDUMPPLAIN=(hexdump -ve '1/1 "%.2x"') # Replaces both xxd -p and tr -cd '[:print:]'
###### some hexbytes for bash network sockets follow ######
# 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)
magenta=$(tput setaf 5)
cyan=$(tput setaf 6)
grey=$(tput setaf 7)
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/$NODEIP.$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
}
strip_lf() {
echo "$1" | tr -d '\n' | tr -d '\r'
}
#problems not handled: chunked
http_header() {
local header
local -i ret
local referer useragent
local url
outln; pr_blue "--> Testing HTTP header response"; outln " @ \"$URL_PATH\"\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 '/^<HTML/,$d' -e '/^<html/,$d' -e '/^<XML /,$d' -e '/<?XML /,$d' \
-e '/^<xml /,$d' -e '/<?xml /,$d' -e '/^<\!DOCTYPE/,$d' -e '/^<\!doctype/,$d' $HEADERFILE >$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
#TODO: attention: if this runs into a timeout, we're dead. Try again differently:
printf "$GET_REQ11" | $OPENSSL s_client $OPTIMAL_PROTO -quiet -ign_eof -connect $NODEIP:$PORT $SNI &>$HEADERFILE
if [ $? -ne 0 ]; then
pr_litemagentaln " failed (HTTP header request stalled)"
return 3
#ret=3
fi
fi
status_code=$(awk '/^HTTP\// { print $2 }' $HEADERFILE)
msg_thereafter=$(awk -F"$status_code" '/^HTTP\// { print $2 }' $HEADERFILE) # dirty trick to use the status code as a
msg_thereafter=$(strip_lf "$msg_thereafter") # field separator, otherwise we need a loop with awk
debugme echo "Status/MSG: $status_code $msg_thereafter"
pr_bold " HTTP Status Code "
[ -z "$status_code" ] && pr_litemagentaln "No status code" && return 3
out " $status_code$msg_thereafter"
case $status_code in
301|302|307|308) out ", redirecting to \"$(grep -a '^Location' $HEADERFILE | sed 's/Location: //' | tr -d '\r\n')\"" ;;
200) ;;
206) out " -- WTF?" ;;
400) pr_litemagenta " (better try another URL)" ;;
401) grep -aq "^WWW-Authenticate" $HEADERFILE && out " "; strip_lf "$(grep -a "^WWW-Authenticate" $HEADERFILE)"
;;
403) ;;
404) out " (Maybe supply a path which doesn't give a \"$status_code$msg_thereafter\")" ;;
405) ;;
*) pr_litemagenta ". Oh, didn't expect a $status_code$msg_thereafter";;
esac
outln
# we don't call "tmpfile_handle $FUNCNAME.txt" as we need the header file in other functions!
return $ret
}
http_date() {
local now difftime
if [ ! -s $HEADERFILE ] ; then
http_header "$1" || return 3 # this is just for the line "Testing HTTP header response"
fi
pr_bold " HTTP clock skew "
if [[ $SERVICE != "HTTP" ]] ; then
out "not tested as we're not targeting HTTP"
else
printf "$GET_REQ11" | $OPENSSL s_client $OPTIMAL_PROTO -ign_eof -connect $NODEIP:$PORT $SNI &>$TMPFILE
now=$(date "+%s") # we need an ACCURATE date here and cannot rely on the headerfile!
HTTP_TIME=$(awk -F': ' '/^date:/ { print $2 } /^Date:/ { print $2 }' $TMPFILE)
if [ -n "$HTTP_TIME" ] ; then
if $HAS_GNUDATE ; then
HTTP_TIME=$(date --date="$HTTP_TIME" "+%s")
else
HTTP_TIME=$(date -j -f "%a, %d %b %Y %T %Z" "$HTTP_TIME" "+%s" 2>/dev/null) # the trailing \r confuses BSD flavors otherwise
fi
difftime=$(($HTTP_TIME - $now))
[[ $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
detect_ipv4
}
# Borrowd from Glenn Jackman, see https://unix.stackexchange.com/users/4667/glenn-jackman
detect_ipv4() {
local octet="(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])"
local ipv4address="$octet\\.$octet\\.$octet\\.$octet"
local your_ip_msg=" (check if it's yours or e.g. a cluster IP)"
local first=true
local spaces=" "
if [ ! -s $HEADERFILE ] ; then
http_header "$1" || return 3
fi
if grep -iqE $ipv4address $HEADERFILE; then
pr_bold " IPv4 address in header "
cat $HEADERFILE | while read line; do
result="$(echo -n "$line" | grep -E $ipv4address )"
result=$(strip_lf "$result")
if [ -n "$result" ] ; then
if ! $first; then
out "$spaces"
your_ip_msg=""
else
first=false
fi
# a little bit of sanitzing, otherwise printf will hiccup @ %
pr_litered "$(echo $result|sed 's/%/%%/g')"
outln "$your_ip_msg"
fi
done
fi
}
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 "
pr_bold " Strict Transport Security "
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 days is too short"
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 "
pr_bold " Public Key Pinning "
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 occurrences and not lines w/ occurrences:
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/Via/"$yellow"Via$off/g" \
-e "s/X-Cache-Lookup/"$yellow"X-Cache-Lookup$off/g" \
-e "s/X-Cache/"$yellow"X-Cache$off/g" \
-e "s/X-Squid/"$yellow"X-Squid$off/g" \
-e "s/X-Varnish/"$yellow"X-Varnish$off/g" \
-e "s/X-OWA-Version/"$yellow"X-OWA-Version$off/g" \
-e "s/X-Version/"$yellow"X-Version$off/g" \
-e "s/X-Powered-By/"$yellow"X-Powered-By$off/g" \
-e "s/X-AspNet-Version/"$yellow"X-AspNet-Version$off/g"
}
server_banner() {
local serverbanner
if [ ! -s $HEADERFILE ] ; then
http_header "$1" || return 3
fi
pr_bold " Server banner "
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 0
}
rp_banner() {
if [ ! -s $HEADERFILE ] ; then
http_header "$1" || return 3
fi
pr_bold " Reverse Proxy banner "
egrep -ai '^Via|^X-Cache|^X-Squid|X-Varnish' $HEADERFILE >$TMPFILE && \
emphasize_stuff_in_headers "$(sed 's/^/ /g' $TMPFILE | tr '\n\r' ' ')" || \
outln "--"
tmpfile_handle $FUNCNAME.txt
return 0
}
application_banner() {
local line
local first=true
local spaces=" "
if [ ! -s $HEADERFILE ] ; then
http_header "$1" || return 3
fi
pr_bold " Application banner "
egrep -ai '^X-Powered-By|^X-AspNet-Version|^X-Version|^Liferay-Portal|^X-OWA-Version' $HEADERFILE >$TMPFILE
if [ $? -ne 0 ] ; then
outln "--"
else
cat $TMPFILE | while read line; do
line=$(strip_lf "$line")
if ! $first; then
out "$spaces"
else
first=false
fi
emphasize_stuff_in_headers "$line"
done
fi
tmpfile_handle $FUNCNAME.txt
return 0
}
cookie_flags() { # ARG1: Path, ARG2: path
local -i nr_cookies
local nr_httponly nr_secure
local negative_word
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 \"$1\")"
fi
outln
tmpfile_handle $FUNCNAME.txt
return 0
}
more_flags() {
local good_flags2test="X-Frame-Options X-XSS-Protection X-Content-Type-Options Content-Security-Policy X-Content-Security-Policy X-WebKit-CSP Content-Security-Policy-Report-Only"
local other_flags2test="Access-Control-Allow-Origin Upgrade X-Served-By"
local egrep_pattern=""
local f2t result_str
local first=true
local spaces=" "
if [ ! -s $HEADERFILE ] ; then
http_header "$1" || return 3
fi
pr_bold " Security headers "
# convert spaces to | (for egrep)
egrep_pattern=$(echo "$good_flags2test $other_flags2test"| sed -e 's/ /|\^/g' -e 's/^/\^/g')
egrep -ai "$egrep_pattern" $HEADERFILE >$TMPFILE
if [ $? -ne 0 ]; then
outln "--"
ret=1
else
ret=0
for f2t in $good_flags2test; do
debugme "---> $f2t"
result_str=$(grep -wi "^$f2t" $TMPFILE | grep -vi "$f2t"-)
result_str=$(strip_lf "$result_str")
[ -z "$result_str" ] && continue
if ! $first; then
out "$spaces" # 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 line feeds 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
#TODO: we should just echo this and avoid the global var HEXC
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() {
pr_bold "$2 " # indent in order to be in the same row as server preferences
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 </dev/null
ret=$?
[[ $DEBUG -ge 2 ]] && cat $TMPFILE
case $3 in
0) # ok to offer
[[ $ret -eq 0 ]] && \
pr_greenln "offered (OK)" || \
pr_brownln "not offered (NOT ok)" ;;
1) # the ugly ones
[[ $ret -eq 0 ]] && \
pr_redln "offered (NOT ok)" || \
pr_greenln "not offered (OK)" ;;
2) # bad but not worst
[[ $ret -eq 0 ]] && \
pr_literedln "offered (NOT ok)" || \
pr_litegreenln "not offered (OK)" ;;
3) # not totally bad
[[ $ret -eq 0 ]] && \
pr_brownln "offered (NOT ok)" || \
outln "not offered (OK)" ;;
*) # we shouldn't reach this
pr_litemagenta "? (please report this)" ;;
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 (!!), separated 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
if $HAS_SED_E; then
data=$(echo "$1" | sed -e 's/# .*$//g' -e 's/ //g' | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//; /^$/d' | sed 's/,/\\/g' | tr -d '\n')
else
data=$(echo "$1" | sed -e 's/# .*$//g' -e 's/ //g' | sed -r 's/^[[:space:]]+//; s/[[:space:]]+$//; /^$/d' | sed 's/,/\\/g' | tr -d '\n')
fi
[[ $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:+----------------------------------------------}"
}
# arg1: hexcode
# arg2: cipher in openssl notation
# arg3: keyexchange
# arg4: encryption (maybe included "export")
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"
# workaround for color escape codes:
if printf -- "$kx" | "${HEXDUMPVIEW[@]}" | grep -q 33 ; then # here's a color code
kx="$kx " # one for color code if ECDH and three digits
[[ "${#kx}" -eq 18 ]] && kx="$kx " # 18 means DH, colored < 1000. Add another space
[[ "${#kx}" -eq 19 ]] && kx="$kx " # 19 means DH, colored >=1000. Add another space
#echo ${#kx} # should be always 20
fi
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(){
local hexcode n ciph sslvers kx auth enc mac export
local dhlen
local ret
pr_blue "--> Testing single cipher with word pattern "\"$1\"" (ignore case)"; outln
! $HAS_DH_BITS && pr_litemagentaln " (Your $OPENSSL is too old to show DH/ECDH bits)"
outln
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
# FIXME: e.g. OpenSSL < 1.0 doesn't understand "-V" --> we can't do anything about it!
normalize_ciphercode $hexcode
neat_list $HEXC $ciph $kx $enc | grep -qwai "$arg"
if [ $? -eq 0 ]; then # string matches, so we can ssl to it:
$OPENSSL s_client -cipher $ciph $STARTTLS -connect $NODEIP:$PORT $SNI &>$TMPFILE </dev/null
ret=$?
if [ $kx == "Kx=ECDH" ] || [ $kx == "Kx=DH" ] || [ $kx == "Kx=EDH" ]; then
if [ $ret -eq 0 ]; then
dhlen=$(read_dhbits_from_file $TMPFILE quiet)
kx="$kx $dhlen"
else
kx="$kx$grey TBD $off "
fi
fi
neat_list $HEXC $ciph "$kx" $enc
if [ $ret -eq 0 ]; then
pr_cyan " available"
else
out " not a/v"
fi
outln
fi
done
done
outln
tmpfile_handle $FUNCNAME.txt
return 0
}
# test for all ciphers locally configured (w/o distinguishing whether they are good or bad
allciphers(){
local tmpfile
local nr_ciphers
local ret
local hexcode n ciph sslvers kx auth enc mac export
local dhlen
nr_ciphers=$($OPENSSL ciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' | sed 's/:/ /g' | wc -w)
pr_blue "--> Testing all locally available $nr_ciphers ciphers against the server"; outln ", ordered by encryption strength"
! $HAS_DH_BITS && pr_litemagentaln " (Your $OPENSSL is too old to show DH/ECDH bits)"
outln
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 </dev/null
ret=$?
if [ $ret -ne 0 ] && [ "$SHOW_EACH_C" -eq 0 ]; then
continue # no successful connect AND not verbose displaying each cipher
fi
normalize_ciphercode $hexcode
if [ $kx == "Kx=ECDH" ] || [ $kx == "Kx=DH" ] || [ $kx == "Kx=EDH" ]; then
dhlen=$(read_dhbits_from_file $TMPFILE quiet)
kx="$kx $dhlen"
fi
neat_list $HEXC $ciph "$kx" $enc
if [ "$SHOW_EACH_C" -ne 0 ]; then
if [ $ret -eq 0 ]; then
pr_cyan " available"
else
out " not a/v"
fi
fi
outln
tmpfile_handle $FUNCNAME.txt
done
outln
return 0
}
# test for all ciphers per protocol locally configured (w/o distinguishing whether they are good or bad
cipher_per_proto(){
local proto proto_text
local hexcode n ciph sslvers kx auth enc mac export
local ret
local dhlen
pr_blue "--> Testing all locally available ciphers per protocol against the server"; outln ", ordered by encryption strength"
! $HAS_DH_BITS && pr_litemagentaln " (Your $OPENSSL is too old to show DH/ECDH bits)"
outln
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 </dev/null
ret=$?
if [ $ret -ne 0 ] && [ "$SHOW_EACH_C" -eq 0 ]; then
continue # no successful connect AND not verbose displaying each cipher
fi
normalize_ciphercode $hexcode
if [ $kx == "Kx=ECDH" ] || [ $kx == "Kx=DH" ] || [ $kx == "Kx=EDH" ]; then
dhlen=$(read_dhbits_from_file $TMPFILE quiet)
kx="$kx $dhlen"
fi
neat_list $HEXC $ciph "$kx" $enc
if [ "$SHOW_EACH_C" -ne 0 ]; then
if [ $ret -eq 0 ]; then
pr_cyan " available"
else
out " not a/v"
fi
fi
outln
tmpfile_handle $FUNCNAME.txt
done
done
return 0
}
locally_supported() {
local ret
[ -n "$2" ] && out "$2 "
$OPENSSL s_client "$1" 2>&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 </dev/null
ret=$?
# FIXME: here FreeBSD9 returns always 0 --> need to read the error
[ "$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
local supported_no_ciph1="supported but couldn't detect a cipher (may need debugging)"
local supported_no_ciph2="supported but couldn't detect a cipher"
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
pr_bold " 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_no_ciph2";
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
pr_bold " 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) pr_magentaln "#FIXME: downgraded. still missing a test case here" ;;
5) pr_litered "$supported_no_ciph2";
outln "(may need debugging)" ;; # protocol ok, but no cipher
7) ;; # no local support
esac
pr_bold " TLS 1 ";
if [ $SSL_NATIVE -eq 0 ] || [ -n "$STARTTLS" ] || [ "$EXPERIMENTAL" != "yes" ] ; then
testprotohelper "-tls1"
else
echo -n "(socket:) "
tls_sockets "01" "$TLS_CIPHER"
fi
case $? in
0) outln "offered" ;; # nothing wrong with it -- per se
1) outln "not offered" ;; # neither good or bad
2) pr_magentaln "downgraded. still missing a testcase here" ;;
5) outln "$supported_no_ciph1" ;; # protocol ok, but no cipher
7) ;; # no local support
esac
pr_bold " TLS 1.1 ";
if [ $SSL_NATIVE -eq 0 ] || [ -n "$STARTTLS" ] || [ "$EXPERIMENTAL" != "yes" ] ; then
testprotohelper "-tls1_1"
else
echo -n "(socket:) "
tls_sockets "02" "$TLS_CIPHER"
fi
case $? in
0) outln "offered" ;; # nothing wrong with it
1) outln "not offered" ;; # neither good or bad
5) outln "$supported_no_ciph1" ;; # protocol ok, but no cipher
7) ;; # no local support
esac
pr_bold " TLS 1.2 ";
if [ $SSL_NATIVE -eq 0 ] || [ -n "$STARTTLS" ] || [ "$EXPERIMENTAL" != "yes" ] ; then
testprotohelper "-tls1_2"
else
echo -n "(socket:) "
tls_sockets "03" "$TLS12_CIPHER"
fi
case $? in
0) pr_greenln "offered (OK)" ;; # GCM cipher in TLS 1.2: very good!
1) pr_brownln "not offered (NOT ok)" ;; # no GCM, penalty
5) outln "$supported_no_ciph1" ;; # protocol ok, but no cipher
7) ;; # no local support
esac
return 0
}
#TODO: work with a fixed list here
run_std_cipherlists() {
outln
pr_blue "--> Testing ~standard cipher lists"; outln "\n"
# see ciphers(1ssl)
std_cipherlists NULL:eNULL " Null Ciphers " 1
std_cipherlists aNULL " Anonymous NULL Ciphers " 1
std_cipherlists ADH " Anonymous DH Ciphers " 1
std_cipherlists EXPORT40 " 40 Bit encryption " 1
std_cipherlists EXPORT56 " 56 Bit encryption " 1
std_cipherlists EXPORT " Export Ciphers (general) " 1
std_cipherlists 'LOW:!ADH' " Low (<=64 Bit) " 1
std_cipherlists 'DES:!ADH:!EXPORT:!aNULL' " DES Ciphers " 1
std_cipherlists 'MEDIUM:!NULL:!aNULL:!SSLv2' " Medium grade encryption " 2
std_cipherlists '3DES:!ADH:!aNULL' " Triple DES Ciphers " 3
std_cipherlists 'HIGH:!NULL:!aNULL:!DES:!3DES:' " High grade encryption " 0
return 0
}
# arg1: file with input for grepping the bit length for ECDH/DHE
# arg2: whether to print sparse or not (empty: no)
read_dhbits_from_file() {
local bits what_dh
local add=""
local old_fart=" (openssl is too old to show DH bits)"
if ! $HAS_DH_BITS; then
if [ -z "$2" ]; then
pr_litemagenta "$old_fart"
fi
return 0
fi
bits=$(awk -F': ' '/^Server Temp Key/ { print $2 }' "$1") # extract line
bits=$(echo $bits | sed -e 's/, P-...//' -e 's/,//g' -e 's/bits//' -e 's/ //g') # now: ??DH [number] K??
what_dh=$(echo $bits | tr -d '[0-9]')
bits=$(echo $bits | tr -d '[DHEC]')
debugme ">$what_dh|$bits<"
[ -n "$bits" ] && [ -z "$2" ] && out ", "
if [[ $what_dh == "DH" ]] || [[ $what_dh == "EDH" ]] ; then
[ -z "$2" ] && add="bit DH"
if [[ "$bits" -le 600 ]]; then
pr_red "$bits $add"
elif [[ "$bits" -le 800 ]]; then
pr_litered "$bits $add"
elif [[ "$bits" -le 1280 ]]; then
pr_brown "$bits $add"
elif [[ "$bits" -ge 2048 ]]; then
pr_litegreen "$bits $add"
else
out "$bits $add"
fi
# https://wiki.openssl.org/index.php/Elliptic_Curve_Cryptography, http://www.keylength.com/en/compare/
elif [[ $what_dh == "ECDH" ]]; then
[ -z "$2" ] && add="bit ECDH"
if [[ "$bits" -le 128 ]]; then # has that ever existed?
pr_red "$bits $add"
elif [[ "$bits" -le 163 ]]; then
pr_litered "$bits $add"
elif [[ "$bits" -ge 224 ]]; then
pr_litegreen "$bits $add"
else
out "$bits $add"
fi
fi
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"
pr_bold " Has server cipher order? "
$OPENSSL s_client $STARTTLS -cipher $list1 -connect $NODEIP:$PORT $SNI </dev/null 2>/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 2>/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 2>/dev/null >$TMPFILE
pr_bold " 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
pr_bold " 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
pr_bold " Negotiated cipher per proto"; out " $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 2>/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 2>/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
pr_bold " Cipher order"
for p in ssl2 ssl3 tls1 tls1_1 tls1_2; do
$OPENSSL s_client $STARTTLS -"$p" -connect $NODEIP:$PORT $SNI </dev/null 2>/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 2>/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?
outln
else
protos=$($OPENSSL s_client -host $NODE -port $PORT -nextprotoneg \"\" </dev/null 2>/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 2>/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 2>/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 </dev/null | \
awk '/-----BEGIN/,/-----END/ { print $0 }' >$HOSTCERT
return $?
}
tls_time() {
local now difftime
if [ -n "$STARTTLS" ] ; then
pr_bold " TLS timestamp"; outln " (not yet implemented for STARTTLS) "
else
tls_sockets "01" "$TLS_CIPHER" # try first TLS 1.0
[[ -z "$TLS_TIME" ]] && tls_sockets "03" "$TLS12_CIPHER" # TLS 1.2
[[ -z "$TLS_TIME" ]] && tls_sockets "02" "$TLS_CIPHER" # TLS 1.1
[[ -z "$TLS_TIME" ]] && tls_sockets "00" "$TLS_CIPHER" # SSL 3
# TODO: maybe too much tests -- timing !
if [[ -n "$TLS_TIME" ]]; then # nothing returned a time!
difftime=$(($TLS_TIME - $TLS_NOW))
if [[ "${#difftime}" -gt 5 ]]; then
# openssl >= 1.0.1f fills this field with random values! --> good for possible fingerprint
pr_bold " TLS timestamp" ; outln " random values, no fingerprinting possible "
else
[[ $difftime != "-"* ]] && [[ $difftime != "0" ]] && difftime="+$difftime"
pr_bold " TLS clock skew" ; outln " $difftime sec from localtime";
fi
debugme out "$TLS_TIME"
outln
else
pr_bold " TLS timestamp" ; outln " "; pr_litemagentaln "SSLv3 through TLS 1.2 didn't return a timestamp"
fi
fi
}
server_defaults() {
local proto
local gost_status_problem=false
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"
#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 2>/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 2>/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 </dev/null | awk '/-----BEGIN/,/-----END/ { print $0 }' >$HOSTCERT.nosni
pr_bold " 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
pr_bold " 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
pr_bold " 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
pr_bold " 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
pr_bold " 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' )"
pr_bold " 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:<unsupported>//g')
# ^^^ CACert
pr_bold " 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
pr_bold " 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_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
pr_bold " 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
if $HAS_GNUDATE ; then
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")
else
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")
fi
outln " ($startdate --> $enddate)"
savedir=$(pwd); cd $TEMPDIR
$OPENSSL s_client -showcerts $STARTTLS -connect $NODEIP:$PORT $SNI 2>/dev/null </dev/null | \
awk -v c=-1 '/-----BEGIN CERTIFICATE-----/{inc=1;c++} inc {print > ("level" c ".crt")} /---END CERTIFICATE-----/{inc=0}'
nrsaved=$(ls $TEMPDIR/level?.crt 2>/dev/null | wc -w | sed 's/^ *//')
pr_bold " # of certificates provided"; outln " $nrsaved"
cd "$savedir"
pr_bold " 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"
pr_bold " OCSP URI "
ocsp_uri=$($OPENSSL x509 -in $HOSTCERT -noout -ocsp_uri)
[ x"$ocsp_uri" == "x" ] && pr_literedln "--" || echo "$ocsp_uri"
pr_bold " 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 " 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
# if we call tls_time before tmpfile_handle it throws an error because the function tls_sockets removed $TMPFILE
# already -- and that was a different one -- means that would get overwritten anyway
tmpfile_handle tlsextdebug+status.txt
tls_time
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 ret2
local -i pfs_offered=1
local tmpfile
local dhlen
local hexcode dash pfs_cipher 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'
#w/o RC4:
#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'
#
# hardcoded: (the exclusion via ! doesn't work with libressl and openssl 0.9.8) and it's reproducible
local pfs_cipher_list="ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-CAMELLIA256-SHA256:DHE-RSA-CAMELLIA256-SHA:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-CAMELLIA256-SHA384:ECDHE-ECDSA-CAMELLIA256-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-CAMELLIA128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-CAMELLIA128-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-CAMELLIA128-SHA256:DHE-RSA-SEED-SHA:DHE-RSA-CAMELLIA128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA"
local -i no_supported_ciphers=0
outln
pr_blue "--> Testing (perfect) forward secrecy, (P)FS"; outln " -- omitting 3DES, RC4 and Null Encryption here"
! $HAS_DH_BITS && pr_litemagentaln " (Your $OPENSSL too old to show DH/ECDH bits)"
no_supported_ciphers=$(count_ciphers $(actually_supported_ciphers $pfs_cipher_list))
if [ "$no_supported_ciphers" -le "$CLIENT_MIN_PFS" ] ; then
outln
pr_magentaln " Local problem: you only have $number_pfs PFS ciphers on the client side "
return 1
fi
$OPENSSL s_client -cipher 'ECDH:DH' $STARTTLS -connect $NODEIP:$PORT $SNI &>$TMPFILE </dev/null
ret=$?
outln
if [ $ret -ne 0 ] || [ $(grep -ac "BEGIN CERTIFICATE" $TMPFILE) -eq 0 ]; then
pr_brownln "Not OK: No ciphers supporting Forward Secrecy offered"
else
pfs_offered=0
if [ $WIDE -ne 0 ] ; then
pr_litegreen " PFS ciphers (OK): "
else
pr_litegreen " PFS is offered (OK) "
outln ", cipher follow (client/browser support is here specially important) \n"
neat_header
fi
while read hexcode dash pfs_cipher sslvers kx auth enc mac; do
tmpfile=$TMPFILE.$hexcode
$OPENSSL s_client -cipher $pfs_cipher $STARTTLS -connect $NODEIP:$PORT $SNI &>$tmpfile </dev/null
ret2=$?
if [[ $ret2 -ne 0 ]] && [[ "$SHOW_EACH_C" -eq 0 ]] ; then
continue # no successful connect AND not verbose displaying each cipher
fi
if [ $WIDE -eq 0 ]; then
normalize_ciphercode $hexcode
if [ $kx == "Kx=ECDH" ] || [ $kx == "Kx=DH" ] || [ $kx == "Kx=EDH" ]; then
dhlen=$(read_dhbits_from_file "$tmpfile" quiet)
kx="$kx $dhlen"
fi
neat_list $HEXC $pfs_cipher "$kx" $enc $strength
let "pfs_offered++"
if [[ "$SHOW_EACH_C" -ne 0 ]] ; then
if [[ $ret2 -eq 0 ]]; then
pr_green "works"
else
out "not a/v"
fi
fi
outln
else
out "$pfs_cipher "
fi
done < <($OPENSSL ciphers -V "$pfs_cipher_list") # -V doesn't work with openssl < 1.0
# ^^^^^ posix redirect as shopt will either segfault or doesn't work with old bash versions
debugme echo $pfs_offered
if [ "$pfs_offered" -eq 1 ] ; then
pr_brown "no PFS ciphers found"
fi
fi
outln
[ $WIDE -ne 0 ] && outln
debugme echo $(actually_supported_ciphers $pfs_cipher_list)
debugme echo $no_supported_ciphers
tmpfile_handle $FUNCNAME.txt
return $pfs_offered
}
# good source for configuration and bugs: https://wiki.mozilla.org/Security/Server_Side_TLS
# good start to read: http://en.wikipedia.org/wiki/Transport_Layer_Security#Attacks_against_TLS.2FSSL
spdy_pre(){
if [ ! -z "$STARTTLS" ]; then
out "(SPDY is a HTTP protocol and thus not tested here)"
return 1
fi
# first, does the current openssl support it?
$OPENSSL s_client help 2>&1 | grep -qw nextprotoneg
if [ $? -ne 0 ]; then
pr_magentaln "Local problem: $OPENSSL doesn't support SPDY/NPN";
return 7
fi
return 0
}
spdy() {
pr_bold " SPDY/NPN "
if ! spdy_pre ; then
echo
return 0
fi
$OPENSSL s_client -host $NODE -port $PORT -nextprotoneg $NPN_PROTOs </dev/null 2>/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
out "$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 timeout 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')
}
hex2dec() {
printf "%d" "0x$1"
}
dec2hex() {
printf "%x" "$1"
}
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
tls_serverhello() {
local tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' $1)
local tls_content_type tls_protocol tls_len_all
#TODO: all vars here
# server hello, handshake details see http://en.wikipedia.org/wiki/Transport_Layer_Security-SSL#TLS_record
# byte 0: content type: 0x14=CCS, 0x15=TLS alert x16=Handshake, 0x17 Aplication, 0x18=HB
# byte 1+2: TLS version word, major is 03, minor 00=SSL3, 01=TLS1 02=TLS1.1 03=TLS 1.2
# byte 3+4: 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 word see byte 1+2
# byte 11-14: TLS timestamp for OpenSSL <1.01f
# 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
[[ "$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_content_type="${tls_hello_ascii:0:2}" # normally this is x16
tls_protocol="${tls_hello_ascii:2:4}"
DETECTED_TLS_VERSION=$tls_protocol
tls_len_all=${tls_hello_ascii:6:4}
sid_len_offset=86
tls_hello="${tls_hello_ascii:10:2}" # normally this is x02
tls_protocol2="${tls_hello_ascii:18:4}"
tls_hello_time="${tls_hello_ascii:22:8}"
if [[ $tls_content_type == "15" ]] ; then # TLS ALERT
tls_err_level=${tls_hello_ascii:10:2} # 1: warning, 2: fatal
tls_err_descr=${tls_hello_ascii:12:2} # 112/0x70: Unrecognized name, 111/0x6F: certificate_unobtainable,
# 113/0x71: bad_certificate_status_response, #114/0x72: bad_certificate_hash_value
if [[ $DEBUG -ge 2 ]]; then
echo "tls_content_type: 0x$tls_content_type"
echo "tls_protocol: 0x$tls_protocol"
echo "tls_len_all: $tls_len_all"
echo "tls_err_descr: 0x${tls_err_descr} / = $(hex2dec ${tls_err_descr})"
echo "tls_err_level: ${tls_err_level} (warning:1, fatal:2)"
fi
# now, here comes a strange thing... -- on the forst glance
# IF an apache 2.2/2.4 server e.g. has a default servername configured but we send SNI <myhostname>
# we get a TLS ALERT saying "unrecognized_name" (0x70) and a warning (0x1), see RFC https://tools.ietf.org/html/rfc6066#page-17
# note that RFC recommended to fail instead: https://tools.ietf.org/html/rfc6066#section-3
# we need to handle this properly -- otherwise we always return that the protocol or cipher is not available!
if [[ "$tls_err_descr" == 70 ]] && [[ "${tls_err_level}" == "01" ]] ; then
# ignore Unrecognized name *warning*
sid_len_offset=100 # we are 2x7 bytes off (formerly: 86 instead of 100)
tls_hello="${tls_hello_ascii:24:2}" # here, too (normally this is (02)
tls_protocol2="${tls_hello_ascii:32:4}"
tls_hello_time="${tls_hello_ascii:36:8}"
else
return 1
fi
fi
TLS_TIME=$(hex2dec $tls_hello_time)
tls_sid_len=$(hex2dec ${tls_hello_ascii:$sid_len_offset:2})
let sid_offset=$sid_len_offset+2+$tls_sid_len*2
tls_cipher_suite="${tls_hello_ascii:$sid_offset:4}"
let sid_offset=$sid_len_offset+6++$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_protocol2: 0x$tls_protocol2"
echo "tls_sid_len: 0x$(dec2hex $tls_sid_len) / = $tls_sid_len"
fi
echo -n "tls_hello_time: 0x$tls_hello_time "
if $HAS_GNUDATE ; then
echo $(date --date="@$TLS_TIME" "+%Y-%m-%d %r")
else
echo $(date -j -f %s "$TLS_TIME" "+%Y-%m-%d %r")
fi
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="$1"
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
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 #TODO: in wireshark this is always 00! (TLS 1.0-1.2)
,$len_all_word # Length <---
# Handshake header:
,01 # Type (x01 for ClientHello)
,00, $len_client_hello_word # Length ClientHello
,03, $tls_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)
#TODO,add (see heartbleed)
# extension lenghth (word)
# extension ec_point_formats (4 words) 1st: 00 0b
#len 00 04
# ec prot formats len: 03
# uncompressed 00
# EC point format: ansiX962_compressed_prime 01
# EC point format: ansiX962_compressed_char2 02
# ec, 1st: 00 0a
# 2nd length: (word) e.g. 0x34
# 3rd: ec curve len ln-2 e.g. 0x32
# 4.-n. curves e.g. 25 words
# Extension: Session Ticket 00 23
# Extension: Signature Algorithms 00 0d
# len e.g. 00 20 (32 bytes)
# bytes... 1-32 16 words
# Extension: Haertbeat 00 0f
# len 00 01
# peer allowed to send requests 01
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
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) "
# 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 # protocol available, TLS version returned equal to the one send
else
[[ $DEBUG -ge 2 ]] && echo -n "protocol send: 0x03$tls_low_byte, returned: 0x$DETECTED_TLS_VERSION"
ret=2 # protocol NOT available, server downgraded to $DETECTED_TLS_VERSION
fi
fi
debugme 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 </dev/null
if $HAS_SED_E; then
tls_proto_offered=$(grep -aw Protocol $TMPFILE | sed -E 's/[^[:digit:]]//g')
else
tls_proto_offered=$(grep -aw Protocol $TMPFILE | sed -r 's/[^[:digit:]]//g')
fi
case $tls_proto_offered in
12) tls_hexcode="x03, x03" ;;
11) tls_hexcode="x03, x02" ;;
*) tls_hexcode="x03, x01" ;;
esac
heartbleed_payload=", x18, $tls_hexcode, x00, x03, x01, x40, x00"
client_hello="
# TLS header ( 5 bytes)
,x16, # content type (x16 for handshake)
$tls_hexcode, # TLS version
x00, xdc, # length
# Handshake header
x01, # type (x01 for ClientHello)
x00, x00, xd8, # length
$tls_hexcode, # TLS version
# Random (32 byte)
x53, x43, x5b, x90, x9d, x9b, x72, x0b,
xbc, x0c, xbc, x2b, x92, xa8, x48, x97,
xcf, xbd, x39, x04, xcc, x16, x0a, x85,
x03, x90, x9f, x77, x04, x33, xd4, xde,
x00, # session ID length
x00, x66, # cipher suites length
# cipher suites (51 suites)
xc0, x14, xc0, x0a, xc0, x22, xc0, x21,
x00, x39, x00, x38, x00, x88, x00, x87,
xc0, x0f, xc0, x05, x00, x35, x00, x84,
xc0, x12, xc0, x08, xc0, x1c, xc0, x1b,
x00, x16, x00, x13, xc0, x0d, xc0, x03,
x00, x0a, xc0, x13, xc0, x09, xc0, x1f,
xc0, x1e, x00, x33, x00, x32, x00, x9a,
x00, x99, x00, x45, x00, x44, xc0, x0e,
xc0, x04, x00, x2f, x00, x96, x00, x41,
xc0, x11, xc0, x07, xc0, x0c, xc0, x02,
x00, x05, x00, x04, x00, x15, x00, x12,
x00, x09, x00, x14, x00, x11, x00, x08,
x00, x06, x00, x03, x00, xff,
x01, # compression methods length
x00, # compression method (x00 for NULL)
x00, x49, # extensions length
# extension: ec_point_formats
x00, x0b, x00, x04, x03, x00, x01, x02,
# extension: elliptic_curves
x00, x0a, x00, x34, x00, x32, x00, x0e,
x00, x0d, x00, x19, x00, x0b, x00, x0c,
x00, x18, x00, x09, x00, x0a, x00, x16,
x00, x17, x00, x08, x00, x06, x00, x07,
x00, x14, x00, x15, x00, x04, x00, x05,
x00, x12, x00, x13, x00, x01, x00, x02,
x00, x03, x00, x0f, x00, x10, x00, x11,
# extension: session ticket TLS
x00, x23, x00, x00,
# extension: heartbeat
x00, x0f, x00, x01, x01"
fd_socket 5 || return 6
[[ $DEBUG -ge 2 ]] && outln "\nsending client hello (TLS version $tls_hexcode)"
socksend "$client_hello" 1
sockread 16384
[[ $DEBUG -ge 2 ]] && outln "\nreading server hello"
if [[ $DEBUG -ge 3 ]]; then
echo "$SOCKREPLY" | "${HEXDUMPVIEW[@]}" | head -20
outln "[...]"
outln "\nsending payload with TLS version $tls_hexcode:"
fi
socksend "$heartbleed_payload" 1
sockread 16384 $HEARTBLEED_MAX_WAITSOCK
retval=$?
if [[ $DEBUG -ge 3 ]]; then
outln "\nheartbleed reply: "
echo "$SOCKREPLY" | "${HEXDUMPVIEW[@]}"
outln
fi
lines_returned=$(echo "$SOCKREPLY" | "${HEXDUMP[@]}" | wc -l | sed 's/ //g')
if [ $lines_returned -gt 1 ]; then
pr_red "VULNERABLE (NOT ok)"
ret=1
else
pr_green "not vulnerable (OK)"
ret=0
fi
[ $retval -eq 3 ] && out " (timed out)"
outln
close_socket
tmpfile_handle $FUNCNAME.txt
return $ret
}
# helper function
ok_ids(){
pr_greenln "\n ok -- something resetted our ccs packets"
return 0
}
#FIXME: At a certain point heartbleed and ccs needs to be changed and make use of code2network using a file, then tls_sockets
ccs_injection(){
# see https://www.openssl.org/news/secadv_20140605.txt
# mainly adapted from Ramon de C Valle's C code from https://gist.github.com/rcvalle/71f4b027d61a78c42607
[ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_blue "--> 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 </dev/null
if $HAS_SED_E; then
tls_proto_offered=$(grep -aw Protocol $TMPFILE | sed -E 's/[^[:digit:]]//g')
else
tls_proto_offered=$(grep -aw Protocol $TMPFILE | sed -r 's/[^[:digit:]]//g')
fi
case $tls_proto_offered in
12) tls_hexcode="x03, x03" ;;
11) tls_hexcode="x03, x02" ;;
*) tls_hexcode="x03, x01" ;;
esac
ccs_message=", x14, $tls_hexcode ,x00, x01, x01"
client_hello="
# TLS header (5 bytes)
,x16, # content type (x16 for handshake)
$tls_hexcode, # TLS version
x00, x93, # length
# Handshake header
x01, # type (x01 for ClientHello)
x00, x00, x8f, # length
$tls_hexcode, # TLS version
# Random (32 byte)
x53, x43, x5b, x90, x9d, x9b, x72, x0b,
xbc, x0c, xbc, x2b, x92, xa8, x48, x97,
xcf, xbd, x39, x04, xcc, x16, x0a, x85,
x03, x90, x9f, x77, x04, x33, xd4, xde,
x00, # session ID length
x00, x68, # cipher suites length
# Cipher suites (51 suites)
xc0, x13, xc0, x12, xc0, x11, xc0, x10,
xc0, x0f, xc0, x0e, xc0, x0d, xc0, x0c,
xc0, x0b, xc0, x0a, xc0, x09, xc0, x08,
xc0, x07, xc0, x06, xc0, x05, xc0, x04,
xc0, x03, xc0, x02, xc0, x01, x00, x39,
x00, x38, x00, x37, x00, x36, x00, x35, x00, x34,
x00, x33, x00, x32, x00, x31, x00, x30,
x00, x2f, x00, x16, x00, x15, x00, x14,
x00, x13, x00, x12, x00, x11, x00, x10,
x00, x0f, x00, x0e, x00, x0d, x00, x0c,
x00, x0b, x00, x0a, x00, x09, x00, x08,
x00, x07, x00, x06, x00, x05, x00, x04,
x00, x03, x00, x02, x00, x01, x01, x00"
fd_socket 5 || return 6
[[ $DEBUG -ge 2 ]] && out "\nsending client hello, "
socksend "$client_hello" 1
sockread 16384
[[ $DEBUG -ge 2 ]] && outln "\nreading server hello"
if [[ $DEBUG -ge 3 ]]; then
echo "$SOCKREPLY" | "${HEXDUMPVIEW[@]}" | head -20
outln "[...]"
outln "\npayload #1 with TLS version $tls_hexcode:"
fi
socksend "$ccs_message" 1 || ok_ids
sockread 2048 $CCS_MAX_WAITSOCK
if [[ $DEBUG -ge 3 ]]; then
outln "\n1st reply: "
out "$SOCKREPLY" | "${HEXDUMPVIEW[@]}" | head -20
# ok: 15 | 0301 | 02 | 02 0a == ALERT | TLS 1.0 | Length=2 | Unexpected Message (0a)
outln
outln "payload #2 with TLS version $tls_hexcode:"
fi
socksend "$ccs_message" 2 || ok_ids
sockread 2048 $CCS_MAX_WAITSOCK
retval=$?
if [[ $DEBUG -ge 3 ]]; then
outln "\n2nd reply: "
out "$SOCKREPLY" | "${HEXDUMPVIEW[@]}"
# not ok: 15 | 0301 | 02 | 02 | 15 == ALERT | TLS 1.0 | Length=2 | Decryption failed (21)
# ok: 0a or nothing: ==> 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 </dev/null | grep -iaq "$insecure_renogo_str"
sec_renego=$? # 0= Secure Renegotiation IS NOT supported
#FIXME: didn't occur to me yet but why not also to check on "Secure Renegotiation IS supported"
case $sec_renego in
0) pr_redln "VULNERABLE (NOT ok)" ;;
1) pr_greenln "not vulnerable (OK)" ;;
*) pr_magentaln "FIXME (bug): $sec_renego" ;;
esac
pr_bold " Secure Client-Initiated Renegotiation " # RFC 5746
# see: https://community.qualys.com/blogs/securitylabs/2011/10/31/tls-renegotiation-and-denial-of-service-attacks
# http://blog.ivanristic.com/2009/12/testing-for-ssl-renegotiation.html -- head/get doesn't seem to be needed though
case "$OSSL_VER" in
0.9.8*) # we need this for Mac OSX unfortunately
case "$OSSL_VER_APPENDIX" in
[a-l]) pr_magentaln "Local Problem: $OPENSSL cannot test this secure renegotiation vulnerability"
return 3 ;;
[m-z]) ;; # all ok
esac ;;
1.0.1*|1.0.2*) legacycmd="-legacy_renegotiation" ;;
0.9.9*|1.0*) ;; # all ok
esac
# We need up to two tries here, as some LiteSpeed servers don't answer on "R" and block. Thus first try in the background
echo R | $OPENSSL s_client $legacycmd $STARTTLS -msg -connect $NODEIP:$PORT &>$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 </dev/null | grep -q zlib
if [ $? -eq 0 ]; then
pr_magentaln "Local Problem: Your $OPENSSL lacks zlib support"
return 7
fi
#STR=$($OPENSSL s_client $ADDCMD $STARTTLS -connect $NODEIP:$PORT $SNI 2>&1 </dev/null | grep Compression )
$OPENSSL s_client $ADDCMD $STARTTLS -connect $NODEIP:$PORT $SNI </dev/null &>$TMPFILE
if grep -a Compression $TMPFILE | grep -aq NONE >/dev/null; then
pr_litegreen "not vulnerable (OK)"
[[ $SERVICE == "HTTP" ]] || out " (not using HTTP anyway)"
ret=0
else
if [[ $SERVICE == "HTTP" ]]; then
pr_litered "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 2>/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 referrers are the important thing here!
breach() {
local header
local -i ret
local referer useragent
local url
[[ $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
}
# trim spaces for BSD and old sed
count_lines() {
echo "$1" | wc -l | sed 's/ //g'
}
count_words() {
echo "$1" | wc -w | sed 's/ //g'
}
### two helper functions for vulnerabilities follow
count_ciphers() {
echo "$1" | sed 's/:/\n/g' | wc -l | sed 's/ //g'
}
actually_supported_ciphers() {
$OPENSSL ciphers "$1"
}
# 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 </dev/null
ret=$?
[ "$VERBERR" -eq 0 ] && egrep -q "error|failure" $TMPFILE | egrep -av "unable to get local|verify error"
if [ $ret -eq 0 ]; then
pr_litered "VULNERABLE (NOT ok)"; out ", uses SSLv3+CBC (check TLS_FALLBACK_SCSV mitigation below)"
else
pr_green "not vulnerable (OK)"
fi
outln
tmpfile_handle $FUNCNAME.txt
return $ret
}
# for appliance which use padding, no fallback needed
tls_poodle() {
pr_bold " POODLE, SSL"; out " CVE-2014-8730), experimental "
#FIXME
echo "#FIXME"
return 7
}
tls_fallback_scsv() {
local ret
[ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_blue "--> Testing for TLS_FALLBACK_SCSV Protection" && outln "\n"
pr_bold " TLS_FALLBACK_SCSV"; out " (RFC 7507) "
# This isn't a vulnerability check per se, but checks for the existence of
# the countermeasure to protect against protocol downgrade attacks.
# First check we have support for TLS_FALLBACK_SCSV in our local OpenSSL
$OPENSSL s_client -h 2>&1 | grep -q "\-fallback_scsv"
if [ $? -gt 0 ]; then
pr_magentaln "Local Problem: Your $OPENSSL lacks TLS_FALLBACK_SCSV support"
return 4
fi
# ...and do the test
$OPENSSL s_client -connect $NODEIP:$PORT $SNI -no_tls1_2 -fallback_scsv &>$TMPFILE </dev/null
if grep -q "CONNECTED(00" "$TMPFILE"; then
if grep -qa "BEGIN CERTIFICATE" "$TMPFILE"; then
pr_brown "Downgrade attack prevention NOT supported"
ret=1
elif grep -qa "alert inappropriate fallback" "$TMPFILE"; then
pr_litegreen "Downgrade attack prevention supported (OK)"
ret=0
elif grep -qa "alert handshake failure" "$TMPFILE"; then
# see RFC 7507, https://github.com/drwetter/testssl.sh/issues/121
pr_brown "\"handshake failure\" instead of \"inappropriate fallback\" (NOT ok)"
ret=2
fi
else
pr_magenta "test failed (couldn't connect)"
ret=3
fi
outln
tmpfile_handle $FUNCNAME.txt
return $ret
}
# Factoring RSA Export Keys: don't use EXPORT RSA ciphers, see https://freakattack.com/
freak() {
local ret
local -i no_supported_ciphers=0
# with correct build it should list these 7 ciphers (plus the two latter as SSLv2 ciphers):
local exportrsa_cipher_list="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-RC2-CBC-MD5:EXP-RC4-MD5:EXP-RC4-MD5"
local addtl_warning=""
[ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_blue "--> Testing for FREAK attack" && outln "\n"
pr_bold " FREAK"; out " (CVE-2015-0204), experimental "
no_supported_ciphers=$(count_ciphers $(actually_supported_ciphers $exportrsa_cipher_list))
case $no_supported_ciphers in
0) pr_magentaln "Local problem: your $OPENSSL doesn't have any EXPORT RSA ciphers configured"
return 3 ;;
1|2|3)
addtl_warning=" ($magenta""tested only with $no_supported_ciphers out of 9 ciphers only!$off)" ;;
8|9|10|11)
addtl_warning="" ;;
4|5|6|7)
addtl_warning=" (tested with $no_supported_ciphers/9 ciphers)" ;;
esac
$OPENSSL s_client $STARTTLS -cipher $exportrsa_cipher_list -connect $NODEIP:$PORT $SNI &>$TMPFILE </dev/null
ret=$?
[ "$VERBERR" -eq 0 ] && egrep -a "error|failure" $TMPFILE | egrep -av "unable to get local|verify error"
if [ $ret -eq 0 ]; then
pr_red "VULNERABLE (NOT ok)"; out ", uses EXPORT RSA ciphers"
else
pr_green "not vulnerable (OK)"; out "$addtl_warning"
fi
outln
debugme echo $(actually_supported_ciphers $exportrsa_cipher_list)
debugme echo $no_supported_ciphers
tmpfile_handle $FUNCNAME.txt
return $ret
}
# see https://weakdh.org/logjam.html
logjam() {
local ret
local exportdhe_cipher_list="EXP1024-DHE-DSS-DES-CBC-SHA:EXP1024-DHE-DSS-RC4-SHA:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA"
local -i no_supported_ciphers=0
local addtl_warning=""
[ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_blue "--> Testing for LOGJAM vulnerability" && outln "\n"
pr_bold " LOGJAM"; out " (CVE-2015-4000), experimental "
no_supported_ciphers=$(count_ciphers $(actually_supported_ciphers $exportdhe_cipher_list))
case $no_supported_ciphers in
0) pr_magentaln "Local problem: your $OPENSSL doesn't have any DHE EXPORT ciphers configured"
return 3 ;;
1|2) addtl_warning=" ($magenta""tested w/ $no_supported_ciphers/4 ciphers only!$off)" ;;
3) addtl_warning=" (tested w/ $no_supported_ciphers/4 ciphers)" ;;
4) ;;
esac
$OPENSSL s_client $STARTTLS -cipher $exportdhe_cipher_list -connect $NODEIP:$PORT $SNI &>$TMPFILE </dev/null
ret=$?
[ "$VERBERR" -eq 0 ] && egrep -a "error|failure" $TMPFILE | egrep -av "unable to get local|verify error"
addtl_warning="$addtl_warning, common primes not checked. \"$PROG_NAME -E\" spots candidates"
if [ $ret -eq 0 ]; then
pr_red "VULNERABLE (NOT ok)"; out ", uses DHE EXPORT ciphers"
else
pr_green "not vulnerable (OK)"; out "$addtl_warning"
fi
outln
debugme echo $(actually_supported_ciphers $exportdhe_cipher_list)
debugme echo $no_supported_ciphers
tmpfile_handle $FUNCNAME.txt
return $ret
}
# FIXME: perfect candidate for replacement by sockets,so is freak
# Browser Exploit Against SSL/TLS: don't use CBC Ciphers in SSLv3 TLSv1.0
beast(){
local hexcode dash cbc_cipher sslvers kx auth enc mac export
local detected_proto
local detected_cbc_ciphers=""
local higher_proto_supported=""
local openssl_ret=0
local vuln_beast=false
local spaces=" "
local cr=$'\n'
local first=true
local continued=false
local cbc_cipher_list="SRP-DSS-AES-256-CBC-SHA:SRP-RSA-AES-256-CBC-SHA:SRP-AES-256-CBC-SHA:RSA-PSK-AES256-CBC-SHA:PSK-AES256-CBC-SHA:SRP-DSS-AES-128-CBC-SHA:SRP-RSA-AES-128-CBC-SHA:SRP-AES-128-CBC-SHA:IDEA-CBC-SHA:IDEA-CBC-MD5:RC2-CBC-MD5:RSA-PSK-AES128-CBC-SHA:PSK-AES128-CBC-SHA:KRB5-IDEA-CBC-SHA:KRB5-IDEA-CBC-MD5:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:SRP-DSS-3DES-EDE-CBC-SHA:SRP-RSA-3DES-EDE-CBC-SHA:SRP-3DES-EDE-CBC-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:DH-RSA-DES-CBC3-SHA:DH-DSS-DES-CBC3-SHA:AECDH-DES-CBC3-SHA:ADH-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:DES-CBC3-SHA:DES-CBC3-MD5:RSA-PSK-3DES-EDE-CBC-SHA:PSK-3DES-EDE-CBC-SHA:KRB5-DES-CBC3-SHA:KRB5-DES-CBC3-MD5:EXP1024-DHE-DSS-DES-CBC-SHA:EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:DH-RSA-DES-CBC-SHA:DH-DSS-DES-CBC-SHA:ADH-DES-CBC-SHA:EXP1024-DES-CBC-SHA:DES-CBC-SHA:DES-CBC-MD5:KRB5-DES-CBC-SHA:KRB5-DES-CBC-MD5:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA:EXP-DH-RSA-DES-CBC-SHA:EXP-DH-DSS-DES-CBC-SHA:EXP-ADH-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-RC2-CBC-MD5:EXP-KRB5-RC2-CBC-SHA:EXP-KRB5-DES-CBC-SHA:EXP-KRB5-RC2-CBC-MD5:EXP-KRB5-DES-CBC-MD5"
if [ $VULN_COUNT -le $VULN_THRESHLD ] || [ $WIDE -eq 0 ] ; then
outln
pr_blue "--> Testing for BEAST vulnerability" && outln "\n"
fi
pr_bold " BEAST"; out " (CVE-2011-3389) "
[[ $WIDE -eq 0 ]] && outln
# 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 </dev/null
if [ $? -ne 0 ]; then # protocol supported?
if $continued; then # second round: we hit TLS1:
pr_litegreenln "no SSL3 or TLS1"
return 0
else # protocol not succeeded but it';s the first time
continued=true
continue # protocol no supported, so we do not need to check each cipher with that protocol
fi
fi # protocol succeeded
# protocol with cbc_cipher check follows now
if [[ $WIDE -eq 0 ]] ; then
outln "\n $(echo $proto | tr '[a-z]' '[A-Z]'):";
neat_header # NOTTHATNICE: we display the header also if in the end no cbc cipher is available on the client side
fi
while read hexcode dash cbc_cipher sslvers kx auth enc mac; do
$OPENSSL s_client -cipher "$cbc_cipher" -"$proto" $STARTTLS -connect $NODEIP:$PORT $SNI >$TMPFILE 2>/dev/null </dev/null
openssl_ret=$?
[[ $openssl_ret -eq 0 ]] && vuln_beast=true
if [ $WIDE -eq 0 ]; then
normalize_ciphercode $hexcode
if [[ "$SHOW_EACH_C" -ne 0 ]]; then
neat_list $HEXC $cbc_cipher $kx $enc
if [[ $openssl_ret -eq 0 ]]; then
pr_brownln "available"
else
outln "not a/v"
fi
else
[[ $openssl_ret -eq 0 ]] && neat_list $HEXC $cbc_cipher $kx $enc && outln
fi
else # short display:
if [ $openssl_ret -eq 0 ]; then
detected_cbc_ciphers="$detected_cbc_ciphers ""$(grep -aw "Cipher" $TMPFILE | egrep -avw "New|is" | sed -e 's/^.*Cipher.*://' -e 's/ //g')"
vuln_beast=true
fi
fi
done < <($OPENSSL ciphers -V 'ALL:eNULL' | grep -a CBC) # -V doesn't work with openssl < 1.0
# ^^^^^ process substitution as shopt will either segfault or doesn't work with old bash versions
if [ $WIDE -ne 0 ]; then
if [ -n "$detected_cbc_ciphers" ]; then
detected_cbc_ciphers=$(echo "$detected_cbc_ciphers" | sed -e "s/ /\\${cr} ${spaces}/9" -e "s/ /\\${cr} ${spaces}/6" -e "s/ /\\${cr} ${spaces}/3")
! $first && out "$spaces"
out "$(echo $proto | tr '[a-z]' '[A-Z]'):"; pr_brownln "$detected_cbc_ciphers"
detected_cbc_ciphers="" # empty for next round
first=false
else
[[ $proto == "tls1" ]] && ! $first && printf "$spaces"
pr_litegreenln "no CBC ciphers for $(echo $proto | tr '[a-z]' '[A-Z]') (OK)"
first=false
fi
else
$vuln_beast || pr_litegreenln " no CBC ciphers for $(echo $proto | tr '[a-z]' '[A-Z]') (OK)"
fi
done # for proto in ssl3 tls1
# 2) support for TLS 1.1+1.2?
for proto in tls1_1 tls1_2; do
$OPENSSL s_client -state -"$proto" $STARTTLS -connect $NODEIP:$PORT $SNI 2>/dev/null >$TMPFILE </dev/null
if [ $? -eq 0 ]; then
higher_proto_supported="$higher_proto_supported ""$(grep -aw "Protocol" $TMPFILE | sed -e 's/^.*Protocol .*://' -e 's/ //g')"
fi
done
if $vuln_beast ; then
if [ ! -z "$higher_proto_supported" ] ; then
if [ $WIDE -eq 0 ]; then
outln
pr_brown "VULNERABLE"
outln " -- but also supports higher protocols (possible mitigation):$higher_proto_supported"
else
outln "${spaces}-- but also supports higher protocols (possible mitigation):$higher_proto_supported"
fi
fi
fi
# printf "For a full individual test of each CBC cipher suites support by your $OPENSSL run \"$0 -x CBC $NODE\"\n"
tmpfile_handle $FUNCNAME.txt
return
}
lucky13() {
#FIXME: to do . CVE-2013-0169
# in a nutshell: don't offer CBC suites (again). MAC as a fix for padding oracles is not enough. Best: TLS v1.2+ AES GCM
echo "FIXME"
return -1
}
# https://tools.ietf.org/html/rfc7465 REQUIRES that TLS clients and servers NEVER negotiate the use of RC4 cipher suites!
# https://en.wikipedia.org/wiki/Transport_Layer_Security#RC4_attacks
# http://blog.cryptographyengineering.com/2013/03/attack-of-week-rc4-is-kind-of-broken-in.html
rc4() {
local ret rc4_offered
local hexcode dash rc4_cipher sslvers kx auth enc mac export
local rc4_ciphers_list="ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:DHE-DSS-RC4-SHA:AECDH-RC4-SHA:ADH-RC4-MD5:ECDH-RSA-RC4-SHA:ECDH-ECDSA-RC4-SHA:RC4-SHA:RC4-MD5:RC4-MD5:RSA-PSK-RC4-SHA:PSK-RC4-SHA:KRB5-RC4-SHA:KRB5-RC4-MD5:RC4-64-MD5:EXP1024-DHE-DSS-RC4-SHA:EXP1024-RC4-SHA:EXP-ADH-RC4-MD5:EXP-RC4-MD5:EXP-RC4-MD5:EXP-KRB5-RC4-SHA:EXP-KRB5-RC4-MD5"
if [ $VULN_COUNT -le $VULN_THRESHLD ] || [ $WIDE -eq 0 ] ; then
outln
pr_blue "--> 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, feeding this into the while loop below
$OPENSSL s_client -cipher $rc4_ciphers_list $STARTTLS -connect $NODEIP:$PORT $SNI &>/dev/null </dev/null
if [ $? -eq 0 ]; then
pr_litered "VULNERABLE (NOT ok): "
[[ $WIDE -eq 0 ]] && outln "\n"
rc4_offered=1
[[ $WIDE -eq 0 ]] && neat_header
while read hexcode dash rc4_cipher sslvers kx auth enc mac; do
$OPENSSL s_client -cipher $rc4_cipher $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 [ $WIDE -eq 0 ]; then
normalize_ciphercode $hexcode
neat_list $HEXC $rc4_cipher $kx $enc
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 "$rc4_cipher "
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
outln
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
}
# try very hard to determine th install path to get ahold of the mapping file
# it provides "keycode/ RFC style name", see RFCs, cipher(1), www.carbonwind.net/TLS_Cipher_Suites_Project/tls_ssl_cipher_suites_simple_table_all.htm
get_install_dir() {
#INSTALL_DIR=$(cd "$(dirname "$0")" && pwd)/$(basename "$0")
INSTALL_DIR=$(dirname ${BASH_SOURCE[0]})
[ -r "$RUN_DIR/mapping-rfc.txt" ] && MAP_RFC_FNAME="$RUN_DIR/mapping-rfc.txt"
[ -r "$INSTALL_DIR/mapping-rfc.txt" ] && MAP_RFC_FNAME="$INSTALL_DIR/mapping-rfc.txt"
# we haven't found the mapping file yet...
if [ ! -r "$MAP_RFC_FNAME" ] && which readlink &>/dev/null ; then
readlink -f ls &>/dev/null && \
INSTALL_DIR=$(readlink -f $(basename ${BASH_SOURCE[0]})) || \
INSTALL_DIR=$(readlink $(basename ${BASH_SOURCE[0]}))
# not sure whether Darwin has -f
INSTALL_DIR=$(dirname $INSTALL_DIR)
[ -r "$INSTALL_DIR/mapping-rfc.txt" ] && MAP_RFC_FNAME="$INSTALL_DIR/mapping-rfc.txt"
fi
# still no mapping file:
if [ ! -r "$MAP_RFC_FNAME" ] && which realpath &>/dev/null ; then
INSTALL_DIR=$(dirname $(realpath ${BASH_SOURCE[0]}))
MAP_RFC_FNAME="$INSTALL_DIR/mapping-rfc.txt"
fi
[ ! -r "$MAP_RFC_FNAME" ] && pr_magentaln "No mapping file found"
debugme echo "$MAP_RFC_FNAME"
}
find_openssl_binary() {
# 0. check environment variable whether it's executable
if [ ! -z "$OPENSSL" ] && [ ! -x "$OPENSSL" ]; then
pr_red "\ncannot find (\$OPENSSL=$OPENSSL) binary."
outln " Looking some place else ..."
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=""
[ $OSSL_VER_MAJOR -ne 1 ] && HAS_DH_BITS=false
[ "$OSSL_VER_MINOR" == "0.1" ] && HAS_DH_BITS=false
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 (<version 1.0) !"
case $SYSTEM in
*BSD|Darwin)
outln " Please use openssl from ports/brew or compile from github.com/PeterMosmans/openssl" ;;
*) outln " Update openssl binaries or compile from github.com/PeterMosmans/openssl" ;;
esac
ignore_no_or_lame " Type \"yes\" to accept some false negatives or positives "
fi
}
help() {
cat << EOF
$PROG_NAME <options>
-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 <pattern> what local cipher with <pattern> is a/v?
$PROG_NAME <options> 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 <pattern> tests matched <pattern> of cipher
-U, --vulnerable tests all vulnerabilities
-B, --heartbleed tests for heartbleed vulnerability
-I, --ccs, --ccs-injectio tests for CCS injection vulnerability
-R, --renegotiation tests for renegotiation vulnerabilities
-C, --compression, --crime tests for CRIME vulnerability
-T, --breach tests for BREACH vulnerability
-O, --poodle tests for POODLE (SSL) vulnerability
-Z, --tls-fallback checks TLS_FALLBACK_SCSV mitigation
-F, --freak tests for FREAK vulnerability
-A, --beast tests for BEAST vulnerability
-J, --logjam tests for LOGJAM 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)
--ip <ipv4> a) tests the supplied <ipv4> instead of resolving host(s) in URI
b) "one" means: just test the first DNS returns (useful for multiple IPs)
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
--openssl <PATH> use this openssl binary (default: look in \$PATH, \$RUN_DIR of $PROG_NAME
--sneaky be less verbose wrt referer headers
--wide wide output for tests like RC4, BEAST. PFS also with hexcode, kx, strength, RFC name
--show-each for wide outputs: display all ciphers tested -- not only succeeded ones
--warnings <batch|off|false> "batch" doesn't wait for keypress, "off|false" skips connection warning
--color <0|1|2> 0: no escape or other codes, 1: b/w escape codes, 2: color (default)
--debug <0-6> 1: screen output normal but debug output in itemp files. 2-6: see line ~60
All options requiring a value can also be called with '=' (e.g. testssl.sh -t=smtp --wide --openssl=/usr/bin/openssl <URI>
Need HTML output? Just pipe through "aha" (Ansi HTML Adapter: github.com/theZiz/aha) like
"$PROG_NAME <options> <URI> | aha >output.html"
EOF
exit $1
}
mybanner() {
local nr_ciphers
local idtag
local bb
nr_ciphers=$($OPENSSL ciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' | sed 's/:/ /g' | wc -w | sed 's/ //g')
[ -z "$GIT_REL" ] && \
idtag="$CVS_REL" || \
idtag="$GIT_REL -- $CVS_REL_SHORT"
[ "$COLOR" -ne 0 ] && idtag="\033[1;30m$idtag\033[m\033[1m"
bb=$(cat <<EOF
#########################################################
$PROG_NAME $VERSION $SWURL
($idtag)
This program is free software. Redistribution +
modification under GPLv2 is permitted.
USAGE w/o ANY WARRANTY. USE IT AT YOUR OWN RISK!
Note: you can only check the server with what is
available (ciphers/protocols) locally on your machine!
#########################################################
EOF
)
pr_bold "$bb"
outln "\n"
outln " Using \"$($OPENSSL version)\" [~$nr_ciphers ciphers] on"
outln " $(hostname):$(which $OPENSSL)"
outln " (built: \"$OSSL_BUILD_DATE\", platform: \"$OSSL_VER_PLATFORM\")\n"
}
maketempf() {
TEMPDIR=$(mktemp -d /tmp/ssltester.XXXXXX) || exit 6
TMPFILE=$TEMPDIR/tempfile.txt || exit 6
HOSTCERT=$TEMPDIR/host_certificate.txt
HEADERFILE=$TEMPDIR/http_header.txt
HEADERFILE_BREACH=$TEMPDIR/http_header_breach.txt
LOGFILE=$TEMPDIR/logfile.txt
if [ $DEBUG -ne 0 ]; then
cat >$TEMPDIR/environment.txt << EOF
CVS_REL: $CVS_REL
GIT_REL: $GIT_REL
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]
OSSL_VER_MAJOR: $OSSL_VER_MAJOR
OSSL_VER_MINOR: $OSSL_VER_MINOR
OSSL_VER_APPENDIX: $OSSL_VER_APPENDIX
OSSL_BUILD_DATE: "$OSSL_BUILD_DATE"
OSSL_VER_PLATFORM: "$OSSL_VER_PLATFORM"
PATH: $PATH
PROG_NAME: $PROG_NAME
INSTALL_DIR: $INSTALL_DIR
RUN_DIR: $RUN_DIR
MAP_RFC_FNAME: $MAP_RFC_FNAME
CAPATH: $CAPATH
ECHO: $ECHO
COLOR: $COLOR
TERM_DWITH: $TERM_DWITH
HAS_GNUDATE: $HAS_GNUDATE
HAS_SED_E: $HAS_SED_E
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
pr_underline "DEBUG (level $DEBUG): see files in $TEMPDIR"
outln
else
[ -d "$TEMPDIR" ] && rm -rf ${TEMPDIR};
fi
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
}
# arg1: URI
# arg2: 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
debugme echo $NODE:$PORT
SNI="-servername $NODE"
URL_PATH=$(echo $1 | sed 's/https:\/\///' | 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="/"
debugme echo $URL_PATH
return 0 # NODE, URL_PATH, PORT is set now
}
newline_to_spaces() {
echo "$1" | tr '\n' ' ' | sed 's/ $//'
}
# now get all IP addresses
determine_ip_addresses() {
local ip4=""
local ip6=""
if [ -z "$(printf $NODE | sed -e 's/[0-9]//g' -e 's/\.//g')" ]; then
ip4=$NODE # only an IPv4 address was supplied as an argument, no hostname
SNI="" # override Server Name Indication as we test the IP only
else
# for security testing sometimes we have local entries. Getent is BS under Linux for localhost: No network, no resolution
ip4=$(grep -w "$NODE" /etc/hosts | egrep -v ':|^#' | egrep "[[:space:]]$NODE" | awk '{ print $1 }')
if [[ -z "$ip4" ]]; then
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
if [[ -z "$ip4" ]] ; then
# MSYS2 has no host or getent, so we need nslookup
ip4=$(nslookup $NODE 2>/dev/null | grep -A10 Name | grep -v Name | sed 's/^Address.*: .//')
[[ -z "$ip4" ]] && return 2
fi
fi
ip6=$(grep -w "$NODE" /etc/hosts | grep ':' | grep -v '^#' | egrep "[[:space:]]$NODE" | awk '{ print $1 }')
if [[ -z "$ip6" ]]; then
# 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 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
# MSYS2 has no host or getent, so we need nslookup
if [[ -z "$ip6" ]]; then
ip6=$(nslookup -type=aaaa $NODE 2>/dev/null | grep -A10 Name | grep -v Name | sed 's/^Address.*: .//')
fi
fi
fi
IPADDRs=$(newline_to_spaces "$ip4")
if [[ -z "$IPADDRs" ]] && [[ -z "$CMDLINE_IP" ]] ; then
pr_magenta "Can't proceed: No IP address for \"$NODE\" available"
outln "\n"
exit -1
fi
[[ ! -z "$ip6" ]] && IP46ADDRs="$ip4 $ip6" || IP46ADDRs="$IPADDRs"
IP46ADDRs=$(newline_to_spaces "$IP46ADDRs")
return 0 # IPADDR and IP46ADDR is set now
}
determine_rdns() {
# we can't do this as some checks and even openssl is not 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 '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
}
# arg1: ftp smtp, pop3, imap, xmpp, telnet, ldap (maybe with trailing s)
determine_service() {
local all_failed
local ua
local protocol
if ! fd_socket; then # check if we can connect to $NODEIP:$PORT
ignore_no_or_lame "Ignore? "
[ $? -ne 0 ] && exit 3
fi
close_socket
datebanner "Testing"
if [[ -z "$1" ]] ; 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 &>/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
[[ $SNEAKY -eq 0 ]] && \
ua="$UA_SNEAKY" || \
ua="$UA_STD"
GET_REQ11="GET $URL_PATH HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $ua\r\nConnection: Close\r\nAccept: text/*\r\n\r\n"
HEAD_REQ11="HEAD $URL_PATH HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $ua\r\nAccept: text/*\r\n\r\n"
GET_REQ10="GET $URL_PATH HTTP/1.0\r\nUser-Agent: $ua\r\nConnection: Close\r\nAccept: text/*\r\n\r\n"
HEAD_REQ10="HEAD $URL_PATH HTTP/1.0\r\nUser-Agent: $ua\r\nAccept: text/*\r\n\r\n"
runs_HTTP $OPTIMAL_PROTO
else
protocol=$(echo "$1" | 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
SNI=""
$OPENSSL s_client -connect $NODEIP:$PORT $STARTTLS 2>/dev/null >$TMPFILE </dev/null
if [ $? -ne 0 ]; then
pr_magentaln " $OPENSSL couldn't establish STARTTLS via $protocol to $NODEIP:$PORT"
debugme cat $TMPFILE
exit 3
fi
out " Service set: STARTTLS via "
echo $protocol | tr '[a-z]' '[A-Z]'
;;
*) pr_litemagentaln "momentarily only ftp, smtp, pop3, imap, xmpp and telnet, ldap allowed" >&2
exit 1
;;
esac
fi
${do_mx_all_ips} || initialize_engine
outln
return 0 # OPTIMAL_PROTO, GET_REQ*/HEAD_REQ* is set now
}
display_rdns_etc() {
local i
if [ $(printf "$IP46ADDRs" | wc -w | sed 's/ //g') -gt 1 ]; then
out " further IP addresses: "
for i in $IP46ADDRs; 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
}
draw_dotted_line() {
printf -- "$1"'%.s' $(eval "echo {1.."$(($2))"}")
}
mx_all_ips() {
local mxs mx
local mxport
local starttls_proto="smtp"
local ret=0
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
[[ $mxport == "465" ]] && \
starttls_proto="" # no starttls for Port 465, on all other ports we speak starttls
pr_bold "Testing now all MX records (on port $mxport): "; outln "$mxs"
for mx in $mxs; do
draw_dotted_line "-" $TERM_DWITH
outln
parse_hn_port "$mx:$mxport"
determine_ip_addresses
NODEIP="$IPADDRs"
lets_roll "${starttls_proto}"
done
draw_dotted_line "-" $TERM_DWITH
outln
pr_bold "Done testing now all MX records (on port $mxport): "; outln "$mxs"
else
pr_boldln " $1 has no MX records(s)"
fi
}
# This initializes 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_logjam=false
do_header=false
do_heartbleed=false
do_mx_all_ips=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_tls_fallback_scsv=false
do_test_just_one=false
do_tls_sockets=false
}
# Set default scanning options
set_scanning_defaults() {
do_allciphers=true
do_vulnerabilities=true
do_beast=true
do_breach=true
do_ccs_injection=true
do_crime=true
do_freak=true
do_logjam=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
do_tls_fallback_scsv=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_logjam do_header do_heartbleed do_mx_all_ips do_pfs do_protocols do_rc4 do_renego \
do_run_std_cipherlists do_server_defaults do_server_preference do_spdy do_ssl_poodle do_tls_fallback_scsv \
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_logjam do_header do_heartbleed do_rc4 do_mx_all_ips do_pfs do_protocols do_rc4 do_renego \
do_run_std_cipherlists do_server_defaults do_server_preference do_spdy do_ssl_poodle do_tls_fallback_scsv \
do_test_just_one do_tls_sockets; do
printf "%-22s = %s\n" $gbl "${!gbl}"
done
printf "%-22s : %s\n" URI: "$URI"
}
# arg1+2 are just the options
parse_opt_equal_sign() {
if [[ "$1" == *=* ]]; then
echo "$1" | awk -F'=' '{ print $2 }'
return 1 # = means we don't need to shift args!
else
echo $2
return 0 # we need to shift
fi
}
# 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
-h|--help)
help 0
;;
-b|--banner|-v|--version)
find_openssl_binary
mybanner
exit 0
;;
--mx)
do_mx_all_ips=true
;;
--mx465) # doesn't work with major ISPs
do_mx_all_ips=true
PORT=465
;;
--mx587) # doesn't work with major ISPs
do_mx_all_ips=true
PORT=587
;;
--ip|--ip=*)
CMDLINE_IP=$(parse_opt_equal_sign "$1" "$2")
[ $? -eq 0 ] && shift
;;
-V|-V=*|--local|--local=*) # this is only displaying local ciphers, thus we don't put it in the loop
find_openssl_binary
mybanner
openssl_age
maketempf
initialize_engine # for GOST support
prettyprint_local $(parse_opt_equal_sign "$1" "$2")
exit $?
;;
-x|-x=*|--single[-_]cipher|--single[-_]cipher=*)
do_test_just_one=true
single_cipher=$(parse_opt_equal_sign "$1" "$2")
[ $? -eq 0 ] && shift
;;
-t|-t=*|--starttls|--starttls=*)
do_starttls=true
STARTTLS_PROTOCOL=$(parse_opt_equal_sign "$1" "$2")
[ $? -eq 0 ] && shift
;;
-e|--each-cipher)
do_allciphers=true
;;
-E|--cipher-per-proto|--cipher_per_proto)
do_cipher_per_proto=true
;;
-p|--protocols)
do_protocols=true
do_spdy=true
;;
-y|--spdy|--npn)
do_spdy=true
;;
-f|--ciphers)
do_run_std_cipherlists=true
;;
-S|--server[-_]defaults)
do_server_defaults=true
;;
-P|--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_tls_fallback_scsv=true
do_freak=true
do_beast=true
do_rc4=true
do_logjam=true
VULN_COUNT=10
;;
-B|--heartbleed)
do_heartbleed=true
let "VULN_COUNT++"
;;
-I|--ccs|--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
do_tls_fallback_scsv=true
let "VULN_COUNT++"
;;
-Z|--tls[_-]fallback|tls[_-]fallback[_-]scs)
do_tls_fallback_scsv=true
let "VULN_COUNT++"
;;
-F|--freak)
do_freak=true
let "VULN_COUNT++"
;;
-J|--logjam)
do_logjam=true
let "VULN_COUNT++"
;;
-A|--beast)
do_beast=true
let "VULN_COUNT++"
;;
-4|--rc4|--appelbaum)
do_rc4=true
let "VULN_COUNT++"
;;
-s|--pfs|--fs|--nsa)
do_pfs=true
;;
-q) ### this is a development feature and will disappear
HEX_CIPHER=""
# DEBUG=3 ./testssl.sh -q 03 "cc, 13, c0, 13" google.de
# DEBUG=3 ./testssl.sh -q 01 yandex.ru
TLS_LOW_BYTE="$2";
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}"
;;
--wide)
WIDE=0
;;
--assuming[_-]http|--assume[-_]http)
ASSUMING_HTTP=0
;;
--sneaky)
SNEAKY=0
;;
--warnings|--warnings=*)
WARNINGS=$(parse_opt_equal_sign "$1" "$2")
[ $? -eq 0 ] && shift
case "$WARNING" in
batch|off|false) ;;
default) pr_magentaln "warnings can be either \"batch\", \"off\" or \"false\"" ;;
esac
;;
--show[-_]each)
SHOW_EACH_C=1 #FIXME: sense is vice versa
;;
--debug|--debug=*)
DEBUG=$(parse_opt_equal_sign "$1" "$2")
[ $? -eq 0 ] && shift
;;
--color|--color=*)
COLOR=$(parse_opt_equal_sign "$1" "$2")
[ $? -eq 0 ] && shift
if [ $COLOR -ne 0 ] && [ $COLOR -ne 1 ] && [ $COLOR -ne 2 ] ; then
COLOR=2
pr_magentaln "$0: unrecognized color: $2" 1>&2
help 1
fi
;;
--openssl|--openssl=*)
OPENSSL=$(parse_opt_equal_sign "$1" "$2")
[ $? -eq 0 ] && 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
[ -z "$NODEIP" ] && pr_magentaln "$NODE doesn't resolve to an IP address" && exit 1
determine_rdns
determine_service "$1" # any starttls service goes here
${do_tls_sockets} && { tls_sockets "$TLS_LOW_BYTE" "$HEX_CIPHER"; exit $?; }
${do_test_just_one} && test_just_one ${single_cipher}
${do_protocols} && { runprotocols; ret=$(($? + ret)); }
${do_spdy} && { spdy; ret=$(($? + ret)); }
${do_run_std_cipherlists} && { run_std_cipherlists; ret=$(($? + ret)); }
${do_pfs} && { pfs; 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
http_header "$URL_PATH"
http_date "$URL_PATH"
hsts "$URL_PATH"
hpkp "$URL_PATH"
server_banner "$URL_PATH"
application_banner "$URL_PATH"
cookie_flags "$URL_PATH"
more_flags "$URL_PATH"
rp_banner "$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_tls_fallback_scsv} && { tls_fallback_scsv; ret=$(($? + ret)); }
${do_freak} && { freak; ret=$(($? + ret)); }
${do_logjam} && { logjam; ret=$(($? + ret)); }
${do_beast} && { beast; ret=$(($? + ret)); }
${do_rc4} && { rc4; ret=$(($? + ret)); }
${do_allciphers} && { allciphers; ret=$(($? + ret)); }
${do_cipher_per_proto} && { cipher_per_proto; ret=$(($? + ret)); }
datebanner "Done"
return $ret
}
################# main #################
get_install_dir
initialize_globals
startup "$@"
find_openssl_binary
mybanner
openssl_age
maketempf
# TODO: it's ugly to have those two vars here --> main()
ret=0
ip=""
#TODO: there shouldn't be the need for a special case for --mx, only the ip adresses we would need upfront and the do-parser
if ${do_mx_all_ips} ; then
query_globals # if we have just 1x "do_*" --> we do a standard run -- otherwise just the one specified
[ $? -eq 1 ] && set_scanning_defaults
initialize_engine
mx_all_ips "${URI}" $PORT
ret=$?
else
parse_hn_port "${URI}" # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now
determine_ip_addresses
if [[ -n "$CMDLINE_IP" ]]; then
[[ "$CMDLINE_IP" == "one" ]] && \
CMDLINE_IP=$(echo -n "$IPADDRs" | awk '{ print $1 }') || \
NODEIP="$CMDLINE_IP" # specific ip address for NODE was supplied
lets_roll "${STARTTLS_PROTOCOL}"
ret=$?
else # no --ip was supplied
if [[ $(printf "$IPADDRs" | wc -w | sed 's/ //g') -gt 1 ]]; then # we have more that one ipv4 address to check
pr_bold "Testing now all IP addresses (on port $PORT): "; outln "$IPADDRs"
for ip in $IPADDRs; do
draw_dotted_line "-" $TERM_DWITH
outln
NODEIP="$ip"
lets_roll "${STARTTLS_PROTOCOL}"
ret=$(($? + ret))
done
draw_dotted_line "-" $TERM_DWITH
outln
pr_bold "Done testing now all IP addresses (on port $PORT): "; outln "$IPADDRs"
else # we need just one ip4v to check
NODEIP="$IPADDRs"
lets_roll "${STARTTLS_PROTOCOL}"
ret=$?
fi
fi
fi
exit $ret
# $Id: testssl.sh,v 1.288 2015/06/22 21:19:07 dirkw Exp $