4198 lines
141 KiB
Bash
Executable File
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 $
|